#![allow(
    clippy::cast_possible_truncation,  // usize → u16 is safe (UI dimensions)
)]

//! Table widget for displaying tabular data
//!
//! Implements R-WIDGET-COMPOSITION and R-STATE-SEPARATION:
//! - Table state separate from rendering
//! - Support for sorting and selection
//! - Theme-aware styling

use crate::config::Theme;
use ratatui::prelude::*;
use ratatui::widgets::{Block, Borders, Cell, Row, Table as RatatuiTable};

/// A single table row
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TableRow {
    /// Row cells (column values)
    cells: Vec<String>,
}

impl TableRow {
    /// Create a new row with cells
    #[must_use]
    pub const fn new(cells: Vec<String>) -> Self {
        Self { cells }
    }

    /// Get the cells
    #[must_use]
    pub fn cells(&self) -> &[String] {
        &self.cells
    }

    /// Get cell at index
    #[must_use]
    pub fn cell(&self, index: usize) -> Option<&str> {
        self.cells.get(index).map(std::string::String::as_str)
    }
}

/// Table column header
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TableColumn {
    /// Column title
    title: String,
    /// Column width (0 = auto)
    width: usize,
}

impl TableColumn {
    /// Create a new column
    #[must_use]
    pub const fn new(title: String) -> Self {
        Self { title, width: 0 }
    }

    /// Set column width
    #[must_use]
    pub const fn with_width(mut self, width: usize) -> Self {
        self.width = width;
        self
    }

    /// Get column title
    #[must_use]
    pub fn title(&self) -> &str {
        &self.title
    }

    /// Get column width
    #[must_use]
    pub const fn width(&self) -> usize {
        self.width
    }
}

/// Table widget for displaying data
///
/// Implements R-STATE-SEPARATION: Table state is held separately
/// from rendering logic.
///
/// # Example
///
/// ```ignore
/// let mut table = Table::new(vec![
///     TableColumn::new("Name".to_string()),
///     TableColumn::new("Email".to_string()),
/// ])
/// .add_row(TableRow::new(vec![
///     "John".to_string(),
///     "john@example.com".to_string(),
/// ]));
///
/// table.select_next();
/// table.render(&theme, frame, area);
/// ```
#[derive(Clone, Debug)]
pub struct Table {
    /// Column definitions
    columns: Vec<TableColumn>,
    /// Data rows
    rows: Vec<TableRow>,
    /// Selected row index
    selected: usize,
    /// Dirty flag for efficient rendering
    dirty: bool,
}

impl Table {
    /// Create a new table with columns
    #[must_use]
    pub const fn new(columns: Vec<TableColumn>) -> Self {
        Self {
            columns,
            rows: Vec::new(),
            selected: 0,
            dirty: true,
        }
    }

    /// Add a row to the table
    #[must_use]
    pub fn add_row(mut self, row: TableRow) -> Self {
        self.rows.push(row);
        self.dirty = true;
        self
    }

    /// Get the number of rows
    #[must_use]
    pub const fn row_count(&self) -> usize {
        self.rows.len()
    }

    /// Get the number of columns
    #[must_use]
    pub const fn column_count(&self) -> usize {
        self.columns.len()
    }

    /// Get columns
    #[must_use]
    pub fn columns(&self) -> &[TableColumn] {
        &self.columns
    }

    /// Get rows
    #[must_use]
    pub fn rows(&self) -> &[TableRow] {
        &self.rows
    }

    /// Get selected row
    #[must_use]
    pub fn selected_row(&self) -> Option<&TableRow> {
        if self.selected < self.rows.len() {
            Some(&self.rows[self.selected])
        } else {
            None
        }
    }

    /// Get selected row index
    #[must_use]
    pub const fn selected_index(&self) -> usize {
        self.selected
    }

    /// Move selection to next row
    pub const fn select_next(&mut self) {
        if !self.rows.is_empty() {
            self.selected = (self.selected + 1) % self.rows.len();
            self.dirty = true;
        }
    }

    /// Move selection to previous row
    pub const fn select_previous(&mut self) {
        if !self.rows.is_empty() {
            self.selected = if self.selected == 0 {
                self.rows.len() - 1
            } else {
                self.selected - 1
            };
            self.dirty = true;
        }
    }

    /// Select row by index
    pub const fn select(&mut self, index: usize) {
        if index < self.rows.len() {
            self.selected = index;
            self.dirty = true;
        }
    }

    /// Check if state has changed since last render
    #[must_use]
    pub const fn is_dirty(&self) -> bool {
        self.dirty
    }

    /// Reset dirty flag after rendering
    pub const fn reset_dirty(&mut self) {
        self.dirty = false;
    }

    /// Render the table using the given theme
    pub fn render(&mut self, theme: &Theme, frame: &mut Frame, area: Rect) {
        if area.height < 2 || self.columns.is_empty() {
            return;
        }

        // Build header row
        let header_cells: Vec<Cell> = self
            .columns
            .iter()
            .map(|col| Cell::from(col.title()).style(theme.header_style()))
            .collect();

        let header = Row::new(header_cells).style(theme.header_style());

        // Column widths are calculated individually based on configured width
        let _col_width = if self.columns.is_empty() {
            area.width as usize
        } else {
            area.width as usize / self.columns.len()
        };

        let constraints: Vec<Constraint> = self
            .columns
            .iter()
            .map(|col| {
                if col.width() > 0 {
                    // SAFETY: col.width() is UI dimension, safely fits in u16
                    Constraint::Length(col.width() as u16)
                } else {
                    // SAFETY: (100 / column_count) is always 0-100, safe for u16
                    Constraint::Percentage((100 / self.columns.len()) as u16)
                }
            })
            .collect();

        // Build data rows
        let mut rows = Vec::new();
        for (idx, row) in self.rows.iter().enumerate() {
            let style = if idx == self.selected {
                theme.selected_style()
            } else {
                theme.normal_style()
            };

            let cells: Vec<Cell> = row
                .cells()
                .iter()
                .map(|cell| Cell::from(cell.as_str()).style(style))
                .collect();

            rows.push(Row::new(cells).style(style));
        }

        let table = RatatuiTable::new(rows, constraints)
            .header(header)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .title("Table")
                    .border_style(theme.focused_style()),
            )
            .row_highlight_style(theme.selected_style())
            .highlight_symbol("> ");

