//! Unit registry population -- registers all built-in units across all categories.

use super::categories::UnitCategory;
use super::{Conversion, UnitDef, UnitRegistry};

/// Register all units across all categories.
pub(crate) fn register_all(reg: &mut UnitRegistry) {
    register_length(reg);
    register_mass(reg);
    register_volume(reg);
    register_area(reg);
    register_speed(reg);
    register_temperature(reg);
    super::data::register_data(reg);
    register_angle(reg);
    register_time(reg);
    register_pressure(reg);
    register_energy(reg);
    register_power(reg);
    register_force(reg);
    super::css::register_css_screen(reg);
}

/// Helper to register a linear unit.
fn linear(
    reg: &mut UnitRegistry,
    name: &'static str,
    abbrev: &'static str,
    category: UnitCategory,
    factor: f64,
    aliases: &[&str],
) {
    reg.register(
        UnitDef {
            name,
            abbreviation: abbrev,
            category,
            conversion: Conversion::Linear(factor),
        },
        aliases,
    );
}

// ─── LENGTH (base: meter) ────────────────────────────────────────────

fn register_length(reg: &mut UnitRegistry) {
    let c = UnitCategory::Length;

    linear(reg, "meter", "m", c, 1.0, &["meters", "metre", "metres"]);
    linear(reg, "kilometer", "km", c, 1000.0, &["kilometers", "kilometre", "kilometres"]);
    linear(reg, "centimeter", "cm", c, 0.01, &["centimeters", "centimetre", "centimetres"]);
    linear(reg, "millimeter", "mm", c, 0.001, &["millimeters", "millimetre", "millimetres"]);
    linear(reg, "micrometer", "\u{03BC}m", c, 1e-6, &["micrometers", "micrometre", "micrometres", "micron", "microns"]);
    linear(reg, "nanometer", "nm", c, 1e-9, &["nanometers", "nanometre", "nanometres"]);
    linear(reg, "picometer", "pm", c, 1e-12, &["picometers", "picometre", "picometres"]);
    linear(reg, "decimeter", "dm", c, 0.1, &["decimeters", "decimetre", "decimetres"]);
    linear(reg, "hectometer", "hm", c, 100.0, &["hectometers", "hectometre", "hectometres"]);
    linear(reg, "mile", "mi", c, 1609.344, &["miles"]);
    linear(reg, "yard", "yd", c, 0.9144, &["yards"]);
    linear(reg, "foot", "ft", c, 0.3048, &["feet"]);
    linear(reg, "inch", "in", c, 0.0254, &["inches"]);
    linear(reg, "nautical mile", "nmi", c, 1852.0, &["nautical miles", "nauticalmile", "nauticalmiles"]);
    linear(reg, "fathom", "ftm", c, 1.8288, &["fathoms"]);
    linear(reg, "furlong", "fur", c, 201.168, &["furlongs"]);
    linear(reg, "chain", "ch", c, 20.1168, &["chains"]);
    linear(reg, "rod", "rd", c, 5.0292, &["rods", "perch", "pole"]);
    linear(reg, "league", "lea", c, 4828.032, &["leagues"]);
    linear(reg, "thou", "th", c, 0.0000254, &["mil", "mils"]);
    linear(reg, "angstrom", "\u{00C5}", c, 1e-10, &["angstroms"]);
    linear(reg, "light-year", "ly", c, 9.461e15, &["lightyear", "lightyears", "light-years"]);
    linear(reg, "astronomical unit", "au", c, 1.496e11, &["astronomical units", "astronomicalunit"]);
    linear(reg, "parsec", "pc", c, 3.086e16, &["parsecs"]);
}

// ─── MASS (base: kilogram) ───────────────────────────────────────────

