use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::{Duration, Instant};

use bone_document::{Document, EditOutcome, Sketch, SketchEdit, SketchEntity};
use bone_interop::{Cancel, CancelFlag, HeaderDefect, StepError, body_of, read, write};
use bone_kernel::{
    BrepFace, BrepSolid, ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeSense,
    MergeResult,
};
use bone_types::{
    Aabb3, DocumentId, ExtrudeId, FaceLabel, FaceRole, Length, Point2, Point3, PositiveLength,
    SketchEntityId, SketchId, SketchPlaneBasis, StepEntityKind, StepSchema, Tolerance, UnitVec3,
    millimeter,
};
use slotmap::KeyData;

const TOL: Tolerance = Tolerance::new(1.0e-9);
const UPDATE_ENV: &str = "BONE_UPDATE_STEP_GOLDENS";

fn xy_basis() -> SketchPlaneBasis {
    let Ok(basis) = SketchPlaneBasis::new(
        Point3::origin(),
        UnitVec3::x_axis(),
        UnitVec3::y_axis(),
        TOL,
    ) else {
        panic!("orthonormal axes");
    };
    basis
}

fn ffi_key(n: u64) -> KeyData {
    KeyData::from_ffi((1u64 << 32) | n)
}

fn sketch_id(n: u64) -> SketchId {
    SketchId::from(ffi_key(n))
}

fn extrude_id(n: u64) -> ExtrudeId {
    ExtrudeId::from(ffi_key(n))
}

fn add_point(sketch: Sketch, x: f64, y: f64) -> (Sketch, SketchEntityId) {
    let Ok((next, EditOutcome::Entity(id))) = sketch.apply(SketchEdit::AddEntity(
        SketchEntity::point(Point2::from_mm(x, y)),
    )) else {
        panic!("add point");
    };
    (next, id)
}

fn add_line(sketch: Sketch, a: SketchEntityId, b: SketchEntityId) -> Sketch {
    let Ok((next, _)) = sketch.apply(SketchEdit::AddEntity(SketchEntity::line(a, b, false))) else {
        panic!("add line");
    };
    next
}

fn add_circle(sketch: Sketch, center: SketchEntityId, radius_mm: f64) -> Sketch {
    let Ok((next, _)) = sketch.apply(SketchEdit::AddEntity(SketchEntity::circle(
        center,
        Length::new::<millimeter>(radius_mm),
        false,
    ))) else {
        panic!("add circle");
    };
    next
}

fn rectangle_sketch() -> Sketch {
    let (sketch, p0) = add_point(Sketch::new(xy_basis()), 0.0, 0.0);
    let (sketch, p1) = add_point(sketch, 4.0, 0.0);
    let (sketch, p2) = add_point(sketch, 4.0, 2.0);
    let (sketch, p3) = add_point(sketch, 0.0, 2.0);
    let sketch = add_line(sketch, p0, p1);
    let sketch = add_line(sketch, p1, p2);
    let sketch = add_line(sketch, p2, p3);
    add_line(sketch, p3, p0)
}

fn circle_sketch(radius_mm: f64) -> Sketch {
    let (sketch, center) = add_point(Sketch::new(xy_basis()), 0.0, 0.0);
    add_circle(sketch, center, radius_mm)
}

fn donut_sketch() -> Sketch {
    let (sketch, center) = add_point(Sketch::new(xy_basis()), 0.0, 0.0);
    let sketch = add_circle(sketch, center, 10.0);
    add_circle(sketch, center, 4.0)
}

fn blind(sketch: SketchId, depth_mm: f64) -> ExtrudeFeature {
    let Ok(depth) = PositiveLength::new(Length::new::<millimeter>(depth_mm)) else {
        panic!("positive depth");
    };
    ExtrudeFeature {
        sketch,
        direction: ExtrudeDirection::Normal {
            sense: ExtrudeSense::Forward,
        },
        end_condition: ExtrudeEndCondition::Blind { depth },
        draft: None,
        thin_wall: None,
        merge_result: MergeResult::Merge,
    }
}

fn document(name: &str, sketch: Sketch, depth_mm: f64) -> Document {
    let mut document = Document::new(DocumentId::default(), name.to_owned());
    let sketch_key = sketch_id(1);
    document.insert_sketch(sketch_key, "Sketch1".to_owned(), sketch);
    document.insert_extrude(extrude_id(1), blind(sketch_key, depth_mm));
    document
}

fn face_labels(solid: &BrepSolid) -> Vec<FaceLabel> {
    solid.iter_faces().map(BrepFace::label).collect()
}

fn is_dumb(solid: &BrepSolid) -> bool {
    solid
        .iter_faces()
        .all(|face| matches!(face.label().role, FaceRole::Imported { .. }))
}

