/// Cassowary-inspired constraint solver for DreamStack layout.
///
/// Solves systems of linear constraints using Gaussian elimination
/// with strength-based priority (Required > Strong > Medium > Weak).
///
/// Each variable represents a layout dimension (x, y, width, height).
/// Constraints express relationships between these dimensions.
///
/// Performance target: solve 500 constraints in <2ms.

use std::collections::HashMap;

// ─── Core types ─────────────────────────────────────────────

/// A layout variable representing a single dimension of an element.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Variable(pub u32);

static NEXT_VAR_ID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(1);

impl Variable {
    pub fn new() -> Self {
        Variable(NEXT_VAR_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
    }
}

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

/// A term in a linear expression: coefficient * variable.
#[derive(Debug, Clone)]
pub struct Term {
    pub var: Variable,
    pub coefficient: f64,
}

/// Constraint strength — determines priority when constraints conflict.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum Strength {
    Weak = 1,
    Medium = 100,
    Strong = 1000,
    Required = 1_000_000,
}

/// What kind of constraint.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConstraintKind {
    Eq,    // expression == 0
    Lte,   // expression <= 0
    Gte,   // expression >= 0
}

/// A linear constraint: sum_i(coeff_i * var_i) + constant <op> 0
#[derive(Debug, Clone)]
pub struct Constraint {
    pub terms: Vec<Term>,
    pub constant: f64,
    pub kind: ConstraintKind,
    pub strength: Strength,
}

impl Constraint {
    /// var == val
    pub fn eq_const(var: Variable, val: f64, strength: Strength) -> Self {
        // var - val == 0
        Constraint {
            terms: vec![Term { var, coefficient: 1.0 }],
            constant: -val,
            kind: ConstraintKind::Eq,
            strength,
        }
    }

    /// a == b
    pub fn eq(a: Variable, b: Variable, strength: Strength) -> Self {
        // a - b == 0
        Constraint {
            terms: vec![
                Term { var: a, coefficient: 1.0 },
                Term { var: b, coefficient: -1.0 },
            ],
            constant: 0.0,
            kind: ConstraintKind::Eq,
            strength,
        }
    }

    /// a + b == val
    pub fn sum_eq(a: Variable, b: Variable, val: f64, strength: Strength) -> Self {
        Constraint {
            terms: vec![
                Term { var: a, coefficient: 1.0 },
                Term { var: b, coefficient: 1.0 },
            ],
            constant: -val,
            kind: ConstraintKind::Eq,
            strength,
        }
    }

    /// var >= val
    pub fn gte_const(var: Variable, val: f64, strength: Strength) -> Self {
        // var - val >= 0
        Constraint {
            terms: vec![Term { var, coefficient: 1.0 }],
            constant: -val,
            kind: ConstraintKind::Gte,
            strength,
        }
    }

    /// var <= val
    pub fn lte_const(var: Variable, val: f64, strength: Strength) -> Self {
        // var - val <= 0
        Constraint {
            terms: vec![Term { var, coefficient: 1.0 }],
            constant: -val,
            kind: ConstraintKind::Lte,
            strength,
        }
    }

    /// a * ratio == b  (i.e., ratio*a - b == 0)
    pub fn ratio(a: Variable, b: Variable, ratio: f64, strength: Strength) -> Self {
        Constraint {
            terms: vec![
                Term { var: a, coefficient: ratio },
                Term { var: b, coefficient: -1.0 },
            ],
            constant: 0.0,
            kind: ConstraintKind::Eq,
            strength,
        }
    }
}

/// Absolute layout rect for a resolved element.
#[derive(Debug, Clone, Copy, Default)]
pub struct LayoutRect {
    pub x: f64,
    pub y: f64,
    pub width: f64,
    pub height: f64,
}

impl LayoutRect {
    /// Serialize to 16 bytes: x(f32) + y(f32) + w(f32) + h(f32)
    pub fn to_bytes(&self) -> [u8; 16] {
        let mut buf = [0u8; 16];
        buf[0..4].copy_from_slice(&(self.x as f32).to_le_bytes());
        buf[4..8].copy_from_slice(&(self.y as f32).to_le_bytes());
        buf[8..12].copy_from_slice(&(self.width as f32).to_le_bytes());
        buf[12..16].copy_from_slice(&(self.height as f32).to_le_bytes());
        buf
    }

    pub fn from_bytes(buf: &[u8; 16]) -> Self {
        Self {
            x: f32::from_le_bytes(buf[0..4].try_into().unwrap()) as f64,
            y: f32::from_le_bytes(buf[4..8].try_into().unwrap()) as f64,
            width: f32::from_le_bytes(buf[8..12].try_into().unwrap()) as f64,
            height: f32::from_le_bytes(buf[12..16].try_into().unwrap()) as f64,
        }
    }
}


