//! Shared type definitions for the Rustelo web framework

use std::collections::HashMap;

use serde::{Deserialize, Deserializer, Serialize};

/// Container for language-agnostic text content
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct Texts {
    /// Language-agnostic text map: language_code -> key_value_pairs
    pub translations: HashMap<String, HashMap<String, String>>,
}

impl Texts {
    /// Get translations for a specific language with fallback
    pub fn get_translations_for_language(&self, language: &str) -> HashMap<String, String> {
        self.translations
            .get(language)
            .cloned()
            .or_else(|| self.translations.values().next().cloned())
            .unwrap_or_default()
    }

    /// Add translations for a language
    pub fn add_language_translations(&mut self, language: String, texts: HashMap<String, String>) {
        self.translations.insert(language, texts);
    }

    /// Get available languages
    pub fn available_languages(&self) -> Vec<&String> {
        self.translations.keys().collect()
    }

    /// Create Texts from language maps (language-agnostic)
    pub fn from_language_maps(language_maps: Vec<(String, HashMap<String, String>)>) -> Self {
        let mut translations = HashMap::new();
        for (lang, map) in language_maps {
            translations.insert(lang, map);
        }
        Self { translations }
    }
}

/// Configuration structure for menus
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MenuConfig {
    pub menu: Vec<MenuItem>,
    pub footer: FooterConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MenuItem {
    pub route: String,
    pub label: Translations,
    pub icon: Option<String>,
    pub priority: Option<i32>,
    pub is_external: Option<bool>,
    /// Alternative localized routes for the same menu item
    /// For example: { "es": "/servicios", "es-alt": "/es/services" }
    pub localized_routes: Option<HashMap<String, String>>,
    /// When true, this item should only be visible to authenticated users.
    /// Derived from `site/rbac.ncl` deny patterns at build time — not set in
    /// config.ncl.
    #[serde(default)]
    pub auth_required: bool,
}

impl MenuItem {
    /// Get the appropriate route for the given language
    /// Falls back to the default route if no localized route is found
    pub fn get_route_for_language(&self, language: &str) -> &str {
        if let Some(ref localized_routes) = self.localized_routes {
            if let Some(route) = localized_routes.get(language) {
                return route;
            }
        }
        &self.route
    }

    /// Get all available routes for this menu item
    pub fn get_all_routes(&self) -> Vec<String> {
        let mut routes = vec![self.route.clone()];
        if let Some(ref localized_routes) = self.localized_routes {
            routes.extend(localized_routes.values().cloned());
        }
        routes
    }
}

#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct Translations {
    /// Language-agnostic translation map: language_code -> translated_text
    pub translations: HashMap<String, String>,
}

/// Structure for deserializing different translation formats
#[derive(Deserialize)]
#[serde(untagged)]
enum TranslationsFormat {
    /// New dynamic format
    Dynamic {
        translations: HashMap<String, String>,
    },
    /// Legacy two-language format (backward compatibility)
    LegacyTwoLang { en: String, es: String },
    /// Generic two-field format (for other language pairs)
    GenericTwoFields(HashMap<String, String>),
}

impl<'de> Deserialize<'de> for Translations {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let format = TranslationsFormat::deserialize(deserializer)?;

        match format {
            TranslationsFormat::Dynamic { translations } => Ok(Self { translations }),
            TranslationsFormat::LegacyTwoLang { en, es } => {
                // Use dynamic language discovery for backward compatibility
                let discovered_languages = crate::i18n::discovery::discover_available_languages();
                let default_lang = crate::i18n::discovery::get_default_language();

                // Try to find a secondary language (not the default)
                let fallback_lang = "es".to_string();
                let secondary_lang = discovered_languages
                    .iter()
                    .find(|&lang| lang != default_lang)
                    .unwrap_or(&fallback_lang);

                Ok(Self::from_two_languages(
                    default_lang.to_string(),
                    en,
                    secondary_lang.clone(),
                    es,
                ))
            }
            TranslationsFormat::GenericTwoFields(map) => Ok(Self { translations: map }),
        }
    }
}

impl Translations {
    /// Create a new empty translations instance
    pub fn new() -> Self {
        Self {
            translations: HashMap::new(),
        }
    }

    /// Create translations from a vector of (language, text) pairs
    pub fn from_pairs(pairs: Vec<(String, String)>) -> Self {
        let mut translations = HashMap::new();
        for (lang, text) in pairs {
            translations.insert(lang, text);
        }
        Self { translations }
    }

    /// Create translations from two languages for backward compatibility
    /// Default languages are configurable, not hardcoded to en/es
    pub fn from_two_languages(lang1: String, text1: String, lang2: String, text2: String) -> Self {
        let mut translations = HashMap::new();
        translations.insert(lang1, text1);
        translations.insert(lang2, text2);
        Self { translations }
    }

    /// Get translation for a specific language
    pub fn get_for_language(&self, lang: &str) -> Option<&String> {
        self.translations.get(lang)
    }

    /// Get translation with fallback to default language
    pub fn get_with_fallback(&self, lang: &str, fallback_lang: &str) -> String {
        self.translations
            .get(lang)
            .or_else(|| self.translations.get(fallback_lang))
            .or_else(|| self.translations.values().next())
            .cloned()
            .unwrap_or_default()
    }

    /// Get translation with primary language fallback (language-agnostic)
    /// Uses the first available language as fallback, not hardcoded English
    pub fn get_with_primary_fallback(&self, lang: &str) -> String {
        if let Some(text) = self.translations.get(lang) {
            return text.clone();
        }
        // Fallback to first available language (language-agnostic)
        self.translations
            .values()
            .next()
            .cloned()
            .unwrap_or_default()
    }

