use crate::primitives::Color;

/// Visual properties for rendering a grid.
///
/// All color, typography, and spacing values live here.
/// `dpr` (device pixel ratio) is intentionally absent — it is a
/// hardware property, not a theme property, and stays on `SceneBuilder`.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Theme {
    // ── palette
    // ──────────────────────────────────────────────────────────────
    /// Default cell background.
    pub bg: Color,
    /// Column header background.
    pub header_bg: Color,
    /// Column header text color.
    pub header_text: Color,
    /// Default cell text color.
    pub cell_text: Color,
    /// Grid line (cell border) color.
    pub grid_line: Color,
    /// Width of the horizontal grid line between data rows, and of the
    /// row-number gutter's matching line, in logical pixels.
    pub grid_line_width: f64,
    /// Color of the vertical line separating adjacent data-row columns.
    /// Defaults to the same value as `grid_line` so enabling this
    /// feature causes no visual change until the user diverges it. See
    /// also `pinned_separator_color` for the pinned-band boundary line.
    pub column_separator_color: Color,
    /// Width of the vertical column-separator line in logical pixels.
    /// See also `pinned_separator_width`.
    pub column_separator_width: f64,
    /// Bottom border below the header row.
    pub header_border: Color,
    /// Vertical inset applied to each end of the column separator
    /// line in the header. 0 = full height; 6 = shorter divider.
    pub header_separator_inset: f64,
    /// Width of the column separator line in the header, in logical
    /// pixels.
    pub header_separator_width: f64,
    /// Fill color for the selection rectangle.
    pub selection_fill: Color,
    /// Border color for the selection rectangle.
    pub selection_border: Color,
    /// Fill color for column headers when the column is in
    /// the selection range. Defaults to `selection_fill`.
    pub header_selection_fill: Color,
    /// Fill color for the row-number gutter when the row is
    /// in the selection range. Defaults to `selection_fill`.
    pub gutter_selection_fill: Color,
    /// Scrollbar track background.
    pub scrollbar_track: Color,
    /// Scrollbar thumb color.
    pub scrollbar_thumb: Color,

    /// Subtle background for odd data rows (0 = same as `bg`).
    pub row_alt_bg: Color,
    /// Background overlay for the row under the cursor (transparent =
    /// disabled).
    pub row_hover_bg: Color,
    /// Background overlay for a row checked via the row-selection checkbox
    /// column (transparent = disabled). Drawn above `row_hover_bg`.
    pub checked_row_bg: Color,

    // ── locked cell (non-editable)
    // ────────────────────────────────────────────────────
    /// Background overlay drawn over a cell that resolves to
    /// non-editable (static `ColumnDef::editable == false`, or the
    /// dynamic `editable_predicate` returns `false`). Transparent =
    /// no visual change.
    pub locked_cell_bg: Color,
    /// Text color for a locked cell's content. Defaults to a dimmed
    /// variant of `cell_text`.
    pub locked_cell_text: Color,

    // ── invalid cell (at-rest validation)
    // ─────────────────────────────────
    /// Background overlay drawn over a cell whose current value fails
    /// `ColumnDef::validate_value`, independent of `invalid_cell_border`
    /// (either, both, or neither can be themed on). Transparent = no
    /// visual change (same "opt out via alpha 0" convention as
    /// `locked_cell_bg`).
    pub invalid_cell_bg: Color,
    /// Border drawn around a cell whose current value fails
    /// `ColumnDef::validate_value` — shown even when the cell isn't
    /// being edited, unlike the DOM editor's invalid style. Transparent
    /// = no visual change (same "opt out via alpha 0" convention as
    /// `locked_cell_bg`).
    pub invalid_cell_border: Color,
    /// Width of `invalid_cell_border` in logical pixels.
    pub invalid_cell_border_width: f64,

    // ── cell decoration (at-rest, consumer-driven)
    // ──────────────────────────
    /// Stroke width for `CellDecoration::border_color`, in logical
    /// pixels. The color is consumer-supplied per cell (not themed —
    /// same precedent as `FormattedCell::color`), but the width is
    /// uniform, so it lives here like `invalid_cell_border_width`.
    pub decoration_border_width: f64,

    // ── row / header dimensions
    // ──────────────────────────────────────────────
    /// Height of the sticky header row in logical pixels.
    pub header_height: f64,
    /// Height of each data row in logical pixels.
    pub row_height: f64,

    // ── typography
    // ───────────────────────────────────────────────────────────
    /// Cell text font size in logical pixels.
    pub font_size: f64,
    /// Header text font size in logical pixels.
    pub header_font_size: f64,
    /// Render column header labels with font-weight 600.
    pub header_font_bold: bool,
    /// Render column header labels with `font-style: italic`.
    pub header_font_italic: bool,

    // ── flash (paste feedback)
    // ───────────────────────────────────────────────
    /// Fill colour for the paste-flash animation (fades from full to
    /// transparent).
    pub flash_fill: Color,
    /// Border colour for the paste-flash animation.
    pub flash_border: Color,
    /// Fill colour for the error-flash animation — cells a `CutSelection`
    /// copied but could not clear (locked or failing validation).
    pub flash_error_fill: Color,

    // ── search ──────────────────────────────────────────────────────────────
    /// Background highlight for cells matching the active search query.
    pub search_highlight: Color,
    /// Background highlight for the current (focused) search match.
    pub search_current: Color,

    // ── skeleton (async loading) ────────────────────────────────────────
    /// Foreground colour of skeleton loading bars.
    pub skeleton_fg: Color,

    // ── progress bar (`CellFormat::ProgressBar`) ─────────────────────────
    /// Unfilled track colour of a progress bar.
    pub progress_track: Color,
    /// Fill colour used when no per-value class resolves to a
    /// background (e.g. no `class_of` callback). Default = DaisyUI
    /// primary.
    pub progress_fill: Color,
    /// Bar thickness in logical pixels.
    pub progress_height: f64,
    /// Corner radius of the track and fill in logical pixels.
    pub progress_radius: f64,

    // ── checkbox column (row-selection checkbox + header tri-state)
    // ──────
    /// Side length of the checkbox box in logical pixels.
    pub checkbox_size: f64,
    /// Corner radius of the checkbox box in logical pixels.
    pub checkbox_radius: f64,
    /// Border color of the checkbox box (unchecked state).
    pub checkbox_border: Color,
    /// Border width of the checkbox box in logical pixels.
    pub checkbox_border_width: f64,
    /// Fill color when checked or indeterminate. Default = DaisyUI
    /// primary.
    pub checkbox_checked_bg: Color,
    /// Color of the check mark (checked) or dash (indeterminate).
    pub checkbox_mark_color: Color,

    // ── spacing
    // ──────────────────────────────────────────────────────────────
    /// Horizontal padding inside each cell in logical pixels.
    pub cell_padding: f64,

    // ── scrollbar
    // ─────────────────────────────────────────────────────────────
    /// Track + thumb total width in logical pixels.
    pub scrollbar_width: f64,
    /// Corner radius of the thumb in logical pixels.
    pub scrollbar_radius: f64,
    /// Gap between the track edge and the thumb on each side, in logical
    /// pixels.
    pub scrollbar_inset: f64,

    // ── column drag
    // ───────────────────────────────────────────────────────────
    /// Dim overlay drawn over the source column header during a column drag.
    pub drag_overlay: Color,
    /// Background of the ghost header that follows the cursor during a column
    /// drag (semi-transparent version of `header_bg`).
    pub drag_ghost_bg: Color,
    /// Text color of the ghost header label during a column drag
    /// (semi-transparent version of `header_text`).
    pub drag_ghost_text: Color,
    /// Width of the column insertion indicator line during a drag, in logical
    /// pixels.
    pub drag_insert_line_width: f64,
    /// Corner radius of the ghost badge in logical pixels.
    /// Set to 0.0 to keep the rectangle shape.
    pub drag_ghost_radius: f64,
    /// Border width of the ghost badge in logical pixels.
    pub drag_ghost_border_width: f64,
    /// Exponential-smoothing factor for the column-drag animation
    /// (0–1). Higher = faster. Default 0.30 ≈ 200 ms at 60 fps.
    pub drag_anim_alpha: f64,

    // ── sort indicator
    // ────────────────────────────────────────────────────────
    /// Half-width of the sort arrow triangle, in logical pixels.
    pub sort_arrow_width: f64,
    /// Half-height of the sort arrow triangle, in logical pixels.
    pub sort_arrow_height: f64,

    // ── column header menu icon
    // ───────────────────────────────────────────────
    /// Color of the three-dot (⋮) menu icon in each column header.
    pub header_menu_icon: Color,
    /// Background color of the menu icon button on hover.
    pub header_menu_icon_hover_bg: Color,
    /// Corner radius of the menu icon hover background, in logical pixels.
    pub header_menu_icon_radius: f64,
    /// Gap between the button right edge and the column right edge,
    /// in logical pixels. Increase to shift the icon leftward.
    pub header_menu_icon_margin_r: f64,
    /// Width of the menu icon button in logical pixels.
    pub header_menu_icon_btn_w: f64,
    /// Height of the menu icon button in logical pixels.
    /// Use 0.0 to auto-size to `header_height − 12`.
    pub header_menu_icon_btn_h: f64,
    /// Radius of each dot in the three-dot icon, in logical pixels.
    pub header_menu_icon_dot_r: f64,

    // ── pinned columns
    // ────────────────────────────────────────────────────────
    /// Background of the pinned-column data band.
    /// Defaults to `bg`.
    pub pinned_bg: Color,
    /// Background of the pinned-column header band.
    /// Defaults to `header_bg`.
    pub pinned_header_bg: Color,
    /// Color of the vertical separator at the right edge of the
    /// pinned band. Defaults to `header_border`.
    pub pinned_separator_color: Color,
    /// Width of the pinned-column separator line in logical pixels.
    pub pinned_separator_width: f64,

    // ── row-number gutter
    // ─────────────────────────────────────────────────────
    /// Background of the row-number gutter column.
    pub gutter_bg: Color,
    /// Text color of row numbers in the gutter.
    pub gutter_text: Color,
    /// Font size of row numbers in logical pixels.
    pub gutter_font_size: f64,
    /// Render row numbers with font-weight 600.
    pub gutter_font_bold: bool,
    /// Render row numbers with `font-style: italic`.
    pub gutter_font_italic: bool,
    /// Right border color of the gutter column.
    pub gutter_border: Color,

    // ── cell buttons
    // ──────────────────────────────────────────────────────────
    /// Background of a Primary-style cell button.
    pub cell_btn_primary_bg: Color,
    /// Text color of a Primary-style cell button.
    pub cell_btn_primary_text: Color,
    /// Background of a Secondary-style cell button.
    pub cell_btn_secondary_bg: Color,
    /// Text color of a Secondary-style cell button.
    pub cell_btn_secondary_text: Color,
    /// Background of a Danger-style cell button.
    pub cell_btn_danger_bg: Color,
    /// Text color of a Danger-style cell button.
    pub cell_btn_danger_text: Color,
    /// Border and text color for Ghost-style buttons
    /// (transparent fill).
    pub cell_btn_ghost_color: Color,
    /// Background of a Neutral-style cell button.
    pub cell_btn_neutral_bg: Color,
    /// Text color of a Neutral-style cell button.
    pub cell_btn_neutral_text: Color,
    /// Background of an Accent-style cell button.
    pub cell_btn_accent_bg: Color,
    /// Text color of an Accent-style cell button.
    pub cell_btn_accent_text: Color,
    /// Background of an Info-style cell button.
    pub cell_btn_info_bg: Color,
    /// Text color of an Info-style cell button.
    pub cell_btn_info_text: Color,
    /// Background of a Success-style cell button.
    pub cell_btn_success_bg: Color,
    /// Text color of a Success-style cell button.
    pub cell_btn_success_text: Color,
    /// Background of a Warning-style cell button.
    pub cell_btn_warning_bg: Color,
    /// Text color of a Warning-style cell button.
    pub cell_btn_warning_text: Color,
    /// Corner radius of cell buttons in logical pixels.
    pub cell_btn_radius: f64,
    /// Vertical padding inside a cell button (top and
    /// bottom each) in logical pixels.
    pub cell_btn_padding_y: f64,
    /// Horizontal padding inside a cell button (left and
    /// right each) in logical pixels.
    pub cell_btn_padding_x: f64,
    /// Gap between consecutive cell buttons in logical px.
    pub cell_btn_gap: f64,
    /// Gap between the last button and the right cell edge
    /// in logical pixels.
    pub cell_btn_margin_r: f64,
}

