use ratatui::{
    buffer::Buffer,
    layout::Rect,
    style::{Color, Modifier, Style},
    widgets::Widget,
};
use unicode_width::UnicodeWidthStr;

use crate::model::Model;
use crate::ui::app::AppMode;
use crate::view::{Axis, View};

pub struct TileBar<'a> {
    pub model: &'a Model,
    pub view: &'a View,
    pub mode: &'a AppMode,
    pub tile_cat_idx: usize,
}

impl<'a> TileBar<'a> {
    pub fn new(model: &'a Model, view: &'a View, mode: &'a AppMode, tile_cat_idx: usize) -> Self {
        Self {
            model,
            view,
            mode,
            tile_cat_idx,
        }
    }
    fn axis_display(axis: Axis) -> (&'static str, Color) {
        match axis {
            Axis::Row => ("Row", Color::Green),
            Axis::Column => ("Col", Color::Blue),
            Axis::Page => ("Pag", Color::Magenta),
            Axis::None => ("·", Color::DarkGray),
        }
    }
}

impl<'a> Widget for TileBar<'a> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        // Clear the line to avoid stale characters from previous renders
        buf.set_string(
            area.x,
            area.y,
            " ".repeat(area.width as usize),
            Style::default(),
        );

        let view = self.view;

        let selected_cat_idx = if matches!(self.mode, AppMode::TileSelect) {
            Some(self.tile_cat_idx)
        } else {
            None
        };

        let prefix = " Tiles: ";
        let prefix_w = prefix.width() as u16;
        buf.set_string(area.x, area.y, prefix, Style::default().fg(Color::Gray));

        let cat_names: Vec<&str> = self.model.category_names();

        // Compute label widths for all tiles
        let labels: Vec<String> = cat_names
            .iter()
            .map(|cat_name| {
                let (axis_symbol, _) = TileBar::axis_display(view.axis_of(cat_name));
                format!(" [{cat_name} {axis_symbol}] ")
            })
            .collect();
        let widths: Vec<u16> = labels.iter().map(|l| l.width() as u16).collect();

        // Available space for tiles (after prefix)
        let avail = area.width.saturating_sub(prefix_w);

        // Find the minimal starting index so the selected tile is fully visible.
        // We scroll by whole tiles: find the first tile to draw such that the
        // selected tile fits within the available width.
        let sel = selected_cat_idx.unwrap_or(0);
        let mut start = 0;
        loop {
            // Check if selected tile is visible when starting from `start`
            let mut used: u16 = 0;
            let mut sel_visible = false;
            for (i, w) in widths.iter().enumerate().take(labels.len()).skip(start) {
                if used + *w > avail {
                    break;
                }
                used += *w;
                if i == sel {
                    sel_visible = true;
                }
            }
            if sel_visible || start >= sel {
                break;
            }
            start += 1;
        }

        // Draw an overflow indicator if we scrolled past the beginning
        let mut x = area.x + prefix_w + 1;
        if start > 0 {
            buf.set_string(
                area.x + prefix_w,
                area.y,
                "◀",
                Style::default().fg(Color::DarkGray),
            );
            x += 1;
        }

        // Render tiles from `start`
        let mut last_drawn = start;
        for i in start..labels.len() {
            let label_w = widths[i];
            if x + label_w > area.x + area.width {
                break;
            }

            let (_, axis_color) = TileBar::axis_display(view.axis_of(cat_names[i]));
            let is_selected = selected_cat_idx == Some(i);
            let style = if is_selected {
                Style::default()
                    .fg(Color::Black)
                    .bg(Color::Cyan)
                    .add_modifier(Modifier::BOLD)
            } else {
                Style::default().fg(axis_color)
            };

            buf.set_string(x, area.y, &labels[i], style);
            x += label_w;
            last_drawn = i;
        }

        // Draw overflow indicator if tiles remain after the visible area
        if last_drawn + 1 < labels.len() && x < area.x + area.width {
            buf.set_string(x, area.y, "▶", Style::default().fg(Color::DarkGray));
            x += 1;
        }

        // Hint
        if matches!(self.mode, AppMode::TileSelect) {
            let hint = " [Enter] cycle axis  [r/c/p] set axis  [←→] select  [Esc] cancel";
            if x + hint.width() as u16 <= area.x + area.width {
                buf.set_string(x, area.y, hint, Style::default().fg(Color::DarkGray));
            }
        } else {
            let hint = " Ctrl+↑↓←→ to move tiles";
            if x + hint.width() as u16 <= area.x + area.width {
                buf.set_string(x, area.y, hint, Style::default().fg(Color::DarkGray));
            }
        }
    }
}
