/// 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()
    }
}

// ─── 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));
    }
}