impl Theme {
    /// Light theme — AG Grid Quartz-inspired.
    /// Must stay in sync with `example-common/themes/light.css`.
    pub fn light() -> Self {
        Self {
            // palette
            bg: Color::rgb(255, 255, 255),
            header_bg: Color::rgb(248, 249, 251),
            header_text: Color::rgb(24, 29, 31),
            cell_text: Color::rgb(24, 29, 31),
            grid_line: Color::rgb(226, 232, 240),
            grid_line_width: 1.0,
            column_separator_color: Color::rgb(226, 232, 240),
            column_separator_width: 1.0,
            header_border: Color::rgb(186, 191, 199),
            header_separator_inset: 15.0,
            header_separator_width: 2.0,
            // rgba(33, 150, 243, 0.20) → a = round(0.20 × 255) = 51
            selection_fill: Color::rgba(33, 150, 243, 51),
            // rgba(33, 150, 243, 0.85) → a = round(0.85 × 255) = 217
            selection_border: Color::rgba(33, 150, 243, 217),
            header_selection_fill: Color::rgba(33, 150, 243, 20),
            gutter_selection_fill: Color::rgba(33, 150, 243, 20),
            scrollbar_track: Color::rgb(241, 242, 244),
            // rgba(0, 0, 0, 0.28) → a = round(0.28 × 255) = 71
            scrollbar_thumb: Color::rgba(0, 0, 0, 71),
            row_alt_bg: Color::rgb(255, 255, 255),
            // rgba(33, 150, 243, 0.07) → a = round(0.07 × 255) = 18
            row_hover_bg: Color::rgba(33, 150, 243, 18),
            // checked row — same primary as checkbox_checked_bg, subtle tint
            checked_row_bg: Color::rgba(66, 42, 213, 22),
            // locked cell — subtle gray wash, dimmed text
            // rgba(0, 0, 0, 0.035) → a = round(0.035 × 255) = 9
            locked_cell_bg: Color::rgba(0, 0, 0, 9),
            locked_cell_text: Color::rgba(24, 29, 31, 140),
            // invalid cell — same red as `cell_btn_danger_bg` below
            // rgba(239, 68, 68, 0.06) → a = round(0.06 × 255) = 15
            invalid_cell_bg: Color::rgba(239, 68, 68, 15),
            invalid_cell_border: Color::rgb(239, 68, 68),
            invalid_cell_border_width: 1.0,
            decoration_border_width: 1.5,
            // row / header dimensions
            header_height: 48.0,
            row_height: 42.0,
            // typography
            font_size: 14.0,
            header_font_size: 14.0,
            header_font_bold: true,
            header_font_italic: false,
            // flash
            flash_fill: Color::rgba(255, 220, 0, 255),
            flash_border: Color::rgba(255, 220, 0, 255),
            flash_error_fill: Color::rgba(220, 38, 38, 255),
            // search
            search_highlight: Color::rgba(255, 213, 0, 77),
            search_current: Color::rgba(255, 165, 0, 140),
            // skeleton
            skeleton_fg: Color::rgba(200, 200, 200, 77),
            // progress bar — track = light gray, fill = DaisyUI primary
            progress_track: Color::rgb(226, 232, 240),
            progress_fill: Color::rgb(66, 42, 213),
            progress_height: 8.0,
            progress_radius: 8.0,
            // checkbox — border = mid gray, checked fill = DaisyUI primary
            checkbox_size: 18.0,
            checkbox_radius: 4.0,
            checkbox_border: Color::rgb(148, 163, 184),
            checkbox_border_width: 1.5,
            checkbox_checked_bg: Color::rgb(66, 42, 213),
            checkbox_mark_color: Color::rgb(255, 255, 255),
            // spacing
            cell_padding: 12.0,
            // scrollbar
            scrollbar_width: 14.0,
            scrollbar_radius: 6.0,
            scrollbar_inset: 3.0,
            // column drag
            drag_overlay: Color::rgba(0, 0, 0, 8),
            drag_ghost_bg: Color::rgb(248, 249, 251),
            drag_ghost_text: Color::rgb(24, 29, 31),
            drag_insert_line_width: 1.0,
            drag_ghost_radius: 4.0,
            drag_ghost_border_width: 1.0,
            drag_anim_alpha: 0.5,
            // sort indicator
            sort_arrow_width: 4.0,
            sort_arrow_height: 3.5,
            // column header menu icon
            header_menu_icon: Color::rgba(24, 29, 31, 180),
            header_menu_icon_hover_bg: Color::rgba(0, 0, 0, 15),
            header_menu_icon_radius: 3.0,
            header_menu_icon_margin_r: 10.0,
            header_menu_icon_btn_w: 22.0,
            header_menu_icon_btn_h: 22.0,
            header_menu_icon_dot_r: 1.2,
            // pinned columns
            pinned_bg: Color::rgb(255, 255, 255),
            pinned_header_bg: Color::rgb(248, 249, 251),
            pinned_separator_color: Color::rgb(186, 191, 199),
            pinned_separator_width: 1.0,
            // row-number gutter
            gutter_bg: Color::rgb(248, 249, 251),
            gutter_text: Color::rgba(24, 29, 31, 180),
            gutter_font_size: 14.0,
            gutter_font_bold: true,
            gutter_font_italic: false,
            gutter_border: Color::rgb(186, 191, 199),
            // cell buttons — match DaisyUI light theme's semantic colors
            // 1:1 (see `examples/example-common/src/class_map_data.rs`,
            // generated from the installed `daisyui` package): primary
            // #422ad5 (same source as `progress_fill`/`checkbox_checked_bg`
            // above), secondary #f43098, error #ff627d.
            cell_btn_primary_bg: Color::rgb(66, 42, 213),
            cell_btn_primary_text: Color::rgb(255, 255, 255),
            cell_btn_secondary_bg: Color::rgb(244, 48, 152),
            cell_btn_secondary_text: Color::rgb(249, 228, 240),
            cell_btn_danger_bg: Color::rgb(255, 98, 125),
            cell_btn_danger_text: Color::rgb(77, 2, 24),
            // rgba(66, 42, 213, 0.78) → a = 200
            cell_btn_ghost_color: Color::rgba(66, 42, 213, 200),
            cell_btn_neutral_bg: Color::rgb(9, 9, 11),
            cell_btn_neutral_text: Color::rgb(228, 228, 231),
            cell_btn_accent_bg: Color::rgb(0, 211, 187),
            cell_btn_accent_text: Color::rgb(8, 77, 73),
            cell_btn_info_bg: Color::rgb(0, 186, 254),
            cell_btn_info_text: Color::rgb(4, 46, 73),
            cell_btn_success_bg: Color::rgb(0, 211, 144),
            cell_btn_success_text: Color::rgb(0, 76, 57),
            cell_btn_warning_bg: Color::rgb(252, 183, 0),
            cell_btn_warning_text: Color::rgb(121, 50, 5),
            cell_btn_radius: 4.0,
            cell_btn_padding_y: 4.0,
            cell_btn_padding_x: 8.0,
            cell_btn_gap: 4.0,
            cell_btn_margin_r: 8.0,
        }
    }

