//! Custom user-defined units.
//!
//! Allows users to define their own units in terms of existing units (built-in or
//! previously defined custom units). Supports chaining, circular dependency detection,
//! auto-generated plural aliases, and warnings when shadowing built-in units.

use std::collections::{HashMap, HashSet};

use super::categories::UnitCategory;

/// A custom user-defined unit entry.
#[derive(Debug, Clone)]
pub struct CustomUnitDef {
    /// Canonical name (e.g., "sprint").
    pub name: String,
    /// Conversion factor: 1 custom_unit = factor * base_unit (in category base).
    pub factor: f64,
    /// The base unit's canonical name (e.g., "second" for time units).
    pub base_unit_name: String,
    /// The category inherited from the base unit.
    pub category: UnitCategory,
}

/// Result of registering a custom unit.
#[derive(Debug, Clone)]
pub struct RegisterResult {
    /// Warning message if this unit shadows a built-in.
    pub warning: Option<String>,
}

/// Registry for custom user-defined units.
///
/// Supports registration, lookup, alias generation (plural forms),
/// circular dependency detection, and built-in shadowing warnings.
#[derive(Debug, Clone)]
pub struct CustomUnitRegistry {
    /// Maps lowercase name/alias -> custom unit definition.
    units: HashMap<String, CustomUnitDef>,
}

impl CustomUnitRegistry {
    pub fn new() -> Self {
        CustomUnitRegistry {
            units: HashMap::new(),
        }
    }

    /// Register a custom unit definition.
    ///
    /// `name`: the new unit name (e.g., "sprint")
    /// `factor`: how many base units equal one custom unit (e.g., 2.0 for "1 sprint = 2 weeks")
    /// `base_unit_name`: the name of the base unit as written (e.g., "weeks")
    ///
    /// The base unit must resolve to either a built-in unit or a previously-registered
    /// custom unit. Returns an error for circular dependencies or unresolvable base units.
    pub fn register(
        &mut self,
        name: &str,
        factor: f64,
        base_unit_name: &str,
    ) -> Result<RegisterResult, String> {
        // Resolve the base unit -- could be built-in or another custom unit
        let (resolved_factor, canonical_base, category) =
            self.resolve_base_chain(base_unit_name, name)?;

        let total_factor = factor * resolved_factor;

        let def = CustomUnitDef {
            name: name.to_string(),
            factor: total_factor,
            base_unit_name: canonical_base,
            category,
        };

        // Check for built-in shadowing
        let warning = {
            let reg = super::registry();
            if reg.lookup(name).is_some() {
                Some(format!(
                    "Custom unit '{}' shadows built-in unit '{}'",
                    name, name
                ))
            } else {
                None
            }
        };

        // Register the unit under its canonical name
        self.units.insert(name.to_lowercase(), def.clone());

        // Auto-generate plural alias: add "s" if name doesn't end with "s"
        let lower_name = name.to_lowercase();
        if !lower_name.ends_with('s') {
            let plural = format!("{}s", lower_name);
            self.units.insert(plural, def);
        }

        Ok(RegisterResult { warning })
    }

    /// Look up a custom unit by name or alias.
    pub fn lookup(&self, name: &str) -> Option<&CustomUnitDef> {
        self.units.get(&name.to_lowercase())
    }

    /// Get all registered custom unit names (including aliases).
    pub fn unit_names(&self) -> HashSet<String> {
        self.units.keys().cloned().collect()
    }

