use std::collections::BTreeSet;

use bone_kernel::{
    BrepFace, ContourSelection, Direction2, DraftAngle, DraftDirection, DraftMagnitude,
    ExtrudeEndCondition, ExtrudeFeature, ExtrudeOperation, FeatureScope, FromCondition,
    MergeResult, RevolveAngle, RevolveAxis, RevolveFeature, ThinWall, ThinWallDirection,
};
use bone_types::{
    Angle, ExtrudeId, FaceFingerprint, FaceLabel, FaceRef, FaceRole, FeatureId, Length, Plane3,
    Point3, PositiveLength, Resolution, RollbackMarker, SketchEntityId, SketchId, SketchPlaneBasis,
    SketchRelationId, Tolerance, UnitVec3, VertexLabel, VertexRole, degree, millimeter,
};

use crate::document::Document;
use crate::matcher::resolve_face;
use crate::recompute::{EvaluatedModel, RecomputeScope};
use crate::sketch3::{Sketch3, Sketch3Entity, Sketch3Relation};

pub(crate) fn xy_basis() -> SketchPlaneBasis {
    let Ok(basis) = SketchPlaneBasis::new(
        Point3::origin(),
        UnitVec3::x_axis(),
        UnitVec3::y_axis(),
        Tolerance::new(1e-9),
    ) else {
        panic!("xy plane basis is orthogonal");
    };
    basis
}

fn lift(plane: SketchPlaneBasis, x: f64, y: f64) -> Point3 {
    Plane3::from(plane).point_at_local(x, y, 0.0)
}

pub(crate) fn add_point_on(
    sketch: &Sketch3,
    plane: SketchPlaneBasis,
    x: f64,
    y: f64,
) -> (Sketch3, SketchEntityId) {
    let (next, id) = sketch.with_entity(Sketch3Entity::point(lift(plane, x, y)));
    let (next, _) = next.with_relation(Sketch3Relation::OnPlane {
        point: id,
        normal: plane.normal(),
        offset: plane_offset(plane),
    });
    (next, id)
}

pub(crate) fn add_point(sketch: &Sketch3, x: f64, y: f64) -> (Sketch3, SketchEntityId) {
    add_point_on(sketch, xy_basis(), x, y)
}

fn plane_offset(plane: SketchPlaneBasis) -> Length {
    let (nx, ny, nz) = plane.normal().components();
    let (ox, oy, oz) = plane.origin().coords_mm();
    Length::new::<millimeter>(ox.mul_add(nx, oy.mul_add(ny, oz * nz)))
}

fn add_circle_on(
    sketch: &Sketch3,
    plane: SketchPlaneBasis,
    center: SketchEntityId,
    radius_mm: f64,
) -> (Sketch3, SketchEntityId) {
    sketch.with_entity(Sketch3Entity::circle(
        center,
        Length::new::<millimeter>(radius_mm),
        plane.normal(),
        false,
    ))
}

fn circle_entities(radius_mm: f64) -> (Sketch3, SketchEntityId) {
    let (sketch, center) = add_point(&Sketch3::new(), 0.0, 0.0);
    add_circle_on(&sketch, xy_basis(), center, radius_mm)
}

pub(crate) fn circle_sketch(radius_mm: f64) -> Sketch3 {
    circle_entities(radius_mm).0
}

pub(crate) fn circle_sketch_at(cx: f64, cy: f64, radius_mm: f64) -> Sketch3 {
    let (sketch, center) = add_point(&Sketch3::new(), cx, cy);
    add_circle_on(&sketch, xy_basis(), center, radius_mm).0
}

pub(crate) fn circle_with_radius_dim(
    radius_mm: f64,
) -> (Sketch3, SketchEntityId, SketchRelationId) {
    let (sketch, circle) = circle_entities(radius_mm);
    let (sketch, dim) = sketch.with_relation(Sketch3Relation::Radius {
        target: circle,
        value: Length::new::<millimeter>(radius_mm),
        reference: false,
    });
    (sketch, circle, dim)
}

pub(crate) fn open_chain_sketch() -> Sketch3 {
    let add_line = |sketch: Sketch3, from: SketchEntityId, to: SketchEntityId| {
        sketch.with_entity(Sketch3Entity::line(from, to, false)).0
    };
    let (sketch, a) = add_point(&Sketch3::new(), 0.0, 0.0);
    let (sketch, b) = add_point(&sketch, 10.0, 0.0);
    let (sketch, c) = add_point(&sketch, 10.0, 5.0);
    add_line(add_line(sketch, a, b), b, c)
}

