use cordial::prelude::*;

fn assert_finite(val: f64, label: &str) {
    assert!(!val.is_nan(), "{label}: got NaN");
    assert!(!val.is_infinite(), "{label}: got infinity");
}

#[test]
fn sphere_through_pipeline() {
    let s = sphere(2.0);

    let val_surface = s.eval(2.0, 0.0, 0.0);
    let val_inside = s.eval(0.0, 0.0, 0.0);
    let val_outside = s.eval(5.0, 0.0, 0.0);

    assert_finite(val_surface, "surface");
    assert_finite(val_inside, "inside");
    assert_finite(val_outside, "outside");

    assert!(val_surface.abs() < 1e-6, "surface: expected ~0, got {val_surface}");
    assert!(val_inside < 0.0, "inside: expected negative, got {val_inside}");
    assert!(val_outside > 0.0, "outside: expected positive, got {val_outside}");

    let graph = s.to_trig();
    assert!(graph.nodes.len() > 0);

    let wgsl = s.to_wgsl();
    assert!(wgsl.contains("fn scene_sdf"));

    let cordic = s.to_cordic();
    assert!(!cordic.instructions.is_empty());
}

#[test]
fn complex_csg_through_pipeline() {
    let body = 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 test_points: &[(f64, f64, f64)] = &[
        (0.0, 0.0, 0.0),
        (1.0, 2.0, 3.0),
        (5.0, 5.0, 5.0),
        (-3.0, -3.0, -3.0),
    ];

    for &(x, y, z) in test_points {
        let val = body.eval(x, y, z);
        assert_finite(val, &format!("csg at ({x},{y},{z})"));
    }

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

    let wgsl = body.to_wgsl();
    assert!(wgsl.contains("fn scene_sdf"));
    assert!(wgsl.contains("fn fs_main"));

    let cordic = body.to_cordic();
    assert!(!cordic.instructions.is_empty());
}

#[test]
fn operator_overloads() {
    let a = sphere(2.0);
    let b = cube(1.5).translate(1.0, 0.0, 0.0);

    let union_shape = a.clone() | b.clone();
    let inter_shape = a.clone() & b.clone();
    let diff_shape = a.clone() - b.clone();

    for (label, shape) in [("union", union_shape), ("inter", inter_shape), ("diff", diff_shape)] {
        let val = shape.eval(0.0, 0.0, 0.0);
        assert_finite(val, label);
        let _ = shape.to_wgsl();
        let _ = shape.to_cordic();
    }
}

#[test]
fn smooth_union_pipeline() {
    let a = sphere(2.0);
    let b = sphere(2.0).translate(3.0, 0.0, 0.0);
    let blended = a.smooth_union(b, 1.0);

    let midpoint = blended.eval(1.5, 0.0, 0.0);
    assert_finite(midpoint, "smooth_union midpoint");
    assert!(midpoint < 0.0, "smooth_union should blend; midpoint={midpoint}");

    let _ = blended.to_wgsl();
    let _ = blended.to_cordic();
}

#[test]
fn variadic_booleans() {
    let shapes = vec![
        sphere(1.0),
        sphere(1.0).translate(3.0, 0.0, 0.0),
        sphere(1.0).translate(0.0, 3.0, 0.0),
    ];

    let union_all = Shape::union_all(shapes.clone());
    assert!(union_all.eval(0.0, 0.0, 0.0) < 0.0);
    assert!(union_all.eval(3.0, 0.0, 0.0) < 0.0);
    assert!(union_all.eval(0.0, 3.0, 0.0) < 0.0);

    let inter_all = Shape::intersection_all(shapes.clone());
    assert!(inter_all.eval(1.5, 1.5, 0.0) > 0.0);

    let base = sphere(5.0);
    let diff_all = base.difference_all(shapes);
    let val = diff_all.eval(0.0, 0.0, 0.0);
    assert_finite(val, "diff_all origin");
    assert!(val > 0.0, "origin should be carved out");
}

#[test]
fn pattern_linear_array() {
    let s = sphere(0.5);
    let arr = pattern::linear_array(&s, 5, [2.0, 0.0, 0.0]);

    assert!(arr.eval(0.0, 0.0, 0.0) < 0.0);
    assert!(arr.eval(8.0, 0.0, 0.0) < 0.0);
    assert!(arr.eval(1.0, 0.0, 0.0) > 0.0);

    let _ = arr.to_wgsl();
    let _ = arr.to_cordic();
}

#[test]
fn transforms_chain() {
    let s = sphere(1.0)
        .rotate_x(45.0)
        .rotate_y(30.0)
        .rotate_z(15.0)
        .scale_uniform(2.0)
        .translate(5.0, 5.0, 5.0);

    let val = s.eval(5.0, 5.0, 5.0);
    assert_finite(val, "center of transformed sphere");
    assert!(val < 0.0, "center should be inside; got {val}");

    let _ = s.to_wgsl();
    let _ = s.to_cordic();
}
