/// Number, date, and currency formatting per locale.
///
/// Formatting is locale-sensitive: number grouping, decimal separators,
/// currency symbol placement, and date ordering all vary by locale.
/// Use these formatters rather than hardcoding formatting logic.

use crate::locale::Locale;

/// Format a number with locale-appropriate grouping and decimals.
///
/// Examples:
/// - en-US: 1,234,567.89
/// - de-DE: 1.234.567,89
/// - fr-FR: 1 234 567,89
pub fn format_number(value: f64, locale: &Locale, decimal_places: usize) -> String {
    let (group_sep, decimal_sep) = separators_for_locale(locale);

    let rounded = round_to(value, decimal_places);
    let is_negative = rounded < 0.0;
    let abs_value = rounded.abs();

    let int_part = abs_value.trunc() as u64;
    let frac_part = ((abs_value.fract() * 10f64.powi(decimal_places as i32)).round()) as u64;

    let int_str = format_integer_with_grouping(int_part, group_sep);

    let result = if decimal_places > 0 {
        format!(
            "{}{}{}",
            int_str,
            decimal_sep,
            format!("{:0>width$}", frac_part, width = decimal_places)
        )
    } else {
        int_str
    };

    if is_negative {
        format!("-{}", result)
    } else {
        result
    }
}

/// Format a currency value with locale-appropriate symbol and placement.
///
/// Examples:
/// - en-US / USD: $1,234.56
/// - de-DE / EUR: 1.234,56 €
/// - ja / JPY: ¥1,235
pub fn format_currency(value: f64, locale: &Locale, currency_code: &str) -> String {
    let (symbol, prefix, decimals) = currency_info(currency_code);
    let formatted = format_number(value, locale, decimals);

    if prefix {
        format!("{}{}", symbol, formatted)
    } else {
        format!("{} {}", formatted, symbol)
    }
}

/// Format an integer with locale-appropriate grouping separators.
pub fn format_integer(value: i64, locale: &Locale) -> String {
    let (group_sep, _) = separators_for_locale(locale);
    let is_negative = value < 0;
    let abs_val = value.unsigned_abs();
    let grouped = format_integer_with_grouping(abs_val, group_sep);
    if is_negative {
        format!("-{}", grouped)
    } else {
        grouped
    }
}

/// Format a percentage (0.85 → "85%", locale-aware).
pub fn format_percent(value: f64, locale: &Locale, decimal_places: usize) -> String {
    let pct = value * 100.0;
    let (_, decimal_sep) = separators_for_locale(locale);
    let int_part = pct.trunc() as u64;
    let frac = ((pct.fract() * 10f64.powi(decimal_places as i32)).round()) as u64;

    if decimal_places > 0 {
        format!(
            "{}{}{}%",
            int_part,
            decimal_sep,
            format!("{:0>width$}", frac, width = decimal_places)
        )
    } else {
        format!("{}%", int_part)
    }
}

// --- Internal helpers ---

fn separators_for_locale(locale: &Locale) -> (char, char) {
    match locale.language.as_str() {
        // Comma grouping, period decimal (en-US style)
        "en" | "ja" | "ko" | "zh" | "th" => (',', '.'),
        // Period grouping, comma decimal (European style)
        "de" | "nl" | "it" | "pt" | "es" | "tr" | "pl" | "ru" | "uk" | "el" => ('.', ','),
        // Thin space grouping, comma decimal (French style)
        "fr" | "sv" | "no" | "nb" | "da" | "fi" => ('\u{202F}', ','),
        // Default: comma grouping, period decimal
        _ => (',', '.'),
    }
}

fn format_integer_with_grouping(value: u64, sep: char) -> String {
    let s = value.to_string();
    if s.len() <= 3 {
        return s;
    }
    let mut result = String::new();
    let chars: Vec<char> = s.chars().collect();
    let len = chars.len();
    for (i, &ch) in chars.iter().enumerate() {
        if i > 0 && (len - i) % 3 == 0 {
            result.push(sep);
        }
        result.push(ch);
    }
    result
}

fn round_to(value: f64, places: usize) -> f64 {
    let factor = 10f64.powi(places as i32);
    (value * factor).round() / factor
}

fn currency_info(code: &str) -> (&'static str, bool, usize) {
    // (symbol, prefix, decimal_places)
    match code.to_uppercase().as_str() {
        "USD" => ("$", true, 2),
        "EUR" => ("€", false, 2),
        "GBP" => ("£", true, 2),
        "JPY" => ("¥", true, 0),
        "CNY" => ("¥", true, 2),
        "KRW" => ("₩", true, 0),
        "INR" => ("₹", true, 2),
        "CHF" => ("CHF", true, 2),
        "CAD" => ("CA$", true, 2),
        "AUD" => ("A$", true, 2),
        "BRL" => ("R$", true, 2),
        "MXN" => ("MX$", true, 2),
        "RUB" => ("₽", false, 2),
        "SEK" => ("kr", false, 2),
        "NOK" => ("kr", false, 2),
        "DKK" => ("kr", false, 2),
        "PLN" => ("zł", false, 2),
        "TRY" => ("₺", true, 2),
        "SAR" => ("﷼", false, 2),
        "AED" => ("د.إ", false, 2),
        _ => ("¤", true, 2), // generic currency sign for unknown codes
    }
}

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

    #[test]
    fn format_number_en_us() {
        let locale = Locale::en_us();
        assert_eq!(format_number(1234567.89, &locale, 2), "1,234,567.89");
    }

    #[test]
    fn format_number_de() {
        let locale = Locale::new("de-DE");
        assert_eq!(format_number(1234.56, &locale, 2), "1.234,56");
    }

    #[test]
    fn format_number_no_decimals() {
        let locale = Locale::en_us();
        assert_eq!(format_number(42.0, &locale, 0), "42");
    }

    #[test]
    fn format_number_negative() {
        let locale = Locale::en_us();
        assert_eq!(format_number(-1000.0, &locale, 2), "-1,000.00");
    }

    #[test]
    fn format_currency_usd() {
        let locale = Locale::en_us();
        assert_eq!(format_currency(1234.56, &locale, "USD"), "$1,234.56");
    }

    #[test]
    fn format_currency_jpy_no_decimals() {
        let locale = Locale::ja();
        assert_eq!(format_currency(1234.0, &locale, "JPY"), "¥1,234");
    }

    #[test]
    fn format_integer_groups() {
        let locale = Locale::en_us();
        assert_eq!(format_integer(1000000, &locale), "1,000,000");
    }

    #[test]
    fn format_integer_small() {
        let locale = Locale::en_us();
        assert_eq!(format_integer(42, &locale), "42");
    }

    #[test]
    fn format_percent_whole() {
        let locale = Locale::en_us();
        assert_eq!(format_percent(0.85, &locale, 0), "85%");
    }

    #[test]
    fn format_percent_with_decimal() {
        let locale = Locale::en_us();
        assert_eq!(format_percent(0.856, &locale, 1), "85.6%");
    }
}