fn write_to_temp(document: &Document, dir: &Path, name: &str) -> PathBuf {
    let path = dir.join(name);
    let Ok(()) = write(document, &path, StepSchema::Ap214, CancelFlag::never()) else {
        panic!("write step");
    };
    path
}

fn check_step_golden(text: &str, golden_rel: &str) {
    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(golden_rel);
    if std::env::var(UPDATE_ENV).is_ok() {
        if let Some(parent) = path.parent() {
            let Ok(()) = std::fs::create_dir_all(parent) else {
                panic!("create goldens dir");
            };
        }
        let Ok(()) = std::fs::write(&path, text.as_bytes()) else {
            panic!("write golden {}", path.display());
        };
        return;
    }
    let Ok(golden) = std::fs::read_to_string(&path) else {
        panic!(
            "golden missing at {}: rerun with {UPDATE_ENV}=1 to generate",
            path.display()
        );
    };
    assert_eq!(text, golden, "step output drifted from golden {golden_rel}");
}

fn matches_golden(name: &str, sketch: Sketch, depth_mm: f64) {
    let document = document(name, sketch, depth_mm);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), &format!("{name}.step"));
    let Ok(text) = std::fs::read_to_string(&path) else {
        panic!("read back");
    };
    check_step_golden(&text, &format!("tests/goldens/{name}.step"));
}

#[test]
fn cube_step_matches_golden() {
    matches_golden("cube", rectangle_sketch(), 4.0);
}

fn evaluates_within_bounds(seed: &Document, min_mm: [f64; 3], max_mm: [f64; 3], label: &str) {
    let Ok(solid) = body_of(seed) else {
        panic!("{label} evaluates to one body");
    };
    assert_bounds(&solid_bounds(&solid), min_mm, max_mm, label);
}

#[test]
fn cylinder_evaluates_within_bounds() {
    evaluates_within_bounds(
        &document("cylinder", circle_sketch(5.0), 8.0),
        [-5.0, -5.0, 0.0],
        [5.0, 5.0, 8.0],
        "cylinder",
    );
}

#[test]
fn donut_evaluates_within_bounds() {
    evaluates_within_bounds(
        &document("donut", donut_sketch(), 6.0),
        [-10.0, -10.0, 0.0],
        [10.0, 10.0, 6.0],
        "donut",
    );
}

fn import_round_trips_labels(seed: &Document) {
    let Ok(expected) = body_of(seed) else {
        panic!("document evaluates to one body");
    };
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(seed, dir.path(), "part.step");
    let Ok(imported) = read(&path, CancelFlag::never()) else {
        panic!("read step");
    };
    let Ok(solid) = body_of(&imported) else {
        panic!("imported document carries one body");
    };
    assert_eq!(
        face_labels(&expected),
        face_labels(&solid),
        "matching sidecar restores labels"
    );
    assert!(!is_dumb(&solid), "a matched sidecar yields a labeled body");
    assert!(solid.validate(TOL).is_ok());
}

#[test]
fn cube_import_round_trips_labels() {
    import_round_trips_labels(&document("cube", rectangle_sketch(), 4.0));
}

#[test]
fn cylinder_import_round_trips_labels() {
    import_round_trips_labels(&document("cylinder", circle_sketch(5.0), 8.0));
}

#[test]
fn donut_import_round_trips_labels() {
    import_round_trips_labels(&document("donut", donut_sketch(), 6.0));
}

fn imported_document(seed: &Document) -> Document {
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(seed, dir.path(), "part.step");
    let Ok(imported) = read(&path, CancelFlag::never()) else {
        panic!("read step");
    };
    imported
}

fn document_round_trips(seed: &Document) {
    let imported = imported_document(seed);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&imported, dir.path(), "part.step");
    let Ok(round) = read(&path, CancelFlag::never()) else {
        panic!("re-read step");
    };
    assert_eq!(
        imported, round,
        "read(write(d)) preserves an imported-body document"
    );
}

#[test]
fn cube_document_round_trips() {
    document_round_trips(&document("cube", rectangle_sketch(), 4.0));
}

#[test]
fn cylinder_document_round_trips() {
    document_round_trips(&document("cylinder", circle_sketch(5.0), 8.0));
}

#[test]
fn donut_document_round_trips() {
    document_round_trips(&document("donut", donut_sketch(), 6.0));
}

#[test]
fn import_enters_one_importable_body() {
    let imported = imported_document(&document("cube", rectangle_sketch(), 4.0));
    assert_eq!(imported.imported_bodies().count(), 1);
    assert!(body_of(&imported).is_ok());
}

#[test]
fn header_carries_document_name() {
    let document = document("bracket", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "export.step");
    let Ok(text) = std::fs::read_to_string(&path) else {
        panic!("read back");
    };
    assert!(
        text.contains("FILE_NAME('bracket'"),
        "header file_name carries the document name, not the step path"
    );
}

