//! Content provider for pre-loaded localized strings in components
//!
//! This provides a lightweight way to access pre-loaded content maps
//! in components, complementing the main UnifiedI18n system.

use std::collections::HashMap;

#[cfg(any(target_arch = "wasm32", feature = "leptos-hydration"))]
use leptos::prelude::*;

/// Provides access to pre-loaded content maps with flexible options
#[derive(Clone, PartialEq)]
pub struct ContentProvider {
    content: Option<HashMap<String, String>>,
    /// When true, always check reactive context first (for client-side
    /// reactivity)
    use_reactive_fallback: bool,
}

impl ContentProvider {
    /// Create a new ContentProvider from optional content map
    pub fn new(content: Option<HashMap<String, String>>) -> Self {
        Self {
            content,
            use_reactive_fallback: false,
        }
    }

    /// Create a ContentProvider that will always check reactive context
    /// This enables automatic updates when language changes
    fn new_reactive(content: Option<HashMap<String, String>>) -> Self {
        Self {
            content,
            use_reactive_fallback: true,
        }
    }

    /// Get translation matching UnifiedI18n.t() signature
    /// Logs warning if key not found, returns empty string
    pub fn t(&self, key: &str) -> String {
        self.t_with_options(key, None, true)
    }

    /// Get translation with default value
    /// Logs warning if key not found
    pub fn t_with_default(&self, key: &str, default: &str) -> String {
        self.t_with_options(key, Some(default), true)
    }

    /// Get translation without logging warnings
    /// Returns empty string if not found
    pub fn t_quiet(&self, key: &str) -> String {
        self.t_with_options(key, None, false)
    }

    /// Get translation with default value, without logging
    pub fn t_quiet_with_default(&self, key: &str, default: &str) -> String {
        self.t_with_options(key, Some(default), false)
    }

    /// Try multiple prefixes in order, then base key, then fallback to default
    ///
    /// # Example
    /// ```rust
    /// // Tries "blog-featured", then "content-featured", then "featured"
    /// content.t_with_prefixes("featured", &["blog", "content"], None)
    ///
    /// // Tries "recipe-technology", then "content-technology", then returns "Technology"
    /// content.t_with_prefixes("technology", &["recipe", "content"], Some("Technology"))
    /// ```
    pub fn t_with_prefixes(&self, key: &str, prefixes: &[&str], default: Option<&str>) -> String {
        if let Some(content) = &self.content {
            // Try each prefix in order
            for prefix in prefixes {
                let prefixed_key = format!("{}-{}", prefix, key);
                if let Some(value) = content.get(&prefixed_key) {
                    return value.clone();
                }
            }

            // Try the base key without any prefix
            if let Some(value) = content.get(key) {
                return value.clone();
            }
        }

        // Return default or empty string
        default.unwrap_or("").to_string()
    }

    /// Convenience method for single prefix with fallback
    ///
    /// # Example
    /// ```rust
    /// // Tries "blog-title", then "content-title", then "title", then returns "Default Title"
    /// content.t_with_prefix("title", "blog", "content", Some("Default Title"))
    /// ```
    pub fn t_with_prefix(
        &self,
        key: &str,
        primary_prefix: &str,
        fallback_prefix: &str,
        default: Option<&str>,
    ) -> String {
        self.t_with_prefixes(key, &[primary_prefix, fallback_prefix], default)
    }

    /// Full control method - all other methods delegate to this
    pub fn t_with_options(&self, key: &str, default: Option<&str>, log_missing: bool) -> String {
        // Static content takes precedence: it was pre-built for the correct language by
        // build_page_content_patterns() and must not be overridden by a potentially
        // stale or wrong-language reactive context (provide_context inside an
        // Effect is not accessible to sibling DynChild scopes in the Leptos 0.7
        // Owner tree).
        if let Some(content) = &self.content {
            if let Some(value) = content.get(key) {
                return value.clone();
            }
        }

        // Reactive fallback for keys absent from the static map (e.g. dynamic content).
        #[cfg(target_arch = "wasm32")]
        if self.use_reactive_fallback {
            use crate::i18n::unified::use_unified_i18n;
            let i18n = use_unified_i18n();
            let translations = i18n
                .translations
                .get_translations_for_language(&i18n.language);
            if let Some(value) = translations.get(key) {
                return value.clone();
            }
        }

        if log_missing {
            if self.content.is_some() {
                tracing::warn!("Content key not found: '{}'", key);
            } else {
                tracing::warn!("Content key not found (no content map): '{}'", key);
            }
        }
        default.unwrap_or("").to_string()
    }
}