    /// Dark theme — matches `example-common/themes/dark.css`.
    /// Must stay in sync with that file.
    /// See also `Theme::dimmed()` for a softer alternative.
    pub fn dark() -> Self {
        Self {
            // palette
            bg: Color::rgb(28, 28, 30),
            header_bg: Color::rgb(44, 44, 46),
            header_text: Color::rgb(176, 176, 176),
            cell_text: Color::rgb(208, 208, 208),
            grid_line: Color::rgb(51, 51, 53),
            grid_line_width: 1.0,
            column_separator_color: Color::rgb(51, 51, 53),
            column_separator_width: 1.0,
            header_border: Color::rgb(51, 51, 53),
            header_separator_inset: 15.0,
            header_separator_width: 2.0,
            // rgba(60, 130, 245, 0.30) → a = round(0.30 × 255) = 77
            selection_fill: Color::rgba(60, 130, 245, 77),
            // rgba(60, 130, 245, 0.90) → a = round(0.90 × 255) = 230
            selection_border: Color::rgba(60, 130, 245, 230),
            // rgba(60, 130, 245, 0.15) → a = round(0.15 × 255) = 38
            header_selection_fill: Color::rgba(60, 130, 245, 38),
            gutter_selection_fill: Color::rgba(60, 130, 245, 38),
            scrollbar_track: Color::rgb(34, 34, 36),
            // rgba(180, 180, 180, 0.35) → a = round(0.35 × 255) = 89
            scrollbar_thumb: Color::rgba(180, 180, 180, 89),
            row_alt_bg: Color::rgb(33, 33, 35),
            // rgba(255, 255, 255, 0.05) → a = round(0.05 × 255) = 13
            row_hover_bg: Color::rgba(255, 255, 255, 13),
            // checked row — same primary as checkbox_checked_bg, subtle tint
            checked_row_bg: Color::rgba(99, 80, 240, 30),
            // locked cell — rgba(255, 255, 255, 0.04) → a = 10
            locked_cell_bg: Color::rgba(255, 255, 255, 10),
            locked_cell_text: Color::rgba(208, 208, 208, 140),
            // invalid cell — same red as `cell_btn_danger_bg` below
            // rgba(220, 38, 38, 0.08) → a = round(0.08 × 255) = 20
            invalid_cell_bg: Color::rgba(220, 38, 38, 20),
            invalid_cell_border: Color::rgb(220, 38, 38),
            invalid_cell_border_width: 1.0,
            decoration_border_width: 1.5,
            // row / header dimensions
            header_height: 48.0,
            row_height: 42.0,
            // typography
            font_size: 14.0,
            header_font_size: 14.0,
            header_font_bold: true,
            header_font_italic: false,
            // flash
            flash_fill: Color::rgba(255, 220, 0, 255),
            flash_border: Color::rgba(255, 220, 0, 255),
            flash_error_fill: Color::rgba(220, 38, 38, 255),
            // search
            search_highlight: Color::rgba(255, 213, 0, 77),
            search_current: Color::rgba(255, 165, 0, 140),
            // skeleton — rgba(80, 80, 82, 0.78) → a = 199
            skeleton_fg: Color::rgba(80, 80, 82, 199),
            // progress bar — track = dark gray, fill = brighter primary
            progress_track: Color::rgb(60, 63, 68),
            progress_fill: Color::rgb(99, 80, 240),
            progress_height: 8.0,
            progress_radius: 8.0,
            // checkbox — border = muted gray, checked fill = brighter primary
            checkbox_size: 18.0,
            checkbox_radius: 4.0,
            checkbox_border: Color::rgb(100, 106, 115),
            checkbox_border_width: 1.5,
            checkbox_checked_bg: Color::rgb(99, 80, 240),
            checkbox_mark_color: Color::rgb(255, 255, 255),
            // spacing
            cell_padding: 12.0,
            // scrollbar
            scrollbar_width: 14.0,
            scrollbar_radius: 6.0,
            scrollbar_inset: 3.0,
            // column drag
            drag_overlay: Color::rgba(0, 0, 0, 8),
            drag_ghost_bg: Color::rgb(44, 44, 46),
            drag_ghost_text: Color::rgb(176, 176, 176),
            drag_insert_line_width: 1.0,
            drag_ghost_radius: 4.0,
            drag_ghost_border_width: 1.0,
            drag_anim_alpha: 0.5,
            // sort indicator
            sort_arrow_width: 4.0,
            sort_arrow_height: 3.5,
            // column header menu icon — rgba(176,176,176, 0.70) → a = 178
            header_menu_icon: Color::rgba(176, 176, 176, 178),
            // rgba(255, 255, 255, 0.06) → a = 15
            header_menu_icon_hover_bg: Color::rgba(255, 255, 255, 15),
            header_menu_icon_radius: 3.0,
            header_menu_icon_margin_r: 10.0,
            header_menu_icon_btn_w: 22.0,
            header_menu_icon_btn_h: 22.0,
            header_menu_icon_dot_r: 1.2,
            // pinned columns
            pinned_bg: Color::rgb(28, 28, 30),
            pinned_header_bg: Color::rgb(44, 44, 46),
            pinned_separator_color: Color::rgb(51, 51, 53),
            pinned_separator_width: 1.0,
            // row-number gutter — rgba(176,176,176, 0.70) → a = 178
            gutter_bg: Color::rgb(44, 44, 46),
            gutter_text: Color::rgba(176, 176, 176, 178),
            gutter_font_size: 14.0,
            gutter_font_bold: true,
            gutter_font_italic: false,
            gutter_border: Color::rgb(51, 51, 53),
            // cell buttons — DaisyUI's built-in `dark` theme, same
            // source as the 5 semantic colors below.
            cell_btn_primary_bg: Color::rgb(96, 93, 255),
            cell_btn_primary_text: Color::rgb(237, 241, 254),
            cell_btn_secondary_bg: Color::rgb(244, 48, 152),
            cell_btn_secondary_text: Color::rgb(249, 228, 240),
            cell_btn_danger_bg: Color::rgb(255, 98, 125),
            cell_btn_danger_text: Color::rgb(77, 2, 24),
            // rgba(96, 93, 255, 0.78) → a = 200
            cell_btn_ghost_color: Color::rgba(96, 93, 255, 200),
            // DaisyUI's built-in `dark` theme reuses the same
            // neutral/accent/info/success/warning oklch values as
            // `light` — so these 5 are numerically identical to light().
            cell_btn_neutral_bg: Color::rgb(9, 9, 11),
            cell_btn_neutral_text: Color::rgb(228, 228, 231),
            cell_btn_accent_bg: Color::rgb(0, 211, 187),
            cell_btn_accent_text: Color::rgb(8, 77, 73),
            cell_btn_info_bg: Color::rgb(0, 186, 254),
            cell_btn_info_text: Color::rgb(4, 46, 73),
            cell_btn_success_bg: Color::rgb(0, 211, 144),
            cell_btn_success_text: Color::rgb(0, 76, 57),
            cell_btn_warning_bg: Color::rgb(252, 183, 0),
            cell_btn_warning_text: Color::rgb(121, 50, 5),
            cell_btn_radius: 4.0,
            cell_btn_padding_y: 4.0,
            cell_btn_padding_x: 8.0,
            cell_btn_gap: 4.0,
            cell_btn_margin_r: 8.0,
        }
    }

