//! CSS and screen units with configurable PPI and em base size.
//!
//! CSS units (px, pt, em, rem, pica) are registered in the global registry with
//! default values (PPI=96, em=16px). For accurate conversions at non-standard
//! display densities, use `convert_css()` with a custom `CssConfig`.

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

/// Configuration for CSS/screen unit conversions.
#[derive(Debug, Clone, Copy)]
pub struct CssConfig {
    /// Pixels per inch. Default: 96.
    pub ppi: f64,
    /// Base font size in pixels (for em/rem). Default: 16.
    pub em_base_px: f64,
}

impl Default for CssConfig {
    fn default() -> Self {
        CssConfig {
            ppi: 96.0,
            em_base_px: 16.0,
        }
    }
}

impl CssConfig {
    /// Get the conversion factor from a CSS unit to pixels.
    fn to_px_factor(&self, unit_name: &str) -> Option<f64> {
        let lower = unit_name.to_lowercase();
        match lower.as_str() {
            "px" | "pixel" | "pixels" => Some(1.0),
            "pt" | "point" | "points" => Some(self.ppi / 72.0),
            "em" | "ems" => Some(self.em_base_px),
            "rem" | "rems" => Some(self.em_base_px),
            "pc" | "pica" | "picas" => Some(12.0 * self.ppi / 72.0),
            "dppx" => Some(1.0),
            _ => None,
        }
    }
}

/// Convert between CSS/screen units with configurable PPI and em base.
pub fn convert_css(value: f64, from: &str, to: &str, config: &CssConfig) -> Result<f64, String> {
    let from_factor = config
        .to_px_factor(from)
        .ok_or_else(|| format!("Unknown CSS unit: {}", from))?;
    let to_factor = config
        .to_px_factor(to)
        .ok_or_else(|| format!("Unknown CSS unit: {}", to))?;

    // Convert: from -> px -> to
    let px_value = value * from_factor;
    Ok(px_value / to_factor)
}

/// Register CSS/screen units in the registry.
/// These are registered with DEFAULT factors (PPI=96, em=16px).
/// Actual conversions should use `convert_css()` for runtime config support.
pub(crate) fn register_css_screen(reg: &mut UnitRegistry) {
    let c = UnitCategory::CssScreen;

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

    // px is the base unit (factor = 1.0)
    linear(reg, "pixel", "px", 1.0, &["pixels"]);
    // pt: 1pt = PPI/72 px. At default PPI=96: 96/72 = 4/3
    linear(reg, "point", "pt", 96.0 / 72.0, &["points"]);
    // em: 1em = em_base px. Default em_base=16
    linear(reg, "em", "em", 16.0, &["ems"]);
    // rem: 1rem = em_base px. Default em_base=16
    linear(reg, "rem", "rem", 16.0, &["rems"]);
    // pc (pica): 1pc = 12pt = 12 * PPI/72 px. At default PPI=96: 16px
    linear(reg, "pica", "pica", 12.0 * 96.0 / 72.0, &["picas"]);
    // dppx (dots per pixel): device pixel ratio unit, base = 1.0
    linear(reg, "dppx", "dppx", 1.0, &[]);

    // Suppress unused variable warning
    let _ = c;
}

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

    #[test]
    fn test_default_config() {
        let config = CssConfig::default();
        assert_eq!(config.ppi, 96.0);
        assert_eq!(config.em_base_px, 16.0);
    }

    #[test]
    fn test_12pt_to_px_default() {
        let config = CssConfig::default();
        let result = convert_css(12.0, "pt", "px", &config).unwrap();
        assert!((result - 16.0).abs() < 1e-10);
    }

    #[test]
    fn test_2em_to_px_default() {
        let config = CssConfig::default();
        let result = convert_css(2.0, "em", "px", &config).unwrap();
        assert!((result - 32.0).abs() < 1e-10);
    }

    #[test]
    fn test_1rem_to_px_default() {
        let config = CssConfig::default();
        let result = convert_css(1.0, "rem", "px", &config).unwrap();
        assert!((result - 16.0).abs() < 1e-10);
    }

    #[test]
    fn test_96px_to_pt_default() {
        let config = CssConfig::default();
        let result = convert_css(96.0, "px", "pt", &config).unwrap();
        assert!((result - 72.0).abs() < 1e-10);
    }

    #[test]
    fn test_12pt_to_px_retina() {
        let config = CssConfig { ppi: 326.0, em_base_px: 16.0 };
        let result = convert_css(12.0, "pt", "px", &config).unwrap();
        let expected = 12.0 * 326.0 / 72.0;
        assert!((result - expected).abs() < 1e-10);
    }

    #[test]
    fn test_2em_custom_base() {
        let config = CssConfig { ppi: 96.0, em_base_px: 20.0 };
        let result = convert_css(2.0, "em", "px", &config).unwrap();
        assert!((result - 40.0).abs() < 1e-10);
    }

    #[test]
    fn test_unknown_css_unit() {
        let config = CssConfig::default();
        let result = convert_css(1.0, "frobbles", "px", &config);
        assert!(result.is_err());
    }

    #[test]
    fn test_px_identity() {
        let config = CssConfig::default();
        let result = convert_css(42.0, "px", "px", &config).unwrap();
        assert!((result - 42.0).abs() < 1e-10);
    }

    #[test]
    fn test_pica_to_px() {
        let config = CssConfig::default();
        let result = convert_css(1.0, "pica", "px", &config).unwrap();
        // 1 pica = 12 pt = 12 * 96/72 = 16 px at default PPI
        assert!((result - 16.0).abs() < 1e-10);
    }
}