/// Create a ContentProvider for component-level content access
///
/// # Example
/// ```rust
/// use rustelo_core_lib::i18n::content_helper::create_content_provider;
/// use std::collections::HashMap;
///
/// let mut content_map = HashMap::new();
/// content_map.insert("title".to_string(), "Welcome".to_string());
/// let content = create_content_provider(Some(content_map));
///
/// assert_eq!(content.t("title"), "Welcome");                           // With warning if missing
/// assert_eq!(content.t_with_default("subtitle", "Default"), "Default"); // With fallback
/// assert_eq!(content.t_quiet("optional-key"), "");                     // No warning if missing
/// ```
pub fn create_content_provider(lang_content: Option<HashMap<String, String>>) -> ContentProvider {
    ContentProvider::new(lang_content)
}

/// Create a ContentProvider with reactive fallback to i18n context
///
/// This always enables reactive fallback, meaning every call to t() will check
/// the current language's translations from the unified i18n context. This
/// ensures that text updates automatically when the user changes languages.
///
/// This should be called in Leptos components only.
#[cfg(target_arch = "wasm32")]
pub fn create_content_provider_reactive(
    lang_content: Option<HashMap<String, String>>,
) -> ContentProvider {
    // Always enable reactive fallback for client-side components
    // This ensures language changes trigger immediate updates
    ContentProvider::new_reactive(lang_content)
}

/// SSR version - just returns the provided content (no reactivity on server)
#[cfg(not(target_arch = "wasm32"))]
pub fn create_content_provider_reactive(
    lang_content: Option<HashMap<String, String>>,
) -> ContentProvider {
    // SSR doesn't need reactive mode - content is fully resolved at render time
    ContentProvider::new(lang_content)
}

/// Hook to get a reactive ContentProvider that updates when language changes
///
/// This returns a Memo that you can use in your component to ensure content
/// updates reactively when the user switches languages.
///
/// # Example
/// ```rust,ignore
/// use rustelo_core_lib::i18n::content_helper::use_reactive_content_provider;
///
/// // In a Leptos component:
/// let content_memo = use_reactive_content_provider();
///
/// view! {
///     <div>
///         {move || {
///             let content = content_memo.get();
///             view! { <p>{content.t("some-key")}</p> }
///         }}
///     </div>
/// }
/// ```
#[cfg(target_arch = "wasm32")]
pub fn use_reactive_content_provider() -> Memo<ContentProvider> {
    use crate::i18n::unified::use_unified_i18n;
    use crate::state::language::use_language;

    let language_context = use_language();

    Memo::new(move |_| {
        let _ = language_context.current.get(); // Track language changes
        let i18n = use_unified_i18n();
        let translations = i18n
            .translations
            .get_translations_for_language(&i18n.language);
        ContentProvider::new(Some(translations))
    })
}

/// Alternative trait-based approach for more flexibility
///
/// This trait can be implemented on various types to provide consistent
/// content access patterns throughout the application.
pub trait ContentAccessor {
    /// Get content by key, returning empty string if not found
    fn get_content(&self, key: &str) -> String;

    /// Get content by key, returning None if not found (no logging)
    fn get_content_optional(&self, key: &str) -> Option<String>;
}

impl ContentAccessor for Option<HashMap<String, String>> {
    fn get_content(&self, key: &str) -> String {
        match self {
            Some(content) => content.get(key).cloned().unwrap_or_else(|| {
                tracing::warn!("i18n key not found: '{}'", key);
                String::new()
            }),
            None => {
                tracing::warn!("i18n key not found (no content): '{}'", key);
                String::new()
            }
        }
    }

