use serde::{Deserialize, Serialize};

/// Describes the colour capability level of the terminal.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ColorMode {
    /// No colour support; only plain text.
    Monochrome,
    /// Standard 16-colour ANSI palette.
    Ansi16,
    /// Extended 256-colour palette.
    Ansi256,
    /// Full 24-bit RGB colours.
    TrueColor,
}

impl ColorMode {
    /// Detects the best colour mode available by inspecting the `COLORTERM`
    /// environment variable.
    ///
    /// * `"truecolor"` or `"24bit"` -> [`TrueColor`](ColorMode::TrueColor)
    /// * any other non-empty value     -> [`Ansi256`](ColorMode::Ansi256)
    /// * variable unset / empty         -> [`Ansi16`](ColorMode::Ansi16)
    pub fn detect() -> Self {
        match std::env::var("COLORTERM").ok().as_deref() {
            Some("truecolor") | Some("24bit") => ColorMode::TrueColor,
            Some(_) => ColorMode::Ansi256,
            None => ColorMode::Ansi16,
        }
    }
}

impl Default for ColorMode {
    fn default() -> Self {
        ColorMode::detect()
    }
}

/// A terminal colour value.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum Color {
    /// Use the terminal's default foreground / background.
    Default,
    /// An index into the ANSI 256-colour palette (0-255).
    Ansi(u8),
    /// A 24-bit true-colour value.
    Rgb(u8, u8, u8),
}

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

/// Visual style applied to a character cell.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct Style {
    pub fg: Color,
    pub bg: Color,
    pub bold: bool,
    pub dim: bool,
}

impl Default for Style {
    fn default() -> Self {
        Self {
            fg: Color::Default,
            bg: Color::Default,
            bold: false,
            dim: false,
        }
    }
}

/// A set of semantic colours used by the renderer.
///
/// The default theme uses green for bullish elements, red for bearish elements,
/// white for axes, and gray for the grid.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Theme {
    bull: Color,
    bear: Color,
    line: Color,
    axis: Color,
    grid: Color,
    text: Color,
    label: Color,
    overlay_palette: Vec<Color>,
}

impl Theme {
    /// Colour used for bullish (up) candles / segments.
    pub fn bull_color(&self) -> Color {
        self.bull
    }

    /// Colour used for bearish (down) candles / segments.
    pub fn bear_color(&self) -> Color {
        self.bear
    }

    /// Colour used for line charts.
    pub fn line_color(&self) -> Color {
        self.line
    }

    /// Colour used for axes.
    pub fn axis_color(&self) -> Color {
        self.axis
    }

    /// Colour used for grid lines.
    pub fn grid_color(&self) -> Color {
        self.grid
    }

    /// Colour used for general text (titles).
    pub fn text_color(&self) -> Color {
        self.text
    }

    /// Colour used for axis tick labels (price / time values).
    pub fn label_color(&self) -> Color {
        self.label
    }

    /// Returns a colour for the overlay at the given index, cycling through
    /// the palette if the index exceeds the palette size.
    pub fn overlay_colors(&self, index: usize) -> Color {
        if self.overlay_palette.is_empty() {
            return Color::Default;
        }
        self.overlay_palette[index % self.overlay_palette.len()]
    }
}

/// Named built-in themes.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ThemeName {
    Default,
    Dracula,
    CatppuccinMocha,
    SolarizedDark,
    Nord,
    Gruvbox,
}

impl ThemeName {
    /// Returns all available theme names.
    pub fn all() -> &'static [ThemeName] {
        &[
            ThemeName::Default,
            ThemeName::Dracula,
            ThemeName::CatppuccinMocha,
            ThemeName::SolarizedDark,
            ThemeName::Nord,
            ThemeName::Gruvbox,
        ]
    }
}

