use cord_sdf::SdfNode;

/// A composable solid shape.
///
/// Wraps an SdfNode tree with a builder API for constructing
/// geometry through method chaining. Every method consumes self
/// and returns a new Shape — ownership tracks geometric scope.
#[derive(Debug, Clone)]
pub struct Shape {
    node: SdfNode,
}

impl Shape {
    pub(crate) fn new(node: SdfNode) -> Self {
        Shape { node }
    }

    pub fn into_sdf(self) -> SdfNode {
        self.node
    }

    pub fn sdf(&self) -> &SdfNode {
        &self.node
    }

    // === Transforms ===

    pub fn translate(self, x: f64, y: f64, z: f64) -> Shape {
        Shape::new(SdfNode::Translate {
            offset: [x, y, z],
            child: Box::new(self.node),
        })
    }

    pub fn rotate_x(self, deg: f64) -> Shape {
        Shape::new(SdfNode::Rotate {
            axis: [1.0, 0.0, 0.0],
            angle_deg: deg,
            child: Box::new(self.node),
        })
    }

    pub fn rotate_y(self, deg: f64) -> Shape {
        Shape::new(SdfNode::Rotate {
            axis: [0.0, 1.0, 0.0],
            angle_deg: deg,
            child: Box::new(self.node),
        })
    }

    pub fn rotate_z(self, deg: f64) -> Shape {
        Shape::new(SdfNode::Rotate {
            axis: [0.0, 0.0, 1.0],
            angle_deg: deg,
            child: Box::new(self.node),
        })
    }

    pub fn scale(self, x: f64, y: f64, z: f64) -> Shape {
        Shape::new(SdfNode::Scale {
            factor: [x, y, z],
            child: Box::new(self.node),
        })
    }

    pub fn scale_uniform(self, s: f64) -> Shape {
        self.scale(s, s, s)
    }

    // === Boolean operations ===

    pub fn union(self, other: Shape) -> Shape {
        Shape::new(SdfNode::Union(vec![self.node, other.node]))
    }

    pub fn intersection(self, other: Shape) -> Shape {
        Shape::new(SdfNode::Intersection(vec![self.node, other.node]))
    }

    pub fn difference(self, other: Shape) -> Shape {
        Shape::new(SdfNode::Difference {
            base: Box::new(self.node),
            subtract: vec![other.node],
        })
    }

    pub fn smooth_union(self, other: Shape, k: f64) -> Shape {
        Shape::new(SdfNode::SmoothUnion {
            children: vec![self.node, other.node],
            k,
        })
    }

    // === Variadic booleans ===

    pub fn union_all(shapes: impl IntoIterator<Item = Shape>) -> Shape {
        let nodes: Vec<SdfNode> = shapes.into_iter().map(|s| s.node).collect();
        assert!(!nodes.is_empty(), "union_all requires at least one shape");
        if nodes.len() == 1 {
            return Shape::new(nodes.into_iter().next().unwrap());
        }
        Shape::new(SdfNode::Union(nodes))
    }

    pub fn intersection_all(shapes: impl IntoIterator<Item = Shape>) -> Shape {
        let nodes: Vec<SdfNode> = shapes.into_iter().map(|s| s.node).collect();
        assert!(!nodes.is_empty(), "intersection_all requires at least one shape");
        if nodes.len() == 1 {
            return Shape::new(nodes.into_iter().next().unwrap());
        }
        Shape::new(SdfNode::Intersection(nodes))
    }

    pub fn difference_all(self, others: impl IntoIterator<Item = Shape>) -> Shape {
        let subtract: Vec<SdfNode> = others.into_iter().map(|s| s.node).collect();
        if subtract.is_empty() {
            return self;
        }
        Shape::new(SdfNode::Difference {
            base: Box::new(self.node),
            subtract,
        })
    }

    // === Output conversions ===

    pub fn to_trig(&self) -> cord_trig::TrigGraph {
        cord_sdf::sdf_to_trig(&self.node)
    }

    pub fn to_wgsl(&self) -> String {
        let graph = self.to_trig();
        cord_shader::generate_wgsl_from_trig(&graph)
    }

    pub fn to_cordic(&self) -> cord_cordic::CORDICProgram {
        let graph = self.to_trig();
        cord_cordic::CORDICProgram::compile(&graph, &Default::default())
    }

    pub fn eval(&self, x: f64, y: f64, z: f64) -> f64 {
        let graph = self.to_trig();
        cord_trig::eval::evaluate(&graph, x, y, z)
    }
}

// Operator overloads for ergonomic composition

impl std::ops::BitOr for Shape {
    type Output = Shape;
    /// `a | b` = union
    fn bitor(self, rhs: Shape) -> Shape {
        self.union(rhs)
    }
}

impl std::ops::BitAnd for Shape {
    type Output = Shape;
    /// `a & b` = intersection
    fn bitand(self, rhs: Shape) -> Shape {
        self.intersection(rhs)
    }
}

impl std::ops::Sub for Shape {
    type Output = Shape;
    /// `a - b` = difference
    fn sub(self, rhs: Shape) -> Shape {
        self.difference(rhs)
    }
}