/// The constraint solver.
///
/// Uses Gaussian elimination: each equality constraint is reduced to
/// `subject = expression_of_other_vars + constant`. Existing definitions
/// are substituted in, so the system converges to concrete values.
/// Inequality constraints are handled by clamping.

pub struct LayoutSolver {
    constraints: Vec<Constraint>,
    /// Solved variable definitions: var → (coefficients_of_other_vars, constant).
    /// When fully reduced, the HashMap of coefficients is empty, and constant is the value.
    definitions: HashMap<Variable, (HashMap<Variable, f64>, f64)>,
}

impl LayoutSolver {
    pub fn new() -> Self {
        LayoutSolver {
            constraints: Vec::new(),
            definitions: HashMap::new(),
        }
    }

    pub fn add_constraint(&mut self, constraint: Constraint) {
        self.constraints.push(constraint);
    }

    /// Solve all constraints.
    pub fn solve(&mut self) {
        self.definitions.clear();

        // Sort constraints: required first (highest strength first)
        let mut constraints = self.constraints.clone();
        constraints.sort_by(|a, b| (b.strength as i32).cmp(&(a.strength as i32)));

        for c in constraints {
            match c.kind {
                ConstraintKind::Eq => self.process_eq(&c),
                ConstraintKind::Gte => self.process_gte(&c),
                ConstraintKind::Lte => self.process_lte(&c),
            }
        }

        // Iteratively resolve until all definitions are concrete
        for _ in 0..20 {
            let mut changed = false;
            let vars: Vec<Variable> = self.definitions.keys().cloned().collect();
            for var in vars {
                let (coeffs, constant) = self.definitions[&var].clone();
                let mut new_coeffs: HashMap<Variable, f64> = HashMap::new();
                let mut new_constant = constant;

                for (&dep_var, &coeff) in &coeffs {
                    if let Some((dep_coeffs, dep_const)) = self.definitions.get(&dep_var) {
                        // Substitute: dep_var = dep_coeffs + dep_const
                        new_constant += coeff * dep_const;
                        for (&v, &c) in dep_coeffs {
                            *new_coeffs.entry(v).or_insert(0.0) += coeff * c;
                        }
                        changed = true;
                    } else {
                        *new_coeffs.entry(dep_var).or_insert(0.0) += coeff;
                    }
                }

                // Clean near-zero coefficients
                new_coeffs.retain(|_, c| c.abs() > 1e-10);

                self.definitions.insert(var, (new_coeffs, new_constant));
            }
            if !changed {
                break;
            }
        }
    }

    fn process_eq(&mut self, c: &Constraint) {
        // Build: sum_i(coeff_i * var_i) + constant == 0
        // Substitute known definitions
        let mut coeffs: HashMap<Variable, f64> = HashMap::new();
        let mut constant = c.constant;

        for term in &c.terms {
            if let Some((def_coeffs, def_const)) = self.definitions.get(&term.var) {
                // Variable already defined — substitute
                constant += term.coefficient * def_const;
                for (&v, &c) in def_coeffs {
                    *coeffs.entry(v).or_insert(0.0) += term.coefficient * c;
                }
            } else {
                *coeffs.entry(term.var).or_insert(0.0) += term.coefficient;
            }
        }

        // Clean near-zero
        coeffs.retain(|_, c| c.abs() > 1e-10);

        // Pick a subject variable to solve for
        if let Some((&subject, &subject_coeff)) = coeffs.iter().find(|(v, c)| {
            c.abs() > 1e-10 && !self.definitions.contains_key(v)
        }).or_else(|| coeffs.iter().find(|(_, c)| c.abs() > 1e-10)) {
            // Solve: subject_coeff * subject + rest == 0
            // subject = -rest / subject_coeff - constant / subject_coeff
            let inv = -1.0 / subject_coeff;
            let mut result_coeffs: HashMap<Variable, f64> = HashMap::new();
            for (&v, &c) in &coeffs {
                if v != subject {
                    result_coeffs.insert(v, c * inv);
                }
            }
            let result_constant = constant * inv;

            self.definitions.insert(subject, (result_coeffs, result_constant));
        }
    }

    fn process_gte(&mut self, c: &Constraint) {
        // var + constant >= 0  →  var >= -constant
        // If var is unsolved, set its minimum
        if c.terms.len() == 1 {
            let term = &c.terms[0];
            let min_val = -c.constant / term.coefficient;

            if let Some((_coeffs, const_val)) = self.definitions.get_mut(&term.var) {
                if *const_val < min_val {
                    *const_val = min_val;
                }
            } else {
                // Not yet defined — set to minimum
                self.definitions.insert(term.var, (HashMap::new(), min_val));
            }
        } else {
            // Multi-variable inequality — convert to equality at boundary
            self.process_eq(c);
        }
    }