pub(crate) fn revolve_axis_sketch() -> (Sketch3, SketchEntityId) {
    let add_line = |sketch: Sketch3, from: SketchEntityId, to: SketchEntityId| {
        sketch.with_entity(Sketch3Entity::line(from, to, false)).0
    };
    let (sketch, axis_a) = add_point(&Sketch3::new(), 0.0, 0.0);
    let (sketch, axis_b) = add_point(&sketch, 0.0, 10.0);
    let (sketch, axis) = sketch.with_entity(Sketch3Entity::line(axis_a, axis_b, true));
    let (sketch, p0) = add_point(&sketch, 2.0, 0.0);
    let (sketch, p1) = add_point(&sketch, 4.0, 0.0);
    let (sketch, p2) = add_point(&sketch, 4.0, 10.0);
    let (sketch, p3) = add_point(&sketch, 2.0, 10.0);
    let sketch = add_line(
        add_line(add_line(add_line(sketch, p0, p1), p1, p2), p2, p3),
        p3,
        p0,
    );
    (sketch, axis)
}

pub(crate) fn revolve_two_contour_sketch() -> (Sketch3, SketchEntityId, SketchEntityId) {
    let add_circle = |sketch: Sketch3, cx: f64, cy: f64| {
        let (sketch, center) = add_point(&sketch, cx, cy);
        add_circle_on(&sketch, xy_basis(), center, 1.0)
    };
    let (sketch, axis_a) = add_point(&Sketch3::new(), 0.0, 0.0);
    let (sketch, axis_b) = add_point(&sketch, 0.0, 12.0);
    let (sketch, axis) = sketch.with_entity(Sketch3Entity::line(axis_a, axis_b, true));
    let (sketch, first) = add_circle(sketch, 5.0, 3.0);
    let (sketch, _second) = add_circle(sketch, 5.0, 9.0);
    (sketch, axis, first)
}

pub(crate) fn contour_revolve(
    sketch: SketchId,
    axis: SketchEntityId,
    angle_deg: f64,
    contour: SketchEntityId,
) -> RevolveFeature {
    RevolveFeature {
        contours: ContourSelection::Contour(contour),
        ..blind_revolve(sketch, axis, angle_deg)
    }
}

pub(crate) fn revolve_circle_profile_sketch() -> (Sketch3, SketchEntityId) {
    let (sketch, axis_a) = add_point(&Sketch3::new(), 0.0, 0.0);
    let (sketch, axis_b) = add_point(&sketch, 0.0, 10.0);
    let (sketch, axis) = sketch.with_entity(Sketch3Entity::line(axis_a, axis_b, true));
    let (sketch, center) = add_point(&sketch, 5.0, 5.0);
    let (sketch, _) = add_circle_on(&sketch, xy_basis(), center, 1.0);
    (sketch, axis)
}

pub(crate) fn blind_revolve(
    sketch: SketchId,
    axis: SketchEntityId,
    angle_deg: f64,
) -> RevolveFeature {
    let Ok(angle) = RevolveAngle::new(Angle::new::<degree>(angle_deg)) else {
        panic!("{angle_deg} deg is a valid revolve angle");
    };
    RevolveFeature {
        operation: ExtrudeOperation::Boss(MergeResult::Separate),
        ..RevolveFeature::blind(sketch, RevolveAxis::SketchLine(axis), angle)
    }
}

pub(crate) fn thin_revolve(
    sketch: SketchId,
    axis: SketchEntityId,
    angle_deg: f64,
    thickness_mm: f64,
) -> RevolveFeature {
    let Ok(thickness) = PositiveLength::new(Length::new::<millimeter>(thickness_mm)) else {
        panic!("{thickness_mm} mm is a positive thickness");
    };
    RevolveFeature {
        thin_wall: Some(ThinWall {
            thickness,
            direction: ThinWallDirection::MidPlane,
            thickness2: None,
            cap_ends: false,
            auto_fillet: None,
        }),
        ..blind_revolve(sketch, axis, angle_deg)
    }
}

