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

/// A 3D point in the trig graph — three node IDs for x, y, z.
#[derive(Clone, Copy)]
pub struct Point3 {
    x: NodeId,
    y: NodeId,
    z: NodeId,
}

/// Lower any SDF-like description into a TrigGraph.
///
/// Builder API for constructing SDF operations from trig primitives.
/// Geometric concepts (sphere, box, rotate, etc.) decompose into
/// TrigOp nodes internally.
pub struct SdfBuilder {
    pub graph: TrigGraph,
    root_point: Point3,
    zero: NodeId,
}

impl SdfBuilder {
    pub fn new() -> Self {
        let mut graph = TrigGraph::new();
        let px = graph.push(TrigOp::InputX);
        let py = graph.push(TrigOp::InputY);
        let pz = graph.push(TrigOp::InputZ);
        let zero = graph.push(TrigOp::Const(0.0));

        SdfBuilder {
            graph,
            root_point: Point3 { x: px, y: py, z: pz },
            zero,
        }
    }

    pub fn finish(mut self, output: NodeId) -> TrigGraph {
        self.graph.set_output(output);
        self.graph
    }

    pub fn constant(&mut self, val: f64) -> NodeId {
        self.graph.push(TrigOp::Const(val))
    }

    // === Primitives ===

    /// Sphere SDF: length(p) - r
    /// Two CORDIC vectoring passes (hypot3) + one subtraction.
    pub fn sphere(&mut self, point: Point3, radius: f64) -> NodeId {
        let mag = self.length3(point);
        let r = self.graph.push(TrigOp::Const(radius));
        self.graph.push(TrigOp::Sub(mag, r))
    }

    /// Box SDF: length(max(abs(p) - h, 0)) + min(max_component(q), 0)
    /// where q = abs(p) - h
    pub fn box_sdf(&mut self, point: Point3, half_extents: [f64; 3]) -> NodeId {
        let hx = self.graph.push(TrigOp::Const(half_extents[0]));
        let hy = self.graph.push(TrigOp::Const(half_extents[1]));
        let hz = self.graph.push(TrigOp::Const(half_extents[2]));

        // q = abs(p) - h
        let ax = self.graph.push(TrigOp::Abs(point.x));
        let ay = self.graph.push(TrigOp::Abs(point.y));
        let az = self.graph.push(TrigOp::Abs(point.z));
        let qx = self.graph.push(TrigOp::Sub(ax, hx));
        let qy = self.graph.push(TrigOp::Sub(ay, hy));
        let qz = self.graph.push(TrigOp::Sub(az, hz));

        // length(max(q, 0))
        let zero = self.zero;
        let cx = self.graph.push(TrigOp::Max(qx, zero));
        let cy = self.graph.push(TrigOp::Max(qy, zero));
        let cz = self.graph.push(TrigOp::Max(qz, zero));
        let outer = self.length3_nodes(cx, cy, cz);

        // min(max(qx, max(qy, qz)), 0)
        let m1 = self.graph.push(TrigOp::Max(qy, qz));
        let m2 = self.graph.push(TrigOp::Max(qx, m1));
        let inner = self.graph.push(TrigOp::Min(m2, zero));

        self.graph.push(TrigOp::Add(outer, inner))
    }

    /// Cylinder SDF (Z-axis aligned, centered at origin).
    /// d = vec2(length(p.xy) - r, abs(p.z) - h/2)
    /// return length(max(d, 0)) + min(max(d.x, d.y), 0)
    pub fn cylinder(&mut self, point: Point3, radius: f64, height: f64) -> NodeId {
        let r = self.graph.push(TrigOp::Const(radius));
        let hh = self.graph.push(TrigOp::Const(height / 2.0));
        let zero = self.zero;

        let len_xy = self.graph.push(TrigOp::Hypot(point.x, point.y));
        let dx = self.graph.push(TrigOp::Sub(len_xy, r));
        let az = self.graph.push(TrigOp::Abs(point.z));
        let dy = self.graph.push(TrigOp::Sub(az, hh));

        let cx = self.graph.push(TrigOp::Max(dx, zero));
        let cy = self.graph.push(TrigOp::Max(dy, zero));
        let outer = self.graph.push(TrigOp::Hypot(cx, cy));

        let inner_max = self.graph.push(TrigOp::Max(dx, dy));
        let inner = self.graph.push(TrigOp::Min(inner_max, zero));

        self.graph.push(TrigOp::Add(outer, inner))
    }

