/// Layout constraints — what the parent is offering the child.
///
/// A parent passes a LayoutConstraints to each child during layout.
/// The child must produce a size within these constraints.
/// Constraints flow down; sizes flow back up.

/// Constraints passed from a parent to a child during layout.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LayoutConstraints {
    /// Minimum width the child must be (dp).
    pub min_width: f32,
    /// Maximum width available to the child (dp). f32::INFINITY = unbounded.
    pub max_width: f32,
    /// Minimum height the child must be (dp).
    pub min_height: f32,
    /// Maximum height available to the child (dp). f32::INFINITY = unbounded.
    pub max_height: f32,
}

impl LayoutConstraints {
    /// Unconstrained — the child can be any size.
    pub fn unbounded() -> Self {
        Self {
            min_width: 0.0,
            max_width: f32::INFINITY,
            min_height: 0.0,
            max_height: f32::INFINITY,
        }
    }

    /// Constrained to a specific width, unconstrained height.
    pub fn with_max_width(max_width: f32) -> Self {
        Self {
            min_width: 0.0,
            max_width,
            min_height: 0.0,
            max_height: f32::INFINITY,
        }
    }

    /// Constrained to an exact width and height.
    pub fn tight(width: f32, height: f32) -> Self {
        Self {
            min_width: width,
            max_width: width,
            min_height: height,
            max_height: height,
        }
    }

    /// Return constraints loosened to allow any size up to the maximums.
    pub fn loosen(&self) -> Self {
        Self {
            min_width: 0.0,
            max_width: self.max_width,
            min_height: 0.0,
            max_height: self.max_height,
        }
    }

    /// Deflate the constraints by padding amounts.
    /// Useful when a parent applies its own padding before offering space to a child.
    pub fn deflate(&self, horizontal: f32, vertical: f32) -> Self {
        Self {
            min_width: (self.min_width - horizontal).max(0.0),
            max_width: (self.max_width - horizontal).max(0.0),
            min_height: (self.min_height - vertical).max(0.0),
            max_height: (self.max_height - vertical).max(0.0),
        }
    }

    /// Is the width dimension bounded?
    pub fn has_bounded_width(&self) -> bool {
        self.max_width.is_finite()
    }

    /// Is the height dimension bounded?
    pub fn has_bounded_height(&self) -> bool {
        self.max_height.is_finite()
    }

    /// Clamp a proposed size to fit within these constraints.
    pub fn clamp_size(&self, width: f32, height: f32) -> (f32, f32) {
        (
            width.clamp(self.min_width, self.max_width),
            height.clamp(self.min_height, self.max_height),
        )
    }
}

/// The size a child reports back to its parent after layout.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Size {
    pub width: f32,
    pub height: f32,
}

impl Size {
    pub fn new(width: f32, height: f32) -> Self {
        Self { width, height }
    }

    pub fn zero() -> Self {
        Self { width: 0.0, height: 0.0 }
    }
}

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

    #[test]
    fn unbounded_has_no_max() {
        let c = LayoutConstraints::unbounded();
        assert!(c.max_width.is_infinite());
        assert!(c.max_height.is_infinite());
    }

    #[test]
    fn tight_constraints() {
        let c = LayoutConstraints::tight(100.0, 50.0);
        assert_eq!(c.min_width, 100.0);
        assert_eq!(c.max_width, 100.0);
    }

    #[test]
    fn deflate_reduces_available_space() {
        let c = LayoutConstraints::tight(200.0, 100.0);
        let deflated = c.deflate(16.0, 8.0);
        assert_eq!(deflated.max_width, 184.0);
        assert_eq!(deflated.max_height, 92.0);
    }

    #[test]
    fn deflate_does_not_go_negative() {
        let c = LayoutConstraints::tight(10.0, 10.0);
        let deflated = c.deflate(20.0, 20.0);
        assert_eq!(deflated.max_width, 0.0);
        assert_eq!(deflated.max_height, 0.0);
    }

    #[test]
    fn clamp_size() {
        let c = LayoutConstraints {
            min_width: 50.0,
            max_width: 200.0,
            min_height: 30.0,
            max_height: 100.0,
        };
        let (w, h) = c.clamp_size(250.0, 20.0);
        assert_eq!(w, 200.0);
        assert_eq!(h, 30.0);
    }

    #[test]
    fn bounded_width_detection() {
        let bounded = LayoutConstraints::with_max_width(400.0);
        let unbounded = LayoutConstraints::unbounded();
        assert!(bounded.has_bounded_width());
        assert!(!unbounded.has_bounded_width());
    }
}
