//! Theme and colour palette.
//!
//! Two themes are provided — one for light terminal backgrounds
//! and one for dark — per NFR-7. The palette is intentionally
//! small for the walking skeleton; it grows as more views are
//! added. Contrast is chosen against the target background so
//! that foreground text meets WCAG-AA (NFR-5) on both variants.
//!
//! Per-token-class colours (the `tok_*` fields) drive ambient
//! typing assistance (ADR-0022 §3). Each token class has a
//! distinct colour so the user can tell keywords from
//! identifiers from string literals at a glance, with punct and
//! identifier intentionally close to `fg` to keep the surface
//! quiet for the dominant content. The `tok_error` colour
//! reuses the existing error palette so lex-error tokens and
//! parse-error overlays read consistently with `[error]`
//! lines elsewhere.

use ratatui::style::Color;

use crate::dsl::grammar::HighlightClass;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Background {
    Light,
    Dark,
}

#[derive(Debug, Clone)]
pub struct Theme {
    pub background: Background,
    pub bg: Color,
    pub fg: Color,
    pub muted: Color,
    pub border: Color,
    pub border_advanced: Color,
    pub mode_simple: Color,
    pub mode_advanced: Color,
    pub system: Color,
    pub error: Color,
    /// Validity-indicator WARNING colour (ADR-0027 §4) — an
    /// amber distinct from `error`'s red. Drives the `[WRN]`
    /// label; `[ERR]` reuses `error`. Also the query-plan
    /// "expensive step" colour (ADR-0028 §6).
    pub warning: Color,
    /// Query-plan "efficient step" colour (ADR-0028 §6) — a
    /// green deliberately distinct from `system` so green never
    /// reads as two things. Indexed lookups carry it; expensive
    /// steps reuse `warning`.
    pub plan_efficient: Color,
    // ---- Per-token-class colours (ADR-0022 §3) -------------------
    pub tok_keyword: Color,
    pub tok_identifier: Color,
    /// Column data-type keyword colour (ADR-0022 Amendment 4) —
    /// a dedicated tone distinct from both `tok_keyword` and
    /// `tok_identifier` so a learner can tell a type from a
    /// clause keyword or an invented name at a glance.
    pub tok_type: Color,
    pub tok_number: Color,
    pub tok_string: Color,
    pub tok_punct: Color,
    pub tok_flag: Color,
    pub tok_error: Color,
    /// SQL function-name candidate colour (ADR-0022 Amendment 6,
    /// issue #15) — a dedicated tone distinct from `tok_keyword`,
    /// `tok_identifier`, and `tok_type` so a learner can tell a
    /// callable (`sum`, `upper`) apart from a clause keyword, a
    /// column reference, and a column type. Drives the
    /// `CandidateKind::Function` colour in the hint panel.
    pub tok_function: Color,
}

impl Theme {
    #[must_use]
    pub const fn dark() -> Self {
        Self {
            background: Background::Dark,
            bg: Color::Rgb(0x18, 0x1B, 0x22),
            fg: Color::Rgb(0xE6, 0xE6, 0xE6),
            muted: Color::Rgb(0x8B, 0x90, 0x9A),
            border: Color::Rgb(0x4A, 0x52, 0x65),
            border_advanced: Color::Rgb(0xE0, 0x60, 0x60),
            mode_simple: Color::Rgb(0x6E, 0xC4, 0xFF),
            mode_advanced: Color::Rgb(0xFF, 0x9E, 0x6B),
            system: Color::Rgb(0x9F, 0xD8, 0x91),
            error: Color::Rgb(0xFF, 0x6B, 0x6B),
            warning: Color::Rgb(0xF5, 0xA9, 0x4B), // amber
            plan_efficient: Color::Rgb(0x4D, 0xD0, 0xA8), // teal-green

            // Token classes — distinct enough to tell apart at a
            // glance, quiet enough that 80-char lines don't read
            // like a Christmas tree. Identifier and punct sit
            // close to `fg`/`muted` so the dominant content
            // remains restful; literals and flags get warm
            // accent tones; keyword takes a cool accent tone
            // distinct from the mode-banner blue.
            tok_keyword: Color::Rgb(0xC7, 0x92, 0xEA),    // muted purple
            tok_identifier: Color::Rgb(0x56, 0xB6, 0xC2), // cyan-teal — identifiers are the user's content, deserve a vivid distinct colour
            tok_type: Color::Rgb(0xF0, 0x8F, 0xC0),       // pink — types sit in the red-purple range, clearly apart from the lavender keyword and teal identifier
            tok_number: Color::Rgb(0xF7, 0x8C, 0x6C),     // warm orange
            tok_string: Color::Rgb(0xC3, 0xE8, 0x8D),     // soft green
            tok_punct: Color::Rgb(0x8B, 0x90, 0x9A),      // == muted
            tok_flag: Color::Rgb(0xFF, 0xCB, 0x6B),       // amber
            tok_error: Color::Rgb(0xFF, 0x6B, 0x6B),      // == error
            tok_function: Color::Rgb(0x82, 0xCF, 0xFD),   // sky blue — cool like keyword but bluer, clearly apart from purple keyword + teal identifier + pink type
        }
    }