pub(crate) fn revolve_open_profile_sketch() -> (Sketch3, SketchEntityId) {
    let add_line = |sketch: Sketch3, from: SketchEntityId, to: SketchEntityId| {
        sketch.with_entity(Sketch3Entity::line(from, to, false)).0
    };
    let (sketch, axis_a) = add_point(&Sketch3::new(), 0.0, 0.0);
    let (sketch, axis_b) = add_point(&sketch, 0.0, 10.0);
    let (sketch, axis) = sketch.with_entity(Sketch3Entity::line(axis_a, axis_b, true));
    let (sketch, p0) = add_point(&sketch, 3.0, 0.0);
    let (sketch, p1) = add_point(&sketch, 3.0, 8.0);
    let (sketch, p2) = add_point(&sketch, 1.5, 10.0);
    (add_line(add_line(sketch, p0, p1), p1, p2), axis)
}

pub(crate) fn revolve_plain_axis_sketch() -> Sketch3 {
    let add_line = |sketch: Sketch3, from: SketchEntityId, to: SketchEntityId| {
        sketch.with_entity(Sketch3Entity::line(from, to, false)).0
    };
    let (sketch, ax0) = add_point(&Sketch3::new(), 0.0, 0.0);
    let (sketch, ax1) = add_point(&sketch, 0.0, 10.0);
    let sketch = add_line(sketch, ax0, ax1);
    let (sketch, p0) = add_point(&sketch, 2.0, 0.0);
    let (sketch, p1) = add_point(&sketch, 4.0, 0.0);
    let (sketch, p2) = add_point(&sketch, 4.0, 10.0);
    let (sketch, p3) = add_point(&sketch, 2.0, 10.0);
    add_line(
        add_line(add_line(add_line(sketch, p0, p1), p1, p2), p2, p3),
        p3,
        p0,
    )
}

pub(crate) fn revolve_single_line_sketch() -> (Sketch3, SketchEntityId) {
    let add_line = |sketch: Sketch3, from: SketchEntityId, to: SketchEntityId| {
        sketch.with_entity(Sketch3Entity::line(from, to, false)).0
    };
    let (sketch, axis_a) = add_point(&Sketch3::new(), 0.0, 0.0);
    let (sketch, axis_b) = add_point(&sketch, 0.0, 10.0);
    let (sketch, axis) = sketch.with_entity(Sketch3Entity::line(axis_a, axis_b, true));
    let (sketch, p0) = add_point(&sketch, 3.0, 0.0);
    let (sketch, p1) = add_point(&sketch, 5.0, 10.0);
    (add_line(sketch, p0, p1), axis)
}

pub(crate) fn revolve_z_axis_tube_sketch(
    r_inner: f64,
    r_outer: f64,
    z0: f64,
    z1: f64,
) -> (Sketch3, SketchEntityId) {
    let Ok(xz) = SketchPlaneBasis::new(
        Point3::origin(),
        UnitVec3::x_axis(),
        UnitVec3::z_axis(),
        Tolerance::new(1e-9),
    ) else {
        panic!("x and z axes are orthonormal");
    };
    let add_line = |sketch: Sketch3, from: SketchEntityId, to: SketchEntityId| {
        sketch.with_entity(Sketch3Entity::line(from, to, false)).0
    };
    let point = |sketch: Sketch3, x: f64, y: f64| add_point_on(&sketch, xz, x, y);
    let (sketch, axis_a) = point(Sketch3::on_plane(xz), 0.0, 0.0);
    let (sketch, axis_b) = point(sketch, 0.0, 1.0);
    let (sketch, axis) = sketch.with_entity(Sketch3Entity::line(axis_a, axis_b, true));
    let (sketch, p0) = point(sketch, r_inner, z0);
    let (sketch, p1) = point(sketch, r_outer, z0);
    let (sketch, p2) = point(sketch, r_outer, z1);
    let (sketch, p3) = point(sketch, r_inner, z1);
    let sketch = add_line(
        add_line(add_line(add_line(sketch, p0, p1), p1, p2), p2, p3),
        p3,
        p0,
    );
    (sketch, axis)
}

pub(crate) fn cut_revolve(
    sketch: SketchId,
    axis: SketchEntityId,
    angle_deg: f64,
) -> RevolveFeature {
    RevolveFeature {
        operation: ExtrudeOperation::Cut,
        scope: FeatureScope::AllBodies,
        ..blind_revolve(sketch, axis, angle_deg)
    }
}

pub(crate) fn flip_side_cut_revolve(
    sketch: SketchId,
    axis: SketchEntityId,
    angle_deg: f64,
) -> RevolveFeature {
    RevolveFeature {
        flip_side: true,
        ..cut_revolve(sketch, axis, angle_deg)
    }
}

