/// Semantic color tokens and the Color type.
///
/// Prefer semantic tokens (Primary, Surface, etc.) over explicit values.
/// The theme maps tokens to actual colors — swap the theme, everything updates.
/// Use Hex/Rgba only as an escape hatch when you need a one-off value.

/// A color value — either a semantic token or an explicit value.
#[derive(Debug, Clone, PartialEq)]
pub enum Color {
    // --- Semantic tokens (preferred) ---
    /// Brand primary color. Use for key actions and highlights.
    Primary,
    /// Content drawn on top of Primary.
    OnPrimary,
    /// A less prominent container for primary-branded content.
    PrimaryContainer,

    /// Secondary accent color.
    Secondary,
    /// Content drawn on top of Secondary.
    OnSecondary,

    /// The page/screen background.
    Background,
    /// Content drawn on top of Background.
    OnBackground,

    /// Surface color for cards, sheets, dialogs.
    Surface,
    /// Content drawn on top of Surface.
    OnSurface,
    /// A surface variant, slightly different from Surface.
    SurfaceVariant,

    /// Error state color.
    Error,
    /// Content drawn on top of Error.
    OnError,

    /// Outline/border color.
    Outline,
    /// Muted outline (dividers, subtle borders).
    OutlineVariant,

    // --- Escape hatches ---
    /// An explicit hex color string (e.g. "#3b82f6" or "#3b82f6ff").
    Hex(String),
    /// An explicit RGBA color (channels 0–255, alpha 0.0–1.0).
    Rgba(u8, u8, u8, f32),

    /// Apply opacity to any color.
    Opacity(Box<Color>, f32),
}

impl Color {
    /// Create an Opacity variant.
    pub fn with_opacity(self, opacity: f32) -> Self {
        Color::Opacity(Box::new(self), opacity.clamp(0.0, 1.0))
    }

    /// Convenience: fully transparent.
    pub fn transparent() -> Self {
        Color::Rgba(0, 0, 0, 0.0)
    }

    /// Convenience: pure white.
    pub fn white() -> Self {
        Color::Rgba(255, 255, 255, 1.0)
    }

    /// Convenience: pure black.
    pub fn black() -> Self {
        Color::Rgba(0, 0, 0, 1.0)
    }

    /// Resolve a hex string like "#rrggbb" or "#rrggbbaa" to Rgba.
    /// Returns None if the string is not a valid hex color.
    pub fn parse_hex(s: &str) -> Option<Self> {
        let s = s.trim_start_matches('#');
        match s.len() {
            6 => {
                let r = u8::from_str_radix(&s[0..2], 16).ok()?;
                let g = u8::from_str_radix(&s[2..4], 16).ok()?;
                let b = u8::from_str_radix(&s[4..6], 16).ok()?;
                Some(Color::Rgba(r, g, b, 1.0))
            }
            8 => {
                let r = u8::from_str_radix(&s[0..2], 16).ok()?;
                let g = u8::from_str_radix(&s[2..4], 16).ok()?;
                let b = u8::from_str_radix(&s[4..6], 16).ok()?;
                let a = u8::from_str_radix(&s[6..8], 16).ok()?;
                Some(Color::Rgba(r, g, b, a as f32 / 255.0))
            }
            _ => None,
        }
    }
}

/// Maps semantic color tokens to actual RGBA values.
/// One ColorScheme per theme mode (light/dark).
#[derive(Debug, Clone)]
pub struct ColorScheme {
    pub primary: (u8, u8, u8, f32),
    pub on_primary: (u8, u8, u8, f32),
    pub primary_container: (u8, u8, u8, f32),
    pub secondary: (u8, u8, u8, f32),
    pub on_secondary: (u8, u8, u8, f32),
    pub background: (u8, u8, u8, f32),
    pub on_background: (u8, u8, u8, f32),
    pub surface: (u8, u8, u8, f32),
    pub on_surface: (u8, u8, u8, f32),
    pub surface_variant: (u8, u8, u8, f32),
    pub error: (u8, u8, u8, f32),
    pub on_error: (u8, u8, u8, f32),
    pub outline: (u8, u8, u8, f32),
    pub outline_variant: (u8, u8, u8, f32),
}

impl ColorScheme {
    /// Default light color scheme.
    pub fn light() -> Self {
        Self {
            primary: (59, 130, 246, 1.0),         // blue-500
            on_primary: (255, 255, 255, 1.0),
            primary_container: (219, 234, 254, 1.0), // blue-100
            secondary: (100, 116, 139, 1.0),       // slate-500
            on_secondary: (255, 255, 255, 1.0),
            background: (255, 255, 255, 1.0),
            on_background: (15, 23, 42, 1.0),      // slate-900
            surface: (248, 250, 252, 1.0),         // slate-50
            on_surface: (15, 23, 42, 1.0),
            surface_variant: (241, 245, 249, 1.0), // slate-100
            error: (239, 68, 68, 1.0),             // red-500
            on_error: (255, 255, 255, 1.0),
            outline: (203, 213, 225, 1.0),         // slate-300
            outline_variant: (226, 232, 240, 1.0), // slate-200
        }
    }

