// SPDX-License-Identifier: GPL-3.0-or-later

use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

/// Per-display settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DisplaySettings {
    pub min_brightness: f64,
    pub max_brightness: f64,
    pub custom_name: Option<String>,
}

impl Default for DisplaySettings {
    fn default() -> Self {
        Self {
            min_brightness: 0.0,
            max_brightness: 100.0,
            custom_name: None,
        }
    }
}

/// Sync mode for multi-display brightness control.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum SyncMode {
    #[default]
    Off,
    SameValue,
    Proportional,
}

/// Theme mode preference for the app UI.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ThemeMode {
    #[default]
    System,
    Light,
    Dark,
}

/// Color theme — which palette to use for the UI.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ColorTheme {
    #[default]
    AngusLight,
    AngusDark,
    CatppuccinLatte,
    CatppuccinFrappe,
    CatppuccinMacchiato,
    CatppuccinMocha,
    Nord,
    NordPolar,
    GruvboxDark,
    GruvboxLight,
    SolarizedDark,
    SolarizedLight,
    Dracula,
    RosePine,
    RosePineMoon,
    RosePineDawn,
}

impl ColorTheme {
    /// All variants in display order with human-readable names.
    pub fn all() -> &'static [(ColorTheme, &'static str)] {
        &[
            (ColorTheme::AngusLight,         "Angus (light)"),
            (ColorTheme::AngusDark,          "Angus (dark)"),
            (ColorTheme::CatppuccinLatte,    "Catppuccin Latte"),
            (ColorTheme::CatppuccinFrappe,   "Catppuccin Frappe"),
            (ColorTheme::CatppuccinMacchiato,"Catppuccin Macchiato"),
            (ColorTheme::CatppuccinMocha,    "Catppuccin Mocha"),
            (ColorTheme::Nord,               "Nord"),
            (ColorTheme::NordPolar,          "Nord Polar"),
            (ColorTheme::GruvboxDark,        "Gruvbox Dark"),
            (ColorTheme::GruvboxLight,       "Gruvbox Light"),
            (ColorTheme::SolarizedDark,      "Solarized Dark"),
            (ColorTheme::SolarizedLight,     "Solarized Light"),
            (ColorTheme::Dracula,            "Dracula"),
            (ColorTheme::RosePine,           "Rose Pine"),
            (ColorTheme::RosePineMoon,       "Rose Pine Moon"),
            (ColorTheme::RosePineDawn,       "Rose Pine Dawn"),
        ]
    }

    /// Whether this is a dark-mode theme (dark background).
    pub fn is_dark_theme(&self) -> bool {
        matches!(self,
            ColorTheme::AngusDark
            | ColorTheme::CatppuccinFrappe
            | ColorTheme::CatppuccinMacchiato
            | ColorTheme::CatppuccinMocha
            | ColorTheme::Nord
            | ColorTheme::GruvboxDark
            | ColorTheme::SolarizedDark
            | ColorTheme::Dracula
            | ColorTheme::RosePine
            | ColorTheme::RosePineMoon
        )
    }

    /// Whether this is a light-mode theme (light background).
    pub fn is_light_theme(&self) -> bool {
        matches!(self,
            ColorTheme::AngusLight
            | ColorTheme::CatppuccinLatte
            | ColorTheme::NordPolar
            | ColorTheme::GruvboxLight
            | ColorTheme::SolarizedLight
            | ColorTheme::RosePineDawn
        )
    }

    /// Get the embedded CSS for this theme.
    pub fn css(&self) -> &'static str {
        match self {
            ColorTheme::AngusLight          => "",
            ColorTheme::AngusDark           => "",
            ColorTheme::CatppuccinLatte     => include_str!("gui/themes/latte.css"),
            ColorTheme::CatppuccinFrappe    => include_str!("gui/themes/frappe.css"),
            ColorTheme::CatppuccinMacchiato => include_str!("gui/themes/macchiato.css"),
            ColorTheme::CatppuccinMocha     => include_str!("gui/themes/mocha.css"),
            ColorTheme::Nord                => include_str!("gui/themes/nord.css"),
            ColorTheme::NordPolar           => include_str!("gui/themes/nord-polar.css"),
            ColorTheme::GruvboxDark         => include_str!("gui/themes/gruvbox-dark.css"),
            ColorTheme::GruvboxLight        => include_str!("gui/themes/gruvbox-light.css"),
            ColorTheme::SolarizedDark       => include_str!("gui/themes/solarized-dark.css"),
            ColorTheme::SolarizedLight      => include_str!("gui/themes/solarized-light.css"),
            ColorTheme::Dracula             => include_str!("gui/themes/dracula.css"),
            ColorTheme::RosePine            => include_str!("gui/themes/rose-pine.css"),
            ColorTheme::RosePineMoon        => include_str!("gui/themes/rose-pine-moon.css"),
            ColorTheme::RosePineDawn        => include_str!("gui/themes/rose-pine-dawn.css"),
        }
    }
}

