/// StyleModifier trait — fluent, zero-cost style chaining.
///
/// Every el-ui component implements StyleModifier so callers can chain style
/// adjustments in a natural builder pattern. The trait is compile-time only;
/// there is no runtime allocation or dynamic dispatch.

use crate::color::Color;
use crate::radius::Radius;
use crate::shadow::Shadow;
use crate::typography::TextStyle;

/// A dimension — how wide or tall something should be.
#[derive(Debug, Clone, PartialEq)]
pub enum Dimension {
    /// Exact density-independent pixels.
    Fixed(u32),
    /// Fill all available space from the parent.
    Fill,
    /// Shrink to fit the content.
    Wrap,
    /// Fraction of the parent's dimension (0.0–1.0).
    Fraction(f32),
    /// Minimum of two dimensions.
    Min(Box<Dimension>, Box<Dimension>),
    /// Maximum of two dimensions.
    Max(Box<Dimension>, Box<Dimension>),
}

impl Dimension {
    pub fn fraction(f: f32) -> Self {
        Dimension::Fraction(f.clamp(0.0, 1.0))
    }

    pub fn half() -> Self {
        Dimension::Fraction(0.5)
    }

    pub fn full() -> Self {
        Dimension::Fill
    }
}

/// A complete set of style properties that can be applied to a component.
///
/// All fields are optional — only the ones set by the caller are applied.
/// The platform backend reads these when rendering and maps them to native
/// style properties.
#[derive(Debug, Clone, Default)]
pub struct StyleSet {
    pub padding: Option<[u32; 4]>,       // top, right, bottom, left
    pub margin: Option<[u32; 4]>,
    pub background: Option<Color>,
    pub foreground: Option<Color>,
    pub font: Option<TextStyle>,
    pub radius: Option<[u32; 4]>,        // top-left, top-right, bottom-right, bottom-left
    pub shadow: Option<Shadow>,
    pub opacity: Option<f32>,
    pub border_width: Option<u32>,
    pub border_color: Option<Color>,
    pub width: Option<Dimension>,
    pub height: Option<Dimension>,
    pub max_width: Option<Dimension>,
    pub max_height: Option<Dimension>,
    pub min_width: Option<Dimension>,
    pub min_height: Option<Dimension>,
    pub flex_grow: Option<f32>,
    pub flex_shrink: Option<f32>,
    pub z_index: Option<i32>,
    pub hidden: bool,
    pub clip: bool,
}

/// The core trait every styled el-ui component implements.
///
/// Returns `Self` — the methods consume and return the component for
/// fluent chaining without allocation.
pub trait StyleModifier: Sized {
    /// Access the mutable StyleSet for this component.
    fn style_mut(&mut self) -> &mut StyleSet;

    /// Apply uniform padding on all four sides.
    fn padding(mut self, value: u32) -> Self {
        self.style_mut().padding = Some([value; 4]);
        self
    }

    /// Apply horizontal (x) and vertical (y) padding.
    fn padding_xy(mut self, x: u32, y: u32) -> Self {
        self.style_mut().padding = Some([y, x, y, x]);
        self
    }

    /// Apply padding individually: top, right, bottom, left.
    fn padding_sides(mut self, top: u32, right: u32, bottom: u32, left: u32) -> Self {
        self.style_mut().padding = Some([top, right, bottom, left]);
        self
    }

    /// Apply uniform margin on all four sides.
    fn margin(mut self, value: u32) -> Self {
        self.style_mut().margin = Some([value; 4]);
        self
    }

    /// Apply horizontal and vertical margin.
    fn margin_xy(mut self, x: u32, y: u32) -> Self {
        self.style_mut().margin = Some([y, x, y, x]);
        self
    }

    /// Set the background color.
    fn background(mut self, color: Color) -> Self {
        self.style_mut().background = Some(color);
        self
    }

    /// Set the foreground (text/icon) color.
    fn foreground(mut self, color: Color) -> Self {
        self.style_mut().foreground = Some(color);
        self
    }

    /// Set the text/font style.
    fn font(mut self, style: TextStyle) -> Self {
        self.style_mut().font = Some(style);
        self
    }

    /// Set a uniform border radius on all four corners.
    fn radius(mut self, radius: Radius) -> Self {
        let r = radius.dp();
        self.style_mut().radius = Some([r; 4]);
        self
    }

    /// Set the shadow/elevation.
    fn shadow(mut self, elevation: Shadow) -> Self {
        self.style_mut().shadow = Some(elevation);
        self
    }

    /// Set the opacity (0.0 = invisible, 1.0 = fully visible).
    fn opacity(mut self, value: f32) -> Self {
        self.style_mut().opacity = Some(value.clamp(0.0, 1.0));
        self
    }

    /// Set a border with width and color.
    fn border(mut self, width: u32, color: Color) -> Self {
        let s = self.style_mut();
        s.border_width = Some(width);
        s.border_color = Some(color);
        self
    }

    /// Set the width.
    fn width(mut self, value: Dimension) -> Self {
        self.style_mut().width = Some(value);
        self
    }

    /// Set the height.
    fn height(mut self, value: Dimension) -> Self {
        self.style_mut().height = Some(value);
        self
    }

    /// Set the maximum width.
    fn max_width(mut self, value: Dimension) -> Self {
        self.style_mut().max_width = Some(value);
        self
    }

