use forge_ipc::{AppIdMatcher, GestureSector, KeyTrigger, Keysyms, Modifiers};
use serde::{Deserialize, Serialize};
use tracing::{info, warn};

#[derive(Debug)]
pub struct GestureBinding {
    pub app_id: AppIdMatcher,
    pub start: GestureSector,
    pub end: GestureSector,
    pub action: GestureAction,
}

#[derive(Clone, Debug)]
pub enum GestureAction {
    Exec { program: String, args: Vec<String> },
    Key { keysym: u32, mods: Modifiers },
}

#[derive(Debug)]
pub struct KeyBinding {
    pub app_id: AppIdMatcher,
    pub mods: Modifiers,
    pub keys: Keysyms,
    pub trigger: KeyTrigger,
    pub program: String,
    pub arguments: Vec<String>,
}

// ============================================================================
// Forge compositor config file (~/.config/forge/forge.toml)
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForgeConfig {
    #[serde(default)]
    pub statusbar: StatusBarConfig,
    #[serde(default)]
    pub decoration: DecorationConfig,
}

/// Decoration policy. The xdg-decoration protocol is the primary mechanism
/// for clients to opt in/out of server-side decorations; this section is the
/// safety valve for clients that paint their own title bar without binding
/// the protocol (and therefore would otherwise end up with two stacked
/// title bars: the client's own + forge's SSD).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecorationConfig {
    /// Regex patterns matched against toplevel `app_id`. A match suppresses
    /// forge's SSD title bar for that window, even when the client has not
    /// requested ClientSide via xdg-decoration. Examples for `forge.toml`:
    ///
    /// ```toml
    /// [decoration]
    /// csd_apps = [
    ///     "^org\\.mozilla\\.firefox$",
    ///     "^Electron$",
    ///     "^code-oss$",
    /// ]
    /// ```
    ///
    /// The default list covers families that always draw their own header
    /// bar (GNOME / libadwaita) so they do not end up with two stacked
    /// title bars out of the box. A user-provided `forge.toml` replaces
    /// this list entirely — set it to `[]` to opt back into SSD on
    /// everything, or extend it for additional CSD apps.
    #[serde(default = "default_csd_apps")]
    pub csd_apps: Vec<String>,
}

fn default_csd_apps() -> Vec<String> {
    vec![
        // GNOME / libadwaita apps draw their own header bar; without
        // matching, forge's SSD strip sits on top of the client's own
        // close/maximize/minimize buttons.
        r"^org\.gnome\..*".to_string(),
        // Firefox paints its own tab strip / window controls. App_id
        // varies by packaging: RPM/DEB use `firefox` (sometimes `Firefox`
        // with capital F), Flatpak uses `org.mozilla.firefox`, ESR uses
        // `firefox-esr`, Developer Edition `firefox-developer-edition`,
        // Nightly `firefox-nightly`. Match all variants case-insensitively
        // so the SSD strip is suppressed regardless of distribution.
        r"(?i)^firefox(-esr|-developer-edition|-nightly|-aurora)?$".to_string(),
        r"(?i)^org\.mozilla\.(firefox|firefox-?nightly|firefox-?esr)$".to_string(),
        // Chromium family: Chrome, Chromium and Brave all paint their
        // own frame and window controls. The default `app_id` strings
        // differ across distributions (`Chromium`, `chromium-browser`,
        // `google-chrome`, `Brave-browser`, `brave-browser`), so match
        // case-insensitively on the common stem.
        r"(?i)^(chromium|chromium-browser|google-chrome|brave|brave-browser)$".to_string(),
        // Electron-family apps (VS Code, Discord, …) follow the same
        // pattern as Chromium. VS Code in particular announces
        // `code` or `code-oss`.
        r"(?i)^(electron|code|code-oss|code-insiders)$".to_string(),
    ]
}

impl Default for DecorationConfig {
    fn default() -> Self {
        Self {
            csd_apps: default_csd_apps(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatusBarConfig {
    /// Enable the built-in status bar. Disabled by default — the external
    /// top bar (crustace shell / liberux-topbar) is the primary status bar.
    /// Set to true only when running forge without crustace.
    #[serde(default)]
    pub enabled: bool,
    /// Status bar height in logical pixels.
    #[serde(default = "default_statusbar_height")]
    pub height: i32,
}

fn default_statusbar_height() -> i32 {
    28
}

impl Default for StatusBarConfig {
    fn default() -> Self {
        Self { enabled: false, height: 28 }
    }
}

impl Default for ForgeConfig {
    fn default() -> Self {
        Self {
            statusbar:  StatusBarConfig::default(),
            decoration: DecorationConfig::default(),
        }
    }
}

impl ForgeConfig {
    pub fn load() -> Self {
        let config_dir = dirs::config_dir().unwrap_or_default().join("forge");
        let path = config_dir.join("forge.toml");

        if path.exists() {
            match std::fs::read_to_string(&path) {
                Ok(content) => match toml::from_str::<ForgeConfig>(&content) {
                    Ok(config) => {
                        info!("Loaded forge config from {}", path.display());
                        return config;
                    }
                    Err(e) => {
                        warn!("Failed to parse {}: {e}, using defaults", path.display());
                    }
                },
                Err(e) => {
                    warn!("Failed to read {}: {e}, using defaults", path.display());
                }
            }
        }

        Self::default()
    }
}