    fn process_lte(&mut self, c: &Constraint) {
        if c.terms.len() == 1 {
            let term = &c.terms[0];
            let max_val = -c.constant / term.coefficient;

            if let Some((_coeffs, const_val)) = self.definitions.get_mut(&term.var) {
                if *const_val > max_val {
                    *const_val = max_val;
                }
            } else {
                self.definitions.insert(term.var, (HashMap::new(), max_val));
            }
        } else {
            self.process_eq(c);
        }
    }

    /// Get the resolved value of a variable.
    pub fn get_value(&self, var: Variable) -> f64 {
        if let Some((coeffs, constant)) = self.definitions.get(&var) {
            if coeffs.is_empty() {
                return *constant;
            }
            // Still has dependencies — try to resolve them
            let mut val = *constant;
            for (&dep, &coeff) in coeffs {
                val += coeff * self.get_value(dep);
            }
            val
        } else {
            0.0
        }
    }

    /// Resolve a layout rect from four variables.
    pub fn get_rect(&self, x: Variable, y: Variable, w: Variable, h: Variable) -> LayoutRect {
        LayoutRect {
            x: self.get_value(x),
            y: self.get_value(y),
            width: self.get_value(w),
            height: self.get_value(h),
        }
    }

    /// Clear all constraints and solutions.
    pub fn reset(&mut self) {
        self.constraints.clear();
        self.definitions.clear();
    }
}

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

// ─── v0.7: Layout Extensions ───

#[derive(Debug, Clone, PartialEq)]
pub struct LayoutExt {
    pub z_index: i32,
    pub overflow: Overflow,
    pub aspect_ratio: Option<(u32, u32)>,
    pub min_width: Option<f64>,
    pub max_width: Option<f64>,
    pub min_height: Option<f64>,
    pub max_height: Option<f64>,
    pub grid_columns: Option<u32>,
    pub grid_rows: Option<u32>,
    pub anchor: Anchor,
    pub visible: bool,
    pub opacity: f64,
    pub transform_origin: (f64, f64),
}

#[derive(Debug, Clone, PartialEq)]
pub enum Overflow { Visible, Hidden, Scroll, Auto }

#[derive(Debug, Clone, PartialEq)]
pub enum Anchor { TopLeft, TopCenter, TopRight, CenterLeft, Center, CenterRight, BottomLeft, BottomCenter, BottomRight }

impl Default for LayoutExt {
    fn default() -> Self {
        LayoutExt { z_index: 0, overflow: Overflow::Visible, aspect_ratio: None, min_width: None, max_width: None, min_height: None, max_height: None, grid_columns: None, grid_rows: None, anchor: Anchor::TopLeft, visible: true, opacity: 1.0, transform_origin: (0.5, 0.5) }
    }
}

impl LayoutExt {
    pub fn new() -> Self { Self::default() }
    pub fn with_z_index(mut self, z: i32) -> Self { self.z_index = z; self }
    pub fn with_overflow(mut self, ov: Overflow) -> Self { self.overflow = ov; self }
    pub fn with_aspect_ratio(mut self, w: u32, h: u32) -> Self { self.aspect_ratio = Some((w, h)); self }
    pub fn with_grid(mut self, cols: u32, rows: u32) -> Self { self.grid_columns = Some(cols); self.grid_rows = Some(rows); self }
    pub fn with_anchor(mut self, a: Anchor) -> Self { self.anchor = a; self }
    pub fn with_opacity(mut self, o: f64) -> Self { self.opacity = o.clamp(0.0, 1.0); self }
    pub fn with_visibility(mut self, v: bool) -> Self { self.visible = v; self }
    pub fn with_min_width(mut self, w: f64) -> Self { self.min_width = Some(w); self }
    pub fn with_max_width(mut self, w: f64) -> Self { self.max_width = Some(w); self }
    pub fn effective_width(&self, proposed: f64) -> f64 {
        let w = if let Some(min) = self.min_width { proposed.max(min) } else { proposed };
        if let Some(max) = self.max_width { w.min(max) } else { w }
    }
}

// ─── v0.8: Flexbox & Position ───