pub(crate) fn blind_extrude(sketch: SketchId, depth_mm: f64) -> ExtrudeFeature {
    let Ok(depth) = PositiveLength::new(Length::new::<millimeter>(depth_mm)) else {
        panic!("{depth_mm} mm is a positive depth");
    };
    ExtrudeFeature {
        operation: ExtrudeOperation::Boss(MergeResult::Separate),
        ..ExtrudeFeature::blind(sketch, depth)
    }
}

pub(crate) fn box_sketch(side_mm: f64) -> Sketch3 {
    box_sketch_at(0.0, 0.0, side_mm)
}

pub(crate) fn rect_sketch_at(x0: f64, y0: f64, width_mm: f64, height_mm: f64) -> Sketch3 {
    let add_line = |sketch: Sketch3, from: SketchEntityId, to: SketchEntityId| {
        sketch.with_entity(Sketch3Entity::line(from, to, false)).0
    };
    let (sketch, a) = add_point(&Sketch3::new(), x0, y0);
    let (sketch, b) = add_point(&sketch, x0 + width_mm, y0);
    let (sketch, c) = add_point(&sketch, x0 + width_mm, y0 + height_mm);
    let (sketch, d) = add_point(&sketch, x0, y0 + height_mm);
    add_line(add_line(add_line(add_line(sketch, a, b), b, c), c, d), d, a)
}

pub(crate) fn box_sketch_at(x0: f64, y0: f64, side_mm: f64) -> Sketch3 {
    let add_line = |sketch: Sketch3, from: SketchEntityId, to: SketchEntityId| {
        sketch.with_entity(Sketch3Entity::line(from, to, false)).0
    };
    let (sketch, a) = add_point(&Sketch3::new(), x0, y0);
    let (sketch, b) = add_point(&sketch, x0 + side_mm, y0);
    let (sketch, c) = add_point(&sketch, x0 + side_mm, y0 + side_mm);
    let (sketch, d) = add_point(&sketch, x0, y0 + side_mm);
    add_line(add_line(add_line(add_line(sketch, a, b), b, c), c, d), d, a)
}

pub(crate) fn merge_extrude(sketch: SketchId, depth_mm: f64) -> ExtrudeFeature {
    ExtrudeFeature {
        operation: ExtrudeOperation::Boss(MergeResult::Merge),
        ..blind_extrude(sketch, depth_mm)
    }
}

pub(crate) fn cut_extrude(sketch: SketchId, depth_mm: f64) -> ExtrudeFeature {
    ExtrudeFeature {
        operation: ExtrudeOperation::Cut,
        ..blind_extrude(sketch, depth_mm)
    }
}

pub(crate) fn cut_extrude_with(
    sketch: SketchId,
    end_condition: ExtrudeEndCondition,
) -> ExtrudeFeature {
    ExtrudeFeature {
        end_condition,
        operation: ExtrudeOperation::Cut,
        ..blind_extrude(sketch, 1.0)
    }
}

pub(crate) fn two_direction_boss(sketch: SketchId, d1_mm: f64, d2_mm: f64) -> ExtrudeFeature {
    let Ok(d2) = PositiveLength::new(Length::new::<millimeter>(d2_mm)) else {
        panic!("{d2_mm} mm is a positive depth");
    };
    ExtrudeFeature {
        direction2: Some(Direction2 {
            end_condition: ExtrudeEndCondition::Blind { depth: d2 },
            draft: None,
        }),
        ..blind_extrude(sketch, d1_mm)
    }
}

pub(crate) fn draft_boss(sketch: SketchId, depth_mm: f64, draft_deg: f64) -> ExtrudeFeature {
    let Ok(magnitude) = DraftMagnitude::new(Angle::new::<degree>(draft_deg)) else {
        panic!("{draft_deg} deg is a valid draft angle");
    };
    ExtrudeFeature {
        draft: Some(DraftAngle::new(magnitude, DraftDirection::Outward)),
        ..blind_extrude(sketch, depth_mm)
    }
}

pub(crate) fn thin_boss(sketch: SketchId, depth_mm: f64, thickness_mm: f64) -> ExtrudeFeature {
    let Ok(thickness) = PositiveLength::new(Length::new::<millimeter>(thickness_mm)) else {
        panic!("{thickness_mm} mm is a positive thickness");
    };
    ExtrudeFeature {
        thin_wall: Some(ThinWall {
            thickness,
            direction: ThinWallDirection::Inward,
            thickness2: None,
            cap_ends: false,
            auto_fillet: None,
        }),
        ..blind_extrude(sketch, depth_mm)
    }
}