    /// Set the maximum height.
    fn max_height(mut self, value: Dimension) -> Self {
        self.style_mut().max_height = Some(value);
        self
    }

    /// Set the minimum width.
    fn min_width(mut self, value: Dimension) -> Self {
        self.style_mut().min_width = Some(value);
        self
    }

    /// Set the minimum height.
    fn min_height(mut self, value: Dimension) -> Self {
        self.style_mut().min_height = Some(value);
        self
    }

    /// How much this component grows to fill available space (flex-grow).
    fn grow(mut self, factor: f32) -> Self {
        self.style_mut().flex_grow = Some(factor);
        self
    }

    /// Z-index for layering.
    fn z_index(mut self, z: i32) -> Self {
        self.style_mut().z_index = Some(z);
        self
    }

    /// Clip content that overflows the component's bounds.
    fn clip(mut self) -> Self {
        self.style_mut().clip = true;
        self
    }

    /// Hide this component (still occupies space in layout).
    fn hidden(mut self, value: bool) -> Self {
        self.style_mut().hidden = value;
        self
    }
}

/// Pre-composed style variants for common component types.

/// Button style variants.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ButtonVariant {
    /// Filled primary-color button (the main CTA).
    Primary,
    /// Filled secondary-color button.
    Secondary,
    /// Destructive action (red).
    Destructive,
    /// Text-only, no fill or border.
    Ghost,
    /// Inline hyperlink style.
    Link,
}

/// Card style variants.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CardStyle {
    /// Card with a drop shadow.
    Elevated,
    /// Card with an outline border, no shadow.
    Outlined,
    /// Card with a filled background color, no shadow.
    Filled,
}

/// Text input style variants.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputStyle {
    /// Standard input (platform default).
    Default,
    /// Outlined/bordered input.
    Outlined,
    /// Filled background input.
    Filled,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::color::Color;
    use crate::radius::Radius;
    use crate::shadow::Shadow;
    use crate::typography::TextStyle;

    /// A minimal test component that implements StyleModifier.
    #[derive(Default)]
    struct TestWidget {
        style: StyleSet,
    }

    impl StyleModifier for TestWidget {
        fn style_mut(&mut self) -> &mut StyleSet {
            &mut self.style
        }
    }

    #[test]
    fn padding_sets_all_sides() {
        let w = TestWidget::default().padding(16);
        assert_eq!(w.style.padding, Some([16; 4]));
    }

    #[test]
    fn padding_xy_sets_correctly() {
        let w = TestWidget::default().padding_xy(8, 16);
        // [top, right, bottom, left] = [y, x, y, x]
        assert_eq!(w.style.padding, Some([16, 8, 16, 8]));
    }

    #[test]
    fn background_stored() {
        let w = TestWidget::default().background(Color::Primary);
        assert_eq!(w.style.background, Some(Color::Primary));
    }

    #[test]
    fn foreground_stored() {
        let w = TestWidget::default().foreground(Color::OnPrimary);
        assert_eq!(w.style.foreground, Some(Color::OnPrimary));
    }

    #[test]
    fn font_stored() {
        let w = TestWidget::default().font(TextStyle::Body);
        assert_eq!(w.style.font, Some(TextStyle::Body));
    }

    #[test]
    fn radius_stored() {
        let w = TestWidget::default().radius(Radius::Md);
        assert_eq!(w.style.radius, Some([8; 4]));
    }

    #[test]
    fn shadow_stored() {
        let w = TestWidget::default().shadow(Shadow::Md);
        assert_eq!(w.style.shadow, Some(Shadow::Md));
    }

    #[test]
    fn opacity_clamped() {
        let w = TestWidget::default().opacity(2.5);
        assert_eq!(w.style.opacity, Some(1.0));
    }

    #[test]
    fn opacity_valid() {
        let w = TestWidget::default().opacity(0.5);
        assert!((w.style.opacity.unwrap() - 0.5).abs() < 0.001);
    }

    #[test]
    fn border_stored() {
        let w = TestWidget::default().border(2, Color::Outline);
        assert_eq!(w.style.border_width, Some(2));
        assert_eq!(w.style.border_color, Some(Color::Outline));
    }

    #[test]
    fn chain_multiple_modifiers() {
        let w = TestWidget::default()
            .padding(16)
            .background(Color::Surface)
            .radius(Radius::Lg)
            .shadow(Shadow::Sm)
            .opacity(0.9);
        assert!(w.style.padding.is_some());
        assert!(w.style.background.is_some());
        assert!(w.style.radius.is_some());
        assert!(w.style.shadow.is_some());
        assert!(w.style.opacity.is_some());
    }

    #[test]
    fn hidden_flag() {
        let w = TestWidget::default().hidden(true);
        assert!(w.style.hidden);
    }

    #[test]
    fn clip_flag() {
        let w = TestWidget::default().clip();
        assert!(w.style.clip);
    }

    #[test]
    fn dimension_fraction_clamped() {
        let d = Dimension::fraction(1.5);
        assert_eq!(d, Dimension::Fraction(1.0));
    }

    #[test]
    fn width_stored() {
        let w = TestWidget::default().width(Dimension::Fill);
        assert_eq!(w.style.width, Some(Dimension::Fill));
    }

    #[test]
    fn max_width_stored() {
        let w = TestWidget::default().max_width(Dimension::Fixed(600));
        assert_eq!(w.style.max_width, Some(Dimension::Fixed(600)));
    }
}