#[derive(Debug, Clone, PartialEq)]
pub struct FlexLayout {
    pub gap: f64,
    pub padding: [f64; 4],
    pub margin: [f64; 4],
    pub border_width: f64,
    pub border_radius: f64,
    pub position: Position,
    pub align_items: Alignment,
    pub justify_content: Justification,
    pub wrap: bool,
    pub auto_width: bool,
    pub auto_height: bool,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Position { Static, Relative, Absolute, Fixed }

#[derive(Debug, Clone, PartialEq)]
pub enum Alignment { Start, Center, End, Stretch }

#[derive(Debug, Clone, PartialEq)]
pub enum Justification { Start, Center, End, SpaceBetween, SpaceAround }

impl Default for FlexLayout {
    fn default() -> Self { FlexLayout { gap: 0.0, padding: [0.0; 4], margin: [0.0; 4], border_width: 0.0, border_radius: 0.0, position: Position::Static, align_items: Alignment::Start, justify_content: Justification::Start, wrap: false, auto_width: false, auto_height: false } }
}

impl FlexLayout {
    pub fn new() -> Self { Self::default() }
    pub fn with_gap(mut self, g: f64) -> Self { self.gap = g; self }
    pub fn with_padding(mut self, p: [f64; 4]) -> Self { self.padding = p; self }
    pub fn with_margin(mut self, m: [f64; 4]) -> Self { self.margin = m; self }
    pub fn with_border(mut self, w: f64, r: f64) -> Self { self.border_width = w; self.border_radius = r; self }
    pub fn with_position(mut self, p: Position) -> Self { self.position = p; self }
    pub fn with_align(mut self, a: Alignment) -> Self { self.align_items = a; self }
    pub fn with_justify(mut self, j: Justification) -> Self { self.justify_content = j; self }
    pub fn with_wrap(mut self, w: bool) -> Self { self.wrap = w; self }
    pub fn with_auto_size(mut self) -> Self { self.auto_width = true; self.auto_height = true; self }
    pub fn inner_width(&self, outer: f64) -> f64 { (outer - self.padding[1] - self.padding[3] - self.border_width * 2.0).max(0.0) }
    pub fn total_gap(&self, items: usize) -> f64 { if items <= 1 { 0.0 } else { self.gap * (items - 1) as f64 } }
}

// ─── v0.9: Advanced Layout ───

#[derive(Debug, Clone, PartialEq)]
pub struct AdvancedLayout {
    pub scroll_x: bool,
    pub scroll_y: bool,
    pub sticky: bool,
    pub sticky_offset: f64,
    pub flex_grow: f64,
    pub flex_shrink: f64,
    pub flex_basis: Option<f64>,
    pub order: i32,
    pub row_gap: f64,
    pub col_gap: f64,
    pub clip: bool,
    pub shadow: Option<Shadow>,
    pub transition: Option<Transition>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Shadow { pub x: f64, pub y: f64, pub blur: f64, pub color: String }

#[derive(Debug, Clone, PartialEq)]
pub struct Transition { pub property: String, pub duration_ms: u32 }

impl Default for AdvancedLayout {
    fn default() -> Self { AdvancedLayout { scroll_x: false, scroll_y: false, sticky: false, sticky_offset: 0.0, flex_grow: 0.0, flex_shrink: 1.0, flex_basis: None, order: 0, row_gap: 0.0, col_gap: 0.0, clip: false, shadow: None, transition: None } }
}

impl AdvancedLayout {
    pub fn new() -> Self { Self::default() }
    pub fn with_scroll(mut self, x: bool, y: bool) -> Self { self.scroll_x = x; self.scroll_y = y; self }
    pub fn with_sticky(mut self, offset: f64) -> Self { self.sticky = true; self.sticky_offset = offset; self }
    pub fn with_flex(mut self, grow: f64, shrink: f64) -> Self { self.flex_grow = grow; self.flex_shrink = shrink; self }
    pub fn with_basis(mut self, b: f64) -> Self { self.flex_basis = Some(b); self }
    pub fn with_order(mut self, o: i32) -> Self { self.order = o; self }
    pub fn with_gaps(mut self, row: f64, col: f64) -> Self { self.row_gap = row; self.col_gap = col; self }
    pub fn with_clip(mut self) -> Self { self.clip = true; self }
    pub fn with_shadow(mut self, x: f64, y: f64, blur: f64, color: &str) -> Self { self.shadow = Some(Shadow { x, y, blur, color: color.to_string() }); self }
    pub fn with_transition(mut self, prop: &str, ms: u32) -> Self { self.transition = Some(Transition { property: prop.to_string(), duration_ms: ms }); self }
    pub fn flex_space(&self, total: f64, items: usize) -> f64 { if items == 0 { 0.0 } else { total * self.flex_grow / items as f64 } }
}

// ─── v1.0: Complete Layout System ───

#[derive(Debug, Clone, PartialEq)]
pub struct Keyframe { pub pct: f64, pub properties: Vec<(String, String)> }

#[derive(Debug, Clone, PartialEq)]
pub struct Animation { pub name: String, pub keyframes: Vec<Keyframe>, pub duration_ms: u32, pub easing: String }

#[derive(Debug, Clone, PartialEq)]
pub struct TextLayout { pub font_size: f64, pub line_height: f64, pub align: String, pub font_family: String }

#[derive(Debug, Clone, PartialEq)]
pub enum ImageSize { Cover, Contain, Fill, Custom(f64, f64) }

#[derive(Debug, Clone, PartialEq)]
pub struct MediaQuery { pub min_width: Option<f64>, pub max_width: Option<f64>, pub rules: Vec<(String, String)> }

#[derive(Debug, Clone, PartialEq)]
pub enum ColorSpace { Rgb(u8, u8, u8), Hsl(f64, f64, f64), Hex(String) }

#[derive(Debug, Clone, PartialEq)]
pub struct Gradient { pub kind: String, pub stops: Vec<(String, f64)> }

#[derive(Debug, Clone, PartialEq)]
pub struct Filter { pub kind: String, pub value: f64 }

impl Animation { pub fn new(name: &str, dur: u32, easing: &str) -> Self { Animation { name: name.to_string(), keyframes: Vec::new(), duration_ms: dur, easing: easing.to_string() } }
    pub fn add_keyframe(&mut self, pct: f64, props: Vec<(&str, &str)>) { self.keyframes.push(Keyframe { pct, properties: props.into_iter().map(|(k,v)| (k.to_string(), v.to_string())).collect() }); }
    pub fn keyframe_count(&self) -> usize { self.keyframes.len() } }
impl TextLayout { pub fn new(size: f64, height: f64, align: &str, font: &str) -> Self { TextLayout { font_size: size, line_height: height, align: align.to_string(), font_family: font.to_string() } } }
impl MediaQuery { pub fn new(min: Option<f64>, max: Option<f64>) -> Self { MediaQuery { min_width: min, max_width: max, rules: Vec::new() } }
    pub fn add_rule(&mut self, prop: &str, val: &str) { self.rules.push((prop.to_string(), val.to_string())); }
    pub fn matches(&self, width: f64) -> bool { self.min_width.map(|m| width >= m).unwrap_or(true) && self.max_width.map(|m| width <= m).unwrap_or(true) } }
impl Gradient { pub fn linear(stops: Vec<(&str, f64)>) -> Self { Gradient { kind: "linear".to_string(), stops: stops.into_iter().map(|(c, p)| (c.to_string(), p)).collect() } }
    pub fn radial(stops: Vec<(&str, f64)>) -> Self { Gradient { kind: "radial".to_string(), stops: stops.into_iter().map(|(c, p)| (c.to_string(), p)).collect() } } }
impl Filter { pub fn blur(px: f64) -> Self { Filter { kind: "blur".to_string(), value: px } }
    pub fn brightness(v: f64) -> Self { Filter { kind: "brightness".to_string(), value: v } } }

pub fn clamp_val(min: f64, preferred: f64, max: f64) -> f64 { preferred.max(min).min(max) }
pub fn calc_subtract(total: f64, subtract: f64) -> f64 { (total - subtract).max(0.0) }

#[derive(Debug, Clone, PartialEq)]
pub struct LayoutStats { pub node_count: u32, pub solve_time_us: u64 }
impl LayoutStats { pub fn new(nodes: u32, time: u64) -> Self { LayoutStats { node_count: nodes, solve_time_us: time } } }

// ─── Tests ──────────────────────────────────────────────────

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