        frame.render_widget(table, area);
        self.reset_dirty();
    }
}

impl Default for Table {
    fn default() -> Self {
        Self::new(vec![
            TableColumn::new("Column 1".to_string()),
            TableColumn::new("Column 2".to_string()),
        ])
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_table_column_new() {
        let col = TableColumn::new("Name".to_string());
        assert_eq!(col.title(), "Name");
        assert_eq!(col.width(), 0);
    }

    #[test]
    fn test_table_column_width() {
        let col = TableColumn::new("Name".to_string()).with_width(20);
        assert_eq!(col.width(), 20);
    }

    #[test]
    fn test_table_row_new() {
        let row = TableRow::new(vec!["John".to_string(), "john@example.com".to_string()]);
        assert_eq!(row.cells().len(), 2);
        assert_eq!(row.cell(0), Some("John"));
        assert_eq!(row.cell(1), Some("john@example.com"));
    }

    #[test]
    fn test_table_row_cell_out_of_bounds() {
        let row = TableRow::new(vec!["John".to_string()]);
        assert_eq!(row.cell(1), None);
    }

    #[test]
    fn test_table_new() {
        let table = Table::new(vec![
            TableColumn::new("Name".to_string()),
            TableColumn::new("Email".to_string()),
        ]);
        assert_eq!(table.column_count(), 2);
        assert_eq!(table.row_count(), 0);
    }

    #[test]
    fn test_table_add_row() {
        let table = Table::new(vec![
            TableColumn::new("Name".to_string()),
            TableColumn::new("Email".to_string()),
        ])
        .add_row(TableRow::new(vec![
            "John".to_string(),
            "john@example.com".to_string(),
        ]))
        .add_row(TableRow::new(vec![
            "Jane".to_string(),
            "jane@example.com".to_string(),
        ]));

        assert_eq!(table.row_count(), 2);
    }

    #[test]
    fn test_table_selected_row() {
        let table = Table::new(vec![TableColumn::new("Name".to_string())])
            .add_row(TableRow::new(vec!["John".to_string()]));

        let row = table.selected_row();
        assert!(row.is_some());
        assert_eq!(row.unwrap().cell(0), Some("John"));
    }

    #[test]
    fn test_table_select_next() {
        let mut table = Table::new(vec![TableColumn::new("Name".to_string())])
            .add_row(TableRow::new(vec!["John".to_string()]))
            .add_row(TableRow::new(vec!["Jane".to_string()]));

        assert_eq!(table.selected_index(), 0);
        table.select_next();
        assert_eq!(table.selected_index(), 1);
        table.select_next();
        assert_eq!(table.selected_index(), 0); // Wraps
    }

    #[test]
    fn test_table_select_previous() {
        let mut table = Table::new(vec![TableColumn::new("Name".to_string())])
            .add_row(TableRow::new(vec!["John".to_string()]))
            .add_row(TableRow::new(vec!["Jane".to_string()]));

        table.select(1);
        table.select_previous();
        assert_eq!(table.selected_index(), 0);
    }

    #[test]
    fn test_table_select() {
        let mut table = Table::new(vec![TableColumn::new("Name".to_string())])
            .add_row(TableRow::new(vec!["John".to_string()]))
            .add_row(TableRow::new(vec!["Jane".to_string()]))
            .add_row(TableRow::new(vec!["Bob".to_string()]));

        table.select(2);
        assert_eq!(table.selected_index(), 2);
    }

    #[test]
    fn test_table_select_out_of_bounds() {
        let mut table = Table::new(vec![TableColumn::new("Name".to_string())])
            .add_row(TableRow::new(vec!["John".to_string()]));

        table.select(5); // Out of bounds
        assert_eq!(table.selected_index(), 0); // Should not change
    }

    #[test]
    fn test_table_dirty_flag() {
        let mut table = Table::new(vec![TableColumn::new("Name".to_string())])
            .add_row(TableRow::new(vec!["John".to_string()]));

        assert!(table.is_dirty());
        table.reset_dirty();
        assert!(!table.is_dirty());

        table.select_next();
        assert!(table.is_dirty());
    }

    #[test]
    fn test_table_columns() {
        let table = Table::new(vec![
            TableColumn::new("Name".to_string()),
            TableColumn::new("Email".to_string()),
        ]);

        assert_eq!(table.columns().len(), 2);
        assert_eq!(table.columns()[0].title(), "Name");
    }

    #[test]
    fn test_table_rows() {
        let table = Table::new(vec![TableColumn::new("Name".to_string())])
            .add_row(TableRow::new(vec!["John".to_string()]))
            .add_row(TableRow::new(vec!["Jane".to_string()]));

        assert_eq!(table.rows().len(), 2);
    }

    #[test]
    fn test_table_default() {
        let table = Table::default();
        assert_eq!(table.column_count(), 2);
    }

    #[test]
    fn test_table_row_clone() {
        let row1 = TableRow::new(vec!["John".to_string()]);
        let row2 = row1.clone();
        assert_eq!(row1, row2);
    }

    #[test]
    fn test_table_column_clone() {
        let col1 = TableColumn::new("Name".to_string());
        let col2 = col1.clone();
        assert_eq!(col1, col2);
    }
}
