use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
use crate::event::Event;
use crate::geom::{Rect, Size};
use crate::layout::Constraint;
use crate::render::RenderCx;
use crate::style::Style;
use crate::text::Text;

/// Cursor mode for selection in a [`Table`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorType {
    /// Select the entire row (default).
    Row,
    /// Select a single cell.
    Cell,
}

/// Column width constraint.
#[derive(Debug, Clone, Copy)]
pub enum ColumnWidth {
    /// Exact width in cells.
    Fixed(u16),
    /// Fill remaining space proportionally (weight).
    Flex(u16),
}

/// Column definition for a [`Table`].
pub struct TableColumn {
    /// Header text displayed in the column header row.
    pub title: Text,
    /// Width constraint for this column.
    pub width: ColumnWidth,
    /// Text alignment within the column. Default: `TextAlign::Left`.
    pub align: crate::style::TextAlign,
}

/// A single cell in a table row.
pub struct TableCell {
    /// Cell content.
    pub content: Text,
    /// Optional per-cell style override.
    pub style: Option<Style>,
}

impl From<&str> for TableCell {
    fn from(s: &str) -> Self { Self { content: Text::from(s), style: None } }
}

impl From<String> for TableCell {
    fn from(s: String) -> Self { Self { content: Text::from(s), style: None } }
}

impl From<Text> for TableCell {
    fn from(content: Text) -> Self { Self { content, style: None } }
}

/// A row in a table, with optional per-row style and height.
pub struct TableRow {
    pub cells: Vec<TableCell>,
    pub height: u16,
    pub style: Option<Style>,
}

impl TableRow {
    pub fn new(cells: Vec<impl Into<TableCell>>) -> Self {
        Self { cells: cells.into_iter().map(|c| c.into()).collect(), height: 1, style: None }
    }

    pub fn height(mut self, height: u16) -> Self { self.height = height.max(1); self }
    pub fn style(mut self, style: Style) -> Self { self.style = Some(style); self }
}

/// A data table widget with column headers, row selection, and keyboard navigation.
pub struct Table {
    columns: Vec<TableColumn>,
    rows: Vec<TableRow>,
    footer: Option<TableRow>,
    selected: usize,
    scroll_offset: usize,
    rect: Rect,
    style: Style,
    header_style: Style,
    select_style: Style,
    /// Cursor mode: Row or Cell.
    cursor_type: CursorType,
    /// Selected column in Cell mode.
    selected_column: usize,
    /// Rows fixed at top (headers) that don't scroll.
    fixed_rows: usize,
    /// Highlight style for the selected column.
    column_highlight_style: Style,
    /// Highlight style for the selected cell (highest priority).
    cell_highlight_style: Style,
}

impl Table {
    /// Creates an empty table.
    pub fn new() -> Self {
        Self {
            columns: Vec::new(),
            rows: Vec::new(),
            selected: 0,
            scroll_offset: 0,
            rect: Rect::default(),
            style: Style::default(),
            header_style: Style::default().bold(),
            select_style: Style::default(),
            footer: None,
            cursor_type: CursorType::Row,
            selected_column: 0,
            fixed_rows: 0,
            column_highlight_style: Style::default(),
            cell_highlight_style: Style::default(),
        }
    }

    /// Sets the cursor type (Row or Cell).
    pub fn cursor_type(mut self, ct: CursorType) -> Self {
        self.cursor_type = ct;
        self
    }

    /// Sets the number of fixed (non-scrolling) rows at the top.
    pub fn fixed_rows(mut self, n: usize) -> Self {
        self.fixed_rows = n;
        self
    }

    /// Sets the column highlight style (applied to the selected column).
    pub fn column_highlight_style(mut self, style: Style) -> Self {
        self.column_highlight_style = style;
        self
    }

    /// Sets the cell highlight style (highest priority highlight).
    pub fn cell_highlight_style(mut self, style: Style) -> Self {
        self.cell_highlight_style = style;
        self
    }

    /// Returns the selected column index.
    pub fn selected_column(&self) -> usize { self.selected_column }

    /// Sets the column definitions.
    pub fn columns(mut self, columns: Vec<TableColumn>) -> Self {
        self.columns = columns;
        self
    }

    /// Sets the row data.
    pub fn rows(mut self, rows: Vec<TableRow>) -> Self {
        self.rows = rows;
        self
    }

    /// Convenience: construct rows from Vec<Vec<impl Into<TableCell>>>.
    pub fn rows_simple(mut self, rows: Vec<Vec<impl Into<TableCell>>>) -> Self {
        self.rows = rows.into_iter().map(|r| TableRow::new(r)).collect();
        self
    }

    /// Sets a footer row displayed below all data rows.
    pub fn footer(mut self, row: TableRow) -> Self {
        self.footer = Some(row);
        self
    }