fn register_mass(reg: &mut UnitRegistry) {
    let c = UnitCategory::Mass;

    linear(reg, "kilogram", "kg", c, 1.0, &["kilograms", "kilo", "kilos"]);
    linear(reg, "gram", "g", c, 0.001, &["grams", "gm"]);
    linear(reg, "milligram", "mg", c, 1e-6, &["milligrams"]);
    linear(reg, "microgram", "\u{03BC}g", c, 1e-9, &["micrograms", "mcg"]);
    linear(reg, "metric ton", "t", c, 1000.0, &["tonne", "tonnes", "metric tons"]);
    linear(reg, "pound", "lb", c, 0.45359237, &["pounds", "lbs"]);
    linear(reg, "ounce", "oz", c, 0.028349523125, &["ounces"]);
    linear(reg, "stone", "st", c, 6.35029318, &["stones"]);
    linear(reg, "short ton", "ton", c, 907.18474, &["tons", "short tons", "us ton"]);
    linear(reg, "long ton", "long ton", c, 1016.0469088, &["long tons", "imperial ton"]);
    linear(reg, "carat", "ct", c, 0.0002, &["carats"]);
    linear(reg, "grain", "gr", c, 0.00006479891, &["grains"]);
    linear(reg, "dram", "dr", c, 0.001771845195, &["drams"]);
    linear(reg, "hundredweight", "cwt", c, 45.359237, &["hundredweights"]);
    linear(reg, "slug", "slug", c, 14.593903, &["slugs"]);
    linear(reg, "atomic mass unit", "amu", c, 1.66053906660e-27, &["dalton", "daltons", "u"]);
    linear(reg, "decigram", "dg", c, 0.0001, &["decigrams"]);
    linear(reg, "centigram", "cg", c, 0.00001, &["centigrams"]);
    linear(reg, "quintal", "q", c, 100.0, &["quintals"]);
    linear(reg, "pennyweight", "dwt", c, 0.00155517384, &["pennyweights"]);
    linear(reg, "troy ounce", "oz t", c, 0.0311034768, &["troy ounces"]);
    linear(reg, "troy pound", "lb t", c, 0.3732417216, &["troy pounds"]);
}

// ─── VOLUME (base: liter) ────────────────────────────────────────────

fn register_volume(reg: &mut UnitRegistry) {
    let c = UnitCategory::Volume;

    linear(reg, "liter", "L", c, 1.0, &["liters", "litre", "litres", "l"]);
    linear(reg, "milliliter", "mL", c, 0.001, &["milliliters", "millilitre", "millilitres", "ml"]);
    linear(reg, "centiliter", "cL", c, 0.01, &["centiliters", "centilitre", "centilitres", "cl"]);
    linear(reg, "deciliter", "dL", c, 0.1, &["deciliters", "decilitre", "decilitres", "dl"]);
    linear(reg, "hectoliter", "hL", c, 100.0, &["hectoliters", "hectolitre", "hectolitres", "hl"]);
    linear(reg, "kiloliter", "kL", c, 1000.0, &["kiloliters", "kilolitre", "kilolitres", "kl"]);
    linear(reg, "cubic meter", "m\u{00B3}", c, 1000.0, &["cubic meters", "m3", "cbm"]);
    linear(reg, "cubic centimeter", "cm\u{00B3}", c, 0.001, &["cubic centimeters", "cm3", "cc"]);
    linear(reg, "cubic millimeter", "mm\u{00B3}", c, 1e-6, &["cubic millimeters", "mm3"]);
    linear(reg, "cubic inch", "in\u{00B3}", c, 0.016387064, &["cubic inches", "in3"]);
    linear(reg, "cubic foot", "ft\u{00B3}", c, 28.316846592, &["cubic feet", "ft3"]);
    linear(reg, "cubic yard", "yd\u{00B3}", c, 764.554857984, &["cubic yards", "yd3"]);
    linear(reg, "gallon", "gal", c, 3.785411784, &["gallons", "us gallon", "us gallons"]);
    linear(reg, "quart", "qt", c, 0.946352946, &["quarts"]);
    linear(reg, "pint", "pt", c, 0.473176473, &["pints"]);
    linear(reg, "cup", "cup", c, 0.2365882365, &["cups"]);
    linear(reg, "fluid ounce", "fl oz", c, 0.0295735295625, &["fluid ounces", "floz"]);
    linear(reg, "tablespoon", "tbsp", c, 0.01478676478125, &["tablespoons", "tbs"]);
    linear(reg, "teaspoon", "tsp", c, 0.00492892159375, &["teaspoons"]);
    linear(reg, "imperial gallon", "imp gal", c, 4.54609, &["imperial gallons", "uk gallon", "uk gallons"]);
    linear(reg, "imperial quart", "imp qt", c, 1.1365225, &["imperial quarts"]);
    linear(reg, "imperial pint", "imp pt", c, 0.56826125, &["imperial pints"]);
    linear(reg, "imperial fluid ounce", "imp fl oz", c, 0.0284130625, &["imperial fluid ounces"]);
    linear(reg, "barrel", "bbl", c, 158.987294928, &["barrels", "oil barrel"]);
    linear(reg, "bushel", "bu", c, 35.23907016688, &["bushels"]);
    linear(reg, "gill", "gi", c, 0.1182941183, &["gills"]);
    linear(reg, "minim", "minim", c, 0.00006161152, &["minims"]);
    linear(reg, "dram (fluid)", "fl dr", c, 0.003696691, &["fluid drams", "fluid dram"]);
    linear(reg, "hogshead", "hhd", c, 238.480942392, &["hogsheads"]);
}