#[test]
fn imported_document_takes_its_name_from_the_file_stem() {
    let document = document("bracket", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "widget.step");
    let Ok(imported) = read(&path, CancelFlag::never()) else {
        panic!("read step");
    };
    assert_eq!(imported.name(), "widget");
}

#[test]
fn apostrophe_in_document_name_round_trips() {
    let document = document("nel's bracket", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "part.step");
    let Ok(text) = std::fs::read_to_string(&path) else {
        panic!("read back");
    };
    assert!(
        text.contains("FILE_NAME('nel''s bracket'"),
        "an apostrophe is doubled per ISO 10303-21"
    );
    let Ok(imported) = read(&path, CancelFlag::never()) else {
        panic!("read step");
    };
    let Ok(solid) = body_of(&imported) else {
        panic!("one body");
    };
    assert!(!is_dumb(&solid));
}

#[test]
fn step_export_is_byte_deterministic() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    let Ok(()) = write(&document, &path, StepSchema::Ap214, CancelFlag::never()) else {
        panic!("write first");
    };
    let Ok(a) = std::fs::read(&path) else {
        panic!("read first");
    };
    let Ok(()) = write(&document, &path, StepSchema::Ap214, CancelFlag::never()) else {
        panic!("write second");
    };
    let Ok(b) = std::fs::read(&path) else {
        panic!("read second");
    };
    assert_eq!(a, b, "same document and path write byte-identical step");
}

#[test]
fn missing_sidecar_imports_dumb_body() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(expected) = body_of(&document) else {
        panic!("one body");
    };
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "part.step");
    let mut labels = path.clone().into_os_string();
    labels.push(".labels");
    let Ok(()) = std::fs::remove_file(PathBuf::from(labels)) else {
        panic!("remove sidecar");
    };
    let Ok(imported) = read(&path, CancelFlag::never()) else {
        panic!("read step");
    };
    let Ok(solid) = body_of(&imported) else {
        panic!("one body");
    };
    assert!(is_dumb(&solid), "no sidecar yields a dumb body");
    assert_eq!(
        solid.iter_faces().count(),
        expected.iter_faces().count(),
        "geometry survives even without labels"
    );
}

#[test]
fn ap242_header_reports_schema_mismatch() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "cube.step");
    let Ok(text) = std::fs::read_to_string(&path) else {
        panic!("read back");
    };
    let swapped = text.replace(
        "AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }",
        "AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF { 1 0 10303 442 3 1 4 }",
    );
    let foreign = dir.path().join("cube242.step");
    let Ok(()) = std::fs::write(&foreign, swapped.as_bytes()) else {
        panic!("write swapped");
    };
    assert!(matches!(
        read(&foreign, CancelFlag::never()),
        Err(StepError::SchemaMismatch {
            found: StepSchema::Ap242E2,
            ..
        })
    ));
}

#[test]
fn ap203_header_imports_best_effort() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "cube.step");
    let Ok(text) = std::fs::read_to_string(&path) else {
        panic!("read back");
    };
    let swapped = text.replace(
        "AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }",
        "CONFIG_CONTROL_DESIGN { 1 0 10303 203 1 1 1 1 }",
    );
    let foreign = dir.path().join("cube203.step");
    let Ok(()) = std::fs::write(&foreign, swapped.as_bytes()) else {
        panic!("write swapped");
    };
    assert!(
        read(&foreign, CancelFlag::never()).is_ok(),
        "an unmodeled schema token still attempts a best-effort import"
    );
}

#[test]
fn text_without_header_reports_malformed_header() {
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("bad.step");
    let Ok(()) = std::fs::write(&path, b"this is not a step file") else {
        panic!("write");
    };
    assert!(matches!(
        read(&path, CancelFlag::never()),
        Err(StepError::MalformedHeader {
            reason: HeaderDefect::NoHeaderSection
        })
    ));
}

#[test]
fn header_without_file_schema_reports_malformed_header() {
    let text = "ISO-10303-21;\nHEADER;\nFILE_NAME('x','1970-01-01T00:00:00',(''),(''),'','','');\nENDSEC;\nDATA;\nENDSEC;\nEND-ISO-10303-21;\n";
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("noschema.step");
    let Ok(()) = std::fs::write(&path, text.as_bytes()) else {
        panic!("write");
    };
    assert!(matches!(
        read(&path, CancelFlag::never()),
        Err(StepError::MalformedHeader {
            reason: HeaderDefect::NoFileSchema
        })
    ));
}

