//! Macro wrapper over `tabled` crate, allowing to create tables inplace or from `Vec<Vec<String>>` treating first data row
//! both as data or as header overcoming specific cont trait treatment as originally in tabled.
//!
//! # Features
//!
//! - **Automatic Column Spanning**: Rows with fewer columns automatically span to fill the table
//! - **Customizable Styling**: Configure table style, header color, first column color, content color, border color, and bold headers
//! - **Simple Macro Syntax**: Create tables inline or from existing collections
//! - **Extensible**: Use the re-exported `tabled` module for advanced customization
//!
//! # Examples
//!
//! ## Basic Table with Literal Data
//! ```rust
//! use lil_tabby::tabby;
//!
//! let t = tabby![
//!     { style: ascii }
//!     ["Header 1", "Header 2", "Header 3"],
//!     ["Short", "Row"], // Auto-spans to fill remaining columns
//!     ["Full", "Width", "Row"]
//! ];
//! println!("{}", t);
//! ```
//!
//! **Output**:
//! ```text
//! +----------+----------+----------+
//! | Header 1 | Header 2 | Header 3 |
//! +----------+----------+----------+
//! | Short    | Row      |          |
//! +----------+----------+----------+
//! | Full     | Width    | Row      |
//! +----------+----------+----------+
//! ```
//!
//! ## Styled Table with Custom Options
//! ```rust
//! use lil_tabby::tabby;
//!
//! let t = tabby![
//!     { style: modern_rounded, header: green, labels: yellow, color: blue, border: red, bold: true }
//!     ["Header 1", "Header 2", "Header 3"],
//!     ["Short", "Row"],
//!     ["Full", "Width", "Row"]
//! ];
//! println!("{}", t);
//! ```
//!
//! ## Table from `Vec<Vec<String>>`
//! ```rust
//! use lil_tabby::tabby;
//!
//! let data: Vec<Vec<String>> = vec![
//!     vec!["Header 1".to_string(), "Header 2".to_string(), "Header 3".to_string()],
//!     vec!["Row 1".to_string()],
//!     vec!["Row 2 Col 1".to_string(), "Row 2 Col 2".to_string()]
//! ];
//! let t = tabby!(data);
//! println!("{}", t);
//! ```
//!
//! ## Post-Creation
//! Just re-export tabled
//! ```rust
//! use lil_tabby::{tabby, tabled};
//!
//! let mut table = tabby![
//!     { style: ascii_rounded, border: bright_green }
//!     ["a", "b", "c"],
//!     ["c", "d"],
//!     ["e"]
//! ];
//! table.modify(tabled::settings::object::Columns::new(0..), tabled::settings::Alignment::right());
//! println!("{}", table);
//! ```
//!
//! **Output:**
//! ```text
//! +---+---+---+
//! | a | b | c |
//! +---+---+---+
//! | d |     e |
//! +---+-------+
//! |         f |
//! +-----------+
//! ```
//!
//! # Styling Options
//!
//! The `tabby!` macro accepts an optional styling block with these parameters:
//! - `style`: Table style (e.g., `modern_rounded`, `ascii_rounded`, `markdown`). Defaults to `modern_rounded`. Maps to `tabled::settings::Style`
//! - `header`: First row color (e.g., `green`, `blue`, `red`). Defaults to `white`. All colors mapped to `tabled::settings::Color::FG_<COLOR>`
//! - `labels`: First column color (e.g., `yellow`, `cyan`). Defaults to `white`
//! - `color`: Content color (e.g., `blue`, `magenta`). Defaults to `white`
//! - `border`: Border color (e.g., `red`, `bright_green`). Defaults to `bright_black`
//! - `bold`: Whether the header row is bold (`true` or `false`). Defaults to `false`
//!
//! # Limitations
//!
//! - Table limited to 20 columns, due to const trait implementation, each used size should be covered. So, 20 like 640kb, enough, anyway wont fit the screen.

/// Main macro
#[macro_export]
macro_rules! tabby {
    ($($rest:tt)+) => {{
        $crate::_tabby_internal!(@start $($rest)+)
    }};
}

/// Re-exported `tabled` crate for advanced table customization
pub mod tabled {
    pub use ::tabled::*;
}

/// `paste` re-export for color identifiers
pub mod paste {
    pub use ::paste::*;
}