    #[test]
    fn test_simple_eq() {
        let mut solver = LayoutSolver::new();
        let x = Variable::new();
        solver.add_constraint(Constraint::eq_const(x, 100.0, Strength::Required));
        solver.solve();
        assert!((solver.get_value(x) - 100.0).abs() < 0.01, "x = {}", solver.get_value(x));
    }

    #[test]
    fn test_two_vars_eq() {
        let mut solver = LayoutSolver::new();
        let x = Variable::new();
        let w = Variable::new();
        solver.add_constraint(Constraint::eq_const(x, 50.0, Strength::Required));
        solver.add_constraint(Constraint::eq(w, x, Strength::Required));
        solver.solve();
        assert!((solver.get_value(x) - 50.0).abs() < 0.01, "x = {}", solver.get_value(x));
        assert!((solver.get_value(w) - 50.0).abs() < 0.01, "w = {}", solver.get_value(w));
    }

    #[test]
    fn test_sum_constraint() {
        let mut solver = LayoutSolver::new();
        let x = Variable::new();
        let w = Variable::new();
        solver.add_constraint(Constraint::eq_const(x, 100.0, Strength::Required));
        solver.add_constraint(Constraint::sum_eq(x, w, 500.0, Strength::Required));
        solver.solve();
        assert!((solver.get_value(x) - 100.0).abs() < 0.01, "x = {}", solver.get_value(x));
        assert!((solver.get_value(w) - 400.0).abs() < 0.01, "w = {}", solver.get_value(w));
    }