#[test]
fn header_with_empty_data_section_is_incomplete() {
    let text = "ISO-10303-21;\nHEADER;\nFILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }'));\nENDSEC;\nDATA;\nENDSEC;\nEND-ISO-10303-21;\n";
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("empty.step");
    let Ok(()) = std::fs::write(&path, text.as_bytes()) else {
        panic!("write");
    };
    assert!(matches!(
        read(&path, CancelFlag::never()),
        Err(StepError::IncompleteFile)
    ));
}

#[test]
fn header_comment_with_an_apostrophe_does_not_break_the_scan() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "part.step");
    let Ok(text) = std::fs::read_to_string(&path) else {
        panic!("read back");
    };
    let commented = text.replace(
        "FILE_SCHEMA(",
        "/* nel's draft, hand-edited */\nFILE_SCHEMA(",
    );
    let Ok(()) = std::fs::write(&path, commented.as_bytes()) else {
        panic!("rewrite with a header comment");
    };
    let Ok(imported) = read(&path, CancelFlag::never()) else {
        panic!("a lone apostrophe inside a header comment must not desync the scan");
    };
    let Ok(solid) = body_of(&imported) else {
        panic!("one body");
    };
    assert!(
        !is_dumb(&solid),
        "a header comment does not block sidecar reattach"
    );
}

#[test]
fn header_comment_carrying_a_foreign_schema_statement_is_ignored() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "part.step");
    let Ok(text) = std::fs::read_to_string(&path) else {
        panic!("read back");
    };
    let commented = text.replace(
        "FILE_SCHEMA(",
        "/* legacy: FILE_SCHEMA(('AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF { 1 0 10303 442 3 1 4 }')); */\nFILE_SCHEMA(",
    );
    let Ok(()) = std::fs::write(&path, commented.as_bytes()) else {
        panic!("rewrite with a header comment");
    };
    let Ok(imported) = read(&path, CancelFlag::never()) else {
        panic!("a FILE_SCHEMA statement buried in a comment must not classify the file");
    };
    let Ok(solid) = body_of(&imported) else {
        panic!("one body");
    };
    assert!(
        !is_dumb(&solid),
        "the real AP214 schema still yields a labeled import"
    );
}

#[test]
fn ap242_export_is_unsupported() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    assert!(matches!(
        write(&document, &path, StepSchema::Ap242E2, CancelFlag::never()),
        Err(StepError::SchemaUnsupported(StepSchema::Ap242E2))
    ));
}

#[test]
fn empty_document_has_no_body() {
    let document = Document::new(DocumentId::default(), "empty".to_owned());
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    assert!(matches!(
        write(&document, &path, StepSchema::Ap214, CancelFlag::never()),
        Err(StepError::BodyCount { count: 0 })
    ));
}

#[test]
fn multiple_bodies_are_rejected() {
    let mut document = Document::new(DocumentId::default(), "two".to_owned());
    let first = sketch_id(1);
    let second = sketch_id(2);
    document.insert_sketch(first, "A".to_owned(), rectangle_sketch());
    document.insert_sketch(second, "B".to_owned(), circle_sketch(5.0));
    document.insert_extrude(extrude_id(1), blind(first, 4.0));
    document.insert_extrude(extrude_id(2), blind(second, 6.0));
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    assert!(matches!(
        write(&document, &path, StepSchema::Ap214, CancelFlag::never()),
        Err(StepError::BodyCount { count: 2 })
    ));
}

#[test]
fn lone_unresolved_extrude_reports_dangling() {
    let mut document = Document::new(DocumentId::default(), "pending".to_owned());
    document.insert_extrude(extrude_id(1), blind(sketch_id(1), 4.0));
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    assert!(matches!(
        write(&document, &path, StepSchema::Ap214, CancelFlag::never()),
        Err(StepError::DanglingExtrude { .. })
    ));
}

#[test]
fn unresolved_body_is_not_silently_dropped() {
    let mut document = Document::new(DocumentId::default(), "mixed".to_owned());
    document.insert_sketch(sketch_id(1), "A".to_owned(), rectangle_sketch());
    document.insert_extrude(extrude_id(1), blind(sketch_id(1), 4.0));
    document.insert_extrude(extrude_id(2), blind(sketch_id(2), 6.0));
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    assert!(matches!(
        write(&document, &path, StepSchema::Ap214, CancelFlag::never()),
        Err(StepError::BodyCount { count: 2 })
    ));
    assert!(!path.exists(), "a rejected export leaves no file behind");
}

#[test]
fn sidecar_failure_leaves_no_orphan_step() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    let mut blocker = path.clone().into_os_string();
    blocker.push(".labels");
    let Ok(()) = std::fs::create_dir(PathBuf::from(blocker)) else {
        panic!("block the sidecar path with a directory");
    };
    assert!(write(&document, &path, StepSchema::Ap214, CancelFlag::never()).is_err());
    assert!(
        !path.exists(),
        "no labelless step is written when the sidecar cannot be"
    );
}