// Internal utilities module - hidden from public API
mod internal {
    #[macro_export]
    #[doc(hidden)]
    macro_rules! _tabby_internal {
        (@start $($rest:tt)+) => {{
            $crate::_tabby_internal!(@style $($rest)+)
        }};

        (@style {$($style:ident: $value:ident),+$(,)?} $($rest:tt)+) => {{
            $crate::_tabby_internal!(@table {$($style: $value),+}; $($rest)+)
        }};

        (@style $($rest:tt)+) => {{
            $crate::_tabby_internal!(@table {}; $($rest)+)
        }};

        // Handle Vec<Vec<String>> or similar collections
        (@table {$($style:ident: $value:ident),*}; $vec:expr) => {{
            use $crate::tabled;

            let mut rows = $vec;
            let (max_columns, spans) = $crate::_process_rows!(rows);
            let mut table = $crate::_table_convert!(max_columns, rows, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
            $crate::_apply_table_styling!({$($style: $value),*}, table, spans, max_columns)
        }};

        // Handle literal array syntax
        (@table {$($style:ident: $value:ident),*}; $([ $($cells:expr),* $(,)? ]),+ $(,)?) => {{
            use $crate::tabled;

            let mut rows = vec![
                $({
                    vec![$( $cells.to_string() ),*]
                }),+
            ];

            let (max_columns, spans) = $crate::_process_rows!(rows);
            let mut table = $crate::_table_convert!(max_columns, rows, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
            $crate::_apply_table_styling!({$($style: $value),*}, table, spans, max_columns)
        }};
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _table_convert {
        ($max_columns:ident, $rows:ident, $($n:literal),+) => {
            match $max_columns {
                $(
                    $n => {
                        let data: Vec<[String; $n]> = $rows.drain(..)
                            .map(|mut r| {
                                r.resize($n, String::new());
                                r.try_into().unwrap_or_else(|v: Vec<String>| {
                                    panic!("Expected vec of length {}, got {}", $n, v.len())
                                })
                            })
                            .collect();
                        tabled::Table::new(data)
                    }
                )+,
                _ => panic!("Table exceeds maximum supported columns (20)")
            }
        };
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _table_style {
        (_default) => {
            tabled::settings::Style::modern_rounded()
        };
        ($style:ident) => {
            tabled::settings::Style::$style()
        };
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _table_border {
        (_default) => {
            tabled::settings::Color::FG_BRIGHT_BLACK
        };
        ($color:ident) => {
            $crate::paste::paste! {
                tabled::settings::Color::[<FG_ $color:upper>]
            }
        };
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _table_labels {
        (_default) => {
            tabled::settings::Color::FG_WHITE
        };
        ($color:ident) => {
            $crate::paste::paste! {
                tabled::settings::Color::[<FG_ $color:upper>]
            }
        };
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _table_header {
        (_default) => {
            tabled::settings::Color::FG_WHITE
        };
        ($color:ident) => {
            $crate::paste::paste! {
                tabled::settings::Color::[<FG_ $color:upper>]
            }
        };
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _table_color {
        (_default) => {
            tabled::settings::Color::FG_WHITE
        };
        ($color:ident) => {
            $crate::paste::paste! {
                tabled::settings::Color::[<FG_ $color:upper>]
            }
        };
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _table_bold {
        (_default) => {
            false
        };
        ($bold:ident) => {
            $bold
        };
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _extract_field {
        ($macro:ident; $field:ident; $($style:ident : $value:ident),* $(,)?) => {
            $crate::_extract_field_inner!($macro; $field; $($style : $value),*; _default)
        };
        ($macro:ident; $field:ident;) => {
            $crate::$macro!(_default)
        };
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _extract_field_inner {
        ($macro:ident; $field:ident; $style:ident : $value:ident $(, $rest_style:ident : $rest_value:ident)*; $default:ident) => {{
            macro_rules! _check_field {
                ($field : $v:ident) => {
                    $crate::$macro!($v)
                };
                ($other:ident : $v:ident) => {
                    $crate::_extract_field_inner!($macro; $field; $($rest_style : $rest_value),*; $default)
                };
            }
            _check_field!($style : $value)
        }};
        ($macro:ident; $field:ident; ; $default:ident) => {
            $crate::$macro!($default)
        };
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _apply_table_styling {
        ({$($style:ident: $value:ident),*}, $table:expr, $spans:expr, $max_columns:expr) => {{
            // Apply column spans
            for (row, &width) in $spans.iter().enumerate() {
                if width > 0 && width < $max_columns {
                    $table.modify(
                        (row, width - 1),
                        tabled::settings::Span::column(isize::MAX)
                    );
                }
            }

            $table
                // Remove the auto-generated header row since we treat first data row as header
                .with(tabled::settings::Remove::row(tabled::settings::object::Rows::first()))
                .with($crate::_extract_field!(_table_style; style; $($style: $value),*))
                .with(tabled::settings::themes::BorderCorrection::span())
                .modify(
                    tabled::settings::object::Columns::new(0..),
                    tabled::settings::style::BorderColor::filled($crate::_extract_field!(_table_border; border; $($style: $value),*))
                )
                .modify(
                    tabled::settings::object::Columns::new(0..),
                    $crate::_extract_field!(_table_color; color; $($style: $value),*)
                )
                .modify(
                    tabled::settings::object::Columns::first(),
                    $crate::_extract_field!(_table_labels; labels; $($style: $value),*)
                )
                .modify(
                    tabled::settings::object::Rows::first(),
                    if $crate::_extract_field!(_table_bold; bold; $($style: $value),*) {
                        $crate::_extract_field!(_table_header; header; $($style: $value),*) | tabled::settings::Color::BOLD
                    } else {
                        $crate::_extract_field!(_table_header; header; $($style: $value),*)
                    }
                );

            $table
        }};
    }

    #[macro_export]
    #[doc(hidden)]
    macro_rules! _process_rows {
        ($rows:expr) => {{
            let mut max_columns = 0;
            let mut spans = Vec::new();

            for row in $rows.iter() {
                spans.push(row.len());
                max_columns = std::cmp::max(max_columns, row.len());
            }

            (max_columns, spans)
        }};
    }
}

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

    /// Test basic literal syntax with default styling
    #[test]
    fn test_literal_syntax() {
        let t = tabby![["a", "b", "c"], ["c", "d"], ["e", "f", "g"]];

        let output = t.to_string();
        assert!(output.contains("a"));
        assert!(output.contains("b"));
        assert!(output.contains("c"));
        assert!(output.contains("d"));
        assert!(output.contains("e"));
        assert!(output.contains("f"));
        assert!(output.contains("g"));
    }

    /// Test Vec<Vec<String>> input
    #[test]
    fn test_vec_syntax() {
        let data = vec![
            vec!["Header1".to_string(), "Header2".to_string()],
            vec!["Row1".to_string()],
            vec!["Row2Col1".to_string(), "Row2Col2".to_string()],
        ];

        let t = tabby!(data);
        let output = t.to_string();
        assert!(output.contains("Header1"));
        assert!(output.contains("Header2"));
        assert!(output.contains("Row1"));
        assert!(output.contains("Row2Col1"));
        assert!(output.contains("Row2Col2"));
    }

    /// Test styling options with full configuration
    #[test]
    fn test_styling_options() {
        let t = tabby![
            {style: modern_rounded, header: green, labels: yellow, color: blue, border: red, bold: true}
            ["Header 1", "Header 2", "Header 3"],
            ["Short", "Row"],
            ["Full", "Width", "Row"]
        ];

        let output = t.to_string();
        // Check if the output contains colored elements (ANSI codes)
        assert!(output.contains("\x1b[31m")); // Red border
        assert!(output.contains("\x1b[32m")); // Green header
        assert!(output.contains("\x1b[33m")); // Yellow labels
        assert!(output.contains("\x1b[34m")); // Blue content
        assert!(output.contains("Header 1"));
        assert!(output.contains("Short"));
    }

    /// Test empty rows handling
    #[test]
    fn test_empty_rows() {
        let t = tabby![["Header 1", "Header 2"], [], ["Row 2 Col 1"]];

        let output = t.to_string();
        assert!(output.contains("Header 1"));
        assert!(output.contains("Header 2"));
        assert!(output.contains("Row 2 Col 1"));
    }

    /// Test single column table
    #[test]
    fn test_single_column() {
        let t = tabby![["Header"], ["Row 1"], ["Row 2"]];

        let output = t.to_string();
        assert!(output.contains("Header"));
        assert!(output.contains("Row 1"));
        assert!(output.contains("Row 2"));
    }
    /// Test maximum column limit (20 columns)
    #[test]
    fn test_max_columns() {
        let row1: Vec<String> = (0..20).map(|i| format!("Col{}", i)).collect();
        let row2: Vec<String> = vec!["Row 1".to_string()];
        let t = tabby![vec!(row1, row2)];
        let output = t.to_string();
        assert!(output.contains("Col0"));
        assert!(output.contains("Col19"));
        assert!(output.contains("Row 1"));
    }
}