impl std::fmt::Display for ThemeName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ThemeName::Default => write!(f, "default"),
            ThemeName::Dracula => write!(f, "dracula"),
            ThemeName::CatppuccinMocha => write!(f, "catppuccin"),
            ThemeName::SolarizedDark => write!(f, "solarized"),
            ThemeName::Nord => write!(f, "nord"),
            ThemeName::Gruvbox => write!(f, "gruvbox"),
        }
    }
}

impl std::str::FromStr for ThemeName {
    type Err = String;
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "default" => Ok(ThemeName::Default),
            "dracula" => Ok(ThemeName::Dracula),
            "catppuccin" | "catppuccin-mocha" | "catppuccin_mocha" => Ok(ThemeName::CatppuccinMocha),
            "solarized" | "solarized-dark" | "solarized_dark" => Ok(ThemeName::SolarizedDark),
            "nord" => Ok(ThemeName::Nord),
            "gruvbox" => Ok(ThemeName::Gruvbox),
            _ => Err(format!("Unknown theme: {}", s)),
        }
    }
}

impl Theme {
    /// Creates a theme from a named preset.
    pub fn from_name(name: ThemeName) -> Self {
        match name {
            ThemeName::Default => Self::default(),
            ThemeName::Dracula => Self {
                bull: Color::Rgb(80, 250, 123),   // green
                bear: Color::Rgb(255, 85, 85),    // red
                line: Color::Rgb(139, 233, 253),  // cyan
                axis: Color::Rgb(98, 114, 164),   // comment
                grid: Color::Rgb(68, 71, 90),     // current line
                text: Color::Rgb(248, 248, 242),  // foreground
                label: Color::Rgb(189, 147, 249), // purple
                overlay_palette: vec![
                    Color::Rgb(255, 184, 108),     // orange
                    Color::Rgb(241, 250, 140),     // yellow
                    Color::Rgb(255, 121, 198),     // pink
                    Color::Rgb(189, 147, 249),     // purple
                    Color::Rgb(139, 233, 253),     // cyan
                ],
            },
            ThemeName::CatppuccinMocha => Self {
                bull: Color::Rgb(166, 227, 161),   // green
                bear: Color::Rgb(243, 139, 168),   // red
                line: Color::Rgb(137, 180, 250),   // blue
                axis: Color::Rgb(127, 132, 156),   // overlay2
                grid: Color::Rgb(69, 71, 90),      // surface1
                text: Color::Rgb(205, 214, 244),   // text
                label: Color::Rgb(180, 190, 254),  // lavender
                overlay_palette: vec![
                    Color::Rgb(250, 179, 135),      // peach
                    Color::Rgb(249, 226, 175),      // yellow
                    Color::Rgb(245, 194, 231),      // pink
                    Color::Rgb(203, 166, 247),      // mauve
                    Color::Rgb(148, 226, 213),      // teal
                ],
            },
            ThemeName::SolarizedDark => Self {
                bull: Color::Rgb(133, 153, 0),     // green
                bear: Color::Rgb(220, 50, 47),     // red
                line: Color::Rgb(38, 139, 210),    // blue
                axis: Color::Rgb(88, 110, 117),    // base01
                grid: Color::Rgb(7, 54, 66),       // base02
                text: Color::Rgb(238, 232, 213),   // base2
                label: Color::Rgb(147, 161, 161),  // base1
                overlay_palette: vec![
                    Color::Rgb(203, 75, 22),        // orange
                    Color::Rgb(181, 137, 0),        // yellow
                    Color::Rgb(211, 54, 130),       // magenta
                    Color::Rgb(108, 113, 196),      // violet
                    Color::Rgb(42, 161, 152),       // cyan
                ],
            },
            ThemeName::Nord => Self {
                bull: Color::Rgb(163, 190, 140),   // green
                bear: Color::Rgb(191, 97, 106),    // red
                line: Color::Rgb(129, 161, 193),   // blue
                axis: Color::Rgb(76, 86, 106),     // nord3
                grid: Color::Rgb(59, 66, 82),      // nord1
                text: Color::Rgb(236, 239, 244),   // nord6
                label: Color::Rgb(216, 222, 233),  // nord4
                overlay_palette: vec![
                    Color::Rgb(208, 135, 112),      // orange
                    Color::Rgb(235, 203, 139),      // yellow
                    Color::Rgb(180, 142, 173),      // purple
                    Color::Rgb(136, 192, 208),      // frost
                    Color::Rgb(143, 188, 187),      // teal
                ],
            },
            ThemeName::Gruvbox => Self {
                bull: Color::Rgb(184, 187, 38),    // green
                bear: Color::Rgb(251, 73, 52),     // red
                line: Color::Rgb(131, 165, 152),   // blue
                axis: Color::Rgb(146, 131, 116),   // fg4
                grid: Color::Rgb(60, 56, 54),      // bg1
                text: Color::Rgb(235, 219, 178),   // fg
                label: Color::Rgb(189, 174, 147),  // fg3
                overlay_palette: vec![
                    Color::Rgb(254, 128, 25),       // orange
                    Color::Rgb(250, 189, 47),       // yellow
                    Color::Rgb(211, 134, 155),      // purple
                    Color::Rgb(142, 192, 124),      // aqua
                    Color::Rgb(69, 133, 136),       // dark aqua
                ],
            },
        }
    }
}