fn solid_bounds(solid: &BrepSolid) -> Aabb3 {
    let Some(bbox) = solid.bounding_box() else {
        panic!("solid has a bounding box");
    };
    bbox
}

fn near_mm(value: Length, mm: f64) -> bool {
    (value.get::<millimeter>() - mm).abs() < 0.2
}

const INBOUND_BOUNDS: &[(&str, [f64; 3], [f64; 3])] =
    &[("wedge.step", [0.0, 0.0, 0.0], [5.0, 5.0, 8.0])];

fn inbound_step_files() -> Vec<PathBuf> {
    let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/goldens/inbound");
    let Ok(entries) = std::fs::read_dir(&dir) else {
        panic!("inbound goldens directory exists");
    };
    let mut files: Vec<PathBuf> = entries
        .filter_map(Result::ok)
        .map(|entry| entry.path())
        .filter(|path| {
            path.extension()
                .is_some_and(|ext| ext.eq_ignore_ascii_case("step"))
        })
        .collect();
    files.sort();
    files
}

fn assert_bounds(bbox: &Aabb3, min_mm: [f64; 3], max_mm: [f64; 3], name: &str) {
    let (min, max) = (bbox.min(), bbox.max());
    assert!(
        near_mm(min.x(), min_mm[0]) && near_mm(min.y(), min_mm[1]) && near_mm(min.z(), min_mm[2]),
        "inbound {name} lower bound {min:?} matches {min_mm:?} mm"
    );
    assert!(
        near_mm(max.x(), max_mm[0]) && near_mm(max.y(), max_mm[1]) && near_mm(max.z(), max_mm[2]),
        "inbound {name} upper bound {max:?} matches {max_mm:?} mm"
    );
}

fn is_non_degenerate(bbox: &Aabb3) -> bool {
    let (min, max) = (bbox.min(), bbox.max());
    let span = |hi: Length, lo: Length| (hi - lo).get::<millimeter>() > 0.1;
    span(max.x(), min.x()) && span(max.y(), min.y()) && span(max.z(), min.z())
}

#[test]
fn inbound_fixtures_import_and_match_bounds() {
    let files = inbound_step_files();
    assert!(
        !files.is_empty(),
        "at least one inbound cross-tool fixture is present"
    );
    files.iter().for_each(|path| {
        let label = path.display();
        let document = match read(path, CancelFlag::never()) {
            Ok(document) => document,
            Err(error) => panic!("inbound {label} imports: {error}"),
        };
        let Ok(solid) = body_of(&document) else {
            panic!("inbound {label} carries one body");
        };
        let bbox = solid_bounds(&solid);
        let name = path
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or_default();
        match INBOUND_BOUNDS.iter().find(|(file, ..)| *file == name) {
            Some((_, min, max)) => assert_bounds(&bbox, *min, *max, name),
            None => assert!(
                is_non_degenerate(&bbox),
                "inbound {name} imports and tessellates to a non-degenerate box; \
                 register its design extent in INBOUND_BOUNDS to pin the match"
            ),
        }
    });
}

fn wedge_text() -> String {
    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/goldens/inbound/wedge.step");
    let Ok(text) = std::fs::read_to_string(&path) else {
        panic!("read wedge golden at {}", path.display());
    };
    text
}

fn read_step_text(text: &str, name: &str) -> Result<Document, StepError> {
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join(name);
    let Ok(()) = std::fs::write(&path, text.as_bytes()) else {
        panic!("write probe step");
    };
    read(&path, CancelFlag::never())
}

#[test]
fn two_solid_roots_are_rejected_as_assembly() {
    let text = wedge_text().replace(
        "#1 = MANIFOLD_SOLID_BREP('wedge', #2);",
        "#1 = MANIFOLD_SOLID_BREP('wedge', #2);\n#950 = MANIFOLD_SOLID_BREP('clone', #2);",
    );
    assert!(
        matches!(
            read_step_text(&text, "assembly.step"),
            Err(StepError::UnsupportedAssembly { solids: 2 })
        ),
        "two manifold_solid_brep roots are an assembly, not one body"
    );
}

#[test]
fn a_stray_closed_shell_is_excluded_not_absorbed() {
    let Ok(clean) = read_step_text(&wedge_text(), "clean_wedge.step") else {
        panic!("the clean wedge imports");
    };
    let Ok(clean_solid) = body_of(&clean) else {
        panic!("clean wedge has one body");
    };
    let text = wedge_text().replace(
        "DATA;\n",
        "DATA;\n#960 = CLOSED_SHELL('stray', (#10, #20, #30, #40, #50));\n",
    );
    let Ok(document) = read_step_text(&text, "stray_closed.step") else {
        panic!("a stray shell with one solid root still imports");
    };
    let Ok(solid) = body_of(&document) else {
        panic!("one solid root yields one body");
    };
    assert_eq!(
        solid.iter_faces().count(),
        clean_solid.iter_faces().count(),
        "a shell unreachable from the solid root is dropped, not merged in"
    );
}

