//! SI prefix handling for unit decomposition.
//!
//! Supports prefixes from nano (10^-9) through tera (10^12).
//! Short-form symbols are case-sensitive: "k" = kilo, "M" = mega, "m" = milli.
//! Long-form names are case-insensitive: "kilo", "Kilo", "KILO" all match.

use std::collections::HashSet;
use std::sync::LazyLock;

/// An SI prefix definition.
#[derive(Debug, Clone, Copy)]
pub struct SiPrefix {
    /// Long-form name (e.g., "kilo").
    pub name: &'static str,
    /// Short-form symbol (e.g., "k").
    pub symbol: &'static str,
    /// Multiplication factor (e.g., 1e3 for kilo).
    pub factor: f64,
}

/// All supported SI prefixes from nano (10^-9) through tera (10^12).
pub static SI_PREFIXES: &[SiPrefix] = &[
    SiPrefix { name: "tera",  symbol: "T",  factor: 1e12 },
    SiPrefix { name: "giga",  symbol: "G",  factor: 1e9 },
    SiPrefix { name: "mega",  symbol: "M",  factor: 1e6 },
    SiPrefix { name: "kilo",  symbol: "k",  factor: 1e3 },
    SiPrefix { name: "centi", symbol: "c",  factor: 1e-2 },
    SiPrefix { name: "milli", symbol: "m",  factor: 1e-3 },
    SiPrefix { name: "micro", symbol: "\u{00B5}", factor: 1e-6 },
    SiPrefix { name: "nano",  symbol: "n",  factor: 1e-9 },
];

/// Base unit abbreviations that accept SI prefixes.
/// Case-sensitive: "m" (meter), "g" (gram), "s" (second), etc.
static SI_COMPATIBLE_ABBREVS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
    HashSet::from([
        "m",    // Length (meter)
        "g",    // Mass (gram)
        "L", "l", // Volume (liter)
        "B", "b", "bit", // Data
        "s",    // Time (second)
        "Pa",   // Pressure (pascal)
        "J",    // Energy (joule)
        "W",    // Power (watt)
        "N",    // Force (newton)
        "rad",  // Angle (radian)
        "eV",   // Energy (electronvolt)
        "Hz",   // Frequency (for future use)
    ])
});

/// Base unit long-form names that accept SI prefixes (lowercase).
static SI_COMPATIBLE_NAMES: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
    HashSet::from([
        "meter", "meters", "metre", "metres",
        "gram", "grams",
        "liter", "liters", "litre", "litres",
        "byte", "bytes", "bit", "bits",
        "second", "seconds",
        "pascal", "pascals",
        "joule", "joules",
        "watt", "watts",
        "newton", "newtons",
        "radian", "radians",
        "electronvolt", "electronvolts",
        "hertz",
    ])
});

/// Try to decompose a unit string into an SI prefix symbol + base unit abbreviation.
/// Returns `(prefix, remaining_unit_str)` if a valid decomposition is found.
///
/// Uses case-sensitive matching for symbols:
/// - "k" = kilo, "M" = mega, "m" = milli, "G" = giga, etc.
pub fn match_short_prefix(input: &str) -> Option<(&'static SiPrefix, &str)> {
    for prefix in SI_PREFIXES {
        if let Some(remainder) = input.strip_prefix(prefix.symbol) {
            if !remainder.is_empty() && SI_COMPATIBLE_ABBREVS.contains(remainder) {
                return Some((prefix, remainder));
            }
        }
    }

    // Also try Greek small letter mu (U+03BC) as alias for micro sign (U+00B5)
    if let Some(remainder) = input.strip_prefix('\u{03BC}') {
        if !remainder.is_empty() && SI_COMPATIBLE_ABBREVS.contains(remainder) {
            let micro = SI_PREFIXES.iter().find(|p| p.name == "micro").unwrap();
            return Some((micro, remainder));
        }
    }

    None
}