    /// Default dark color scheme.
    pub fn dark() -> Self {
        Self {
            primary: (96, 165, 250, 1.0),          // blue-400
            on_primary: (15, 23, 42, 1.0),
            primary_container: (30, 58, 138, 1.0), // blue-900
            secondary: (148, 163, 184, 1.0),       // slate-400
            on_secondary: (15, 23, 42, 1.0),
            background: (15, 23, 42, 1.0),         // slate-900
            on_background: (248, 250, 252, 1.0),
            surface: (30, 41, 59, 1.0),            // slate-800
            on_surface: (248, 250, 252, 1.0),
            surface_variant: (51, 65, 85, 1.0),    // slate-700
            error: (248, 113, 113, 1.0),           // red-400
            on_error: (15, 23, 42, 1.0),
            outline: (71, 85, 105, 1.0),           // slate-600
            outline_variant: (51, 65, 85, 1.0),    // slate-700
        }
    }

    /// Resolve a semantic Color token to its RGBA tuple.
    pub fn resolve(&self, color: &Color) -> (u8, u8, u8, f32) {
        match color {
            Color::Primary => self.primary,
            Color::OnPrimary => self.on_primary,
            Color::PrimaryContainer => self.primary_container,
            Color::Secondary => self.secondary,
            Color::OnSecondary => self.on_secondary,
            Color::Background => self.background,
            Color::OnBackground => self.on_background,
            Color::Surface => self.surface,
            Color::OnSurface => self.on_surface,
            Color::SurfaceVariant => self.surface_variant,
            Color::Error => self.error,
            Color::OnError => self.on_error,
            Color::Outline => self.outline,
            Color::OutlineVariant => self.outline_variant,
            Color::Hex(s) => Color::parse_hex(s)
                .map(|c| self.resolve(&c))
                .unwrap_or((0, 0, 0, 1.0)),
            Color::Rgba(r, g, b, a) => (*r, *g, *b, *a),
            Color::Opacity(inner, opacity) => {
                let (r, g, b, a) = self.resolve(inner);
                (r, g, b, a * opacity)
            }
        }
    }
}

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

    #[test]
    fn parse_hex_6_chars() {
        let c = Color::parse_hex("#3b82f6").unwrap();
        assert_eq!(c, Color::Rgba(0x3b, 0x82, 0xf6, 1.0));
    }

    #[test]
    fn parse_hex_8_chars() {
        let c = Color::parse_hex("#3b82f680").unwrap();
        if let Color::Rgba(r, g, b, a) = c {
            assert_eq!(r, 0x3b);
            assert_eq!(g, 0x82);
            assert_eq!(b, 0xf6);
            assert!((a - 0x80 as f32 / 255.0).abs() < 0.01);
        } else {
            panic!("expected Rgba");
        }
    }

    #[test]
    fn parse_hex_invalid() {
        assert!(Color::parse_hex("not-a-color").is_none());
        assert!(Color::parse_hex("#gg0000").is_none());
    }

    #[test]
    fn with_opacity() {
        let c = Color::Primary.with_opacity(0.5);
        assert!(matches!(c, Color::Opacity(_, _)));
    }

    #[test]
    fn color_scheme_light_resolves_primary() {
        let scheme = ColorScheme::light();
        let (r, g, b, a) = scheme.resolve(&Color::Primary);
        assert_eq!((r, g, b), (59, 130, 246));
        assert!((a - 1.0).abs() < 0.001);
    }

    #[test]
    fn color_scheme_dark_resolves_background() {
        let scheme = ColorScheme::dark();
        let (r, g, b, _) = scheme.resolve(&Color::Background);
        assert_eq!((r, g, b), (15, 23, 42));
    }

    #[test]
    fn opacity_modifies_alpha() {
        let scheme = ColorScheme::light();
        let color = Color::Primary.with_opacity(0.5);
        let (_, _, _, a) = scheme.resolve(&color);
        assert!((a - 0.5).abs() < 0.001);
    }

    #[test]
    fn rgba_passthrough() {
        let scheme = ColorScheme::light();
        let color = Color::Rgba(10, 20, 30, 0.8);
        let (r, g, b, a) = scheme.resolve(&color);
        assert_eq!((r, g, b), (10, 20, 30));
        assert!((a - 0.8).abs() < 0.001);
    }
}