    /// Convert a value from a custom unit to a target unit.
    ///
    /// First converts to the category's base unit using the custom unit's
    /// total factor, then converts from base to target using the built-in registry.
    pub fn convert(
        &self,
        value: f64,
        from: &str,
        to: &str,
    ) -> Result<f64, String> {
        let from_custom = self.lookup(from);
        let to_custom = self.lookup(to);

        match (from_custom, to_custom) {
            (Some(from_def), Some(to_def)) => {
                // Both are custom units -- must be same category
                if from_def.category != to_def.category {
                    return Err(format!(
                        "Cannot convert between '{}' ({}) and '{}' ({})",
                        from, from_def.category, to, to_def.category
                    ));
                }
                let base_value = self.custom_to_category_base(value, from_def)?;
                let result = self.category_base_to_custom(base_value, to_def)?;
                Ok(result)
            }
            (Some(from_def), None) => {
                // Source is custom, target is built-in
                let base_value = self.custom_to_category_base(value, from_def)?;
                let reg = super::registry();
                let to_resolved = reg
                    .resolve_with_prefix(to)
                    .ok_or_else(|| format!("Unknown unit: {}", to))?;
                if to_resolved.unit.category != from_def.category {
                    return Err(format!(
                        "Cannot convert between '{}' ({}) and '{}' ({})",
                        from, from_def.category, to_resolved.unit.name, to_resolved.unit.category
                    ));
                }
                Ok(to_resolved.from_base(base_value))
            }
            (None, Some(to_def)) => {
                // Source is built-in, target is custom
                let reg = super::registry();
                let from_resolved = reg
                    .resolve_with_prefix(from)
                    .ok_or_else(|| format!("Unknown unit: {}", from))?;
                if from_resolved.unit.category != to_def.category {
                    return Err(format!(
                        "Cannot convert between '{}' ({}) and '{}' ({})",
                        from_resolved.unit.name, from_resolved.unit.category, to, to_def.category
                    ));
                }
                let base_value = from_resolved.to_base(value);
                let result = self.category_base_to_custom(base_value, to_def)?;
                Ok(result)
            }
            (None, None) => {
                // Neither is custom -- delegate to built-in conversion
                super::convert(value, from, to)
            }
        }
    }

    /// Convert a value in a custom unit to the category's base unit.
    fn custom_to_category_base(&self, value: f64, def: &CustomUnitDef) -> Result<f64, String> {
        let reg = super::registry();
        let base_unit = reg
            .resolve_with_prefix(&def.base_unit_name)
            .ok_or_else(|| format!("Base unit '{}' not found", def.base_unit_name))?;
        Ok(base_unit.to_base(value * def.factor))
    }

    /// Convert a value from the category's base unit to a custom unit.
    fn category_base_to_custom(&self, base_value: f64, def: &CustomUnitDef) -> Result<f64, String> {
        let reg = super::registry();
        let base_unit = reg
            .resolve_with_prefix(&def.base_unit_name)
            .ok_or_else(|| format!("Base unit '{}' not found", def.base_unit_name))?;
        let value_in_base_unit = base_unit.from_base(base_value);
        Ok(value_in_base_unit / def.factor)
    }

    /// Resolve a base unit chain, detecting circular dependencies.
    fn resolve_base_chain(
        &self,
        base_name: &str,
        defining_name: &str,
    ) -> Result<(f64, String, UnitCategory), String> {
        let mut visited: HashSet<String> = HashSet::new();
        visited.insert(defining_name.to_lowercase());
        self.resolve_base_chain_inner(base_name, &mut visited)
    }

    fn resolve_base_chain_inner(
        &self,
        base_name: &str,
        visited: &mut HashSet<String>,
    ) -> Result<(f64, String, UnitCategory), String> {
        let lower = base_name.to_lowercase();

        // Check for circular dependency
        if visited.contains(&lower) {
            return Err(format!(
                "Circular dependency detected in custom unit definitions involving '{}'",
                base_name
            ));
        }

        // Try built-in registry first
        let reg = super::registry();
        if let Some(resolved) = reg.resolve_with_prefix(base_name) {
            return Ok((
                resolved.prefix_factor,
                resolved.unit.name.to_string(),
                resolved.unit.category,
            ));
        }

        // Try custom unit registry
        if let Some(custom) = self.units.get(&lower) {
            visited.insert(lower);
            let (chain_factor, canonical, category) =
                self.resolve_base_chain_inner(&custom.base_unit_name, visited)?;
            Ok((custom.factor * chain_factor, canonical, category))
        } else {
            Err(format!("Unknown base unit: '{}'", base_name))
        }
    }
}

impl Default for CustomUnitRegistry {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_register_custom_unit() {
        let mut reg = CustomUnitRegistry::new();
        let result = reg.register("sprint", 2.0, "weeks").unwrap();
        assert!(result.warning.is_none());

        let def = reg.lookup("sprint").unwrap();
        assert_eq!(def.name, "sprint");
        assert_eq!(def.category, UnitCategory::Time);
    }

    #[test]
    fn test_plural_alias() {
        let mut reg = CustomUnitRegistry::new();
        reg.register("sprint", 2.0, "weeks").unwrap();
        assert!(reg.lookup("sprints").is_some());
        assert!(reg.lookup("sprint").is_some());
    }