/// Try to decompose a unit string into an SI prefix long-form name + base unit name.
/// Returns `(prefix, remaining_unit_str)` if a valid decomposition is found.
///
/// Case-insensitive matching for long-form names.
pub fn match_long_prefix(input: &str) -> Option<(&'static SiPrefix, &str)> {
    let lower = input.to_lowercase();

    for prefix in SI_PREFIXES {
        if let Some(remainder) = lower.strip_prefix(prefix.name) {
            if !remainder.is_empty() && SI_COMPATIBLE_NAMES.contains(remainder) {
                return Some((prefix, &input[prefix.name.len()..]));
            }
        }
    }

    None
}

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

    #[test]
    fn test_short_prefix_km() {
        let result = match_short_prefix("km");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "kilo");
        assert_eq!(prefix.factor, 1e3);
        assert_eq!(base, "m");
    }

    #[test]
    fn test_short_prefix_mw_is_milliwatt() {
        let result = match_short_prefix("mW");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "milli");
        assert_eq!(base, "W");
    }

    #[test]
    fn test_short_prefix_mega_w() {
        let result = match_short_prefix("MW");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "mega");
        assert_eq!(base, "W");
    }

    #[test]
    fn test_short_prefix_micro_s_greek_mu() {
        let result = match_short_prefix("\u{03BC}s");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "micro");
        assert_eq!(base, "s");
    }

    #[test]
    fn test_short_prefix_micro_sign() {
        let result = match_short_prefix("\u{00B5}s");
        assert!(result.is_some());
        let (prefix, _) = result.unwrap();
        assert_eq!(prefix.name, "micro");
    }

    #[test]
    fn test_short_prefix_ns() {
        let result = match_short_prefix("ns");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "nano");
        assert_eq!(base, "s");
    }

    #[test]
    fn test_short_prefix_gb() {
        let result = match_short_prefix("GB");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "giga");
        assert_eq!(base, "B");
    }

    #[test]
    fn test_short_prefix_tb() {
        let result = match_short_prefix("TB");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "tera");
        assert_eq!(base, "B");
    }

    #[test]
    fn test_short_prefix_cm() {
        let result = match_short_prefix("cm");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "centi");
        assert_eq!(base, "m");
    }

    #[test]
    fn test_short_prefix_mega_pa() {
        let result = match_short_prefix("MPa");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "mega");
        assert_eq!(base, "Pa");
    }

    #[test]
    fn test_short_prefix_giga_j() {
        let result = match_short_prefix("GJ");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "giga");
        assert_eq!(base, "J");
    }

    #[test]
    fn test_short_prefix_no_match_standalone() {
        assert!(match_short_prefix("k").is_none());
    }

    #[test]
    fn test_short_prefix_no_match_incompatible() {
        assert!(match_short_prefix("kft").is_none());
    }

    // ─── Long-form tests ────────────────────────────────────────────────

    #[test]
    fn test_long_prefix_kilometers() {
        let result = match_long_prefix("kilometers");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "kilo");
        assert_eq!(base, "meters");
    }

    #[test]
    fn test_long_prefix_milligrams() {
        let result = match_long_prefix("milligrams");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "milli");
        assert_eq!(base, "grams");
    }

    #[test]
    fn test_long_prefix_megawatts() {
        let result = match_long_prefix("megawatts");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "mega");
        assert_eq!(base, "watts");
    }

    #[test]
    fn test_long_prefix_nanoseconds() {
        let result = match_long_prefix("nanoseconds");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "nano");
        assert_eq!(base, "seconds");
    }

    #[test]
    fn test_long_prefix_terabytes() {
        let result = match_long_prefix("terabytes");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "tera");
        assert_eq!(base, "bytes");
    }

    #[test]
    fn test_long_prefix_microseconds() {
        let result = match_long_prefix("microseconds");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "micro");
        assert_eq!(base, "seconds");
    }

    #[test]
    fn test_long_prefix_centimeters() {
        let result = match_long_prefix("centimeters");
        assert!(result.is_some());
        let (prefix, base) = result.unwrap();
        assert_eq!(prefix.name, "centi");
        assert_eq!(base, "meters");
    }

    #[test]
    fn test_long_prefix_case_insensitive() {
        let result = match_long_prefix("Kilometers");
        assert!(result.is_some());
        let (prefix, _) = result.unwrap();
        assert_eq!(prefix.name, "kilo");
    }

    #[test]
    fn test_long_prefix_no_match_kilofahrenheit() {
        assert!(match_long_prefix("kilofahrenheit").is_none());
    }

    #[test]
    fn test_long_prefix_no_match_kilomiles() {
        assert!(match_long_prefix("kilomiles").is_none());
    }

    #[test]
    fn test_all_prefix_factors() {
        let expected = vec![
            ("tera",  1e12),
            ("giga",  1e9),
            ("mega",  1e6),
            ("kilo",  1e3),
            ("centi", 1e-2),
            ("milli", 1e-3),
            ("micro", 1e-6),
            ("nano",  1e-9),
        ];
        for (name, factor) in expected {
            let prefix = SI_PREFIXES.iter().find(|p| p.name == name);
            assert!(prefix.is_some(), "Missing prefix: {}", name);
            assert_eq!(prefix.unwrap().factor, factor, "Wrong factor for {}", name);
        }
    }
}