#[test]
fn bare_shells_without_a_solid_root_are_rejected() {
    let text = wedge_text()
        .replace("#1 = MANIFOLD_SOLID_BREP('wedge', #2);\n", "")
        .replace(
            "DATA;\n",
            "DATA;\n#960 = CLOSED_SHELL('stray', (#10, #20, #30, #40, #50));\n",
        );
    assert!(
        matches!(
            read_step_text(&text, "rootless.step"),
            Err(StepError::UnsupportedAssembly { solids: 2 })
        ),
        "two closed shells with no solid root cannot be one body"
    );
}

#[test]
fn a_stray_open_shell_is_ignored() {
    let text = wedge_text().replace("DATA;\n", "DATA;\n#970 = OPEN_SHELL('stray', (#10));\n");
    let Ok(document) = read_step_text(&text, "open_shell.step") else {
        panic!("a construction open shell does not block the import");
    };
    let Ok(solid) = body_of(&document) else {
        panic!("one body");
    };
    assert_bounds(
        &solid_bounds(&solid),
        [0.0, 0.0, 0.0],
        [5.0, 5.0, 8.0],
        "wedge with a stray open shell",
    );
}

#[test]
fn spherical_face_reports_unsupported_entity() {
    let text = wedge_text().replace(
        "CYLINDRICAL_SURFACE('', #360, 5.0)",
        "SPHERICAL_SURFACE('', #360, 5.0)",
    );
    assert!(matches!(
        read_step_text(&text, "sphere.step"),
        Err(StepError::UnsupportedEntity {
            kind: StepEntityKind::SphericalSurface
        })
    ));
}

#[test]
fn file_schema_catches_an_unsupported_token_past_the_first() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "cube.step");
    let Ok(text) = std::fs::read_to_string(&path) else {
        panic!("read back");
    };
    let swapped = text.replace(
        "('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }')",
        "('GARBAGE_SCHEMA','AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF { 1 0 10303 442 3 1 4 }')",
    );
    let foreign = dir.path().join("cube_multi.step");
    let Ok(()) = std::fs::write(&foreign, swapped.as_bytes()) else {
        panic!("write swapped");
    };
    assert!(matches!(
        read(&foreign, CancelFlag::never()),
        Err(StepError::SchemaMismatch {
            found: StepSchema::Ap242E2,
            ..
        })
    ));
}

#[test]
fn corrupt_sidecar_imports_dumb_body() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "part.step");
    let mut labels = path.clone().into_os_string();
    labels.push(".labels");
    let Ok(()) = std::fs::write(PathBuf::from(labels), b"not valid ron @@@ {") else {
        panic!("overwrite sidecar with garbage");
    };
    let Ok(imported) = read(&path, CancelFlag::never()) else {
        panic!("a corrupt sidecar still imports best-effort");
    };
    let Ok(solid) = body_of(&imported) else {
        panic!("one body");
    };
    assert!(is_dumb(&solid), "a corrupt sidecar yields a dumb body");
}

#[test]
fn imported_body_survives_folder_round_trip() {
    let wedge = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/goldens/inbound/wedge.step");
    let Ok(imported) = read(&wedge, CancelFlag::never()) else {
        panic!("import wedge");
    };
    let Ok(expected) = body_of(&imported) else {
        panic!("imported wedge carries one body");
    };
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let folder = bone_document::DocumentFolder::new(dir.path().join("wedge.bone"));
    let Ok(()) = bone_document::save(&imported, &folder) else {
        panic!("save imported document");
    };
    let Ok(loaded) = bone_document::load(&folder) else {
        panic!("load imported document");
    };
    assert_eq!(
        loaded.imported_bodies().count(),
        1,
        "the body survives on disk"
    );
    let Ok(restored) = body_of(&loaded) else {
        panic!("the reloaded document still resolves its imported body");
    };
    assert_eq!(
        face_labels(&expected),
        face_labels(&restored),
        "a persisted body keeps its labels across a folder round trip"
    );
    assert!(restored.validate(TOL).is_ok());
}

#[test]
fn cylinder_export_writes_step_and_sidecar() {
    let document = document("cyl", circle_sketch(5.0), 8.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "cyl.step");
    assert!(path.exists());
    let mut labels = path.clone().into_os_string();
    labels.push(".labels");
    assert!(PathBuf::from(labels).exists());
}