// ─── AREA (base: square meter) ───────────────────────────────────────

fn register_area(reg: &mut UnitRegistry) {
    let c = UnitCategory::Area;

    linear(reg, "square meter", "m\u{00B2}", c, 1.0, &["square meters", "sq m", "sqm", "m2"]);
    linear(reg, "square kilometer", "km\u{00B2}", c, 1e6, &["square kilometers", "sq km", "sqkm", "km2"]);
    linear(reg, "square centimeter", "cm\u{00B2}", c, 1e-4, &["square centimeters", "sq cm", "sqcm", "cm2"]);
    linear(reg, "square millimeter", "mm\u{00B2}", c, 1e-6, &["square millimeters", "sq mm", "sqmm", "mm2"]);
    linear(reg, "hectare", "ha", c, 10000.0, &["hectares"]);
    linear(reg, "acre", "ac", c, 4046.8564224, &["acres"]);
    linear(reg, "square mile", "mi\u{00B2}", c, 2_589_988.110336, &["square miles", "sq mi", "sqmi", "mi2"]);
    linear(reg, "square yard", "yd\u{00B2}", c, 0.83612736, &["square yards", "sq yd", "sqyd", "yd2"]);
    linear(reg, "square foot", "ft\u{00B2}", c, 0.09290304, &["square feet", "sq ft", "sqft", "ft2"]);
    linear(reg, "square inch", "in\u{00B2}", c, 0.00064516, &["square inches", "sq in", "sqin", "in2"]);
    linear(reg, "are", "a", c, 100.0, &["ares"]);
    linear(reg, "barn", "b", c, 1e-28, &["barns"]);
    linear(reg, "dunam", "dunam", c, 1000.0, &["dunams", "dunum"]);
    linear(reg, "township", "twp", c, 93_239_571.972, &["townships"]);
    linear(reg, "rood", "rood", c, 1011.7141056, &["roods"]);
}

// ─── SPEED (base: meter per second) ──────────────────────────────────

fn register_speed(reg: &mut UnitRegistry) {
    let c = UnitCategory::Speed;

    linear(reg, "meter per second", "m/s", c, 1.0, &["meters per second", "mps"]);
    linear(reg, "kilometer per hour", "km/h", c, 1.0 / 3.6, &["kilometers per hour", "kph", "kmh", "kmph"]);
    linear(reg, "mile per hour", "mph", c, 0.44704, &["miles per hour"]);
    linear(reg, "knot", "kn", c, 0.514444, &["knots", "kt"]);
    linear(reg, "foot per second", "ft/s", c, 0.3048, &["feet per second", "fps"]);
    linear(reg, "centimeter per second", "cm/s", c, 0.01, &["centimeters per second"]);
    linear(reg, "mach", "Ma", c, 340.29, &["machs"]);
    linear(reg, "speed of light", "c", c, 299_792_458.0, &[]);
    linear(reg, "inch per second", "in/s", c, 0.0254, &["inches per second"]);
    linear(reg, "yard per second", "yd/s", c, 0.9144, &["yards per second"]);
    linear(reg, "mile per second", "mi/s", c, 1609.344, &["miles per second"]);
}