    /// Get translation with configurable fallback language
    pub fn get_with_custom_fallback(&self, lang: &str, fallback_lang: &str) -> String {
        self.get_with_fallback(lang, fallback_lang)
    }

    /// Add or update a translation for a language
    pub fn add_translation(&mut self, lang: String, text: String) {
        self.translations.insert(lang, text);
    }

    /// Get all available languages
    pub fn available_languages(&self) -> Vec<&String> {
        self.translations.keys().collect()
    }

    /// Check if a language is available
    pub fn has_language(&self, lang: &str) -> bool {
        self.translations.contains_key(lang)
    }

    /// Check if all translations are empty (for compatibility)
    pub fn is_empty(&self) -> bool {
        self.translations.is_empty() || self.translations.values().all(|v| v.is_empty())
    }
}

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

// FooterConfig removed - using SimpleFooterConfig as the unified footer
// structure

/// Footer configuration structure
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct FooterConfig {
    pub title: String,
    #[serde(rename = "company-desc", alias = "subtitle")]
    pub company_desc: String,
    pub copyright_text: String,
    pub social_links: Vec<SocialLinkConfig>,
    #[serde(default)]
    pub sections: Vec<FooterSection>,
    #[serde(default)]
    pub menu: Vec<SimpleMenuItem>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FooterSection {
    pub title: Translations,
    pub links: Vec<FooterLink>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FooterLink {
    pub route: String,
    pub label: Translations,
    #[serde(default)]
    pub is_external: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SocialLinkConfig {
    pub name: String,
    pub url: String,
    pub icon_class: String,
}

impl FooterConfig {
    pub fn new() -> Self {
        Self {
            title: "".to_string(),
            company_desc: "".to_string(),
            copyright_text: "© All rights reserved.".to_string(),
            social_links: vec![SocialLinkConfig {
                name: "".to_string(),
                url: "".to_string(),
                icon_class: "fab fa-github".to_string(),
            }],
            sections: vec![],
            menu: vec![],
        }
    }
}

/// Simple menu configuration structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimpleMenuConfig {
    pub menu: Vec<SimpleMenuItem>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SimpleMenuItem {
    pub route: String,
    pub label: Translations,
    pub icon: Option<String>,
    pub priority: Option<i32>,
    pub is_external: Option<bool>,
    pub localized_routes: Option<HashMap<String, String>>,
    #[serde(default)]
    pub auth_required: bool,
}

/// Theme configuration structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThemeConfig {
    pub name: String,
    pub primary_color: String,
    pub secondary_color: String,
    pub background_color: String,
    pub text_color: String,
    pub accent_color: Option<String>,
    pub font_family: Option<String>,
}

impl Default for ThemeConfig {
    fn default() -> Self {
        Self {
            name: "default".to_string(),
            primary_color: "#3b82f6".to_string(),
            secondary_color: "#64748b".to_string(),
            background_color: "#ffffff".to_string(),
            text_color: "#1f2937".to_string(),
            accent_color: Some("#f59e0b".to_string()),
            font_family: Some("Inter, sans-serif".to_string()),
        }
    }
}

/// Logo configuration structure
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct LogoConfig {
    pub light_src: String,
    pub dark_src: String,
    pub alt: String,
    pub href: String,
    pub show_in_nav: bool,
    pub show_in_footer: bool,
    pub width: Option<String>,
    pub height: Option<String>,
}

impl LogoConfig {
    /// Check if dark variant is available
    pub fn has_dark_variant(&self) -> bool {
        !self.dark_src.is_empty()
    }

    /// Get the appropriate src for the given theme
    pub fn get_src_for_theme(&self, is_dark: bool) -> &str {
        if is_dark && self.has_dark_variant() {
            &self.dark_src
        } else {
            &self.light_src
        }
    }
}

/// Design system configuration structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesignSystemConfig {
    pub version: String,
    pub spacing: SpacingConfig,
    pub typography: TypographyConfig,
    pub colors: ColorConfig,
    pub breakpoints: BreakpointConfig,
}

impl Default for DesignSystemConfig {
    fn default() -> Self {
        Self {
            version: "1.0.0".to_string(),
            spacing: SpacingConfig::default(),
            typography: TypographyConfig::default(),
            colors: ColorConfig::default(),
            breakpoints: BreakpointConfig::default(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpacingConfig {
    pub xs: String,
    pub sm: String,
    pub md: String,
    pub lg: String,
    pub xl: String,
}

impl Default for SpacingConfig {
    fn default() -> Self {
        Self {
            xs: "0.25rem".to_string(),
            sm: "0.5rem".to_string(),
            md: "1rem".to_string(),
            lg: "1.5rem".to_string(),
            xl: "3rem".to_string(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypographyConfig {
    pub font_family: String,
    pub font_sizes: HashMap<String, String>,
    pub line_heights: HashMap<String, String>,
}

impl Default for TypographyConfig {
    fn default() -> Self {
        Self {
            font_family: "Inter, sans-serif".to_string(),
            font_sizes: HashMap::new(),
            line_heights: HashMap::new(),
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ColorConfig {
    pub primary: HashMap<String, String>,
    pub secondary: HashMap<String, String>,
    pub neutral: HashMap<String, String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreakpointConfig {
    pub sm: String,
    pub md: String,
    pub lg: String,
    pub xl: String,
}

impl Default for BreakpointConfig {
    fn default() -> Self {
        Self {
            sm: "640px".to_string(),
            md: "768px".to_string(),
            lg: "1024px".to_string(),
            xl: "1280px".to_string(),
        }
    }
}
