//! Rust DSL for constructive solid geometry via trig decomposition.
//!
//! Build 3D geometry with Rust syntax: primitives, transforms, boolean ops,
//! and parallel composition patterns. Everything compiles down to a
//! [`cord_trig::TrigGraph`] which can then target WGSL shaders or CORDIC hardware.
//!
//! ```rust
//! use cordial::prelude::*;
//!
//! let part = sphere(2.0)
//!     .difference(cube(1.5))
//!     .union(cylinder(0.5, 6.0).rotate_x(90.0))
//!     .translate(1.0, 2.0, 3.0);
//!
//! let wgsl = part.to_wgsl();
//! let cordic = part.to_cordic();
//! ```

mod shape;
mod primitives;
pub mod pattern;
pub mod par;

pub use shape::Shape;
pub use primitives::*;

/// Import everything needed to write Cordial geometry.
pub mod prelude {
    pub use crate::shape::Shape;
    pub use crate::primitives::*;
    pub use crate::pattern;
    pub use crate::par;
}

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

    #[test]
    fn sphere_at_origin() {
        let s = sphere(2.0);
        let val = s.eval(2.0, 0.0, 0.0);
        assert!(val.abs() < 1e-6, "surface should be zero, got {val}");
        assert!(s.eval(0.0, 0.0, 0.0) < 0.0, "interior should be negative");
        assert!(s.eval(5.0, 0.0, 0.0) > 0.0, "exterior should be positive");
    }

    #[test]
    fn translated_sphere() {
        let s = sphere(1.0).translate(3.0, 0.0, 0.0);
        let val = s.eval(4.0, 0.0, 0.0);
        assert!(val.abs() < 1e-6, "surface at (4,0,0), got {val}");
    }

    #[test]
    fn union_operator() {
        let a = sphere(1.0);
        let b = sphere(1.0).translate(3.0, 0.0, 0.0);
        let u = a | b;
        // Inside first sphere
        assert!(u.eval(0.0, 0.0, 0.0) < 0.0);
        // Inside second sphere
        assert!(u.eval(3.0, 0.0, 0.0) < 0.0);
        // Outside both
        assert!(u.eval(1.5, 5.0, 0.0) > 0.0);
    }

    #[test]
    fn difference_operator() {
        let a = sphere(2.0);
        let b = sphere(1.0);
        let d = a - b;
        // Inside the shell
        assert!(d.eval(1.5, 0.0, 0.0) < 0.0);
        // Inside the hole
        assert!(d.eval(0.0, 0.0, 0.0) > 0.0);
    }

    #[test]
    fn intersection_operator() {
        let a = sphere(2.0);
        let b = sphere(2.0).translate(1.0, 0.0, 0.0);
        let i = a & b;
        // In the overlap
        assert!(i.eval(0.5, 0.0, 0.0) < 0.0);
        // Outside overlap but inside a
        assert!(i.eval(-1.5, 0.0, 0.0) > 0.0);
    }

    #[test]
    fn cube_basic() {
        let c = cube(1.0);
        assert!(c.eval(0.0, 0.0, 0.0) < 0.0);
        assert!(c.eval(2.0, 0.0, 0.0) > 0.0);
    }

    #[test]
    fn cylinder_basic() {
        let c = cylinder(1.0, 4.0);
        assert!(c.eval(0.0, 0.0, 0.0) < 0.0);
        assert!(c.eval(2.0, 0.0, 0.0) > 0.0);
    }

    #[test]
    fn to_wgsl_contains_scene_sdf() {
        let s = sphere(1.0);
        let wgsl = s.to_wgsl();
        assert!(wgsl.contains("fn scene_sdf"));
        assert!(wgsl.contains("fn fs_main"));
    }

    #[test]
    fn to_cordic_produces_instructions() {
        let s = sphere(1.0);
        let prog = s.to_cordic();
        assert!(!prog.instructions.is_empty());
    }

    #[test]
    fn linear_array_three() {
        let s = sphere(0.5);
        let arr = pattern::linear_array(&s, 3, [2.0, 0.0, 0.0]);
        // Inside first
        assert!(arr.eval(0.0, 0.0, 0.0) < 0.0);
        // Inside third
        assert!(arr.eval(4.0, 0.0, 0.0) < 0.0);
        // Between
        assert!(arr.eval(1.0, 0.0, 0.0) > 0.0);
    }

    #[test]
    fn method_chaining() {
        // Complex part via chaining
        let part = cube(2.0)
            .difference(sphere(2.5))
            .union(cylinder(0.5, 6.0).rotate_x(90.0))
            .translate(1.0, 2.0, 3.0);

        let graph = part.to_trig();
        assert!(graph.nodes.len() > 10);
    }
}
