//! Central language discovery module for dynamic language support
//!
//! This module provides the core language discovery functionality that
//! automatically detects available languages from the filesystem structure,
//! eliminating the need for hardcoded language lists.

use std::path::{Path, PathBuf};
use std::sync::LazyLock;

use tracing::debug;

/// Cached discovered languages to avoid repeated filesystem access
static DISCOVERED_LANGUAGES: LazyLock<Vec<String>> = LazyLock::new(|| {
    discover_languages_from_filesystem().unwrap_or_else(|_| {
        debug!("Failed to discover languages from filesystem, using fallback");
        get_fallback_languages()
    })
});

/// Discover available languages from the content/locales directory
pub fn discover_available_languages() -> &'static Vec<String> {
    &DISCOVERED_LANGUAGES
}

/// Get the primary/default language from discovered languages
pub fn get_default_language() -> &'static str {
    // Check environment variable first
    if let Ok(env_default) = std::env::var("DEFAULT_LANGUAGE") {
        if DISCOVERED_LANGUAGES.contains(&env_default) {
            // Return static reference by leaking the string (acceptable for static config)
            return Box::leak(env_default.into_boxed_str());
        }
    }

    // Priority order: en, es, first available
    for preferred in ["en", "es"] {
        if DISCOVERED_LANGUAGES.contains(&preferred.to_string()) {
            return preferred;
        }
    }

    // Fallback to first available language
    DISCOVERED_LANGUAGES
        .first()
        .map(|s| s.as_str())
        .unwrap_or("en")
}

/// Check if a language is supported (discovered)
pub fn is_supported_language(lang: &str) -> bool {
    DISCOVERED_LANGUAGES.contains(&lang.to_string())
}

/// Get the number of supported languages
pub fn supported_language_count() -> usize {
    DISCOVERED_LANGUAGES.len()
}

/// Internal function to discover languages from filesystem
fn discover_languages_from_filesystem() -> Result<Vec<String>, Box<dyn std::error::Error>> {
    // Use unified path resolution system from rustelo_utils
    let i18n_path = rustelo_utils::manifest::i18n_path_safe()
        .map_err(|e| format!("Failed to resolve i18n path: {}", e))?;
    let locales_dir = i18n_path.join("locales");
    let mut languages = Vec::new();

    if !locales_dir.exists() {
        return Err(format!("Locales directory not found: {}", locales_dir.display()).into());
    }

    let entries = std::fs::read_dir(&locales_dir)?;
    for entry in entries {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
                // Validate that this looks like a language code (2-5 characters, alphanumeric
                // or dash)
                if is_valid_language_code(dir_name) {
                    // Check if it has the expected FTL structure
                    if has_ftl_structure(&path) {
                        languages.push(dir_name.to_string());
                    }
                }
            }
        }
    }

    // Sort for consistent ordering
    languages.sort();

    // Ensure we have at least one language
    if languages.is_empty() {
        debug!("No valid language directories found in locales, using fallback");
        return Ok(get_fallback_languages());
    }

    debug!("Discovered available languages: {:?}", languages);
    Ok(languages)
}

/// Validate that a directory name looks like a language code
fn is_valid_language_code(dir_name: &str) -> bool {
    dir_name.len() >= 2
        && dir_name.len() <= 5
        && dir_name.chars().all(|c| c.is_alphanumeric() || c == '-')
}

/// Check if a language directory has the expected FTL structure
fn has_ftl_structure(path: &Path) -> bool {
    let has_common = path.join("common.ftl").exists();
    let has_pages = path.join("pages").is_dir();
    let has_components = path.join("components").is_dir();

    // At least one of these should exist for a valid language directory
    has_common || has_pages || has_components
}

/// Get project root path for locating content
fn get_root_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
    let root_path = std::env::var("CARGO_MANIFEST_DIR")
        .or_else(|_| std::env::var("PWD"))
        .or_else(|_| std::env::current_dir().map(|p| p.to_string_lossy().into_owned()))
        .unwrap_or_else(|_| {
            debug!("Could not determine project root from env vars, using current directory");
            ".".to_string()
        });

    Ok(PathBuf::from(root_path))
}

/// Fallback languages when filesystem discovery fails
fn get_fallback_languages() -> Vec<String> {
    // Check environment variable for supported languages
    if let Ok(env_supported) = std::env::var("SUPPORTED_LANGUAGES") {
        let languages: Vec<String> = env_supported
            .split(',')
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .collect();

        if !languages.is_empty() {
            return languages;
        }
    }

    // Ultimate fallback
    vec!["en".to_string(), "es".to_string()]
}

/// Force refresh of discovered languages (useful for testing or runtime
/// updates)
pub fn refresh_language_discovery() {
    // This would require making DISCOVERED_LANGUAGES mutable,
    // which is complex with LazyLock. For now, this is a placeholder
    // that could be implemented with a different caching strategy if needed.
    debug!("Language discovery refresh requested - restart required for changes to take effect");
}

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

    #[test]
    fn test_language_code_validation() {
        assert!(is_valid_language_code("en"));
        assert!(is_valid_language_code("es"));
        assert!(is_valid_language_code("fr"));
        assert!(is_valid_language_code("zh-CN"));
        assert!(is_valid_language_code("pt-BR"));

        assert!(!is_valid_language_code(""));
        assert!(!is_valid_language_code("a"));
        assert!(!is_valid_language_code("toolong"));
        assert!(!is_valid_language_code("en@US"));
        assert!(!is_valid_language_code("en_US"));
    }

    #[test]
    fn test_discovery_functions() {
        // Test that discovery functions don't panic
        let languages = discover_available_languages();
        assert!(!languages.is_empty());

        let default = get_default_language();
        assert!(!default.is_empty());

        assert!(is_supported_language(default));

        let count = supported_language_count();
        assert!(count > 0);
    }

    #[test]
    fn test_fallback_behavior() {
        let fallback = get_fallback_languages();
        assert!(!fallback.is_empty());
        assert!(fallback.contains(&"en".to_string()));
    }
}