/// Global application settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Settings {
    #[serde(default)]
    pub sync_mode: SyncMode,
    #[serde(default)] // backward compat with old settings.json
    pub proportional_sync: bool,
    #[serde(default = "default_true")]
    pub show_display_numbers: bool,
    #[serde(default)]
    pub autostart: bool,
    #[serde(default)]
    pub display_order: Vec<String>,
    pub displays: HashMap<String, DisplaySettings>,
    /// Latitude for sunrise/sunset calculation (default: 41.88 = Chicago)
    #[serde(default = "default_latitude")]
    pub latitude: f64,
    /// Longitude for sunrise/sunset calculation (default: -87.63 = Chicago)
    #[serde(default = "default_longitude")]
    pub longitude: f64,
    /// Use 24h time format by default in schedule editor (false = 12h AM/PM)
    #[serde(default = "default_true")]
    pub use_24h_time: bool,
    /// Show min/max range labels on main window display rows
    #[serde(default)]
    pub show_range_labels: bool,
    /// UI theme mode: system, light, or dark
    #[serde(default)]
    pub theme_mode: ThemeMode,
    /// Color palette for light mode
    #[serde(default)]
    pub light_theme: ColorTheme,
    /// Color palette for dark mode
    #[serde(default = "default_dark_theme")]
    pub dark_theme: ColorTheme,
}

fn default_true() -> bool { true }
fn default_dark_theme() -> ColorTheme { ColorTheme::AngusDark }
fn default_latitude() -> f64 { 41.88 }
fn default_longitude() -> f64 { -87.63 }

impl Default for Settings {
    fn default() -> Self {
        Self {
            sync_mode: SyncMode::Off,
            proportional_sync: false,
            show_display_numbers: true,
            autostart: false,
            display_order: Vec::new(),
            displays: HashMap::new(),
            latitude: 41.88,
            longitude: -87.63,
            use_24h_time: true,
            show_range_labels: false,
            theme_mode: ThemeMode::System,
            light_theme: ColorTheme::AngusLight,
            dark_theme: ColorTheme::AngusDark,
        }
    }
}

impl Settings {
    /// Load settings from the default config file.
    pub fn load() -> Result<Self> {
        let path = config_path()?;
        if !path.exists() {
            return Ok(Self::default());
        }
        let content = fs::read_to_string(&path)?;
        let settings: Settings = serde_json::from_str(&content)?;
        Ok(settings)
    }

    /// Load settings from a specific path (for testing).
    pub fn load_from(path: &std::path::Path) -> Result<Self> {
        if !path.exists() {
            return Ok(Self::default());
        }
        let content = fs::read_to_string(path)?;
        let settings: Settings = serde_json::from_str(&content)?;
        Ok(settings)
    }

    /// Save settings to the default config file.
    pub fn save(&self) -> Result<()> {
        let path = config_path()?;
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        let content = serde_json::to_string_pretty(self)?;
        fs::write(&path, content)?;
        Ok(())
    }

    /// Save settings to a specific path (for testing).
    pub fn save_to(&self, path: &std::path::Path) -> Result<()> {
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        let content = serde_json::to_string_pretty(self)?;
        fs::write(path, content)?;
        Ok(())
    }

    /// Get custom name for a display, if set.
    pub fn get_custom_name(&self, key: &str) -> Option<String> {
        self.displays
            .get(key)
            .and_then(|d| d.custom_name.clone())
    }

    /// Set custom name for a display.
    pub fn set_custom_name(&mut self, key: &str, name: &str) {
        let entry = self
            .displays
            .entry(key.to_string())
            .or_default();
        entry.custom_name = Some(name.to_string());
    }

    /// Get settings for a specific display.
    pub fn get_display(&self, key: &str) -> DisplaySettings {
        self.displays.get(key).cloned().unwrap_or_default()
    }

    /// Set settings for a specific display.
    pub fn set_display(&mut self, key: &str, settings: DisplaySettings) {
        self.displays.insert(key.to_string(), settings);
    }
}

fn config_path() -> Result<PathBuf> {
    let config_dir = dirs::config_dir()
        .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?;
    Ok(config_dir.join("temperlux").join("settings.json"))
}