    /// Sets the default cell style.
    pub fn style(mut self, style: Style) -> Self {
        self.style = style;
        self
    }

    /// Sets the header row style.
    pub fn header_style(mut self, style: Style) -> Self {
        self.header_style = style;
        self
    }

    /// Sets the selected row style.
    pub fn select_style(mut self, style: Style) -> Self {
        self.select_style = style;
        self
    }

    /// Returns the currently selected row index.
    pub fn selected(&self) -> usize { self.selected }

    /// Sets the selected row programmatically.
    pub fn set_selected(&mut self, index: usize, cx: &mut EventCx) {
        if index < self.rows.len() {
            self.selected = index;
            cx.invalidate_paint();
        }
    }

    /// Returns the number of rows.
    pub fn row_count(&self) -> usize {
        self.rows.len()
    }

    /// Sorts rows by the given column index, in ascending order.
    /// Invalid column index is a no-op.
    pub fn sort_by_column(&mut self, col: usize, cx: &mut EventCx) {
        if col < self.columns.len() {
            self.rows.sort_by(|a, b| {
                let ca = a.cells.get(col).map(|c| c.content.first_text()).unwrap_or("");
                let cb = b.cells.get(col).map(|c| c.content.first_text()).unwrap_or("");
                ca.cmp(cb)
            });
            cx.invalidate_paint();
        }
    }

    /// Sets the width of a specific column. Minimum width is 3.
    pub fn set_column_width(&mut self, col: usize, width: u16, cx: &mut EventCx) {
        if col < self.columns.len() {
            self.columns[col].width = ColumnWidth::Fixed(width.max(3));
            cx.invalidate_layout();
        }
    }

    /// Adjusts the width of a column by `delta` (positive = wider).
    pub fn adjust_column_width(&mut self, col: usize, delta: i16, cx: &mut EventCx) {
        if col < self.columns.len() {
            if let ColumnWidth::Fixed(w) = self.columns[col].width {
                self.columns[col].width = ColumnWidth::Fixed((w as i16 + delta).max(3) as u16);
                cx.invalidate_layout();
            }
        }
    }

    /// Resolve column widths from constraints given the available total width.
    fn resolved_widths(&self, available: u16) -> Vec<u16> {
        let col_count = self.columns.len();
        if col_count == 0 { return Vec::new(); }

        let sep_w = (col_count.saturating_sub(1)) as u16;
        let usable = available.saturating_sub(sep_w);

        let mut widths = vec![0u16; col_count];
        let mut flex_total: u16 = 0;

        // First pass: assign fixed widths
        for (i, col) in self.columns.iter().enumerate() {
            if let ColumnWidth::Fixed(w) = col.width {
                widths[i] = w;
            } else if let ColumnWidth::Flex(w) = col.width {
                flex_total += w;
            }
        }

        let fixed_sum: u16 = widths.iter().sum();
        let flex_space = usable.saturating_sub(fixed_sum);

        // Second pass: assign flex widths
        if flex_total > 0 {
            let per_flex = flex_space / flex_total;
            let mut allocated: u16 = 0;
            for (i, col) in self.columns.iter().enumerate() {
                if let ColumnWidth::Flex(w) = col.width {
                    widths[i] = w.saturating_mul(per_flex).max(3);
                    allocated += widths[i];
                }
            }
            // Distribute remainder to last flex column
            if allocated < flex_space {
                for i in (0..col_count).rev() {
                    if matches!(self.columns[i].width, ColumnWidth::Flex(_)) {
                        widths[i] += flex_space - allocated;
                        break;
                    }
                }
            }
        }

        widths
    }

    /// Returns the number of visible rows that fit in `height`.
    fn visible_rows(&self, height: u16) -> usize {
        let usable = height.saturating_sub(2); // header + separator
        usable as usize
    }
}