#[test]
fn step_keywords_in_document_name_round_trip() {
    ["endsec", "file_schema", "header"]
        .iter()
        .for_each(|keyword| {
            let name = format!("{keyword} bracket");
            let document = document(&name, rectangle_sketch(), 4.0);
            let Ok(dir) = tempfile::tempdir() else {
                panic!("temp dir");
            };
            let path = write_to_temp(&document, dir.path(), "part.step");
            let Ok(imported) = read(&path, CancelFlag::never()) else {
                panic!("a name carrying '{keyword}' must not derail header parsing");
            };
            let Ok(solid) = body_of(&imported) else {
                panic!("{keyword}: imports one body");
            };
            assert!(
                !is_dumb(&solid),
                "{keyword}: a header keyword inside the name does not block sidecar reattach"
            );
        });
}

fn role_z_extents(solid: &BrepSolid) -> std::collections::BTreeMap<String, (String, String)> {
    use std::collections::BTreeMap;
    let loop_edges: BTreeMap<_, Vec<_>> = solid
        .iter_loops()
        .map(|l| (l.id(), l.edges().to_vec()))
        .collect();
    let edge_verts: BTreeMap<_, _> = solid.iter_edges().map(|e| (e.id(), e.vertices())).collect();
    let vert_z: BTreeMap<_, f64> = solid
        .iter_vertices()
        .map(|v| (v.id(), v.position().coords_mm().2))
        .collect();
    let mm = |z: f64| format!("{z:.3}");
    solid
        .iter_faces()
        .map(|face| {
            let zs: Vec<f64> = face
                .loops()
                .iter()
                .flat_map(|lid| loop_edges.get(lid).into_iter().flatten())
                .flat_map(|eid| edge_verts.get(eid).into_iter().flatten())
                .filter_map(|vid| vert_z.get(vid).copied())
                .collect();
            let lo = zs.iter().copied().fold(f64::INFINITY, f64::min);
            let hi = zs.iter().copied().fold(f64::NEG_INFINITY, f64::max);
            (format!("{:?}", face.label().role), (mm(lo), mm(hi)))
        })
        .collect()
}

#[test]
fn imported_labels_bind_to_original_geometry() {
    let seed = document("cube", rectangle_sketch(), 4.0);
    let Ok(expected) = body_of(&seed) else {
        panic!("one body");
    };
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&seed, dir.path(), "part.step");
    let Ok(imported) = read(&path, CancelFlag::never()) else {
        panic!("read step");
    };
    let Ok(round) = body_of(&imported) else {
        panic!("one body");
    };
    let after = role_z_extents(&round);
    assert_eq!(
        role_z_extents(&expected),
        after,
        "each label binds to the same geometry before and after import, not merely the same label set"
    );
    assert_eq!(
        after.get("StartCap").map(|extent| extent.0.as_str()),
        Some("0.000"),
        "the StartCap label lands on the z=0 face"
    );
    assert_eq!(
        after.get("EndCap").map(|extent| extent.0.as_str()),
        Some("4.000"),
        "the EndCap label lands on the z=depth face"
    );
}

#[test]
fn a_set_flag_cancels_export_and_leaves_no_file() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    let flag = AtomicBool::new(true);
    assert!(matches!(
        write(&document, &path, StepSchema::Ap214, CancelFlag::new(&flag)),
        Err(StepError::Canceled)
    ));
    assert!(!path.exists(), "a canceled export writes no step file");
    let mut labels = path.into_os_string();
    labels.push(".labels");
    assert!(
        !PathBuf::from(labels).exists(),
        "a canceled export writes no sidecar either"
    );
}

#[test]
fn a_set_flag_cancels_import() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "part.step");
    let flag = AtomicBool::new(true);
    assert!(matches!(
        read(&path, CancelFlag::new(&flag)),
        Err(StepError::Canceled)
    ));
}

#[test]
fn a_clear_flag_does_not_block_a_round_trip() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    let flag = AtomicBool::new(false);
    let observed = CancelFlag::new(&flag);
    let Ok(()) = write(&document, &path, StepSchema::Ap214, observed) else {
        panic!("a clear flag permits export");
    };
    let Ok(imported) = read(&path, observed) else {
        panic!("a clear flag permits import");
    };
    let Ok(solid) = body_of(&imported) else {
        panic!("one body");
    };
    assert!(!is_dumb(&solid));
}

struct CancelAfter {
    seen: AtomicUsize,
    trip: usize,
}

impl CancelAfter {
    fn new(trip: usize) -> Self {
        Self {
            seen: AtomicUsize::new(0),
            trip,
        }
    }

    fn observations(&self) -> usize {
        self.seen.load(Ordering::Relaxed)
    }
}

impl Cancel for CancelAfter {
    fn is_canceled(&self) -> bool {
        self.seen.fetch_add(1, Ordering::Relaxed) >= self.trip
    }
}

