//! Mergeable configuration types for the layered override system
//!
//! This module provides concrete implementations of mergeable configurations
//! that can be used with the layered override system.

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::layered_override::Mergeable;

/// Rustelo project configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RusteloConfig {
    /// Project metadata
    pub project: Option<ProjectConfig>,
    /// Feature flags and configuration
    pub features: HashMap<String, FeatureConfig>,
    /// Development settings
    pub dev: Option<DevConfig>,
    /// Build settings
    pub build: Option<BuildConfig>,
    /// Custom configuration extensions
    #[serde(flatten)]
    pub extensions: HashMap<String, serde_json::Value>,
}

impl Mergeable for RusteloConfig {
    fn default() -> Self {
        Self {
            project: None,
            features: HashMap::new(),
            dev: None,
            build: None,
            extensions: HashMap::new(),
        }
    }

    fn merge(&mut self, other: Self) {
        // Project config: other takes precedence
        if other.project.is_some() {
            self.project = other.project;
        }

        // Features: merge individual feature configs
        for (key, value) in other.features {
            self.features.insert(key, value);
        }

        // Dev config: other takes precedence
        if other.dev.is_some() {
            self.dev = other.dev;
        }

        // Build config: other takes precedence
        if other.build.is_some() {
            self.build = other.build;
        }

        // Extensions: merge with other taking precedence
        for (key, value) in other.extensions {
            self.extensions.insert(key, value);
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectConfig {
    pub name: String,
    pub version: String,
    pub description: Option<String>,
    pub authors: Vec<String>,
    pub license: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureConfig {
    pub enabled: bool,
    pub config: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DevConfig {
    pub hot_reload: Option<bool>,
    pub debug_mode: Option<bool>,
    pub log_level: Option<String>,
    pub port: Option<u16>,
    pub host: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildConfig {
    pub optimize: Option<bool>,
    pub target: Option<String>,
    pub features: Vec<String>,
    pub env: HashMap<String, String>,
}

/// UnoCSS configuration that can be merged across layers
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnoConfig {
    /// Theme configuration
    pub theme: Option<UnoTheme>,
    /// Shortcuts (utility combinations)
    pub shortcuts: HashMap<String, String>,
    /// Presets to include
    pub presets: Vec<String>,
    /// Rules for custom utilities
    pub rules: Vec<UnoRule>,
    /// Transformers
    pub transformers: Vec<String>,
    /// Safelist (always included classes)
    pub safelist: Vec<String>,
    /// Content sources to scan
    pub content: Vec<String>,
}

impl Mergeable for UnoConfig {
    fn default() -> Self {
        Self {
            theme: None,
            shortcuts: HashMap::new(),
            presets: Vec::new(),
            rules: Vec::new(),
            transformers: Vec::new(),
            safelist: Vec::new(),
            content: Vec::new(),
        }
    }

    fn merge(&mut self, other: Self) {
        // Theme: merge themes recursively
        match (&mut self.theme, other.theme) {
            (Some(existing), Some(other_theme)) => existing.merge(other_theme),
            (None, Some(other_theme)) => self.theme = Some(other_theme),
            _ => {}
        }

        // Shortcuts: other takes precedence for conflicts
        for (key, value) in other.shortcuts {
            self.shortcuts.insert(key, value);
        }

        // Presets: merge and deduplicate
        for preset in other.presets {
            if !self.presets.contains(&preset) {
                self.presets.push(preset);
            }
        }

        // Rules: append (order matters)
        self.rules.extend(other.rules);

        // Transformers: merge and deduplicate
        for transformer in other.transformers {
            if !self.transformers.contains(&transformer) {
                self.transformers.push(transformer);
            }
        }

        // Safelist: merge and deduplicate
        for class in other.safelist {
            if !self.safelist.contains(&class) {
                self.safelist.push(class);
            }
        }

        // Content: merge and deduplicate
        for path in other.content {
            if !self.content.contains(&path) {
                self.content.push(path);
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnoTheme {
    pub colors: HashMap<String, String>,
    pub fonts: HashMap<String, Vec<String>>,
    pub spacing: HashMap<String, String>,
    pub breakpoints: HashMap<String, String>,
    #[serde(flatten)]
    pub extensions: HashMap<String, serde_json::Value>,
}

impl UnoTheme {
    fn merge(&mut self, other: Self) {
        // Colors: other takes precedence
        for (key, value) in other.colors {
            self.colors.insert(key, value);
        }

        // Fonts: other takes precedence
        for (key, value) in other.fonts {
            self.fonts.insert(key, value);
        }

        // Spacing: other takes precedence
        for (key, value) in other.spacing {
            self.spacing.insert(key, value);
        }

        // Breakpoints: other takes precedence
        for (key, value) in other.breakpoints {
            self.breakpoints.insert(key, value);
        }

        // Extensions: other takes precedence
        for (key, value) in other.extensions {
            self.extensions.insert(key, value);
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnoRule {
    pub pattern: String,
    pub css: String,
}

/// Package.json configuration for Node.js dependencies and scripts
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageConfig {
    pub name: Option<String>,
    pub version: Option<String>,
    pub description: Option<String>,
    pub scripts: HashMap<String, String>,
    pub dependencies: HashMap<String, String>,
    #[serde(rename = "devDependencies")]
    pub dev_dependencies: HashMap<String, String>,
    #[serde(flatten)]
    pub extensions: HashMap<String, serde_json::Value>,
}

impl Mergeable for PackageConfig {
    fn default() -> Self {
        Self {
            name: None,
            version: None,
            description: None,
            scripts: HashMap::new(),
            dependencies: HashMap::new(),
            dev_dependencies: HashMap::new(),
            extensions: HashMap::new(),
        }
    }

    fn merge(&mut self, other: Self) {
        // Basic fields: other takes precedence
        if other.name.is_some() {
            self.name = other.name;
        }
        if other.version.is_some() {
            self.version = other.version;
        }
        if other.description.is_some() {
            self.description = other.description;
        }

        // Scripts: other takes precedence for conflicts
        for (key, value) in other.scripts {
            self.scripts.insert(key, value);
        }

        // Dependencies: other takes precedence for conflicts
        for (key, value) in other.dependencies {
            self.dependencies.insert(key, value);
        }

        // Dev dependencies: other takes precedence for conflicts
        for (key, value) in other.dev_dependencies {
            self.dev_dependencies.insert(key, value);
        }

        // Extensions: other takes precedence
        for (key, value) in other.extensions {
            self.extensions.insert(key, value);
        }
    }
}

/// Just (command runner) configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JustConfig {
    /// Variables
    pub variables: HashMap<String, String>,
    /// Recipes (commands)
    pub recipes: HashMap<String, JustRecipe>,
    /// Settings
    pub settings: HashMap<String, String>,
}

impl Mergeable for JustConfig {
    fn default() -> Self {
        Self {
            variables: HashMap::new(),
            recipes: HashMap::new(),
            settings: HashMap::new(),
        }
    }

    fn merge(&mut self, other: Self) {
        // Variables: other takes precedence
        for (key, value) in other.variables {
            self.variables.insert(key, value);
        }

        // Recipes: other takes precedence (full recipe replacement)
        for (key, value) in other.recipes {
            self.recipes.insert(key, value);
        }

        // Settings: other takes precedence
        for (key, value) in other.settings {
            self.settings.insert(key, value);
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JustRecipe {
    pub description: Option<String>,
    pub dependencies: Vec<String>,
    pub commands: Vec<String>,
    pub private: Option<bool>,
}

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

    #[test]
    fn test_rustelo_config_merge() {
        let mut base = RusteloConfig::default();
        base.features.insert(
            "analytics".to_string(),
            FeatureConfig {
                enabled: true,
                config: Some(serde_json::json!({"level": "basic"})),
            },
        );

        let override_config = RusteloConfig {
            project: Some(ProjectConfig {
                name: "test-project".to_string(),
                version: "1.0.0".to_string(),
                description: None,
                authors: vec!["Test Author".to_string()],
                license: Some("MIT".to_string()),
            }),
            features: {
                let mut features = HashMap::new();
                features.insert(
                    "auth".to_string(),
                    FeatureConfig {
                        enabled: true,
                        config: None,
                    },
                );
                features
            },
            dev: Some(DevConfig {
                hot_reload: Some(true),
                debug_mode: Some(true),
                log_level: Some("debug".to_string()),
                port: Some(3000),
                host: Some("localhost".to_string()),
            }),
            build: None,
            extensions: HashMap::new(),
        };

        base.merge(override_config);

        // Should have both features
        assert!(base.features.contains_key("analytics"));
        assert!(base.features.contains_key("auth"));

        // Should have project config
        assert!(base.project.is_some());
        assert_eq!(base.project.unwrap().name, "test-project");

        // Should have dev config
        assert!(base.dev.is_some());
        assert_eq!(base.dev.unwrap().port, Some(3000));
    }

    #[test]
    fn test_uno_config_merge() {
        let mut base = UnoConfig {
            theme: Some(UnoTheme {
                colors: {
                    let mut colors = HashMap::new();
                    colors.insert("primary".to_string(), "#3b82f6".to_string());
                    colors
                },
                fonts: HashMap::new(),
                spacing: HashMap::new(),
                breakpoints: HashMap::new(),
                extensions: HashMap::new(),
            }),
            shortcuts: {
                let mut shortcuts = HashMap::new();
                shortcuts.insert("btn".to_string(), "px-4 py-2 rounded".to_string());
                shortcuts
            },
            presets: vec!["@unocss/preset-uno".to_string()],
            rules: Vec::new(),
            transformers: Vec::new(),
            safelist: Vec::new(),
            content: vec!["src/**/*.rs".to_string()],
        };

        let override_config = UnoConfig {
            theme: Some(UnoTheme {
                colors: {
                    let mut colors = HashMap::new();
                    colors.insert("primary".to_string(), "#ef4444".to_string()); // Override
                    colors.insert("secondary".to_string(), "#10b981".to_string()); // New
                    colors
                },
                fonts: HashMap::new(),
                spacing: HashMap::new(),
                breakpoints: HashMap::new(),
                extensions: HashMap::new(),
            }),
            shortcuts: {
                let mut shortcuts = HashMap::new();
                shortcuts.insert(
                    "card".to_string(),
                    "bg-white shadow-md rounded-lg p-4".to_string(),
                );
                shortcuts
            },
            presets: vec!["@unocss/preset-wind".to_string()],
            rules: Vec::new(),
            transformers: Vec::new(),
            safelist: vec!["text-red-500".to_string()],
            content: vec!["templates/**/*.html".to_string()],
        };

        base.merge(override_config);

        // Colors should be merged with override taking precedence
        let theme = base.theme.unwrap();
        assert_eq!(theme.colors.get("primary"), Some(&"#ef4444".to_string()));
        assert_eq!(theme.colors.get("secondary"), Some(&"#10b981".to_string()));

        // Shortcuts should be merged
        assert!(base.shortcuts.contains_key("btn"));
        assert!(base.shortcuts.contains_key("card"));

        // Presets should be merged and deduplicated
        assert_eq!(base.presets.len(), 2);
        assert!(base.presets.contains(&"@unocss/preset-uno".to_string()));
        assert!(base.presets.contains(&"@unocss/preset-wind".to_string()));

        // Content should be merged
        assert_eq!(base.content.len(), 2);
        assert!(base.content.contains(&"src/**/*.rs".to_string()));
        assert!(base.content.contains(&"templates/**/*.html".to_string()));
    }
}