// ─── TEMPERATURE (base: kelvin) ──────────────────────────────────────

fn register_temperature(reg: &mut UnitRegistry) {
    let c = UnitCategory::Temperature;

    reg.register(
        UnitDef {
            name: "kelvin",
            abbreviation: "K",
            category: c,
            conversion: Conversion::Linear(1.0),
        },
        &["kelvins"],
    );

    reg.register(
        UnitDef {
            name: "celsius",
            abbreviation: "\u{00B0}C",
            category: c,
            conversion: Conversion::Formula {
                to_base: |v| v + 273.15,
                from_base: |v| v - 273.15,
            },
        },
        &["degc", "degC", "\u{00B0}c", "C"],
    );

    reg.register(
        UnitDef {
            name: "fahrenheit",
            abbreviation: "\u{00B0}F",
            category: c,
            conversion: Conversion::Formula {
                to_base: |v| (v - 32.0) * 5.0 / 9.0 + 273.15,
                from_base: |v| (v - 273.15) * 9.0 / 5.0 + 32.0,
            },
        },
        &["degf", "degF", "\u{00B0}f", "F"],
    );

    reg.register(
        UnitDef {
            name: "rankine",
            abbreviation: "\u{00B0}R",
            category: c,
            conversion: Conversion::Formula {
                to_base: |v| v / 1.8,
                from_base: |v| v * 1.8,
            },
        },
        &["degr", "degR", "\u{00B0}r", "R"],
    );
}

// ─── ANGLE (base: radian) ───────────────────────────────────────────

fn register_angle(reg: &mut UnitRegistry) {
    let c = UnitCategory::Angle;

    linear(reg, "radian", "rad", c, 1.0, &["radians"]);
    linear(reg, "degree", "deg", c, std::f64::consts::PI / 180.0, &["degrees", "\u{00B0}"]);
    linear(reg, "gradian", "gon", c, std::f64::consts::PI / 200.0, &["gradians", "grad", "grads"]);
    linear(reg, "arcminute", "arcmin", c, std::f64::consts::PI / 10800.0, &["arcminutes", "arc minute", "arc minutes", "MOA"]);
    linear(reg, "arcsecond", "arcsec", c, std::f64::consts::PI / 648000.0, &["arcseconds", "arc second", "arc seconds"]);
    linear(reg, "revolution", "rev", c, 2.0 * std::f64::consts::PI, &["revolutions", "turn", "turns"]);
    linear(reg, "milliradian", "mrad", c, 0.001, &["milliradians"]);
}

// ─── TIME (base: second) ────────────────────────────────────────────

fn register_time(reg: &mut UnitRegistry) {
    let c = UnitCategory::Time;

    linear(reg, "second", "s", c, 1.0, &["seconds", "sec", "secs"]);
    linear(reg, "millisecond", "ms", c, 0.001, &["milliseconds"]);
    linear(reg, "microsecond", "\u{03BC}s", c, 1e-6, &["microseconds", "us"]);
    linear(reg, "nanosecond", "ns", c, 1e-9, &["nanoseconds"]);
    linear(reg, "minute", "min", c, 60.0, &["minutes", "mins"]);
    linear(reg, "hour", "hr", c, 3600.0, &["hours", "hrs", "h"]);
    linear(reg, "day", "d", c, 86400.0, &["days"]);
    linear(reg, "week", "wk", c, 604800.0, &["weeks", "wks"]);
    linear(reg, "fortnight", "fn", c, 1_209_600.0, &["fortnights"]);
    linear(reg, "month", "mo", c, 2_629_746.0, &["months"]); // average month
    linear(reg, "year", "yr", c, 31_556_952.0, &["years", "yrs"]); // average year
    linear(reg, "decade", "dec", c, 315_569_520.0, &["decades"]);
    linear(reg, "century", "cent", c, 3_155_695_200.0, &["centuries"]);
    linear(reg, "millennium", "mill", c, 31_556_952_000.0, &["millennia", "millenniums"]);
    linear(reg, "picosecond", "ps", c, 1e-12, &["picoseconds"]);
    linear(reg, "shake", "shake", c, 1e-8, &["shakes"]);
    linear(reg, "sidereal day", "sid day", c, 86164.0905, &["sidereal days"]);
}