    #[test]
    fn test_layout_rect() {
        let mut solver = LayoutSolver::new();
        let x = Variable::new();
        let y = Variable::new();
        let w = Variable::new();
        let h = Variable::new();

        solver.add_constraint(Constraint::eq_const(x, 10.0, Strength::Required));
        solver.add_constraint(Constraint::eq_const(y, 20.0, Strength::Required));
        solver.add_constraint(Constraint::eq_const(w, 300.0, Strength::Required));
        solver.add_constraint(Constraint::eq_const(h, 400.0, Strength::Required));
        solver.solve();

        let rect = solver.get_rect(x, y, w, h);
        assert!((rect.x - 10.0).abs() < 0.01);
        assert!((rect.y - 20.0).abs() < 0.01);
        assert!((rect.width - 300.0).abs() < 0.01);
        assert!((rect.height - 400.0).abs() < 0.01);
    }

    #[test]
    fn test_gte_constraint() {
        let mut solver = LayoutSolver::new();
        let w = Variable::new();
        solver.add_constraint(Constraint::gte_const(w, 50.0, Strength::Required));
        solver.solve();
        let val = solver.get_value(w);
        assert!(val >= 49.99, "w should be >= 50, got {}", val);
    }

    #[test]
    fn test_ratio_constraint() {
        let mut solver = LayoutSolver::new();
        let w = Variable::new();
        let h = Variable::new();
        solver.add_constraint(Constraint::eq_const(w, 200.0, Strength::Required));
        solver.add_constraint(Constraint::ratio(w, h, 0.5, Strength::Required));
        solver.solve();
        assert!((solver.get_value(w) - 200.0).abs() < 0.01, "w = {}", solver.get_value(w));
        assert!((solver.get_value(h) - 100.0).abs() < 0.01, "h = {}", solver.get_value(h));
    }

    #[test]
    fn test_three_panel_layout() {
        let mut solver = LayoutSolver::new();

        let sidebar_x = Variable::new();
        let sidebar_w = Variable::new();
        let main_x = Variable::new();
        let main_w = Variable::new();

        solver.add_constraint(Constraint::eq_const(sidebar_x, 0.0, Strength::Required));
        solver.add_constraint(Constraint::eq_const(sidebar_w, 200.0, Strength::Required));
        solver.add_constraint(Constraint::eq_const(main_x, 200.0, Strength::Required));
        solver.add_constraint(Constraint::sum_eq(main_x, main_w, 1000.0, Strength::Required));

        solver.solve();

        assert!((solver.get_value(sidebar_x) - 0.0).abs() < 0.01);
        assert!((solver.get_value(sidebar_w) - 200.0).abs() < 0.01);
        assert!((solver.get_value(main_x) - 200.0).abs() < 0.01);
        assert!((solver.get_value(main_w) - 800.0).abs() < 0.01,
            "main_w = {}", solver.get_value(main_w));
    }

    // ── v0.7 Layout Solver Tests ────────────────────────────

    #[test]
    fn test_lte_constraint() {
        let mut solver = LayoutSolver::new();
        let w = Variable::new();
        // Set width to 600 then constrain <= 500
        solver.add_constraint(Constraint::eq_const(w, 600.0, Strength::Medium));
        solver.add_constraint(Constraint::lte_const(w, 500.0, Strength::Required));
        solver.solve();
        let val = solver.get_value(w);
        assert!(val <= 500.01, "w should be <= 500, got {}", val);
    }

    #[test]
    fn test_viewport_proportion() {
        let mut solver = LayoutSolver::new();
        let viewport_w = Variable::new();
        let sidebar_w = Variable::new();
        // viewport = 1000, sidebar = 0.25 * viewport
        solver.add_constraint(Constraint::eq_const(viewport_w, 1000.0, Strength::Required));
        solver.add_constraint(Constraint::ratio(viewport_w, sidebar_w, 0.25, Strength::Required));
        solver.solve();
        assert!((solver.get_value(sidebar_w) - 250.0).abs() < 0.01,
            "sidebar_w = {}", solver.get_value(sidebar_w));
    }

    #[test]
    fn test_cascading_eq() {
        let mut solver = LayoutSolver::new();
        let a = Variable::new();
        let b = Variable::new();
        let c = Variable::new();
        // a = 100, b = a, c = b → all should be 100
        solver.add_constraint(Constraint::eq_const(a, 100.0, Strength::Required));
        solver.add_constraint(Constraint::eq(b, a, Strength::Required));
        solver.add_constraint(Constraint::eq(c, b, Strength::Required));
        solver.solve();
        assert!((solver.get_value(a) - 100.0).abs() < 0.01);
        assert!((solver.get_value(b) - 100.0).abs() < 0.01);
        assert!((solver.get_value(c) - 100.0).abs() < 0.01, "c = {}", solver.get_value(c));
    }

    #[test]
    fn test_combined_gte_lte_clamp() {
        let mut solver = LayoutSolver::new();
        let w = Variable::new();
        // Clamp: 200 <= w <= 400
        solver.add_constraint(Constraint::gte_const(w, 200.0, Strength::Required));
        solver.add_constraint(Constraint::lte_const(w, 400.0, Strength::Required));
        solver.solve();
        let val = solver.get_value(w);
        assert!(val >= 199.99 && val <= 400.01,
            "w should be in [200, 400], got {}", val);
    }

