use crate::ir::{TrigGraph, TrigOp};

/// CPU reference evaluator. Walks the trig graph forward,
/// evaluating each node with f64 math.
///
/// This is the ground truth. The CORDIC evaluator must produce
/// results that converge to this within its word-size precision.
pub fn evaluate(graph: &TrigGraph, x: f64, y: f64, z: f64) -> f64 {
    let mut vals = vec![0.0f64; graph.nodes.len()];

    for (i, op) in graph.nodes.iter().enumerate() {
        vals[i] = match op {
            TrigOp::InputX => x,
            TrigOp::InputY => y,
            TrigOp::InputZ => z,
            TrigOp::Const(c) => *c,

            TrigOp::Add(a, b) => vals[*a as usize] + vals[*b as usize],
            TrigOp::Sub(a, b) => vals[*a as usize] - vals[*b as usize],
            TrigOp::Mul(a, b) => vals[*a as usize] * vals[*b as usize],
            TrigOp::Div(a, b) => vals[*a as usize] / vals[*b as usize],
            TrigOp::Neg(a) => -vals[*a as usize],
            TrigOp::Abs(a) => vals[*a as usize].abs(),

            TrigOp::Sin(a) => vals[*a as usize].sin(),
            TrigOp::Cos(a) => vals[*a as usize].cos(),
            TrigOp::Tan(a) => vals[*a as usize].tan(),
            TrigOp::Asin(a) => vals[*a as usize].asin(),
            TrigOp::Acos(a) => vals[*a as usize].acos(),
            TrigOp::Atan(a) => vals[*a as usize].atan(),
            TrigOp::Sinh(a) => vals[*a as usize].sinh(),
            TrigOp::Cosh(a) => vals[*a as usize].cosh(),
            TrigOp::Tanh(a) => vals[*a as usize].tanh(),
            TrigOp::Asinh(a) => vals[*a as usize].asinh(),
            TrigOp::Acosh(a) => vals[*a as usize].acosh(),
            TrigOp::Atanh(a) => vals[*a as usize].atanh(),
            TrigOp::Sqrt(a) => vals[*a as usize].sqrt(),
            TrigOp::Exp(a) => vals[*a as usize].exp(),
            TrigOp::Ln(a) => vals[*a as usize].ln(),

            TrigOp::Hypot(a, b) => vals[*a as usize].hypot(vals[*b as usize]),
            TrigOp::Atan2(a, b) => vals[*a as usize].atan2(vals[*b as usize]),

            TrigOp::Min(a, b) => vals[*a as usize].min(vals[*b as usize]),
            TrigOp::Max(a, b) => vals[*a as usize].max(vals[*b as usize]),

            TrigOp::Clamp { val, lo, hi } => {
                vals[*val as usize].clamp(vals[*lo as usize], vals[*hi as usize])
            }
        };
    }

    vals[graph.output as usize]
}

/// Evaluate on a regular 3D grid. Returns a flat array in x-major order.
/// Grid spans [min, max] with `res` samples per axis.
pub fn evaluate_grid(
    graph: &TrigGraph,
    min: [f64; 3],
    max: [f64; 3],
    res: usize,
) -> Vec<f64> {
    let mut data = vec![0.0f64; res * res * res];
    let step = [
        (max[0] - min[0]) / (res - 1).max(1) as f64,
        (max[1] - min[1]) / (res - 1).max(1) as f64,
        (max[2] - min[2]) / (res - 1).max(1) as f64,
    ];

    for iz in 0..res {
        let z = min[2] + iz as f64 * step[2];
        for iy in 0..res {
            let y = min[1] + iy as f64 * step[1];
            for ix in 0..res {
                let x = min[0] + ix as f64 * step[0];
                data[iz * res * res + iy * res + ix] = evaluate(graph, x, y, z);
            }
        }
    }

    data
}