// ─── PRESSURE (base: pascal) ─────────────────────────────────────────

fn register_pressure(reg: &mut UnitRegistry) {
    let c = UnitCategory::Pressure;

    linear(reg, "pascal", "Pa", c, 1.0, &["pascals"]);
    linear(reg, "kilopascal", "kPa", c, 1000.0, &["kilopascals"]);
    linear(reg, "megapascal", "MPa", c, 1e6, &["megapascals"]);
    linear(reg, "gigapascal", "GPa", c, 1e9, &["gigapascals"]);
    linear(reg, "bar", "bar", c, 100_000.0, &["bars"]);
    linear(reg, "millibar", "mbar", c, 100.0, &["millibars"]);
    linear(reg, "atmosphere", "atm", c, 101_325.0, &["atmospheres"]);
    linear(reg, "pound per square inch", "psi", c, 6894.757293168, &["pounds per square inch"]);
    linear(reg, "torr", "Torr", c, 133.322368421, &["torrs"]);
    linear(reg, "millimeter of mercury", "mmHg", c, 133.322387415, &["millimeters of mercury", "mm Hg"]);
    linear(reg, "inch of mercury", "inHg", c, 3386.389, &["inches of mercury", "in Hg"]);
    linear(reg, "inch of water", "inH2O", c, 249.08891, &["inches of water", "in H2O"]);
}

// ─── ENERGY (base: joule) ───────────────────────────────────────────

fn register_energy(reg: &mut UnitRegistry) {
    let c = UnitCategory::Energy;

    linear(reg, "joule", "J", c, 1.0, &["joules"]);
    linear(reg, "kilojoule", "kJ", c, 1000.0, &["kilojoules"]);
    linear(reg, "megajoule", "MJ", c, 1e6, &["megajoules"]);
    linear(reg, "gigajoule", "GJ", c, 1e9, &["gigajoules"]);
    linear(reg, "calorie", "cal", c, 4.184, &["calories"]);
    linear(reg, "kilocalorie", "kcal", c, 4184.0, &["kilocalories", "Cal", "food calorie", "food calories"]);
    linear(reg, "watt-hour", "Wh", c, 3600.0, &["watt-hours", "watthour", "watthours"]);
    linear(reg, "kilowatt-hour", "kWh", c, 3_600_000.0, &["kilowatt-hours", "kilowatthour"]);
    linear(reg, "megawatt-hour", "MWh", c, 3.6e9, &["megawatt-hours"]);
    linear(reg, "british thermal unit", "BTU", c, 1055.05585262, &["btu", "btus", "british thermal units"]);
    linear(reg, "therm", "thm", c, 105_505_585.262, &["therms"]);
    linear(reg, "electronvolt", "eV", c, 1.602176634e-19, &["electronvolts", "electron volt"]);
    linear(reg, "kiloelectronvolt", "keV", c, 1.602176634e-16, &["kiloelectronvolts"]);
    linear(reg, "megaelectronvolt", "MeV", c, 1.602176634e-13, &["megaelectronvolts"]);
    linear(reg, "erg", "erg", c, 1e-7, &["ergs"]);
    linear(reg, "foot-pound", "ft\u{00B7}lbf", c, 1.3558179483, &["foot-pounds", "ft-lbf", "ftlbf", "foot pound"]);
}

// ─── POWER (base: watt) ─────────────────────────────────────────────

fn register_power(reg: &mut UnitRegistry) {
    let c = UnitCategory::Power;

    linear(reg, "watt", "W", c, 1.0, &["watts"]);
    linear(reg, "milliwatt", "mW", c, 0.001, &["milliwatts"]);
    linear(reg, "kilowatt", "kW", c, 1000.0, &["kilowatts"]);
    linear(reg, "megawatt", "MW", c, 1e6, &["megawatts"]);
    linear(reg, "gigawatt", "GW", c, 1e9, &["gigawatts"]);
    linear(reg, "horsepower", "hp", c, 745.69987158227022, &["horsepowers"]);
    linear(reg, "metric horsepower", "PS", c, 735.49875, &["metric horsepowers", "cv"]);
    linear(reg, "btu per hour", "BTU/h", c, 0.29307107017, &["btu/hr", "btus per hour"]);
    linear(reg, "foot-pound per second", "ft\u{00B7}lbf/s", c, 1.3558179483, &["foot-pounds per second", "ft-lbf/s"]);
    linear(reg, "ton of refrigeration", "TR", c, 3516.8528, &["tons of refrigeration"]);
    linear(reg, "volt-ampere", "VA", c, 1.0, &["volt-amperes"]);
    linear(reg, "kilovolt-ampere", "kVA", c, 1000.0, &["kilovolt-amperes"]);
}

