use bone_kernel::{
    BrepSolid, Curve2Kind, ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeProfile,
    ExtrudeSense, Line2, MergeResult, ProfileEdge, ProfileLoop, evaluate_extrude,
};
use bone_render::{SolidRenderer, SolidScene, Style, frame_isometric};
use bone_types::{
    AngleTolerance, ChordHeightTolerance, FeatureId, Length, Plane3, Point2, Point3,
    PositiveLength, SketchEntityId, SketchId, Tolerance, UnitVec3, millimeter,
};
use slotmap::{Key, SlotMap};

mod common;

use common::{check_golden, extent_square as extent, make_context};

const GOLDEN: &str = "tests/goldens/cube_iso_256.png";
const UPDATE_ENV: &str = "BONE_UPDATE_CUBE_ISO_GOLDEN";
const TOLERANCE: Tolerance = Tolerance::new(1.0e-9);

fn unit_cube() -> BrepSolid {
    let mut entities: SlotMap<SketchEntityId, ()> = SlotMap::with_key();
    let mut features: SlotMap<FeatureId, ()> = SlotMap::with_key();
    let Ok(plane) = Plane3::new(
        Point3::origin(),
        UnitVec3::x_axis(),
        UnitVec3::y_axis(),
        TOLERANCE,
    ) else {
        panic!("x and y axes are orthonormal");
    };
    let corners = [
        Point2::from_mm(0.0, 0.0),
        Point2::from_mm(1.0, 0.0),
        Point2::from_mm(1.0, 1.0),
        Point2::from_mm(0.0, 1.0),
    ];
    let edges = (0..4)
        .map(|index| {
            let start = corners[index];
            let end = corners[(index + 1) % 4];
            let Ok(segment) = Line2::new(start, end, TOLERANCE) else {
                panic!("rectangle endpoints are distinct");
            };
            ProfileEdge::new(
                Curve2Kind::Line(segment),
                entities.insert(()),
                entities.insert(()),
            )
        })
        .collect();
    let profile = ExtrudeProfile::new(plane, vec![ProfileLoop::Open(edges)]);
    let Ok(depth) = PositiveLength::new(Length::new::<millimeter>(1.0)) else {
        panic!("1 mm is a positive length");
    };
    let feature = ExtrudeFeature {
        sketch: SketchId::null(),
        direction: ExtrudeDirection::Normal {
            sense: ExtrudeSense::Forward,
        },
        end_condition: ExtrudeEndCondition::Blind { depth },
        draft: None,
        thin_wall: None,
        merge_result: MergeResult::Merge,
    };
    let Ok(solid) = evaluate_extrude(features.insert(()), &profile, &feature) else {
        panic!("the unit square extrudes into a cube");
    };
    solid
}

#[test]
fn cube_iso_matches_golden() {
    let size = extent(256);
    let ctx = make_context(size);
    let mut renderer = SolidRenderer::new(ctx.gpu(), ctx.color_format());
    let solid = unit_cube();
    let Ok(mesh) = solid.tessellate(
        ChordHeightTolerance::from_mm(0.05),
        AngleTolerance::from_radians(0.2),
    ) else {
        panic!("the cube tessellates");
    };
    let Ok(scene) = SolidScene::from_mesh(&mesh) else {
        panic!("the cube mesh packs face pick ids");
    };
    let Some(aabb) = solid.bounding_box() else {
        panic!("the cube has a bounding box");
    };
    let Ok(camera) = frame_isometric(aabb, size) else {
        panic!("the cube frames isometrically");
    };
    let style = Style::default();

    let Ok(frame) = renderer.render(&ctx, &scene, camera, &style) else {
        panic!("SolidRenderer::render failed");
    };

    check_golden(&frame, GOLDEN, UPDATE_ENV);
}