    #[test]
    fn test_no_duplicate_plural_for_s_ending() {
        let mut reg = CustomUnitRegistry::new();
        reg.register("kudos", 1.0, "hours").unwrap();
        assert!(reg.lookup("kudos").is_some());
        assert!(reg.lookup("kudoss").is_none());
    }

    #[test]
    fn test_convert_custom_to_builtin() {
        let mut reg = CustomUnitRegistry::new();
        reg.register("sprint", 2.0, "weeks").unwrap();

        // 3 sprints = 6 weeks = 42 days
        let result = reg.convert(3.0, "sprints", "days").unwrap();
        assert!(
            (result - 42.0).abs() < 1e-6,
            "Expected 42.0, got {}",
            result
        );
    }

    #[test]
    fn test_convert_custom_story_points() {
        let mut reg = CustomUnitRegistry::new();
        reg.register("story_point", 4.0, "hours").unwrap();

        // 10 story_points = 40 hours
        let result = reg.convert(10.0, "story_points", "hours").unwrap();
        assert!(
            (result - 40.0).abs() < 1e-6,
            "Expected 40.0, got {}",
            result
        );
    }

    #[test]
    fn test_chained_custom_units() {
        let mut reg = CustomUnitRegistry::new();
        reg.register("sprint", 2.0, "weeks").unwrap();
        reg.register("quarter", 6.0, "sprints").unwrap();

        // 1 quarter = 6 sprints = 12 weeks = 84 days
        let result = reg.convert(1.0, "quarter", "days").unwrap();
        assert!(
            (result - 84.0).abs() < 1e-6,
            "Expected 84.0, got {}",
            result
        );
    }

    #[test]
    fn test_circular_dependency_self_reference() {
        let mut reg = CustomUnitRegistry::new();
        let result = reg.register("foo", 2.0, "foo");
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Circular dependency"));
    }

    #[test]
    fn test_unknown_base_unit() {
        let mut reg = CustomUnitRegistry::new();
        let result = reg.register("foo", 2.0, "nonexistent");
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Unknown base unit"));
    }

    #[test]
    fn test_builtin_shadowing_warning() {
        let mut reg = CustomUnitRegistry::new();
        let result = reg.register("meter", 100.0, "cm").unwrap();
        assert!(result.warning.is_some());
        assert!(result.warning.unwrap().contains("shadows"));
    }

    #[test]
    fn test_case_insensitive_lookup() {
        let mut reg = CustomUnitRegistry::new();
        reg.register("Sprint", 2.0, "weeks").unwrap();
        assert!(reg.lookup("sprint").is_some());
        assert!(reg.lookup("SPRINT").is_some());
        assert!(reg.lookup("Sprint").is_some());
    }

    #[test]
    fn test_chaining_is_ok() {
        let mut reg = CustomUnitRegistry::new();
        reg.register("foo", 2.0, "hours").unwrap();
        let result = reg.register("bar", 3.0, "foo");
        assert!(result.is_ok());
        let result = reg.register("baz", 1.0, "bar");
        assert!(result.is_ok());
    }

    #[test]
    fn test_builtin_to_custom_conversion() {
        let mut reg = CustomUnitRegistry::new();
        reg.register("sprint", 2.0, "weeks").unwrap();

        // 42 days = 3 sprints
        let result = reg.convert(42.0, "days", "sprints").unwrap();
        assert!(
            (result - 3.0).abs() < 1e-6,
            "Expected 3.0, got {}",
            result
        );
    }

    #[test]
    fn test_custom_to_custom_conversion() {
        let mut reg = CustomUnitRegistry::new();
        reg.register("sprint", 2.0, "weeks").unwrap();
        reg.register("milestone", 4.0, "weeks").unwrap();

        // 1 milestone = 4 weeks, 1 sprint = 2 weeks
        // So 1 milestone = 2 sprints
        let result = reg.convert(1.0, "milestones", "sprints").unwrap();
        assert!(
            (result - 2.0).abs() < 1e-6,
            "Expected 2.0, got {}",
            result
        );
    }

    #[test]
    fn test_fallback_to_builtin_conversion() {
        let reg = CustomUnitRegistry::new();
        // Neither unit is custom -- should delegate to built-in
        let result = reg.convert(5.0, "km", "miles").unwrap();
        assert!(
            (result - 3.10686).abs() < 1e-4,
            "Expected ~3.10686, got {}",
            result
        );
    }
}