// ─── FORCE (base: newton) ───────────────────────────────────────────

fn register_force(reg: &mut UnitRegistry) {
    let c = UnitCategory::Force;

    linear(reg, "newton", "N", c, 1.0, &["newtons"]);
    linear(reg, "kilonewton", "kN", c, 1000.0, &["kilonewtons"]);
    linear(reg, "meganewton", "MN", c, 1e6, &["meganewtons"]);
    linear(reg, "dyne", "dyn", c, 1e-5, &["dynes"]);
    linear(reg, "pound-force", "lbf", c, 4.4482216152605, &["pounds-force", "pound force"]);
    linear(reg, "kilogram-force", "kgf", c, 9.80665, &["kilograms-force", "kilogram force", "kilopond", "kp"]);
    linear(reg, "gram-force", "gf", c, 0.00980665, &["grams-force", "gram force"]);
    linear(reg, "ounce-force", "ozf", c, 0.278013851, &["ounces-force", "ounce force"]);
    linear(reg, "poundal", "pdl", c, 0.138254954376, &["poundals"]);
    linear(reg, "millinewton", "mN", c, 0.001, &["millinewtons"]);
    linear(reg, "micronewton", "\u{03BC}N", c, 1e-6, &["micronewtons"]);
    linear(reg, "sthene", "sn", c, 1000.0, &["sthenes"]);
    linear(reg, "kip", "kip", c, 4448.2216152605, &["kips", "kilopound-force"]);
    linear(reg, "ton-force", "tnf", c, 8896.443230521, &["tons-force", "ton force", "short ton-force"]);
}

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

    fn make_registry() -> UnitRegistry {
        let mut reg = UnitRegistry {
            lookup: HashMap::new(),
            case_sensitive_lookup: HashMap::new(),
            units: Vec::new(),
        };
        register_all(&mut reg);
        reg
    }

    #[test]
    fn test_all_categories_have_units() {
        let reg = make_registry();
        for cat in UnitCategory::all() {
            let count = reg.all_units().iter().filter(|u| u.category == *cat).count();
            assert!(count > 0, "Category {} has no units", cat);
        }
    }

    #[test]
    fn test_length_meter_is_base() {
        let reg = make_registry();
        let m = reg.lookup("meter").unwrap();
        assert_eq!(m.to_base(1.0), 1.0);
        assert_eq!(m.from_base(1.0), 1.0);
    }

    #[test]
    fn test_length_km_conversion() {
        let reg = make_registry();
        let km = reg.lookup("km").unwrap();
        assert!((km.to_base(1.0) - 1000.0).abs() < 1e-10);
        assert!((km.from_base(1000.0) - 1.0).abs() < 1e-10);
    }

    #[test]
    fn test_temperature_conversions() {
        let reg = make_registry();
        let c = reg.lookup("C").unwrap();
        let f = reg.lookup("F").unwrap();

        assert!((c.to_base(0.0) - 273.15).abs() < 1e-10);
        assert!((f.to_base(32.0) - 273.15).abs() < 1e-10);
        assert!((f.to_base(212.0) - 373.15).abs() < 1e-10);
    }

    #[test]
    fn test_registry_builds_without_panic() {
        let reg = make_registry();
        assert!(reg.lookup.len() > 200);
    }

    #[test]
    fn test_count_per_category() {
        let reg = make_registry();
        let mut total = 0;
        for cat in UnitCategory::all() {
            let count = reg.all_units().iter().filter(|u| u.category == *cat).count();
            total += count;
        }
        assert_eq!(total, reg.unit_count());
    }
}
