use crate::model::cell::CellValue;

/// Format a CellValue for display with number formatting options.
pub fn format_value(v: Option<&CellValue>, comma: bool, decimals: u8) -> String {
    match v {
        Some(CellValue::Number(n)) => format_f64(*n, comma, decimals),
        Some(CellValue::Text(s)) => s.clone(),
        Some(CellValue::Error(e)) => format!("ERR:{e}"),
        None => String::new(),
    }
}

/// Parse a number format string like ",.0" into (use_commas, decimal_places).
pub fn parse_number_format(fmt: &str) -> (bool, u8) {
    let comma = fmt.contains(',');
    let decimals = fmt
        .rfind('.')
        .and_then(|i| fmt[i + 1..].parse::<u8>().ok())
        .unwrap_or(0);
    (comma, decimals)
}

/// Round half away from zero (the "normal" rounding people expect).
fn round_half_away(n: f64, decimals: u8) -> f64 {
    let factor = 10_f64.powi(decimals as i32);
    (n * factor + n.signum() * 0.5).trunc() / factor
}

/// Format an f64 with optional comma grouping and decimal places.
pub fn format_f64(n: f64, comma: bool, decimals: u8) -> String {
    let rounded = round_half_away(n, decimals);
    let formatted = format!("{:.prec$}", rounded, prec = decimals as usize);
    if !comma {
        return formatted;
    }
    let (int_part, dec_part) = if let Some(dot) = formatted.find('.') {
        (&formatted[..dot], Some(&formatted[dot..]))
    } else {
        (&formatted[..], None)
    };
    let is_neg = int_part.starts_with('-');
    let digits = if is_neg { &int_part[1..] } else { int_part };
    let mut result = String::new();
    for (idx, c) in digits.chars().rev().enumerate() {
        if idx > 0 && idx % 3 == 0 {
            result.push(',');
        }
        result.push(c);
    }
    if is_neg {
        result.push('-');
    }
    let mut out: String = result.chars().rev().collect();
    if let Some(dec) = dec_part {
        out.push_str(dec);
    }
    out
}

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

    // ── parse_number_format ────────────────────────────────────────────

    #[test]
    fn parse_comma_and_zero_decimals() {
        assert_eq!(parse_number_format(",.0"), (true, 0));
    }

    #[test]
    fn parse_comma_and_two_decimals() {
        assert_eq!(parse_number_format(",.2"), (true, 2));
    }

    #[test]
    fn parse_no_comma_two_decimals() {
        assert_eq!(parse_number_format(".2"), (false, 2));
    }

    #[test]
    fn parse_comma_only() {
        assert_eq!(parse_number_format(","), (true, 0));
    }

    #[test]
    fn parse_empty_string() {
        assert_eq!(parse_number_format(""), (false, 0));
    }

    #[test]
    fn parse_dot_no_digits_after() {
        // "." has nothing after the dot — parse::<u8> fails → default 0
        assert_eq!(parse_number_format("."), (false, 0));
    }

    #[test]
    fn parse_multiple_dots_uses_last() {
        // rfind picks the last dot
        assert_eq!(parse_number_format(",.1.3"), (true, 3));
    }

    // ── format_f64 basic ───────────────────────────────────────────────

    #[test]
    fn format_no_comma_zero_decimals() {
        assert_eq!(format_f64(1234.5, false, 0), "1235");
    }

    #[test]
    fn format_no_comma_two_decimals() {
        assert_eq!(format_f64(1234.5, false, 2), "1234.50");
    }

    #[test]
    fn format_comma_zero_decimals() {
        assert_eq!(format_f64(1234.0, true, 0), "1,234");
    }

    #[test]
    fn format_comma_two_decimals() {
        assert_eq!(format_f64(1234.56, true, 2), "1,234.56");
    }

    // ── comma placement boundaries ─────────────────────────────────────

    #[test]
    fn format_comma_exactly_three_digits() {
        assert_eq!(format_f64(999.0, true, 0), "999");
    }

    #[test]
    fn format_comma_four_digits() {
        assert_eq!(format_f64(1000.0, true, 0), "1,000");
    }

    #[test]
    fn format_comma_seven_digits() {
        assert_eq!(format_f64(1234567.0, true, 0), "1,234,567");
    }

    #[test]
    fn format_comma_millions_with_decimals() {
        assert_eq!(format_f64(1234567.89, true, 2), "1,234,567.89");
    }

    // ── negative numbers ───────────────────────────────────────────────

    #[test]
    fn format_negative_with_comma() {
        assert_eq!(format_f64(-1234.0, true, 0), "-1,234");
    }

    #[test]
    fn format_negative_with_comma_and_decimals() {
        assert_eq!(format_f64(-1234567.89, true, 2), "-1,234,567.89");
    }

    #[test]
    fn format_negative_no_comma() {
        assert_eq!(format_f64(-42.5, false, 1), "-42.5");
    }

    // ── edge values ────────────────────────────────────────────────────

    #[test]
    fn format_zero() {
        assert_eq!(format_f64(0.0, true, 2), "0.00");
    }

    #[test]
    fn format_small_fraction() {
        assert_eq!(format_f64(0.123, true, 2), "0.12");
    }

    #[test]
    fn format_negative_small_fraction() {
        assert_eq!(format_f64(-0.5, true, 1), "-0.5");
    }

    // ── rounding: half-away-from-zero ─────────────────────────────────

    #[test]
    fn round_half_up_positive() {
        // 2.5 → 3, not 2 (banker's would give 2)
        assert_eq!(format_f64(2.5, false, 0), "3");
    }

    #[test]
    fn round_half_down_negative() {
        // -2.5 → -3, not -2 (away from zero)
        assert_eq!(format_f64(-2.5, false, 0), "-3");
    }

    #[test]
    fn round_half_at_one_decimal() {
        // 1.25 → 1.3
        assert_eq!(format_f64(1.25, false, 1), "1.3");
    }

    #[test]
    fn round_below_half_truncates() {
        assert_eq!(format_f64(1.24, false, 1), "1.2");
    }

    #[test]
    fn round_above_half_rounds_up() {
        assert_eq!(format_f64(1.26, false, 1), "1.3");
    }

    // ── format_value dispatch ──────────────────────────────────────────

    #[test]
    fn format_value_number() {
        let v = CellValue::Number(1234.0);
        assert_eq!(format_value(Some(&v), true, 0), "1,234");
    }

    #[test]
    fn format_value_text() {
        let v = CellValue::Text("hello".into());
        assert_eq!(format_value(Some(&v), true, 2), "hello");
    }

    #[test]
    fn format_value_none() {
        assert_eq!(format_value(None, true, 2), "");
    }
}