    #[test]
    fn test_over_constrained_no_panic() {
        // Conflicting constraints should not panic
        let mut solver = LayoutSolver::new();
        let w = Variable::new();
        solver.add_constraint(Constraint::eq_const(w, 100.0, Strength::Required));
        solver.add_constraint(Constraint::eq_const(w, 200.0, Strength::Required));
        solver.solve(); // Should not panic
        let _val = solver.get_value(w); // Just assert it resolves
    }

    #[test]
    fn test_zero_width() {
        let mut solver = LayoutSolver::new();
        let w = Variable::new();
        solver.add_constraint(Constraint::eq_const(w, 0.0, Strength::Required));
        solver.solve();
        assert!((solver.get_value(w) - 0.0).abs() < 0.01, "w = {}", solver.get_value(w));
    }

    // ─── v0.7 Tests ───

    #[test]
    fn test_z_index() { let l = LayoutExt::new().with_z_index(10); assert_eq!(l.z_index, 10); }

    #[test]
    fn test_overflow() { let l = LayoutExt::new().with_overflow(Overflow::Hidden); assert_eq!(l.overflow, Overflow::Hidden); }

    #[test]
    fn test_aspect_ratio() { let l = LayoutExt::new().with_aspect_ratio(16, 9); assert_eq!(l.aspect_ratio, Some((16, 9))); }

    #[test]
    fn test_min_max() { let l = LayoutExt::new().with_min_width(100.0).with_max_width(500.0); assert_eq!(l.effective_width(50.0), 100.0); assert_eq!(l.effective_width(1000.0), 500.0); assert_eq!(l.effective_width(300.0), 300.0); }

    #[test]
    fn test_grid() { let l = LayoutExt::new().with_grid(3, 2); assert_eq!(l.grid_columns, Some(3)); assert_eq!(l.grid_rows, Some(2)); }

    #[test]
    fn test_anchor() { let l = LayoutExt::new().with_anchor(Anchor::Center); assert_eq!(l.anchor, Anchor::Center); }

    #[test]
    fn test_visibility() { let l = LayoutExt::new().with_visibility(false); assert!(!l.visible); }

    #[test]
    fn test_opacity() { let l = LayoutExt::new().with_opacity(0.5); assert!((l.opacity - 0.5).abs() < 0.01); }

    #[test]
    fn test_opacity_clamp() { let l = LayoutExt::new().with_opacity(1.5); assert_eq!(l.opacity, 1.0); }

    // ─── v0.8 Tests ───

    #[test]
    fn test_flex_gap() { let f = FlexLayout::new().with_gap(8.0); assert_eq!(f.total_gap(3), 16.0); }

    #[test]
    fn test_flex_padding() { let f = FlexLayout::new().with_padding([10.0, 20.0, 10.0, 20.0]); assert_eq!(f.inner_width(100.0), 60.0); }

    #[test]
    fn test_flex_margin() { let f = FlexLayout::new().with_margin([5.0, 10.0, 5.0, 10.0]); assert_eq!(f.margin[1], 10.0); }

    #[test]
    fn test_flex_border() { let f = FlexLayout::new().with_border(2.0, 8.0); assert_eq!(f.border_width, 2.0); assert_eq!(f.border_radius, 8.0); }

    #[test]
    fn test_flex_position() { let f = FlexLayout::new().with_position(Position::Absolute); assert_eq!(f.position, Position::Absolute); }

    #[test]
    fn test_flex_align() { let f = FlexLayout::new().with_align(Alignment::Center); assert_eq!(f.align_items, Alignment::Center); }

    #[test]
    fn test_flex_justify() { let f = FlexLayout::new().with_justify(Justification::SpaceBetween); assert_eq!(f.justify_content, Justification::SpaceBetween); }

    #[test]
    fn test_flex_wrap() { let f = FlexLayout::new().with_wrap(true); assert!(f.wrap); }

    #[test]
    fn test_flex_auto_size() { let f = FlexLayout::new().with_auto_size(); assert!(f.auto_width); assert!(f.auto_height); }

    // ─── v0.9 Tests ───

    #[test]
    fn test_scroll() { let l = AdvancedLayout::new().with_scroll(true, false); assert!(l.scroll_x); assert!(!l.scroll_y); }

    #[test]
    fn test_sticky() { let l = AdvancedLayout::new().with_sticky(10.0); assert!(l.sticky); assert_eq!(l.sticky_offset, 10.0); }

    #[test]
    fn test_flex_grow_shrink() { let l = AdvancedLayout::new().with_flex(2.0, 0.5); assert_eq!(l.flex_grow, 2.0); assert_eq!(l.flex_shrink, 0.5); }

