/// Spacing scale — 4px base grid.
///
/// Use named scale values, not raw numbers. This ensures visual consistency
/// and makes it easy to tweak the entire system by changing the base unit.

/// Named spacing scale values.
///
/// The base unit is 4dp/pt. All values are multiples of 4.
/// Use these for padding, margin, gap, and any other spatial measurement.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Spacing {
    /// 0 — no spacing
    None,
    /// 4dp — hairline gap, tight list items
    Xs,
    /// 8dp — default tight padding, icon margins
    Sm,
    /// 12dp — compact component padding
    Md,
    /// 16dp — standard component padding (the workhorse)
    Lg,
    /// 24dp — generous padding, card internal spacing
    Xl,
    /// 32dp — section separation
    Xxl,
    /// 48dp — hero sections, major layout gaps
    Xxxl,
    /// 64dp — page-level margins, maximum separation
    Max,
    /// Custom value in dp (escape hatch)
    Custom(u32),
}

impl Spacing {
    /// Resolve to a concrete dp/pt value.
    pub fn dp(&self) -> u32 {
        match self {
            Spacing::None => 0,
            Spacing::Xs => 4,
            Spacing::Sm => 8,
            Spacing::Md => 12,
            Spacing::Lg => 16,
            Spacing::Xl => 24,
            Spacing::Xxl => 32,
            Spacing::Xxxl => 48,
            Spacing::Max => 64,
            Spacing::Custom(v) => *v,
        }
    }

    /// Resolve to a concrete CSS pixel string.
    pub fn to_css(&self) -> String {
        format!("{}px", self.dp())
    }
}

/// The spacing scale exposed by a theme.
/// Provides the mapping from scale names to concrete values.
#[derive(Debug, Clone)]
pub struct SpacingScale {
    /// Base unit in dp (default: 4).
    pub base: u32,
}

impl SpacingScale {
    pub fn default() -> Self {
        Self { base: 4 }
    }

    /// Resolve a Spacing token to dp.
    pub fn resolve(&self, spacing: &Spacing) -> u32 {
        match spacing {
            Spacing::Custom(v) => *v,
            other => {
                let multiplier = match other {
                    Spacing::None => 0,
                    Spacing::Xs => 1,
                    Spacing::Sm => 2,
                    Spacing::Md => 3,
                    Spacing::Lg => 4,
                    Spacing::Xl => 6,
                    Spacing::Xxl => 8,
                    Spacing::Xxxl => 12,
                    Spacing::Max => 16,
                    Spacing::Custom(_) => unreachable!(),
                };
                self.base * multiplier
            }
        }
    }
}

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

    #[test]
    fn spacing_none_is_zero() {
        assert_eq!(Spacing::None.dp(), 0);
    }

    #[test]
    fn spacing_lg_is_16() {
        assert_eq!(Spacing::Lg.dp(), 16);
    }

    #[test]
    fn spacing_max_is_64() {
        assert_eq!(Spacing::Max.dp(), 64);
    }

    #[test]
    fn spacing_custom() {
        assert_eq!(Spacing::Custom(20).dp(), 20);
    }

    #[test]
    fn spacing_scale_resolves_base_unit() {
        let scale = SpacingScale { base: 4 };
        assert_eq!(scale.resolve(&Spacing::Xs), 4);
        assert_eq!(scale.resolve(&Spacing::Sm), 8);
        assert_eq!(scale.resolve(&Spacing::Lg), 16);
    }

    #[test]
    fn spacing_to_css() {
        assert_eq!(Spacing::Lg.to_css(), "16px");
        assert_eq!(Spacing::None.to_css(), "0px");
    }

    #[test]
    fn spacing_scale_custom_scale() {
        let scale = SpacingScale { base: 8 };
        assert_eq!(scale.resolve(&Spacing::Xs), 8);
        assert_eq!(scale.resolve(&Spacing::Sm), 16);
    }
}