    // === Transforms ===

    /// Translate: evaluate child at (p - offset).
    pub fn translate(&mut self, point: Point3, offset: [f64; 3]) -> Point3 {
        let ox = self.graph.push(TrigOp::Const(offset[0]));
        let oy = self.graph.push(TrigOp::Const(offset[1]));
        let oz = self.graph.push(TrigOp::Const(offset[2]));
        Point3 {
            x: self.graph.push(TrigOp::Sub(point.x, ox)),
            y: self.graph.push(TrigOp::Sub(point.y, oy)),
            z: self.graph.push(TrigOp::Sub(point.z, oz)),
        }
    }

    /// Rotate around X axis by angle (radians).
    /// p' = (px, py*cos + pz*sin, -py*sin + pz*cos)
    /// Cost: 1 CORDIC rotation pass (sin/cos fused) + 4 linear mul passes.
    /// CORDIC rotation mode handles the full 2D rotation natively.
    pub fn rotate_x(&mut self, point: Point3, angle_rad: f64) -> Point3 {
        let (ny, nz) = self.rotate_2d(point.y, point.z, angle_rad);
        Point3 { x: point.x, y: ny, z: nz }
    }

    pub fn rotate_y(&mut self, point: Point3, angle_rad: f64) -> Point3 {
        // Y rotation: (x*cos + z*sin, y, -x*sin + z*cos)
        // = rotate(-angle) in XZ plane
        let (nz, nx) = self.rotate_2d(point.z, point.x, angle_rad);
        Point3 { x: nx, y: point.y, z: nz }
    }

    pub fn rotate_z(&mut self, point: Point3, angle_rad: f64) -> Point3 {
        let (nx, ny) = self.rotate_2d(point.x, point.y, angle_rad);
        Point3 { x: nx, y: ny, z: point.z }
    }

    /// Scale: evaluate child at (p / factor), multiply result by min(factor).
    pub fn scale(&mut self, point: Point3, factor: [f64; 3]) -> (Point3, f64) {
        let fx = self.graph.push(TrigOp::Const(1.0 / factor[0]));
        let fy = self.graph.push(TrigOp::Const(1.0 / factor[1]));
        let fz = self.graph.push(TrigOp::Const(1.0 / factor[2]));
        let sp = Point3 {
            x: self.graph.push(TrigOp::Mul(point.x, fx)),
            y: self.graph.push(TrigOp::Mul(point.y, fy)),
            z: self.graph.push(TrigOp::Mul(point.z, fz)),
        };
        let min_scale = factor[0].abs().min(factor[1].abs()).min(factor[2].abs());
        (sp, min_scale)
    }

    pub fn scale_distance(&mut self, dist: NodeId, min_scale: f64) -> NodeId {
        let s = self.graph.push(TrigOp::Const(min_scale));
        self.graph.push(TrigOp::Mul(dist, s))
    }

    // === Boolean operations ===

    pub fn union(&mut self, a: NodeId, b: NodeId) -> NodeId {
        self.graph.push(TrigOp::Min(a, b))
    }

    pub fn intersection(&mut self, a: NodeId, b: NodeId) -> NodeId {
        self.graph.push(TrigOp::Max(a, b))
    }

    pub fn difference(&mut self, a: NodeId, b: NodeId) -> NodeId {
        let neg_b = self.graph.push(TrigOp::Neg(b));
        self.graph.push(TrigOp::Max(a, neg_b))
    }