    /// Dimmed theme — GitHub Dimmed-inspired, softer than `dark()`.
    /// Matches `example-common/themes/dimmed.css`.
    pub fn dimmed() -> Self {
        Self {
            // palette
            bg: Color::rgb(34, 39, 46),
            header_bg: Color::rgb(45, 51, 59),
            header_text: Color::rgb(173, 186, 199),
            cell_text: Color::rgb(173, 186, 199),
            grid_line: Color::rgb(55, 62, 71),
            grid_line_width: 1.0,
            column_separator_color: Color::rgb(55, 62, 71),
            column_separator_width: 1.0,
            header_border: Color::rgb(68, 76, 86),
            header_separator_inset: 15.0,
            header_separator_width: 2.0,
            // rgba(56, 139, 253, 0.20) → a = round(0.20 × 255) = 51
            selection_fill: Color::rgba(56, 139, 253, 51),
            // rgba(56, 139, 253, 0.85) → a = round(0.85 × 255) = 217
            selection_border: Color::rgba(56, 139, 253, 217),
            // rgba(56, 139, 253, 0.12) → a = round(0.12 × 255) = 31
            header_selection_fill: Color::rgba(56, 139, 253, 31),
            gutter_selection_fill: Color::rgba(56, 139, 253, 31),
            scrollbar_track: Color::rgb(28, 33, 40),
            // rgba(176, 188, 200, 0.30) → a = round(0.30 × 255) = 77
            scrollbar_thumb: Color::rgba(176, 188, 200, 77),
            row_alt_bg: Color::rgb(34, 39, 46),
            // rgba(255, 255, 255, 0.04) → a = round(0.04 × 255) = 10
            row_hover_bg: Color::rgba(255, 255, 255, 10),
            // checked row — same primary as checkbox_checked_bg, subtle tint
            checked_row_bg: Color::rgba(99, 80, 240, 26),
            // locked cell — rgba(255, 255, 255, 0.035) → a = 9
            locked_cell_bg: Color::rgba(255, 255, 255, 9),
            locked_cell_text: Color::rgba(173, 186, 199, 140),
            // invalid cell — same red as `cell_btn_danger_bg` below
            // rgba(218, 54, 51, 0.08) → a = round(0.08 × 255) = 20
            invalid_cell_bg: Color::rgba(218, 54, 51, 20),
            invalid_cell_border: Color::rgb(218, 54, 51),
            invalid_cell_border_width: 1.0,
            decoration_border_width: 1.5,
            // row / header dimensions
            header_height: 48.0,
            row_height: 42.0,
            // typography
            font_size: 14.0,
            header_font_size: 14.0,
            header_font_bold: true,
            header_font_italic: false,
            // flash
            flash_fill: Color::rgba(255, 220, 0, 255),
            flash_border: Color::rgba(255, 220, 0, 255),
            flash_error_fill: Color::rgba(220, 38, 38, 255),
            // search
            search_highlight: Color::rgba(255, 213, 0, 77),
            search_current: Color::rgba(255, 165, 0, 140),
            // skeleton — rgba(99, 110, 123, 0.78) → a = 199
            skeleton_fg: Color::rgba(99, 110, 123, 199),
            // progress bar — track = muted slate, fill = brighter primary
            progress_track: Color::rgb(55, 62, 71),
            progress_fill: Color::rgb(99, 80, 240),
            progress_height: 8.0,
            progress_radius: 8.0,
            // checkbox — border = muted slate, checked fill = brighter primary
            checkbox_size: 18.0,
            checkbox_radius: 4.0,
            checkbox_border: Color::rgb(90, 98, 110),
            checkbox_border_width: 1.5,
            checkbox_checked_bg: Color::rgb(99, 80, 240),
            checkbox_mark_color: Color::rgb(255, 255, 255),
            // spacing
            cell_padding: 12.0,
            // scrollbar
            scrollbar_width: 14.0,
            scrollbar_radius: 6.0,
            scrollbar_inset: 3.0,
            // column drag
            drag_overlay: Color::rgba(0, 0, 0, 8),
            drag_ghost_bg: Color::rgb(45, 51, 59),
            drag_ghost_text: Color::rgb(173, 186, 199),
            drag_insert_line_width: 1.0,
            drag_ghost_radius: 4.0,
            drag_ghost_border_width: 1.0,
            drag_anim_alpha: 0.5,
            // sort indicator
            sort_arrow_width: 4.0,
            sort_arrow_height: 3.5,
            // column header menu icon
            // rgba(173, 186, 199, 0.70) → a = 178
            header_menu_icon: Color::rgba(173, 186, 199, 178),
            // rgba(255, 255, 255, 0.06) → a = 15
            header_menu_icon_hover_bg: Color::rgba(255, 255, 255, 15),
            header_menu_icon_radius: 3.0,
            header_menu_icon_margin_r: 10.0,
            header_menu_icon_btn_w: 22.0,
            header_menu_icon_btn_h: 22.0,
            header_menu_icon_dot_r: 1.2,
            // pinned columns
            pinned_bg: Color::rgb(34, 39, 46),
            pinned_header_bg: Color::rgb(45, 51, 59),
            pinned_separator_color: Color::rgb(68, 76, 86),
            pinned_separator_width: 1.0,
            // row-number gutter
            // rgba(173, 186, 199, 0.70) → a = 178
            gutter_bg: Color::rgb(45, 51, 59),
            gutter_text: Color::rgba(173, 186, 199, 178),
            gutter_font_size: 14.0,
            gutter_font_bold: true,
            gutter_font_italic: false,
            gutter_border: Color::rgb(68, 76, 86),
            // cell buttons — DaisyUI's built-in `dim` theme, same source
            // as the 5 semantic colors below (see class_map_data.rs-style
            // oklch → sRGB conversion of daisyui's theme/dim/object.js).
            cell_btn_primary_bg: Color::rgb(159, 232, 141),
            cell_btn_primary_text: Color::rgb(9, 19, 7),
            cell_btn_secondary_bg: Color::rgb(255, 125, 93),
            cell_btn_secondary_text: Color::rgb(22, 5, 3),
            cell_btn_danger_bg: Color::rgb(255, 174, 155),
            cell_btn_danger_text: Color::rgb(22, 11, 9),
            // rgba(159, 232, 141, 0.78) → a = 200
            cell_btn_ghost_color: Color::rgba(159, 232, 141, 200),
            // DaisyUI's built-in `dim` theme.
            cell_btn_neutral_bg: Color::rgb(28, 33, 43),
            cell_btn_neutral_text: Color::rgb(178, 204, 214),
            cell_btn_accent_bg: Color::rgb(199, 146, 233),
            cell_btn_accent_text: Color::rgb(14, 8, 19),
            cell_btn_info_bg: Color::rgb(40, 235, 255),
            cell_btn_info_text: Color::rgb(1, 19, 22),
            cell_btn_success_bg: Color::rgb(98, 239, 189),
            cell_btn_success_text: Color::rgb(3, 20, 13),
            cell_btn_warning_bg: Color::rgb(239, 208, 87),
            cell_btn_warning_text: Color::rgb(20, 16, 3),
            cell_btn_radius: 4.0,
            cell_btn_padding_y: 4.0,
            cell_btn_padding_x: 8.0,
            cell_btn_gap: 4.0,
            cell_btn_margin_r: 8.0,
        }
    }
}