impl Component for Table {
    fn render(&self, cx: &mut RenderCx) {
        let columns = &self.columns;
        if columns.is_empty() { return; }

        let col_count = columns.len();
        let widths = self.resolved_widths(cx.rect.width);
        let visible = self.visible_rows(cx.rect.height);
        let start_row = self.scroll_offset;
        let end_row = (start_row + visible).min(self.rows.len());

        // --- header row ---
        cx.set_style(self.header_style.clone());
        for (i, col) in columns.iter().enumerate() {
            let text = truncate_to_width(col.title.first_text(), widths[i], col.align);
            cx.text(&text);
            if i < col_count - 1 {
                cx.text("│");
            }
        }
        cx.line("");

        // --- separator ---
        cx.set_style(self.style.clone());
        for (i, _col) in columns.iter().enumerate() {
            cx.text(&"─".repeat(widths[i] as usize));
            if i < col_count - 1 { cx.text("┼"); }
        }
        cx.line("");

        // --- data rows ---
        for row_idx in start_row..end_row {
            let is_selected = self.selected == row_idx;
            let is_col_selected = |ci: usize| {
                self.cursor_type == CursorType::Cell
                    && self.selected == row_idx
                    && self.selected_column == ci
            };

            let row = &self.rows[row_idx];
            let row_style = row.style.clone().unwrap_or(self.style.clone());
            for (i, _col) in columns.iter().enumerate() {
                let cell_text = row.cells.get(i).map(|c| c.content.first_text()).unwrap_or("");
                let cell_style = row.cells.get(i).and_then(|c| c.style.clone()).unwrap_or(row_style.clone());
                // Three-level highlight: row → column → cell
                let mut final_style = cell_style;
                if is_selected {
                    final_style = crate::style_parser::merge_styles(final_style, &self.select_style);
                }
                if is_col_selected(i) {
                    final_style = crate::style_parser::merge_styles(final_style, &self.column_highlight_style);
                }
                if self.cursor_type == CursorType::Cell && is_selected && self.selected_column == i {
                    final_style = crate::style_parser::merge_styles(final_style, &self.cell_highlight_style);
                }
                cx.set_style(final_style);
                let text = truncate_to_width(cell_text, widths[i], columns[i].align);
                cx.text(&text);
                if i < col_count - 1 {
                    cx.text("│");
                }
            }
            cx.line("");
        }

        // --- footer ---
        if let Some(footer_row) = &self.footer {
            cx.set_style(self.style.clone());
            for (i, _col) in columns.iter().enumerate() {
                cx.text(&"─".repeat(widths[i] as usize));
                if i < col_count - 1 { cx.text("┼"); }
            }
            cx.line("");

            for (i, col) in columns.iter().enumerate() {
                let cell_text = footer_row.cells.get(i).map(|c| c.content.first_text()).unwrap_or("");
                let text = truncate_to_width(cell_text, widths[i], col.align);
                cx.text(&text);
                if i < col_count - 1 { cx.text("│"); }
            }
            cx.line("");
        }
    }

    fn measure(&self, _constraint: Constraint, _cx: &mut MeasureCx) -> Size {
        if self.columns.is_empty() { return Size { width: 0, height: 0 }; }
        let widths = self.resolved_widths(80);
        let width: u16 = widths.iter().sum::<u16>()
            + (self.columns.len() as u16).saturating_sub(1);

        let footer_height = if self.footer.is_some() { 2 } else { 0 }; // separator + footer
        let visible = self.rows.len().min(u16::MAX as usize) as u16;
        let height = 2u16.saturating_add(visible).saturating_add(footer_height);

        Size { width, height }
    }

    fn event(&mut self, event: &Event, cx: &mut EventCx) {
        if matches!(event, Event::Focus | Event::Blur | Event::Tick) { return; }
        if self.rows.is_empty() { return; }

        if let Event::Key(key_event) = event {
            match &key_event.key {
                crate::event::Key::Up => {
                    if self.selected > 0 {
                        self.selected -= 1;
                        self.scroll_to_visible(self.selected);
                        cx.invalidate_paint();
                    }
                }
                crate::event::Key::Down => {
                    if self.selected + 1 < self.rows.len() {
                        self.selected += 1;
                        self.scroll_to_visible(self.selected);
                        cx.invalidate_paint();
                    }
                }
                crate::event::Key::Left => {
                    if self.cursor_type == CursorType::Cell && self.selected_column > 0 {
                        self.selected_column -= 1;
                        cx.invalidate_paint();
                    }
                }
                crate::event::Key::Right => {
                    if self.cursor_type == CursorType::Cell
                        && self.selected_column + 1 < self.columns.len()
                    {
                        self.selected_column += 1;
                        cx.invalidate_paint();
                    }
                }
                _ => {}
            }
        }
    }

    fn layout(&mut self, rect: Rect, _cx: &mut LayoutCx) {
        self.rect = rect;
    }

    fn focusable(&self) -> bool {
        false
    }

    fn style(&self) -> Style {
        self.style.clone()
    }
}

impl Table {
    fn scroll_to_visible(&mut self, idx: usize) {
        let visible = self.visible_rows(self.rect.height);
        if visible == 0 {
            return;
        }
        if idx < self.scroll_offset {
            self.scroll_offset = idx;
        } else if idx >= self.scroll_offset + visible {
            self.scroll_offset = idx.saturating_sub(visible.saturating_sub(1));
        }
    }
}