    #[must_use]
    pub const fn light() -> Self {
        Self {
            background: Background::Light,
            bg: Color::Rgb(0xFA, 0xFA, 0xF7),
            fg: Color::Rgb(0x1A, 0x1F, 0x2C),
            muted: Color::Rgb(0x60, 0x66, 0x73),
            border: Color::Rgb(0xB6, 0xBC, 0xC8),
            border_advanced: Color::Rgb(0xC2, 0x3A, 0x3A),
            mode_simple: Color::Rgb(0x21, 0x69, 0xC7),
            mode_advanced: Color::Rgb(0xB0, 0x4A, 0x12),
            system: Color::Rgb(0x2E, 0x7C, 0x3C),
            error: Color::Rgb(0xC0, 0x39, 0x2B),
            warning: Color::Rgb(0xA6, 0x5A, 0x00), // burnt amber
            plan_efficient: Color::Rgb(0x0B, 0x80, 0x6A), // deep teal-green

            // Light-theme token palette: same intent as dark —
            // identifier/punct close to fg/muted; warm tones for
            // literals + flags; cool accent for keyword.
            tok_keyword: Color::Rgb(0x6F, 0x42, 0xC1),    // royal purple
            tok_identifier: Color::Rgb(0x0F, 0x6B, 0x76), // deep teal — same role as dark variant: identifiers stand out
            tok_type: Color::Rgb(0xA8, 0x2D, 0x73),       // deep magenta — red-purple, distinct from royal-purple keyword + teal identifier
            tok_number: Color::Rgb(0xBC, 0x4F, 0x1F),     // burnt orange
            tok_string: Color::Rgb(0x22, 0x86, 0x3A),     // forest green
            tok_punct: Color::Rgb(0x60, 0x66, 0x73),      // == muted
            tok_flag: Color::Rgb(0xB0, 0x88, 0x00),       // mustard
            tok_error: Color::Rgb(0xC0, 0x39, 0x2B),      // == error
            tok_function: Color::Rgb(0x1A, 0x5F, 0xB0),   // strong blue — cool like keyword but bluer, apart from royal-purple keyword + teal identifier + magenta type
        }
    }

    /// Map a walker `HighlightClass` to its display colour
    /// (ADR-0024 §architecture, Phase F). This is the walker-side
    /// equivalent of `token_color` — the renderer consumes
    /// `walker::highlight_runs` output, which produces
    /// `HighlightClass` per byte range, and looks up colours
    /// through this method.
    #[must_use]
    pub const fn highlight_class_color(&self, class: HighlightClass) -> Color {
        match class {
            HighlightClass::Keyword => self.tok_keyword,
            HighlightClass::Identifier => self.tok_identifier,
            HighlightClass::Type => self.tok_type,
            HighlightClass::Number => self.tok_number,
            HighlightClass::String => self.tok_string,
            HighlightClass::Punct => self.tok_punct,
            HighlightClass::Flag => self.tok_flag,
            HighlightClass::Error => self.tok_error,
        }
    }
}

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

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

    #[test]
    fn dark_theme_token_colours_differ_from_background() {
        let t = Theme::dark();
        for (name, c) in [
            ("tok_keyword", t.tok_keyword),
            ("tok_type", t.tok_type),
            ("tok_number", t.tok_number),
            ("tok_string", t.tok_string),
            ("tok_flag", t.tok_flag),
            ("tok_error", t.tok_error),
            ("tok_function", t.tok_function),
            ("warning", t.warning),
        ] {
            assert_ne!(
                c, t.bg,
                "{name} must contrast against bg in dark theme",
            );
        }
    }

    #[test]
    fn light_theme_token_colours_differ_from_background() {
        let t = Theme::light();
        for (name, c) in [
            ("tok_keyword", t.tok_keyword),
            ("tok_type", t.tok_type),
            ("tok_number", t.tok_number),
            ("tok_string", t.tok_string),
            ("tok_flag", t.tok_flag),
            ("tok_error", t.tok_error),
            ("tok_function", t.tok_function),
            ("warning", t.warning),
        ] {
            assert_ne!(
                c, t.bg,
                "{name} must contrast against bg in light theme",
            );
        }
    }

    #[test]
    fn highlight_class_color_maps_each_variant() {
        let t = Theme::dark();
        assert_eq!(t.highlight_class_color(HighlightClass::Keyword), t.tok_keyword);
        assert_eq!(t.highlight_class_color(HighlightClass::Identifier), t.tok_identifier);
        assert_eq!(t.highlight_class_color(HighlightClass::Type), t.tok_type);
        assert_eq!(t.highlight_class_color(HighlightClass::Number), t.tok_number);
        assert_eq!(t.highlight_class_color(HighlightClass::String), t.tok_string);
        assert_eq!(t.highlight_class_color(HighlightClass::Punct), t.tok_punct);
        assert_eq!(t.highlight_class_color(HighlightClass::Flag), t.tok_flag);
        assert_eq!(t.highlight_class_color(HighlightClass::Error), t.tok_error);
    }

    #[test]
    fn type_colour_is_distinct_from_keyword_and_identifier() {
        // ADR-0022 Amendment 4 / issue #8: the whole point of a
        // dedicated type class is that types do NOT share a colour
        // with clause keywords or invented identifiers.
        for t in [Theme::dark(), Theme::light()] {
            assert_ne!(t.tok_type, t.tok_keyword);
            assert_ne!(t.tok_type, t.tok_identifier);
        }
    }

    #[test]
    fn function_colour_is_distinct_from_keyword_identifier_and_type() {
        // ADR-0022 Amendment 6 / issue #15: function-name candidates
        // get their own tone so a callable reads apart from a clause
        // keyword, a column reference, and a column type.
        for t in [Theme::dark(), Theme::light()] {
            assert_ne!(t.tok_function, t.tok_keyword);
            assert_ne!(t.tok_function, t.tok_identifier);
            assert_ne!(t.tok_function, t.tok_type);
        }
    }
}