impl Default for Theme {
    fn default() -> Self {
        Self::light()
    }
}

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

    #[test]
    fn default_is_light() {
        assert_eq!(Theme::default(), Theme::light());
    }

    #[test]
    fn light_and_dark_differ() {
        assert_ne!(Theme::light(), Theme::dark());
    }

    // ── light theme sanity checks ───────────────────────────

    #[test]
    fn light_bg_is_white() {
        let t = Theme::light();
        assert_eq!(t.bg, Color::rgb(255, 255, 255));
    }

    #[test]
    fn light_header_height_positive() {
        let t = Theme::light();
        assert!(t.header_height > 0.0);
    }

    #[test]
    fn light_row_height_positive() {
        let t = Theme::light();
        assert!(t.row_height > 0.0);
    }

    #[test]
    fn light_font_sizes_positive() {
        let t = Theme::light();
        assert!(t.font_size > 0.0);
        assert!(t.header_font_size > 0.0);
    }

    #[test]
    fn light_cell_padding_positive() {
        let t = Theme::light();
        assert!(t.cell_padding > 0.0);
    }

    #[test]
    fn light_scrollbar_dimensions_positive() {
        let t = Theme::light();
        assert!(t.scrollbar_width > 0.0);
        assert!(t.scrollbar_radius > 0.0);
        assert!(t.scrollbar_inset >= 0.0);
    }

    #[test]
    fn light_selection_fill_is_semi_transparent() {
        let t = Theme::light();
        assert!(t.selection_fill.a > 0);
        assert!(t.selection_fill.a < 255);
    }

    #[test]
    fn light_locked_cell_bg_is_subtle() {
        let t = Theme::light();
        assert!(t.locked_cell_bg.a > 0 && t.locked_cell_bg.a < 40);
    }

    #[test]
    fn dark_locked_cell_bg_is_subtle() {
        let t = Theme::dark();
        assert!(t.locked_cell_bg.a > 0 && t.locked_cell_bg.a < 40);
    }

    #[test]
    fn dimmed_locked_cell_bg_is_subtle() {
        let t = Theme::dimmed();
        assert!(t.locked_cell_bg.a > 0 && t.locked_cell_bg.a < 40);
    }

    #[test]
    fn light_invalid_cell_border_is_opaque_and_positive_width() {
        let t = Theme::light();
        assert_eq!(t.invalid_cell_border.a, 255);
        assert!(t.invalid_cell_border_width > 0.0);
    }

    #[test]
    fn dark_invalid_cell_border_is_opaque_and_positive_width() {
        let t = Theme::dark();
        assert_eq!(t.invalid_cell_border.a, 255);
        assert!(t.invalid_cell_border_width > 0.0);
    }

    #[test]
    fn dimmed_invalid_cell_border_is_opaque_and_positive_width() {
        let t = Theme::dimmed();
        assert_eq!(t.invalid_cell_border.a, 255);
        assert!(t.invalid_cell_border_width > 0.0);
    }

    #[test]
    fn light_invalid_cell_bg_is_subtle() {
        let t = Theme::light();
        assert!(t.invalid_cell_bg.a > 0 && t.invalid_cell_bg.a < 40);
    }

    #[test]
    fn dark_invalid_cell_bg_is_subtle() {
        let t = Theme::dark();
        assert!(t.invalid_cell_bg.a > 0 && t.invalid_cell_bg.a < 40);
    }

    #[test]
    fn dimmed_invalid_cell_bg_is_subtle() {
        let t = Theme::dimmed();
        assert!(t.invalid_cell_bg.a > 0 && t.invalid_cell_bg.a < 40);
    }

    #[test]
    fn light_decoration_border_width_positive() {
        assert!(Theme::light().decoration_border_width > 0.0);
    }

    #[test]
    fn dark_decoration_border_width_positive() {
        assert!(Theme::dark().decoration_border_width > 0.0);
    }

    #[test]
    fn dimmed_decoration_border_width_positive() {
        assert!(Theme::dimmed().decoration_border_width > 0.0);
    }

    #[test]
    fn light_column_separator_width_positive() {
        assert!(Theme::light().column_separator_width > 0.0);
    }

    #[test]
    fn dark_column_separator_width_positive() {
        assert!(Theme::dark().column_separator_width > 0.0);
    }

    #[test]
    fn dimmed_column_separator_width_positive() {
        assert!(Theme::dimmed().column_separator_width > 0.0);
    }

    #[test]
    fn light_column_separator_color_matches_grid_line_by_default() {
        let t = Theme::light();
        assert_eq!(t.column_separator_color, t.grid_line);
    }

    #[test]
    fn dark_column_separator_color_matches_grid_line_by_default() {
        let t = Theme::dark();
        assert_eq!(t.column_separator_color, t.grid_line);
    }

    #[test]
    fn dimmed_column_separator_color_matches_grid_line_by_default() {
        let t = Theme::dimmed();
        assert_eq!(t.column_separator_color, t.grid_line);
    }

    #[test]
    fn light_header_font_bold() {
        assert!(Theme::light().header_font_bold);
    }

    #[test]
    fn light_drag_anim_alpha_in_range() {
        let t = Theme::light();
        assert!(t.drag_anim_alpha > 0.0);
        assert!(t.drag_anim_alpha <= 1.0);
    }

    #[test]
    fn light_sort_arrow_dimensions_positive() {
        let t = Theme::light();
        assert!(t.sort_arrow_width > 0.0);
        assert!(t.sort_arrow_height > 0.0);
    }

    // ── dark theme sanity checks ────────────────────────────

    #[test]
    fn dark_bg_is_dark() {
        let t = Theme::dark();
        // All channels below 50 indicates a dark background.
        assert!(t.bg.r < 50 && t.bg.g < 50 && t.bg.b < 50);
    }

    #[test]
    fn dark_header_height_positive() {
        assert!(Theme::dark().header_height > 0.0);
    }

    #[test]
    fn dark_row_height_positive() {
        assert!(Theme::dark().row_height > 0.0);
    }

    #[test]
    fn dark_cell_text_is_light() {
        let t = Theme::dark();
        // At least one channel above 150 to be readable on dark bg.
        assert!(
            t.cell_text.r > 150 || t.cell_text.g > 150 || t.cell_text.b > 150
        );
    }

    #[test]
    fn dark_font_sizes_positive() {
        let t = Theme::dark();
        assert!(t.font_size > 0.0);
        assert!(t.header_font_size > 0.0);
    }

    // ── clone / equality ────────────────────────────────────

    #[test]
    fn theme_clone_equals_original() {
        let t = Theme::light();
        let t2 = t.clone();
        assert_eq!(t, t2);
    }

    #[test]
    fn theme_debug_does_not_panic() {
        let _ = format!("{:?}", Theme::light());
        let _ = format!("{:?}", Theme::dark());
        let _ = format!("{:?}", Theme::dimmed());
    }

    // ── dimmed theme sanity checks ───────────────────────────

    #[test]
    fn dimmed_bg_is_dark() {
        let t = Theme::dimmed();
        assert!(t.bg.r < 100 && t.bg.g < 100 && t.bg.b < 100);
    }

    #[test]
    fn dimmed_cell_text_is_light() {
        let t = Theme::dimmed();
        // Check that at least one channel is bright (dimmed theme
        // has light text). Evaluate each channel separately so
        // coverage instruments every branch.
        let bright = [t.cell_text.r, t.cell_text.g, t.cell_text.b]
            .iter()
            .any(|&c| c > 100);
        assert!(bright);
    }

    #[test]
    fn dimmed_header_height_positive() {
        assert!(Theme::dimmed().header_height > 0.0);
    }

    #[test]
    fn dimmed_row_height_positive() {
        assert!(Theme::dimmed().row_height > 0.0);
    }

    #[test]
    fn dimmed_font_sizes_positive() {
        let t = Theme::dimmed();
        assert!(t.font_size > 0.0);
        assert!(t.header_font_size > 0.0);
    }

    #[test]
    fn dimmed_scrollbar_dimensions_positive() {
        let t = Theme::dimmed();
        assert!(t.scrollbar_width > 0.0);
        assert!(t.scrollbar_radius > 0.0);
        assert!(t.scrollbar_inset >= 0.0);
    }

    #[test]
    fn dimmed_drag_anim_alpha_in_range() {
        let t = Theme::dimmed();
        assert!(t.drag_anim_alpha > 0.0);
        assert!(t.drag_anim_alpha <= 1.0);
    }

    #[test]
    fn dimmed_differs_from_light_and_dark() {
        assert_ne!(Theme::dimmed(), Theme::light());
        assert_ne!(Theme::dimmed(), Theme::dark());
    }
}
