pub use geonames::City;

pub static CITIES_BYTES: &[u8] = include_bytes!("../res/cities.bitcode-v0-6");

pub fn load_cities() -> Vec<City> {
    geonames::bitcode::decode(CITIES_BYTES).unwrap_or_default()
}

/// A locale entry with both the raw code (for applying) and a human-readable display name.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocaleEntry {
    pub code: String,
    pub display_name: String,
}

/// Lists available locales from `locale -a`, filtered to UTF-8 entries.
/// Returns human-readable names via locales-rs (e.g. "English (United States)").
pub fn list_locales() -> Vec<LocaleEntry> {
    let Ok(out) = std::process::Command::new("locale").arg("-a").output() else {
        return vec![];
    };

    let registry = locales_rs::Registry::new().ok();

    let mut entries: Vec<LocaleEntry> = String::from_utf8_lossy(&out.stdout)
        .lines()
        .map(|l| l.trim().to_string())
        .filter(|l| {
            if l.is_empty() { return false; }
            // Filter out C and POSIX pseudo-locales
            if l == "C" || l == "POSIX" { return false; }
            if l.starts_with("C.") || l.starts_with("POSIX.") { return false; }
            let lower = l.to_lowercase();
            lower.contains("utf-8") || lower.contains("utf8")
        })
        .map(|code| {
            let display_name = registry.as_ref()
                .and_then(|r| r.locale(&code))
                .map(|locale| {
                    let lang = &locale.language.display_name;
                    let territory = &locale.territory.display_name;
                    // Title-case the language name
                    let mut chars = lang.chars();
                    let lang_cased = match chars.next() {
                        Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
                        None => lang.clone(),
                    };
                    format!("{} ({})", lang_cased, territory)
                })
                .unwrap_or_else(|| code.clone());
            LocaleEntry { code, display_name }
        })
        .collect();

    entries.sort_by(|a, b| a.display_name.cmp(&b.display_name));
    entries.dedup_by_key(|e| e.code.clone());
    entries
}

/// A flat keyboard layout+variant entry ready for display and application.
pub struct LayoutEntry {
    pub layout: String,
    pub variant: String,
    pub description: String,
}

/// Returns all xkb layout+variant combinations sorted with "us" first.
pub fn keyboard_layout_entries() -> Vec<LayoutEntry> {
    use std::cmp;

    let base = match xkb_data::keyboard_layouts() {
        Ok(v) => v,
        Err(_) => return vec![],
    };
    let extra = xkb_data::extra_keyboard_layouts().ok();

    let mut sorted: Vec<(&xkb_data::KeyboardLayout, bool)> = base
        .layouts()
        .iter()
        .map(|l| (l, false))
        .chain(
            extra
                .iter()
                .flat_map(|e| e.layouts().iter().map(|l| (l, true))),
        )
        .collect();

    sorted.sort_unstable_by(|(a, _), (b, _)| match (a.name(), b.name()) {
        ("us", _) => cmp::Ordering::Less,
        (_, "us") => cmp::Ordering::Greater,
        ("custom", _) => cmp::Ordering::Greater,
        (_, "custom") => cmp::Ordering::Less,
        _ => a.description().cmp(b.description()),
    });

    let mut entries = Vec::new();
    for (layout, _) in &sorted {
        entries.push(LayoutEntry {
            layout: layout.name().to_string(),
            variant: String::new(),
            description: layout.description().to_string(),
        });
        if let Some(variants) = layout.variants() {
            let mut vs: Vec<LayoutEntry> = variants
                .iter()
                .map(|v| LayoutEntry {
                    layout: layout.name().to_string(),
                    variant: v.name().to_string(),
                    description: format!("{} – {}", layout.description(), v.description()),
                })
                .collect();
            vs.sort_unstable_by(|a, b| a.description.cmp(&b.description));
            entries.extend(vs);
        }
    }
    entries
}

/// Apply system-wide locale via `pkexec localectl set-locale`.
pub fn apply_locale(locale: &str) {
    let arg = format!("LANG={locale}");
    let _ = std::process::Command::new("pkexec")
        .args(["localectl", "set-locale", &arg])
        .status();
}

/// Apply system-wide X11 keyboard layout via `pkexec localectl set-x11-keymap`.
pub fn apply_keyboard(layout: &str, variant: &str) {
    let mut cmd = std::process::Command::new("pkexec");
    cmd.args(["localectl", "set-x11-keymap", layout]);
    if !variant.is_empty() {
        cmd.arg(""); // empty MODEL positional arg
        cmd.arg(variant);
    }
    let _ = cmd.status();
}

/// Apply system-wide timezone via `pkexec timedatectl set-timezone`.
pub fn apply_timezone(tz: &str) {
    let _ = std::process::Command::new("pkexec")
        .args(["timedatectl", "set-timezone", tz])
        .status();
}