pub(crate) fn from_offset_boss(sketch: SketchId, offset_mm: f64, depth_mm: f64) -> ExtrudeFeature {
    ExtrudeFeature {
        from: FromCondition::Offset {
            distance: Length::new::<millimeter>(offset_mm),
        },
        ..blind_extrude(sketch, depth_mm)
    }
}

pub(crate) struct Chain {
    pub(crate) sketch: FeatureId,
    pub(crate) extrude: FeatureId,
    pub(crate) sketch_id: SketchId,
    pub(crate) extrude_id: ExtrudeId,
}

pub(crate) fn chain_handles(
    document: &Document,
    sketch_id: SketchId,
    extrude_id: ExtrudeId,
) -> Chain {
    let tree = document.feature_tree();
    let (Some(sketch), Some(extrude)) = (
        tree.feature_of_sketch(sketch_id),
        tree.feature_of_extrude(extrude_id),
    ) else {
        panic!("inserted chain resolves to feature ids");
    };
    Chain {
        sketch,
        extrude,
        sketch_id,
        extrude_id,
    }
}

pub(crate) fn push_chain(
    document: &mut Document,
    label: &str,
    radius_mm: f64,
    depth_mm: f64,
) -> Chain {
    let sketch_id = document.allocate_sketch();
    document.insert_sketch(sketch_id, label.to_owned(), circle_sketch(radius_mm));
    let extrude_id = document.commit_extrude(blind_extrude(sketch_id, depth_mm));
    chain_handles(document, sketch_id, extrude_id)
}

pub(crate) fn placeholder_fingerprint() -> FaceFingerprint {
    FaceFingerprint {
        plane: Plane3::new_unchecked(Point3::origin(), UnitVec3::x_axis(), UnitVec3::y_axis()),
        centroid: Point3::origin(),
    }
}

fn face_label_of(
    model: &EvaluatedModel,
    body: FeatureId,
    matches_role: impl Fn(FaceRole) -> bool,
) -> FaceLabel {
    let Some(solid) = model.body(body) else {
        panic!("the body is built");
    };
    let Some(label) = solid
        .iter_faces()
        .map(BrepFace::label)
        .find(|label| matches_role(label.role))
    else {
        panic!("the body carries the requested face role");
    };
    label
}

pub(crate) fn end_cap_ref(model: &EvaluatedModel, body: FeatureId) -> FaceRef {
    let label = face_label_of(model, body, |role| matches!(role, FaceRole::EndCap));
    let Resolution::Resolved(resolved) = resolve_face(model, label, placeholder_fingerprint())
    else {
        panic!("the end cap resolves on the model that built it");
    };
    let Some(face_ref) = model.face_ref(resolved) else {
        panic!("a planar cap yields a face reference");
    };
    face_ref
}

pub(crate) fn side_face_label(model: &EvaluatedModel, body: FeatureId) -> FaceLabel {
    face_label_of(model, body, |role| matches!(role, FaceRole::Side { .. }))
}

pub(crate) fn side_face_ref(model: &EvaluatedModel, body: FeatureId) -> FaceRef {
    let label = side_face_label(model, body);
    let Resolution::Resolved(resolved) = resolve_face(model, label, placeholder_fingerprint())
    else {
        panic!("the side face resolves on the model that built it");
    };
    let Some(face_ref) = model.face_ref(resolved) else {
        panic!("a planar side yields a face reference");
    };
    face_ref
}

pub(crate) fn start_cap_label(model: &EvaluatedModel, body: FeatureId) -> FaceLabel {
    face_label_of(model, body, |role| matches!(role, FaceRole::StartCap))
}

pub(crate) fn start_cap_vertex_label(model: &EvaluatedModel, body: FeatureId) -> VertexLabel {
    let Some(solid) = model.body(body) else {
        panic!("the body is built");
    };
    let Some(vertex) = solid
        .iter_vertices()
        .find(|vertex| matches!(vertex.label().role, VertexRole::StartCapVertex { .. }))
    else {
        panic!("the body carries a start-cap vertex");
    };
    vertex.label()
}

pub(crate) fn push_face_bound_chain(
    document: &mut Document,
    label: &str,
    face: FaceRef,
    radius_mm: f64,
    depth_mm: f64,
) -> Chain {
    let sketch_id = document.allocate_sketch();
    document.insert_sketch(sketch_id, label.to_owned(), circle_sketch(radius_mm));
    let Ok(()) = document.bind_sketch_to_face(sketch_id, face) else {
        panic!("the face binding is acyclic");
    };
    let extrude_id = document.commit_extrude(blind_extrude(sketch_id, depth_mm));
    chain_handles(document, sketch_id, extrude_id)
}

