//! Language context and reactive language switching system
//!
//! This module provides a reactive language context that enables seamless
//! language switching without page reloads while maintaining application state.

use std::collections::HashMap;

use leptos::prelude::*;

use crate::i18n::language_config::get_language_registry;
use crate::routing::utils::{build_route_mappings, get_localized_route};

/// Language context for reactive language switching
#[derive(Clone, Debug)]
pub struct LanguageContext {
    /// Current active language
    pub current: RwSignal<String>,
    /// Available languages in the application
    pub available: Vec<String>,
    /// Route mappings between languages (en->es, es->en)
    pub route_mappings: (HashMap<String, String>, HashMap<String, String>),
}

impl LanguageContext {
    /// Create new language context with dynamic language detection
    pub fn new() -> Self {
        let language_registry = get_language_registry();
        let default_language = language_registry.get_default_language().to_string();
        let available_languages = language_registry.get_available_languages();

        let current = RwSignal::new(default_language);
        let available = available_languages;
        let route_mappings = build_route_mappings();

        tracing::info!(
            "Initialized language context with default: {:?}, available: {:?}",
            current.get_untracked(),
            available
        );

        Self {
            current,
            available,
            route_mappings,
        }
    }

    /// Switch to a new language without page reload
    pub fn switch_language(&self, new_lang: &str) {
        if !self.available.contains(&new_lang.to_string()) {
            tracing::warn!("Attempted to switch to unsupported language: {}", new_lang);
            return;
        }

        let current_lang = self.current.get();
        if current_lang == new_lang {
            return; // No change needed
        }

        tracing::info!("Switching language from {} to {}", current_lang, new_lang);

        // 1. Update language signal (this triggers reactive updates)
        self.current.set(new_lang.to_string());

        // 2. Don't update browser URL - keep user on same page with reactive content
        // self.update_browser_url(new_lang);

        // 3. Update document language attribute
        self.update_document_lang(new_lang);
    }

    /// Update browser URL to reflect language change
    fn update_browser_url(&self, new_lang: &str) {
        #[cfg(target_arch = "wasm32")]
        {
            use wasm_bindgen::JsValue;
            use web_sys::window;

            if let Some(window) = window() {
                if let Ok(location) = window.location().pathname() {
                    let current_path = location;
                    let localized_path = get_localized_route(&current_path, new_lang);

                    if let Some(history) = window.history().ok() {
                        let _ =
                            history.push_state_with_url(&JsValue::NULL, "", Some(&localized_path));

                        tracing::debug!("Updated URL to: {}", localized_path);
                    }
                }
            }
        }

        #[cfg(not(target_arch = "wasm32"))]
        {
            // For SSR, we can't update browser URL, but we can log the change
            tracing::debug!("Would update URL for language: {}", new_lang);
        }
    }

    /// Update document language attribute
    fn update_document_lang(&self, _new_lang: &str) {
        #[cfg(target_arch = "wasm32")]
        {
            use web_sys::window;

            if let Some(window) = window() {
                if let Some(document) = window.document() {
                    if let Some(html_element) = document.document_element() {
                        let _ = html_element.set_attribute("lang", _new_lang);
                        tracing::debug!("Updated document language attribute to: {}", _new_lang);
                    }
                }
            }
        }
    }

    /// Get the current language
    pub fn get_current(&self) -> String {
        self.current.get()
    }

    /// Get display name for a language code
    pub fn get_language_display_name(&self, lang_code: &str) -> String {
        let language_registry = get_language_registry();
        language_registry.get_display_name(lang_code, false) // Use native names
    }

    /// Get available languages with display names
    pub fn get_available_with_names(&self) -> Vec<(String, String)> {
        self.available
            .iter()
            .map(|code| (code.clone(), self.get_language_display_name(code)))
            .collect()
    }

    /// Get available languages with display names and flags
    pub fn get_available_with_flags(&self) -> Vec<(String, String, Option<String>)> {
        let language_registry = get_language_registry();
        self.available
            .iter()
            .map(|code| {
                (
                    code.clone(),
                    self.get_language_display_name(code),
                    language_registry.get_flag(code),
                )
            })
            .collect()
    }

    /// Check if a language is supported
    pub fn is_language_supported(&self, lang: &str) -> bool {
        self.available.contains(&lang.to_string())
    }

    /// Get localized route for current language
    pub fn get_current_localized_route(&self, path: &str) -> String {
        get_localized_route(path, &self.get_current())
    }
}

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

/// Language provider component to provide context to the app
#[component]
pub fn LanguageProvider(children: Children) -> impl IntoView {
    let language_context = LanguageContext::new();
    provide_context(language_context);

    children()
}

/// Hook to use language context in components
/// Provides a fallback for SSR when context is not available
pub fn use_language() -> LanguageContext {
    use_context::<LanguageContext>().unwrap_or_else(|| {
        tracing::debug!("LanguageContext not found in context, creating fallback instance for SSR");
        LanguageContext::new()
    })
}

/// Hook to get current language as a signal
pub fn use_current_language() -> ReadSignal<String> {
    let context = use_language();
    context.current.read_only()
}

/// Hook to switch language from components
pub fn use_language_switcher() -> impl Fn(&str) + Clone {
    let context = use_language();
    move |new_lang: &str| {
        context.switch_language(new_lang);
    }
}

#[cfg(test)]
mod tests {
    use leptos::prelude::Owner;

    use super::*;

    #[test]
    fn test_language_context_creation() {
        Owner::new().with(|| {
            let context = LanguageContext::new();
            let current = context.get_current();
            let available = &context.available;

            // Should have a default language
            assert!(!current.is_empty());

            // Should have at least the current language available
            assert!(context.is_language_supported(&current));

            // Should have at least one language available
            assert!(!available.is_empty());
        });
    }

    #[test]
    fn test_language_display_names() {
        Owner::new().with(|| {
            let context = LanguageContext::new();
            let current = context.get_current();

            // Should be able to get display name for current language
            let display_name = context.get_language_display_name(&current);
            assert!(!display_name.is_empty());

            // Should handle unknown language gracefully
            let unknown_name = context.get_language_display_name("zz");
            assert!(!unknown_name.is_empty()); // Should return something (even
                                               // if uppercase)
        });
    }

    #[test]
    fn test_available_languages_with_names() {
        Owner::new().with(|| {
            let context = LanguageContext::new();
            let langs = context.get_available_with_names();

            // Should have at least one language
            assert!(!langs.is_empty());

            // Each language should have a code and display name
            for (code, name) in langs {
                assert!(!code.is_empty());
                assert!(!name.is_empty());
            }
        });
    }
}