impl Default for Theme {
    fn default() -> Self {
        Self {
            // Bright green for bullish candles
            bull: Color::Rgb(0, 220, 100),
            // Bright red for bearish candles
            bear: Color::Rgb(240, 60, 60),
            // Blue for line charts
            line: Color::Rgb(60, 140, 255),
            // Dim gray for axis lines (subtle structural element)
            axis: Color::Rgb(90, 90, 110),
            // Darker gray for grid lines
            grid: Color::Rgb(60, 60, 70),
            // Bright for titles (clean white)
            text: Color::Rgb(220, 225, 235),
            // Muted for axis tick labels (soft blue-gray, recedes visually)
            label: Color::Rgb(130, 140, 160),
            // Vibrant overlay palette
            overlay_palette: vec![
                Color::Rgb(255, 175, 50),  // warm orange
                Color::Rgb(80, 180, 255),  // sky blue
                Color::Rgb(255, 220, 60),  // golden yellow
                Color::Rgb(180, 120, 230), // soft purple
                Color::Rgb(255, 140, 100), // salmon
            ],
        }
    }
}

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

    #[test]
    fn theme_from_name_default() {
        let theme = Theme::from_name(ThemeName::Default);
        assert_eq!(theme, Theme::default());
    }

    #[test]
    fn theme_from_name_all_variants() {
        for name in ThemeName::all() {
            let theme = Theme::from_name(*name);
            // Verify non-default colors are set
            assert_ne!(theme.bull_color(), Color::Default);
            assert_ne!(theme.bear_color(), Color::Default);
        }
    }

    #[test]
    fn theme_name_parse() {
        assert_eq!("dracula".parse::<ThemeName>().unwrap(), ThemeName::Dracula);
        assert_eq!("catppuccin-mocha".parse::<ThemeName>().unwrap(), ThemeName::CatppuccinMocha);
        assert_eq!("catppuccin".parse::<ThemeName>().unwrap(), ThemeName::CatppuccinMocha);
        assert_eq!("solarized".parse::<ThemeName>().unwrap(), ThemeName::SolarizedDark);
        assert_eq!("nord".parse::<ThemeName>().unwrap(), ThemeName::Nord);
        assert_eq!("gruvbox".parse::<ThemeName>().unwrap(), ThemeName::Gruvbox);
        assert!("unknown".parse::<ThemeName>().is_err());
    }

    #[test]
    fn theme_name_display() {
        assert_eq!(ThemeName::Dracula.to_string(), "dracula");
        assert_eq!(ThemeName::CatppuccinMocha.to_string(), "catppuccin");
    }
}