    #[test]
    fn test_flex_basis() { let l = AdvancedLayout::new().with_basis(200.0); assert_eq!(l.flex_basis, Some(200.0)); }

    #[test]
    fn test_order() { let l = AdvancedLayout::new().with_order(-1); assert_eq!(l.order, -1); }

    #[test]
    fn test_row_col_gap() { let l = AdvancedLayout::new().with_gaps(8.0, 16.0); assert_eq!(l.row_gap, 8.0); assert_eq!(l.col_gap, 16.0); }

    #[test]
    fn test_clip() { let l = AdvancedLayout::new().with_clip(); assert!(l.clip); }

    #[test]
    fn test_shadow() { let l = AdvancedLayout::new().with_shadow(2.0, 4.0, 8.0, "#000"); assert!(l.shadow.is_some()); assert_eq!(l.shadow.unwrap().blur, 8.0); }

    #[test]
    fn test_transition() { let l = AdvancedLayout::new().with_transition("opacity", 300); let t = l.transition.unwrap(); assert_eq!(t.property, "opacity"); assert_eq!(t.duration_ms, 300); }

    // ─── v1.0 Tests ───

    #[test]
    fn test_animation() { let mut a = Animation::new("fadeIn", 500, "ease-in-out"); a.add_keyframe(0.0, vec![("opacity", "0")]); a.add_keyframe(100.0, vec![("opacity", "1")]); assert_eq!(a.keyframe_count(), 2); }
    #[test]
    fn test_animation_empty() { let a = Animation::new("slide", 300, "linear"); assert_eq!(a.keyframe_count(), 0); }
    #[test]
    fn test_text_layout() { let t = TextLayout::new(16.0, 1.5, "center", "Inter"); assert_eq!(t.font_size, 16.0); assert_eq!(t.align, "center"); }
    #[test]
    fn test_image_size() { assert_eq!(ImageSize::Cover, ImageSize::Cover); let c = ImageSize::Custom(100.0, 200.0); if let ImageSize::Custom(w, h) = c { assert_eq!(w, 100.0); } else { panic!(); } }
    #[test]
    fn test_media_query() { let mut mq = MediaQuery::new(Some(768.0), Some(1024.0)); assert!(mq.matches(800.0)); assert!(!mq.matches(500.0)); assert!(!mq.matches(1200.0)); }
    #[test]
    fn test_media_query_open() { let mq = MediaQuery::new(None, None); assert!(mq.matches(9999.0)); }
    #[test]
    fn test_color_rgb() { let c = ColorSpace::Rgb(255, 0, 128); assert_eq!(c, ColorSpace::Rgb(255, 0, 128)); }
    #[test]
    fn test_color_hsl() { let c = ColorSpace::Hsl(180.0, 50.0, 50.0); if let ColorSpace::Hsl(h, _, _) = c { assert_eq!(h, 180.0); } else { panic!(); } }
    #[test]
    fn test_gradient_linear() { let g = Gradient::linear(vec![("red", 0.0), ("blue", 100.0)]); assert_eq!(g.kind, "linear"); assert_eq!(g.stops.len(), 2); }
    #[test]
    fn test_gradient_radial() { let g = Gradient::radial(vec![("white", 0.0)]); assert_eq!(g.kind, "radial"); }
    #[test]
    fn test_filter_blur() { let f = Filter::blur(4.0); assert_eq!(f.kind, "blur"); assert_eq!(f.value, 4.0); }
    #[test]
    fn test_filter_brightness() { let f = Filter::brightness(1.2); assert_eq!(f.kind, "brightness"); }
    #[test]
    fn test_clamp() { assert_eq!(clamp_val(10.0, 5.0, 20.0), 10.0); assert_eq!(clamp_val(10.0, 15.0, 20.0), 15.0); assert_eq!(clamp_val(10.0, 25.0, 20.0), 20.0); }
    #[test]
    fn test_calc() { assert_eq!(calc_subtract(100.0, 20.0), 80.0); assert_eq!(calc_subtract(10.0, 20.0), 0.0); }
    #[test]
    fn test_layout_stats() { let s = LayoutStats::new(50, 1200); assert_eq!(s.node_count, 50); }
    #[test]
    fn test_media_add_rule() { let mut mq = MediaQuery::new(Some(0.0), None); mq.add_rule("display", "flex"); assert_eq!(mq.rules.len(), 1); }
    #[test]
    fn test_color_hex() { let c = ColorSpace::Hex("#FF0000".into()); if let ColorSpace::Hex(h) = c { assert_eq!(h, "#FF0000"); } else { panic!(); } }
    #[test]
    fn test_keyframe_props() { let k = Keyframe { pct: 50.0, properties: vec![("transform".into(), "scale(1.5)".into())] }; assert_eq!(k.pct, 50.0); }
}