pub(crate) fn push_box(document: &mut Document, label: &str, side_mm: f64, depth_mm: f64) -> Chain {
    let sketch_id = document.allocate_sketch();
    document.insert_sketch(sketch_id, label.to_owned(), box_sketch(side_mm));
    let extrude_id = document.commit_extrude(blind_extrude(sketch_id, depth_mm));
    chain_handles(document, sketch_id, extrude_id)
}

pub(crate) fn push_face_bound_box_merge(
    document: &mut Document,
    label: &str,
    face: FaceRef,
    side_mm: f64,
    depth_mm: f64,
) -> Chain {
    let sketch_id = document.allocate_sketch();
    document.insert_sketch(sketch_id, label.to_owned(), box_sketch(side_mm));
    let Ok(()) = document.bind_sketch_to_face(sketch_id, face) else {
        panic!("the face binding is acyclic");
    };
    let extrude_id = document.commit_extrude(merge_extrude(sketch_id, depth_mm));
    chain_handles(document, sketch_id, extrude_id)
}

pub(crate) fn push_face_bound_box_cut(
    document: &mut Document,
    label: &str,
    face: FaceRef,
    inset_mm: f64,
    side_mm: f64,
    depth_mm: f64,
) -> Chain {
    let sketch_id = document.allocate_sketch();
    document.insert_sketch(
        sketch_id,
        label.to_owned(),
        box_sketch_at(inset_mm, inset_mm, side_mm),
    );
    let Ok(()) = document.bind_sketch_to_face(sketch_id, face) else {
        panic!("the face binding is acyclic");
    };
    let extrude_id = document.commit_extrude(cut_extrude(sketch_id, depth_mm));
    chain_handles(document, sketch_id, extrude_id)
}

pub(crate) fn push_face_bound_circle_cut(
    document: &mut Document,
    label: &str,
    face: FaceRef,
    cx: f64,
    cy: f64,
    radius_mm: f64,
    depth_mm: f64,
) -> Chain {
    let sketch_id = document.allocate_sketch();
    document.insert_sketch(
        sketch_id,
        label.to_owned(),
        circle_sketch_at(cx, cy, radius_mm),
    );
    let Ok(()) = document.bind_sketch_to_face(sketch_id, face) else {
        panic!("the face binding is acyclic");
    };
    let extrude_id = document.commit_extrude(cut_extrude(sketch_id, depth_mm));
    chain_handles(document, sketch_id, extrude_id)
}

pub(crate) fn push_face_bound_box_cut_flip(
    document: &mut Document,
    label: &str,
    face: FaceRef,
    inset_mm: f64,
    side_mm: f64,
    depth_mm: f64,
) -> Chain {
    let sketch_id = document.allocate_sketch();
    document.insert_sketch(
        sketch_id,
        label.to_owned(),
        box_sketch_at(inset_mm, inset_mm, side_mm),
    );
    let Ok(()) = document.bind_sketch_to_face(sketch_id, face) else {
        panic!("the face binding is acyclic");
    };
    let extrude_id = document.commit_extrude(ExtrudeFeature {
        flip_side: true,
        ..cut_extrude(sketch_id, depth_mm)
    });
    chain_handles(document, sketch_id, extrude_id)
}

pub(crate) fn push_face_bound_box_cut_with(
    document: &mut Document,
    label: &str,
    face: FaceRef,
    inset_mm: f64,
    side_mm: f64,
    end_condition: ExtrudeEndCondition,
) -> Chain {
    let sketch_id = document.allocate_sketch();
    document.insert_sketch(
        sketch_id,
        label.to_owned(),
        box_sketch_at(inset_mm, inset_mm, side_mm),
    );
    let Ok(()) = document.bind_sketch_to_face(sketch_id, face) else {
        panic!("the face binding is acyclic");
    };
    let extrude_id = document.commit_extrude(cut_extrude_with(sketch_id, end_condition));
    chain_handles(document, sketch_id, extrude_id)
}

pub(crate) fn full_model(document: &Document) -> EvaluatedModel {
    let mut model = EvaluatedModel::new();
    model.recompute(
        document,
        &BTreeSet::new(),
        RollbackMarker::AtEnd,
        RecomputeScope::Full,
    );
    model
}