    fn get_content_optional(&self, key: &str) -> Option<String> {
        self.as_ref().and_then(|content| content.get(key).cloned())
    }
}

impl ContentAccessor for HashMap<String, String> {
    fn get_content(&self, key: &str) -> String {
        self.get(key).cloned().unwrap_or_else(|| {
            tracing::warn!("i18n key not found: '{}'", key);
            String::new()
        })
    }

    fn get_content_optional(&self, key: &str) -> Option<String> {
        self.get(key).cloned()
    }
}

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

    #[test]
    fn test_content_provider_with_content() {
        let mut content = HashMap::new();
        content.insert("title".to_string(), "Welcome".to_string());
        content.insert("subtitle".to_string(), "Hello World".to_string());

        let provider = create_content_provider(Some(content));

        // Test basic t() method
        assert_eq!(provider.t("title"), "Welcome");
        assert_eq!(provider.t("subtitle"), "Hello World");
        assert_eq!(provider.t("missing"), ""); // Should return empty string

        // Test t_with_default() method
        assert_eq!(provider.t_with_default("title", "Default"), "Welcome"); // Existing key
        assert_eq!(provider.t_with_default("missing", "Default"), "Default"); // Missing key

        // Test t_quiet() method (no logging)
        assert_eq!(provider.t_quiet("title"), "Welcome");
        assert_eq!(provider.t_quiet("missing"), ""); // Should return empty string

        // Test t_quiet_with_default() method
        assert_eq!(
            provider.t_quiet_with_default("title", "Fallback"),
            "Welcome"
        );
        assert_eq!(
            provider.t_quiet_with_default("missing", "Fallback"),
            "Fallback"
        );

        // Test t_with_options() method (full control)
        assert_eq!(provider.t_with_options("title", None, true), "Welcome");
        assert_eq!(
            provider.t_with_options("missing", Some("Custom"), false),
            "Custom"
        );
        assert_eq!(provider.t_with_options("missing", None, false), "");
    }

    #[test]
    fn test_content_provider_without_content() {
        let provider = create_content_provider(None);

        // Test all methods with no content
        assert_eq!(provider.t("anything"), "");
        assert_eq!(provider.t_with_default("anything", "Default"), "Default");
        assert_eq!(provider.t_quiet("anything"), "");
        assert_eq!(
            provider.t_quiet_with_default("anything", "Fallback"),
            "Fallback"
        );
        assert_eq!(
            provider.t_with_options("anything", Some("Custom"), false),
            "Custom"
        );
    }

    #[test]
    fn test_content_provider_direct_creation() {
        let mut content = HashMap::new();
        content.insert("test".to_string(), "value".to_string());

        let provider = ContentProvider::new(Some(content));
        assert_eq!(provider.t("test"), "value");
        assert_eq!(provider.t("missing"), "");

        let empty_provider = ContentProvider::new(None);
        assert_eq!(empty_provider.t("anything"), "");
        assert_eq!(
            empty_provider.t_with_default("anything", "Backup"),
            "Backup"
        );
    }

    #[test]
    fn test_content_accessor_trait_some() {
        let mut content = HashMap::new();
        content.insert("key1".to_string(), "value1".to_string());
        let option_content = Some(content);

        assert_eq!(option_content.get_content("key1"), "value1");
        assert_eq!(option_content.get_content("missing"), "");

        assert_eq!(
            option_content.get_content_optional("key1"),
            Some("value1".to_string())
        );
        assert_eq!(option_content.get_content_optional("missing"), None);
    }

    #[test]
    fn test_content_accessor_trait_none() {
        let option_content: Option<HashMap<String, String>> = None;

        assert_eq!(option_content.get_content("anything"), "");
        assert_eq!(option_content.get_content_optional("anything"), None);
    }

    #[test]
    fn test_content_accessor_trait_hashmap() {
        let mut content = HashMap::new();
        content.insert("test".to_string(), "result".to_string());

        assert_eq!(content.get_content("test"), "result");
        assert_eq!(content.get_content("missing"), "");

        assert_eq!(
            content.get_content_optional("test"),
            Some("result".to_string())
        );
        assert_eq!(content.get_content_optional("missing"), None);
    }

    #[test]
    fn test_t_with_prefixes_multiple_prefixes() {
        let mut content = HashMap::new();
        content.insert(
            "blog-featured".to_string(),
            "Blog Featured Posts".to_string(),
        );
        content.insert(
            "content-featured".to_string(),
            "Featured Content".to_string(),
        );
        content.insert("featured".to_string(), "Featured".to_string());

        let provider = create_content_provider(Some(content));

        // Should find the first matching prefix
        assert_eq!(
            provider.t_with_prefixes("featured", &["blog", "content"], None),
            "Blog Featured Posts"
        );

        // Should try second prefix if first not found
        assert_eq!(
            provider.t_with_prefixes("featured", &["recipe", "content"], None),
            "Featured Content"
        );

        // Should fall back to base key if no prefixed keys found
        assert_eq!(
            provider.t_with_prefixes("featured", &["recipe", "news"], None),
            "Featured"
        );

        // Should return default if nothing found
        assert_eq!(
            provider.t_with_prefixes("missing", &["blog", "content"], Some("Default")),
            "Default"
        );
        assert_eq!(
            provider.t_with_prefixes("missing", &["blog", "content"], None),
            ""
        );
    }

    #[test]
    fn test_t_with_prefixes_empty_prefixes() {
        let mut content = HashMap::new();
        content.insert("title".to_string(), "Base Title".to_string());

        let provider = create_content_provider(Some(content));

        // Should work with empty prefix array
        assert_eq!(provider.t_with_prefixes("title", &[], None), "Base Title");
        assert_eq!(
            provider.t_with_prefixes("missing", &[], Some("Default")),
            "Default"
        );
    }

    #[test]
    fn test_t_with_prefixes_no_content() {
        let provider = create_content_provider(None);

        // Should return default when no content map
        assert_eq!(
            provider.t_with_prefixes("title", &["blog", "content"], Some("Default")),
            "Default"
        );
        assert_eq!(
            provider.t_with_prefixes("title", &["blog", "content"], None),
            ""
        );
    }

    #[test]
    fn test_t_with_prefix_convenience_method() {
        let mut content = HashMap::new();
        content.insert("blog-title".to_string(), "Blog Title".to_string());
        content.insert(
            "content-subtitle".to_string(),
            "Content Subtitle".to_string(),
        );
        content.insert("description".to_string(), "Base Description".to_string());

        let provider = create_content_provider(Some(content));

        // Should find primary prefix
        assert_eq!(
            provider.t_with_prefix("title", "blog", "content", None),
            "Blog Title"
        );

        // Should fall back to secondary prefix
        assert_eq!(
            provider.t_with_prefix("subtitle", "blog", "content", None),
            "Content Subtitle"
        );

        // Should fall back to base key
        assert_eq!(
            provider.t_with_prefix("description", "blog", "content", None),
            "Base Description"
        );

        // Should return default when nothing found
        assert_eq!(
            provider.t_with_prefix("missing", "blog", "content", Some("Fallback")),
            "Fallback"
        );
    }

    #[test]
    fn test_t_with_prefixes_prefix_order() {
        let mut content = HashMap::new();
        content.insert("blog-title".to_string(), "Blog Title".to_string());
        content.insert("content-title".to_string(), "Content Title".to_string());
        content.insert("news-title".to_string(), "News Title".to_string());

        let provider = create_content_provider(Some(content));

        // Should respect prefix order
        assert_eq!(
            provider.t_with_prefixes("title", &["blog", "content", "news"], None),
            "Blog Title"
        );
        assert_eq!(
            provider.t_with_prefixes("title", &["content", "news", "blog"], None),
            "Content Title"
        );
        assert_eq!(
            provider.t_with_prefixes("title", &["news", "blog", "content"], None),
            "News Title"
        );
    }
}
