// SPDX-License-Identifier: GPL-3.0-or-later
//! Internationalization (i18n) for Temperlux.
//!
//! Uses `rust-i18n` for compile-time-embedded translations.
//! Translation files live in `locales/*.yml` relative to `Cargo.toml`.
//!
//! ## Usage
//!
//! ```ignore
//! use temperlux::i18n::tr;
//!
//! let label = tr("gui.title");         // -> "Temperlux" (or translated)
//! ```
//!
//! ## Adding a new language
//!
//! 1. Copy `locales/en.yml` to `locales/<lang>.yml` (e.g. `de.yml`)
//! 2. Translate the values (keep keys identical)
//! 3. Rebuild — translations are embedded at compile time
//! 4. The app auto-detects locale from `LANG` / `LC_ALL` env vars

/// Translate a string by key. Returns the translated string,
/// or the English fallback if no translation is found.
pub fn tr(key: &str) -> String {
    rust_i18n::t!(key).to_string()
}

/// Translate with named interpolation parameters.
///
/// Keys in the YAML use `%{name}` placeholders, e.g.:
/// `msg.caps_set: "Display %{num} caps set: min=%{min}%, max=%{max}%"`
///
/// Example:
/// ```ignore
/// tr_args("msg.caps_set", &[("num", "1"), ("min", "10"), ("max", "90")])
/// ```
#[allow(clippy::needless_pass_by_value)]
pub fn tr_args(key: &str, args: &[(&str, &str)]) -> String {
    let mut s = rust_i18n::t!(key).to_string();
    for (k, v) in args {
        s = s.replace(&format!("%{{{}}}", k), v);
    }
    s
}

/// Initialize i18n. Called at startup from both CLI (`main.rs`) and GUI (`gui.rs`).
///
/// `rust-i18n` reads `LC_ALL`/`LANG` raw (e.g. `de_DE.UTF-8`), but locale files
/// use short codes (`de`). We normalize here so the lookup actually matches.
pub fn init() {
    let raw = std::env::var("LC_ALL")
        .or_else(|_| std::env::var("LANG"))
        .unwrap_or_default();
    // "de_DE.UTF-8" → "de"
    let short = raw.split(['_', '.']).next().unwrap_or("");
    if !short.is_empty() && short != "en" {
        rust_i18n::set_locale(short);
    }
    log::debug!("i18n initialized, locale: {} (from {})", short, raw);
}

/// Set the active locale (e.g., "de", "fr", "en").
pub fn set_locale(locale: &str) {
    rust_i18n::set_locale(locale);
}

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

    /// Lock to prevent locale-changing tests from racing with other tests
    /// that depend on `tr()` using the default English locale.
    static LOCALE_LOCK: Mutex<()> = Mutex::new(());

    // ── Expected locales (Tier 1 + Tier 2 + Tier 3) ──────────────────────
    const EXPECTED_LOCALES: &[&str] = &[
        "en", "de", "es", "fr", "pt", "ru", "zh",  // Tier 1
        "it", "pl", "nl", "ja", "ko", "tr", "uk", "id",  // Tier 2
        "hi", "ar", "bn", "ur", "fa", "vi", "th", "ms", "cs", "hu", "ro",  // Tier 3
    ];

    // ── Helper: extract all keys from en.yml at compile time ──────────────
    fn all_english_keys() -> Vec<&'static str> {
        let yml = include_str!("../locales/en.yml");
        yml.lines()
            .filter_map(|line| {
                let line = line.trim();
                if line.starts_with('"') {
                    // Format: "key.name": "value"
                    let end = line[1..].find("\":")?;
                    Some(&line[1..1 + end])
                } else {
                    None
                }
            })
            .collect()
    }

    // ── Basic lookup tests ───────────────────────────────────────────────

    #[test]
    fn test_tr_finds_key() {
        let _guard = LOCALE_LOCK.lock().unwrap();
        assert_eq!(tr("triggers.at_sunset"), "at sunset");
        assert_eq!(tr("actions.save"), "Save");
        assert_eq!(tr("sync.off"), "Off");
    }

    #[test]
    fn test_tr_missing_key_returns_key() {
        let _guard = LOCALE_LOCK.lock().unwrap();
        assert_eq!(tr("nonexistent.key"), "nonexistent.key");
    }

    #[test]
    fn test_tr_args_interpolation() {
        let _guard = LOCALE_LOCK.lock().unwrap();
        let result = tr_args("msg.caps_set", &[
            ("num", "1"),
            ("min", "10"),
            ("max", "90"),
        ]);
        assert_eq!(result, "Display 1 caps set: min=10%, max=90%");
    }

    // ── Locale coverage tests ────────────────────────────────────────────

    #[test]
    fn test_all_expected_locales_loaded() {
        let available = rust_i18n::available_locales!();
        for &expected in EXPECTED_LOCALES {
            assert!(
                available.iter().any(|a| *a == expected),
                "Locale '{}' is not loaded. Available: {:?}",
                expected,
                available
            );
        }
    }

    #[test]
    fn test_every_key_resolves_in_every_locale() {
        let _guard = LOCALE_LOCK.lock().unwrap();
        let keys = all_english_keys();
        assert!(!keys.is_empty(), "No keys found in en.yml — parser is broken");

        let non_en: &[&str] = &[
            "de", "es", "fr", "pt", "ru", "zh", "it", "pl", "nl", "ja", "ko", "tr", "uk", "id",
            "hi", "ar", "bn", "ur", "fa", "vi", "th", "ms", "cs", "hu", "ro",
        ];

        for &locale in non_en {
            rust_i18n::set_locale(locale);
            for &key in &keys {
                let val = tr(key);
                assert_ne!(
                    val, key,
                    "[{}] key '{}' not translated (returned key string as-is)",
                    locale, key
                );
                assert!(
                    !val.is_empty(),
                    "[{}] key '{}' returned empty string",
                    locale, key
                );
            }
        }

        // Always restore English
        rust_i18n::set_locale("en");
    }
}