#[test]
fn a_cancel_after_evaluation_stops_export_before_writing() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    let cancel = CancelAfter::new(1);
    assert!(matches!(
        write(
            &document,
            &path,
            StepSchema::Ap214,
            CancelFlag::new(&cancel)
        ),
        Err(StepError::Canceled)
    ));
    assert_eq!(
        cancel.observations(),
        2,
        "the export clears the first guard, evaluates, then trips on the second"
    );
    assert!(
        !path.exists(),
        "a cancel seen after evaluation writes no step file"
    );
    let mut labels = path.into_os_string();
    labels.push(".labels");
    assert!(
        !PathBuf::from(labels).exists(),
        "and writes no label sidecar"
    );
}

#[test]
fn a_cancel_after_read_stops_import_before_assembling() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "part.step");
    let cancel = CancelAfter::new(1);
    assert!(matches!(
        read(&path, CancelFlag::new(&cancel)),
        Err(StepError::Canceled)
    ));
    assert_eq!(
        cancel.observations(),
        2,
        "the import clears the first guard, reads the file, then trips on the second"
    );
}

const WRITE_BUDGET: Duration = Duration::from_millis(100);
const BUDGET_SAMPLES: u32 = 8;

#[test]
fn rectangle_extrude_write_stays_within_budget() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let never = CancelFlag::never();
    let Ok(()) = write(
        &document,
        &dir.path().join("warmup.step"),
        StepSchema::Ap214,
        never,
    ) else {
        panic!("warmup write");
    };
    let best = (0..BUDGET_SAMPLES).fold(Duration::MAX, |best, sample| {
        let path = dir.path().join(format!("bench{sample}.step"));
        let start = Instant::now();
        let outcome = write(&document, &path, StepSchema::Ap214, never);
        let elapsed = start.elapsed();
        let Ok(()) = outcome else {
            panic!("benchmark write");
        };
        best.min(elapsed)
    });
    assert!(
        cfg!(debug_assertions) || best < WRITE_BUDGET,
        "rectangle extrude step write took {best:?} over the {WRITE_BUDGET:?} release budget"
    );
}

#[test]
fn a_cancel_seen_after_parsing_discards_the_import() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = write_to_temp(&document, dir.path(), "part.step");
    let cancel = CancelAfter::new(2);
    assert!(matches!(
        read(&path, CancelFlag::new(&cancel)),
        Err(StepError::Canceled)
    ));
    assert_eq!(
        cancel.observations(),
        3,
        "the import clears both pre-parse guards, parses, then trips on the post-parse guard"
    );
}

#[test]
fn a_failed_step_write_leaves_no_orphan_sidecar() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    let Ok(()) = std::fs::create_dir(&path) else {
        panic!("occupy the step path with a directory so the body write fails");
    };
    assert!(matches!(
        write(&document, &path, StepSchema::Ap214, CancelFlag::never()),
        Err(StepError::Io { .. })
    ));
    assert_eq!(
        read_dir_names(dir.path()),
        vec!["part.step".to_owned()],
        "a failed commit rolls back the sidecar and leaves no staging file"
    );
}

fn read_dir_names(dir: &Path) -> Vec<String> {
    let Ok(entries) = std::fs::read_dir(dir) else {
        panic!("read temp dir");
    };
    entries
        .filter_map(Result::ok)
        .map(|entry| entry.file_name().to_string_lossy().into_owned())
        .collect::<std::collections::BTreeSet<_>>()
        .into_iter()
        .collect()
}

#[test]
fn a_clean_export_renames_its_staging_file_into_place() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    let Ok(()) = write(&document, &path, StepSchema::Ap214, CancelFlag::never()) else {
        panic!("export");
    };
    assert_eq!(
        read_dir_names(dir.path()),
        vec!["part.step".to_owned(), "part.step.labels".to_owned()],
        "a clean export leaves the step and its sidecar with no staging temp behind"
    );
}

#[test]
fn a_failed_re_export_keeps_the_prior_step_file_intact() {
    let document = document("cube", rectangle_sketch(), 4.0);
    let Ok(dir) = tempfile::tempdir() else {
        panic!("temp dir");
    };
    let path = dir.path().join("part.step");
    let Ok(()) = std::fs::write(&path, b"PRIOR GOOD STEP") else {
        panic!("seed a prior export");
    };
    let mut staging = path.clone().into_os_string();
    staging.push(".staging");
    let Ok(()) = std::fs::create_dir(PathBuf::from(&staging)) else {
        panic!("block the staging path so the commit cannot stage the new body");
    };
    assert!(matches!(
        write(&document, &path, StepSchema::Ap214, CancelFlag::never()),
        Err(StepError::Io { .. })
    ));
    let Ok(kept) = std::fs::read(&path) else {
        panic!("the prior export vanished");
    };
    assert_eq!(
        kept,
        b"PRIOR GOOD STEP".to_vec(),
        "a failed commit must not truncate or clobber the previous export"
    );
}