/// Truncates and aligns `text` to fit within `max_width` character cells.
///
/// Takes Unicode width into account. Pads with spaces according to alignment.
fn truncate_to_width(text: &str, max_width: u16, align: crate::style::TextAlign) -> String {
    let mut result = String::new();
    let mut used: u16 = 0;
    for ch in text.chars() {
        let w = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) as u16;
        if used + w > max_width {
            break;
        }
        used += w;
        result.push(ch);
    }
    let padding = max_width.saturating_sub(used);
    match align {
        crate::style::TextAlign::Left => {
            while used < max_width { result.push(' '); used += 1; }
        }
        crate::style::TextAlign::Center => {
            let left = padding / 2;
            let right = padding - left;
            let mut s = String::new();
            for _ in 0..left { s.push(' '); }
            s.push_str(&result);
            for _ in 0..right { s.push(' '); }
            result = s;
        }
        crate::style::TextAlign::Right => {
            let mut s = String::new();
            for _ in 0..padding { s.push(' '); }
            s.push_str(&result);
            result = s;
        }
    }
    result
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::style::{Color, TextAlign};
    use crate::testbuffer::TestBuffer;

    #[test]
    fn test_table_headers() {
        let mut tb = TestBuffer::new(30, 3);
        let cols = vec![TableColumn { title: Text::from("Name"), width: ColumnWidth::Fixed(10), align: TextAlign::Left }];
        let rows = vec![TableRow::new(vec![TableCell::from("val")])];
        tb.render(&Table::new().columns(cols).rows(rows));
        assert!(tb.buffer.cells.iter().any(|c| c.symbol == "N"));
    }

    #[test]
    fn test_column_width_flex() {
        let table = Table::new().columns(vec![
            TableColumn { title: Text::from("A"), width: ColumnWidth::Flex(1), align: TextAlign::Left },
            TableColumn { title: Text::from("B"), width: ColumnWidth::Flex(1), align: TextAlign::Left },
        ]);
        let widths = table.resolved_widths(25); // 25 - 1 sep = 24, split 12/12
        assert_eq!(widths.len(), 2);
        assert!(widths[0] >= 10);
        assert!(widths[1] >= 10);
    }

    #[test]
    fn test_cell_style() {
        let mut tb = TestBuffer::new(40, 3);
        let table = Table::new()
            .columns(vec![TableColumn { title: Text::from("X"), width: ColumnWidth::Fixed(10), align: TextAlign::Left }])
            .rows(vec![TableRow::new(vec![TableCell { content: Text::from("hi"), style: Some(Style::default().fg(Color::Cyan)) }])]);
        tb.render(&table);
        assert_eq!(tb.cell_fg(0, 2), Some(Color::Cyan));
    }

    #[test]
    fn test_merge_style_preserves_select_bg() {
        let base = Style::default();
        let sel = Style::default().bg(Color::White).fg(Color::Black);
        let merged = crate::style_parser::merge_styles(base, &sel);
        assert_eq!(merged.bg, Some(Color::White));
        assert_eq!(merged.fg, Some(Color::Black));
    }

    #[test]
    fn test_render_with_style() {
        use crate::render::RenderCx;
        let mut buf = crate::buffer::Buffer::new(crate::geom::Size { width: 10, height: 1 });
        let rect = crate::geom::Rect { x: 0, y: 0, width: 10, height: 1 };
        let mut cx = RenderCx::new(rect, &mut buf, Style::default());
        cx.set_style(Style::default().bg(Color::White).fg(Color::Black));
        cx.text("test");
        assert_eq!(buf.cells[0].style.bg, Some(Color::White), "render bg");
    }

    #[test]
    fn test_selection_highlight() {
        let mut tb = TestBuffer::new(40, 5);
        let mut table = Table::new()
            .columns(vec![TableColumn { title: Text::from("X"), width: ColumnWidth::Fixed(10), align: TextAlign::Left }])
            .rows(vec![TableRow::new(vec!["row0"]), TableRow::new(vec!["row1"])])
            .select_style(Style::default().bg(Color::White).fg(Color::Black));
        table.selected = 0;
        tb.render(&table);
        // Data cell (0,2) symbol and bg
        let cell = &tb.buffer.cells[2 * 40 + 0];
        eprintln!("cell(0,2): sym={:?} fg={:?} bg={:?}", cell.symbol, cell.style.fg, cell.style.bg);
        assert_eq!(tb.cell_bg(0, 2), Some(Color::White), "selected row should have white bg, got {:?}", tb.cell_bg(0, 2));
    }

    #[test]
    fn test_footer_renders() {
        let mut tb = TestBuffer::new(40, 5);
        let table = Table::new()
            .columns(vec![TableColumn { title: Text::from("X"), width: ColumnWidth::Fixed(10), align: TextAlign::Left }])
            .rows(vec![TableRow::new(vec!["data"])])
            .footer(TableRow::new(vec![TableCell { content: Text::from("sum"), style: Some(Style::default().bold()) }]));
        tb.render(&table);
        // Footer text should appear somewhere in the buffer
        assert!(tb.buffer.cells.iter().any(|c| c.symbol == "s"));
    }
}