    pub fn smooth_union(&mut self, a: NodeId, b: NodeId, k: f64) -> NodeId {
        // Polynomial smooth min:
        // h = clamp(0.5 + 0.5*(b-a)/k, 0, 1)
        // result = mix(b, a, h) - k*h*(1-h)
        let k_const = self.graph.push(TrigOp::Const(k));
        let half = self.graph.push(TrigOp::Const(0.5));
        let one = self.graph.push(TrigOp::Const(1.0));
        let zero = self.zero;

        let diff = self.graph.push(TrigOp::Sub(b, a));
        let inv_k = self.graph.push(TrigOp::Const(1.0 / k));
        let div = self.graph.push(TrigOp::Mul(diff, inv_k));
        let scaled = self.graph.push(TrigOp::Mul(div, half));
        let shifted = self.graph.push(TrigOp::Add(half, scaled));
        let h = self.graph.push(TrigOp::Clamp { val: shifted, lo: zero, hi: one });

        // mix(b, a, h) = b + h*(a-b) = b*(1-h) + a*h
        let one_minus_h = self.graph.push(TrigOp::Sub(one, h));
        let term_b = self.graph.push(TrigOp::Mul(b, one_minus_h));
        let term_a = self.graph.push(TrigOp::Mul(a, h));
        let mixed = self.graph.push(TrigOp::Add(term_b, term_a));

        // k*h*(1-h)
        let kh = self.graph.push(TrigOp::Mul(k_const, h));
        let correction = self.graph.push(TrigOp::Mul(kh, one_minus_h));

        self.graph.push(TrigOp::Sub(mixed, correction))
    }

    // === Internal helpers ===

    /// 3D vector magnitude: sqrt(x² + y² + z²)
    /// = hypot(hypot(x, y), z) — two CORDIC vectoring passes.
    fn length3(&mut self, p: Point3) -> NodeId {
        let xy = self.graph.push(TrigOp::Hypot(p.x, p.y));
        self.graph.push(TrigOp::Hypot(xy, p.z))
    }

    fn length3_nodes(&mut self, x: NodeId, y: NodeId, z: NodeId) -> NodeId {
        let xy = self.graph.push(TrigOp::Hypot(x, y));
        self.graph.push(TrigOp::Hypot(xy, z))
    }

    /// 2D rotation via decomposed trig.
    /// (x', y') = (x*cos(θ) + y*sin(θ), -x*sin(θ) + y*cos(θ))
    ///
    /// Single CORDIC rotation pass — the compiler recognizes the
    /// sin/cos pair sharing the same angle input and fuses them.
    fn rotate_2d(&mut self, a: NodeId, b: NodeId, angle_rad: f64) -> (NodeId, NodeId) {
        let theta = self.graph.push(TrigOp::Const(angle_rad));
        let s = self.graph.push(TrigOp::Sin(theta));
        let c = self.graph.push(TrigOp::Cos(theta));

        // a' = a*c + b*s
        let ac = self.graph.push(TrigOp::Mul(a, c));
        let bs = self.graph.push(TrigOp::Mul(b, s));
        let a_new = self.graph.push(TrigOp::Add(ac, bs));

        // b' = -a*s + b*c
        let as_ = self.graph.push(TrigOp::Mul(a, s));
        let bc = self.graph.push(TrigOp::Mul(b, c));
        let b_new = self.graph.push(TrigOp::Sub(bc, as_));

        (a_new, b_new)
    }

    /// Get the root evaluation point.
    pub fn root_point(&self) -> Point3 {
        self.root_point
    }
}

/// Public interface for the Point3 type used in lowering.
impl Point3 {
    pub fn new(x: NodeId, y: NodeId, z: NodeId) -> Self {
        Self { x, y, z }
    }
}

// Re-export Point3 for use by other crates
pub type TrigPoint3 = Point3;
