use std::collections::{HashMap, HashSet};

use bone_types::{
    Aabb3, Angle, AngleTolerance, Axis3, ChordHeightTolerance, EdgeLabel, EdgeRole, FaceLabel,
    FaceRole, FeatureId, ImportOrdinal, Length, LoopIndex, Parameter, Plane3, Point2, Point3,
    PositiveLength, SideKind, SketchEntityId, Tolerance, UnitVec3, Vec3, VertexLabel, VertexRole,
};
use core::f64::consts::{PI, TAU};
use truck_meshalgo::prelude::PolygonMesh;
use truck_meshalgo::tessellation::RobustMeshableShape;
use truck_modeling::{
    BSplineCurve, BSplineSurface, BoundedCurve, Curve, Edge, EdgeID, Face, FaceID, Invertible,
    KnotVec, NurbsCurve, NurbsSurface, ParametricCurve, Rad, Shell, Solid, Surface, Vector3,
    Vector4, Vertex, VertexID, Wire, builder,
};
use uom::si::angle::radian;
use uom::si::length::millimeter;

use crate::arc2::Arc2;
use crate::arc3::Arc3;
use crate::circle2::Circle2;
use crate::curve2::{Curve2, Curve2Kind};
use crate::curve3::{Curve3, Curve3Kind};
use crate::extrude::{
    DirectionPlan, ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeOperation,
    ExtrudeSense, FromCondition, ResolvedSweep, ThinPlan,
};
use crate::intersect::{IntersectionSet, intersect_curves};
use crate::line2::Line2;
use crate::line3::Line3;
use crate::offset2::{OffsetError, offset_loop, offset_open_chain, reverse_curve};
use crate::revolve::{ResolvedRevolve, RevolveEndCondition, RevolveFeature};
use crate::sweep::{
    SweepAlignment, SweepDirection, SweepFeature, SweepOrientation, SweepProfileKind,
    SweepTangency, SweepTwist,
};
use crate::sweep_surface::{
    ArcFraction, BSpline3, ClampedInterpolator, CompositePath3, Frame, FrameGuide,
    PathInterpolator, SampleCount, SectionSpline, StationCount, SweepDefect, SweepPath3,
    chord_params, circle_arc_spline, ellipse_arc_spline, reverse_section, section_spline,
    station_fractions, transport_stations,
};
use slotmap::{Key, KeyData};

use super::boolean::model::{project_onto_axis, radial_distance};
use super::boolean::rim_geometry;
use super::build::{FragmentId, SolidLabeling, assemble, face_plane_basis};
use super::convert::{point_from_truck, point_to_truck};
use super::edges::{EdgeCurve3, lift_curve2};
use super::profile::{ExtrudeProfile, ProfileEdge, ProfileLoop};
use super::{BrepError, BrepFace, BrepSolid, ProfileDefect, TruckGap};

const ANGLE_EPS_RAD: f64 = 1.0e-9;
const REVOLVE_AXIS_TOLERANCE_MM: f64 = 1.0e-6;
const REVOLVE_AXIS_DOT_TOLERANCE: f64 = 1.0e-6;
const REVOLVE_ATTRIBUTION_TOLERANCE_MM: f64 = 1.0e-4;
const REVOLVE_ATTRIBUTION_TOLERANCE: Tolerance = Tolerance::new(REVOLVE_ATTRIBUTION_TOLERANCE_MM);

const PROFILE_TOLERANCE_MM: f64 = 1.0e-6;
const PROFILE_TOLERANCE: Tolerance = Tolerance::new(PROFILE_TOLERANCE_MM);
const AREA_TOLERANCE_MM2: f64 = 1.0e-9;
const EXTRUDE_MIN_DEPTH_MM: f64 = 1.0e-6;
const THROUGH_ALL_CLEARANCE_FRACTION: f64 = 0.5;
const THROUGH_ALL_MIN_SPAN_MM: f64 = 1.0;
const PARALLEL_TARGET_EPS: f64 = 1.0e-9;
const MAX_ARC_SEGMENTS: u32 = 64;
const MAX_SUBARC_RAD: f64 = TAU / 3.0;
const AREA_SAMPLES: u32 = 32;

pub fn evaluate_extrude(
    extrude: FeatureId,
    profile: &ExtrudeProfile,
    feature: &ExtrudeFeature,
) -> Result<BrepSolid, BrepError> {
    let plan = resolve_sweep(feature, &ExtrudeScene::detached(profile.plane()))?;
    evaluate_extrude_plan(extrude, profile, &plan)
}

pub fn evaluate_extrude_plan(
    extrude: FeatureId,
    profile: &ExtrudeProfile,
    plan: &ResolvedSweep,
) -> Result<BrepSolid, BrepError> {
    let plane = profile.plane();
    match (plan_has_draft(plan), plan.thin) {
        (false, None) => build_prismatic(extrude, profile.loops(), plane, span_of(plan)?),
        (false, Some(thin)) => {
            let loops = profile.loops();
            let closed_profile = loops
                .iter()
                .all(|loop_| !matches!(loop_, ProfileLoop::Chain(_)));
            match (thin.cap_ends, loops.len()) {
                (true, 1) if closed_profile => {
                    build_thin_capped(extrude, &loops[0], plane, span_of(plan)?, thin)
                }
                (false, count) if count > 1 && closed_profile => {
                    build_thin_regions(extrude, loops, plane, span_of(plan)?, thin)
                }
                _ => {
                    let augmented = augment_thin(loops, thin)?;
                    build_prismatic(extrude, &augmented, plane, span_of(plan)?)
                }
            }
        }
        (true, thin) => {
            let loops = match thin {
                None => profile.loops().to_vec(),
                Some(thin) => augment_thin(profile.loops(), thin)?,
            };
            build_drafted(extrude, &loops, plane, plan)
        }
    }
}

fn plan_has_draft(plan: &ResolvedSweep) -> bool {
    plan.forward
        .is_some_and(|direction| direction.draft.is_some())
        || plan
            .backward
            .is_some_and(|direction| direction.draft.is_some())
}

const UP_TO_NEXT_SKIP_MM: f64 = 1.0e-6;
const UP_TO_NEXT_CHORD_MM: f64 = 0.01;
const RAY_DET_EPS: f64 = 1.0e-12;
const RAY_BARY_EPS: f64 = 1.0e-9;
const RAY_ORIGIN_LINE_SAMPLES: u32 = 4;
const RAY_ORIGIN_INTERIOR_GRID: u32 = 8;

fn vec_sub(a: (f64, f64, f64), b: (f64, f64, f64)) -> (f64, f64, f64) {
    (a.0 - b.0, a.1 - b.1, a.2 - b.2)
}

fn vec_cross(a: (f64, f64, f64), b: (f64, f64, f64)) -> (f64, f64, f64) {
    (
        a.1 * b.2 - a.2 * b.1,
        a.2 * b.0 - a.0 * b.2,
        a.0 * b.1 - a.1 * b.0,
    )
}

fn vec_dot(a: (f64, f64, f64), b: (f64, f64, f64)) -> f64 {
    a.0 * b.0 + a.1 * b.1 + a.2 * b.2
}

fn ray_triangle_distance(
    origin: (f64, f64, f64),
    direction: (f64, f64, f64),
    triangle: [(f64, f64, f64); 3],
) -> Option<f64> {
    let edge1 = vec_sub(triangle[1], triangle[0]);
    let edge2 = vec_sub(triangle[2], triangle[0]);
    let pvec = vec_cross(direction, edge2);
    let det = vec_dot(edge1, pvec);
    if det.abs() < RAY_DET_EPS {
        return None;
    }
    let inv_det = 1.0 / det;
    let tvec = vec_sub(origin, triangle[0]);
    let u = vec_dot(tvec, pvec) * inv_det;
    if !(-RAY_BARY_EPS..=1.0 + RAY_BARY_EPS).contains(&u) {
        return None;
    }
    let qvec = vec_cross(tvec, edge1);
    let v = vec_dot(direction, qvec) * inv_det;
    if v < -RAY_BARY_EPS || u + v > 1.0 + RAY_BARY_EPS {
        return None;
    }
    let distance = vec_dot(edge2, qvec) * inv_det;
    (distance > UP_TO_NEXT_SKIP_MM).then_some(distance)
}

fn boundary_sample_count(curve: Curve2Kind) -> u32 {
    match curve {
        Curve2Kind::Line(_) => RAY_ORIGIN_LINE_SAMPLES,
        Curve2Kind::Arc(_)
        | Curve2Kind::Circle(_)
        | Curve2Kind::Ellipse(_)
        | Curve2Kind::Bezier(_) => AREA_SAMPLES,
    }
}

fn interior_origin_samples(resolved: &[ResolvedLoop]) -> Vec<(f64, f64)> {
    let polygons: Vec<(LoopIndex, Vec<(f64, f64)>)> = resolved
        .iter()
        .map(|resolved_loop| {
            (
                resolved_loop.loop_index,
                loop_polygon(&resolved_loop.segments),
            )
        })
        .collect();
    let Some((_, outer)) = polygons
        .iter()
        .find(|(index, _)| *index == LoopIndex::OUTER)
    else {
        return Vec::new();
    };
    let (min_x, min_y, max_x, max_y) = outer.iter().fold(
        (
            f64::INFINITY,
            f64::INFINITY,
            f64::NEG_INFINITY,
            f64::NEG_INFINITY,
        ),
        |(lo_x, lo_y, hi_x, hi_y), &(x, y)| (lo_x.min(x), lo_y.min(y), hi_x.max(x), hi_y.max(y)),
    );
    let holes: Vec<&Vec<(f64, f64)>> = polygons
        .iter()
        .filter(|(index, _)| *index != LoopIndex::OUTER)
        .map(|(_, polygon)| polygon)
        .collect();
    let grid = RAY_ORIGIN_INTERIOR_GRID;
    (1..grid)
        .flat_map(move |i| (1..grid).map(move |j| (i, j)))
        .map(|(i, j)| {
            (
                min_x + (max_x - min_x) * f64::from(i) / f64::from(grid),
                min_y + (max_y - min_y) * f64::from(j) / f64::from(grid),
            )
        })
        .filter(|&point| {
            point_in_polygon(point, outer)
                && !holes.iter().any(|hole| point_in_polygon(point, hole))
        })
        .collect()
}

fn ray_origin_samples(resolved: &[ResolvedLoop]) -> Vec<(f64, f64)> {
    let boundary = resolved
        .iter()
        .flat_map(|resolved_loop| resolved_loop.segments.iter())
        .flat_map(|segment| {
            let curve = segment.curve;
            let count = boundary_sample_count(curve);
            (0..count).map(move |i| {
                let t = f64::from(i) / f64::from(count);
                curve.evaluate(Parameter::new(t)).coords_mm()
            })
        });
    boundary.chain(interior_origin_samples(resolved)).collect()
}

#[must_use]
pub fn next_face_distance(
    profile: &ExtrudeProfile,
    solids: &[&BrepSolid],
    sign: f64,
    base_offset_mm: f64,
) -> Option<f64> {
    let plane = profile.plane();
    let resolved = resolve_loops(profile.loops()).ok()?;
    let samples = ray_origin_samples(&resolved);
    if samples.is_empty() {
        return None;
    }
    let (ox, oy, oz) = plane.origin().coords_mm();
    let (xx, xy, xz) = plane.x_axis().components();
    let (yx, yy, yz) = plane.y_axis().components();
    let (nx, ny, nz) = plane.normal().components();
    let origins: Vec<(f64, f64, f64)> = samples
        .iter()
        .map(|&(u, v)| {
            (
                ox + u * xx + v * yx + base_offset_mm * nx,
                oy + u * xy + v * yy + base_offset_mm * ny,
                oz + u * xz + v * yz + base_offset_mm * nz,
            )
        })
        .collect();
    let direction = (sign * nx, sign * ny, sign * nz);
    let chord = ChordHeightTolerance::from_mm(UP_TO_NEXT_CHORD_MM);
    let angle = AngleTolerance::from_radians(0.1);
    let meshes = solids
        .iter()
        .map(|solid| solid.tessellate(chord, angle).ok())
        .collect::<Option<Vec<_>>>()?;
    meshes
        .iter()
        .flat_map(|mesh| mesh.faces().iter())
        .flat_map(|slab| {
            let positions = slab.positions().to_vec();
            let at = move |index: u32| {
                positions
                    .get(usize::try_from(index).ok()?)
                    .map(|point| point.coords_mm())
            };
            slab.triangles()
                .iter()
                .filter_map(move |triangle| {
                    Some([at(triangle[0])?, at(triangle[1])?, at(triangle[2])?])
                })
                .collect::<Vec<_>>()
        })
        .flat_map(|triangle| {
            origins
                .iter()
                .filter_map(move |&origin| ray_triangle_distance(origin, direction, triangle))
        })
        .fold(None, |best: Option<f64>, distance| {
            Some(best.map_or(distance, |current| current.min(distance)))
        })
}

pub struct RevolveScene<'a> {
    axis: Axis3,
    profile: Option<&'a ExtrudeProfile>,
    face_plane: Option<&'a dyn Fn(FaceLabel) -> Option<Plane3>>,
    vertex_point: Option<&'a dyn Fn(VertexLabel) -> Option<Point3>>,
}

impl<'a> RevolveScene<'a> {
    #[must_use]
    pub fn angles(axis: Axis3) -> Self {
        Self {
            axis,
            profile: None,
            face_plane: None,
            vertex_point: None,
        }
    }

    #[must_use]
    pub fn new(
        axis: Axis3,
        profile: &'a ExtrudeProfile,
        face_plane: &'a dyn Fn(FaceLabel) -> Option<Plane3>,
        vertex_point: &'a dyn Fn(VertexLabel) -> Option<Point3>,
    ) -> Self {
        Self {
            axis,
            profile: Some(profile),
            face_plane: Some(face_plane),
            vertex_point: Some(vertex_point),
        }
    }

    fn frame(&self) -> Result<AxisFrame, BrepError> {
        let profile = self.profile.ok_or(BrepError::EndConditionUnresolved)?;
        let resolved = resolve_loops(profile.loops())?;
        AxisFrame::build(profile.plane(), self.axis, &resolved)
    }

    fn face(&self, surface: FaceLabel) -> Result<Plane3, BrepError> {
        self.face_plane
            .and_then(|resolve| resolve(surface))
            .ok_or(BrepError::EndConditionUnresolved)
    }

    fn vertex(&self, vertex: VertexLabel) -> Result<Point3, BrepError> {
        self.vertex_point
            .and_then(|resolve| resolve(vertex))
            .ok_or(BrepError::EndConditionUnresolved)
    }
}

fn condition_needs_frame(condition: RevolveEndCondition) -> bool {
    !matches!(
        condition,
        RevolveEndCondition::Blind { .. } | RevolveEndCondition::MidPlane { .. }
    )
}

pub fn resolve_revolve(
    feature: &RevolveFeature,
    scene: &RevolveScene,
) -> Result<ResolvedRevolve, BrepError> {
    let thin = thin_wall_to_plan(feature.thin_wall);
    let needs_frame = condition_needs_frame(feature.end_condition)
        || feature
            .direction2
            .is_some_and(|second| condition_needs_frame(second.end_condition));
    let frame = needs_frame.then(|| scene.frame()).transpose()?;

    let (forward, backward) = if let RevolveEndCondition::MidPlane { .. } = feature.end_condition {
        if feature.direction2.is_some() {
            return Err(unsupported(TruckGap::MidPlaneDirection2));
        }
        let half = revolve_reach(feature.end_condition, scene, frame.as_ref(), 1.0)? / 2.0;
        (half, half)
    } else {
        let primary_sign = match feature.sense {
            ExtrudeSense::Forward => 1.0,
            ExtrudeSense::Reverse => -1.0,
        };
        let primary = revolve_reach(feature.end_condition, scene, frame.as_ref(), primary_sign)?;
        let secondary = feature
            .direction2
            .map(|second| {
                let sign = if second.reverse {
                    primary_sign
                } else {
                    -primary_sign
                };
                revolve_reach(second.end_condition, scene, frame.as_ref(), sign)
            })
            .transpose()?
            .unwrap_or(0.0);
        if primary_sign > 0.0 {
            (primary, secondary)
        } else {
            (secondary, primary)
        }
    };
    let total = forward + backward;
    if total <= ANGLE_EPS_RAD || total > TAU + ANGLE_EPS_RAD {
        return Err(BrepError::RevolveAngleOutOfRange);
    }
    Ok(ResolvedRevolve {
        axis: scene.axis,
        forward_rad: forward,
        backward_rad: backward,
        thin,
    })
}

fn revolve_reach(
    condition: RevolveEndCondition,
    scene: &RevolveScene,
    frame: Option<&AxisFrame>,
    sense: f64,
) -> Result<f64, BrepError> {
    match condition {
        RevolveEndCondition::Blind { angle } | RevolveEndCondition::MidPlane { angle } => {
            Ok(angle.radians())
        }
        RevolveEndCondition::UpToVertex { vertex } => {
            let frame = frame.ok_or(BrepError::EndConditionUnresolved)?;
            let azimuth = frame.angle_of(scene.vertex(vertex)?);
            normalize_sweep(-sense * azimuth).ok_or(BrepError::EndConditionBehindProfile)
        }
        RevolveEndCondition::UpToSurface { surface } => {
            let frame = frame.ok_or(BrepError::EndConditionUnresolved)?;
            let plane = scene.face(surface)?;
            let (a, b) = frame
                .plane_azimuths(plane)
                .ok_or_else(|| unsupported(TruckGap::UpToSurface))?;
            nearest_sweep(-sense, a, b).ok_or(BrepError::EndConditionBehindProfile)
        }
        RevolveEndCondition::OffsetFromSurface {
            surface,
            offset,
            reverse_offset,
        } => {
            let frame = frame.ok_or(BrepError::EndConditionUnresolved)?;
            let plane = scene.face(surface)?;
            let (a, b) = frame
                .plane_azimuths(plane)
                .ok_or_else(|| unsupported(TruckGap::OffsetFromSurface))?;
            let base = nearest_sweep(-sense, a, b).ok_or(BrepError::EndConditionBehindProfile)?;
            let signed = if reverse_offset {
                -offset.get::<radian>()
            } else {
                offset.get::<radian>()
            };
            normalize_sweep(base + signed).ok_or(BrepError::EndConditionBehindProfile)
        }
    }
}

pub fn evaluate_revolve(
    revolve: FeatureId,
    profile: &ExtrudeProfile,
    plan: &ResolvedRevolve,
) -> Result<BrepSolid, BrepError> {
    let total = plan.total_rad();
    if !(plan.forward_rad >= 0.0
        && plan.backward_rad >= 0.0
        && total > ANGLE_EPS_RAD
        && total <= TAU + ANGLE_EPS_RAD)
    {
        return Err(BrepError::RevolveAngleOutOfRange);
    }
    let plane = profile.plane();
    let augmented = match plan.thin {
        Some(thin) => Some(augment_thin(profile.loops(), thin)?),
        None => close_open_profile(profile.loops())?,
    };
    let loops = augmented.as_deref().unwrap_or_else(|| profile.loops());
    let resolved = resolve_loops(loops)?;
    let frame = AxisFrame::build(plane, plan.axis, &resolved)?;

    if let Some(chain) = pole_chain(&resolved, &frame) {
        if plan.thin.is_some() {
            return Err(unsupported(TruckGap::RevolvePoleThin));
        }
        if total < TAU - ANGLE_EPS_RAD {
            return Err(unsupported(TruckGap::RevolvePolePartial));
        }
        return build_pole_revolve(revolve, plane, plan, &frame, &resolved, &chain);
    }
    if frame.touches {
        return Err(BrepError::RevolveProfileTouchesAxis);
    }

    let mut book = Bookkeeping::default();
    let wires: Vec<Wire> = resolved
        .iter()
        .map(|resolved_loop| {
            let steps = loop_steps(&resolved_loop.segments, resolved_loop.reversed);
            build_loop(plane, 0.0, &steps, resolved_loop.loop_index, &mut book)
        })
        .collect();
    let attached = builder::try_attach_plane(&wires)
        .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?;
    let face = if revolve_seed_inverted(plane, &frame) {
        attached.inverse()
    } else {
        attached
    };

    let (ox, oy, oz) = plan.axis.origin().coords_mm();
    let origin = truck_modeling::Point3::new(ox, oy, oz);
    let (ax, ay, az) = plan.axis.direction().components();
    let axis_vec = Vector3::new(ax, ay, az);
    let placed = if plan.backward_rad > ANGLE_EPS_RAD {
        builder::rotated(&face, origin, axis_vec, Rad(-plan.backward_rad))
    } else {
        face
    };
    let solid = builder::rsweep(&placed, origin, axis_vec, Rad(plan.total_rad()));

    let curves = revolve_curve_table(&resolved);
    let corners = revolve_corner_pairs(&resolved);
    let labeling = label_revolve_solid(
        &solid,
        revolve,
        &frame,
        &curves,
        &corners,
        plan.forward_rad,
        plan.backward_rad,
    );
    let assembled = assemble(solid, &labeling)?;
    assembled.validate(Tolerance::new(REVOLVE_AXIS_TOLERANCE_MM))?;
    Ok(assembled)
}

const SWEEP_PERP_TOLERANCE: f64 = 1.0e-6;

#[derive(Copy, Clone, Default)]
pub struct SweepRails<'a> {
    pub guides: &'a [Vec<Curve3Kind>],
    pub direction: Option<UnitVec3>,
    pub tool: Option<&'a BrepSolid>,
    pub bodies: &'a [&'a BrepSolid],
}

#[derive(Copy, Clone)]
enum SectionProfile {
    Sketch,
    Circular(PositiveLength),
}

pub fn evaluate_sweep(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    path: &[Curve3Kind],
    rails: SweepRails<'_>,
    feature: &SweepFeature,
) -> Result<BrepSolid, BrepError> {
    let composite =
        CompositePath3::chain(path).map_err(|reason| BrepError::InvalidSweep { reason })?;
    let section = match feature.profile_kind {
        SweepProfileKind::Sketch => SectionProfile::Sketch,
        SweepProfileKind::Circular { diameter } => SectionProfile::Circular(diameter),
        SweepProfileKind::Solid { .. } => {
            return solid_tool_envelope(sweep, path, &composite, rails, feature);
        }
    };
    validate_sweep_config(feature, rails)?;
    let thin = thin_wall_to_plan(feature.thin_wall);
    if rails.guides.is_empty()
        && feature.twist.is_none()
        && matches!(feature.start_tangency, SweepTangency::None)
        && matches!(feature.end_tangency, SweepTangency::None)
        && let [segment] = path
        && reduction_preserves_orientation(feature.orientation, segment)
        && reduction_preserves_alignment(feature.alignment)
        && let Some(solid) =
            reduce_single(sweep, profile, segment, section, thin, feature.direction)?
    {
        return Ok(solid);
    }
    skin_sweep(sweep, profile, &composite, rails, feature, section)
}

fn validate_sweep_config(feature: &SweepFeature, rails: SweepRails<'_>) -> Result<(), BrepError> {
    let guides = rails.guides;
    if guides.iter().any(Vec::is_empty) {
        return Err(invalid_sweep(SweepDefect::GuideEmpty));
    }
    if guides.len() > 2 {
        return Err(unsupported(TruckGap::SweepGuideCount));
    }
    if guides.len() < feature.orientation.needs_guides() {
        return Err(invalid_sweep(SweepDefect::OrientationNeedsGuides));
    }
    if !guides.is_empty() {
        if matches!(feature.direction, SweepDirection::Bidirectional) {
            return Err(invalid_sweep(SweepDefect::GuideBidirectional));
        }
        if feature.twist.is_some() {
            return Err(invalid_sweep(SweepDefect::GuideTwist));
        }
        if matches!(feature.orientation, SweepOrientation::KeepNormalConstant) {
            return Err(invalid_sweep(SweepDefect::GuideKeepNormal));
        }
    }
    let tangent = matches!(feature.start_tangency, SweepTangency::PathTangent)
        || matches!(feature.end_tangency, SweepTangency::PathTangent);
    if tangent && matches!(feature.direction, SweepDirection::Bidirectional) {
        return Err(invalid_sweep(SweepDefect::TangencyBidirectional));
    }
    match feature.alignment {
        SweepAlignment::NonePlanar
        | SweepAlignment::DirectionVector { .. }
        | SweepAlignment::AllFaces
            if !matches!(feature.orientation, SweepOrientation::FollowPath) =>
        {
            Err(invalid_sweep(SweepDefect::AlignmentOrientation))
        }
        SweepAlignment::DirectionVector { .. } => rails
            .direction
            .map(|_| ())
            .ok_or(BrepError::SweepAlignmentUnresolved),
        SweepAlignment::AllFaces if rails.bodies.is_empty() => {
            Err(BrepError::SweepAlignmentUnresolved)
        }
        SweepAlignment::MinimumTwist | SweepAlignment::NonePlanar | SweepAlignment::AllFaces => {
            Ok(())
        }
    }
}

fn reduction_preserves_alignment(alignment: SweepAlignment) -> bool {
    match alignment {
        SweepAlignment::MinimumTwist | SweepAlignment::NonePlanar => true,
        SweepAlignment::DirectionVector { .. } | SweepAlignment::AllFaces => false,
    }
}

const PLANAR_PATH_SAMPLES: u32 = 64;
const ALIGNMENT_FACE_TOLERANCE_MM: f64 = 5.0e-2;

fn nearest_face_up(bodies: &[&BrepSolid], point: Point3) -> Option<UnitVec3> {
    bodies
        .iter()
        .filter_map(|body| body.nearest_face_normal(point))
        .min_by(|a, b| a.1.total_cmp(&b.1))
        .map(|(normal, _)| normal)
}

fn path_lies_on_face(path: &CompositePath3, bodies: &[&BrepSolid]) -> bool {
    !bodies.is_empty()
        && (0..=PLANAR_PATH_SAMPLES).all(|i| {
            let point = path.evaluate(Parameter::new(
                f64::from(i) / f64::from(PLANAR_PATH_SAMPLES),
            ));
            bodies
                .iter()
                .filter_map(|body| body.nearest_face_normal(point))
                .map(|(_, distance)| distance)
                .fold(f64::INFINITY, f64::min)
                <= ALIGNMENT_FACE_TOLERANCE_MM
        })
}

fn path_is_planar(composite: &CompositePath3) -> bool {
    let samples: Vec<Point3> = (0..=PLANAR_PATH_SAMPLES)
        .map(|i| {
            composite.evaluate(Parameter::new(
                f64::from(i) / f64::from(PLANAR_PATH_SAMPLES),
            ))
        })
        .collect();
    let origin = samples[0];
    let normal = samples
        .windows(2)
        .fold(Vec3::from_mm(0.0, 0.0, 0.0), |acc, pair| {
            acc + (pair[0] - origin).cross(pair[1] - origin)
        });
    let scale = samples
        .iter()
        .map(|point| (*point - origin).norm_mm())
        .fold(0.0_f64, f64::max);
    let Ok(unit) = normal.try_normalize(PROFILE_TOLERANCE) else {
        return true;
    };
    let along = |point: Point3| {
        let (x, y, z) = unit.components();
        let (dx, dy, dz) = (point - origin).coords_mm();
        x * dx + y * dy + z * dz
    };
    samples
        .iter()
        .all(|point| along(*point).abs() <= PROFILE_TOLERANCE_MM * scale.max(1.0))
}

fn alignment_clear_of_path(composite: &CompositePath3, axis: UnitVec3) -> bool {
    (0..=PLANAR_PATH_SAMPLES).all(|i| {
        let t = Parameter::new(f64::from(i) / f64::from(PLANAR_PATH_SAMPLES));
        composite
            .derivative(t)
            .try_normalize(PROFILE_TOLERANCE)
            .map_or(true, |tangent| {
                tangent.dot(axis).abs() < 1.0 - PROFILE_TOLERANCE_MM
            })
    })
}

fn reduction_preserves_orientation(orientation: SweepOrientation, segment: &Curve3Kind) -> bool {
    matches!(orientation, SweepOrientation::FollowPath) || matches!(segment, Curve3Kind::Line(_))
}

fn frame_guide(orientation: SweepOrientation) -> FrameGuide {
    match orientation {
        SweepOrientation::FollowPath
        | SweepOrientation::FollowPathAndFirstGuide
        | SweepOrientation::FollowFirstAndSecondGuide => FrameGuide::MinimumTwist,
        SweepOrientation::KeepNormalConstant => FrameGuide::KeepNormal,
    }
}

fn reduce_single(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    segment: &Curve3Kind,
    section: SectionProfile,
    thin: Option<ThinPlan>,
    direction: SweepDirection,
) -> Result<Option<BrepSolid>, BrepError> {
    match section {
        SectionProfile::Sketch => reduce_segment(sweep, profile, segment, thin, direction),
        SectionProfile::Circular(diameter) => {
            let synth = circular_profile(diameter, segment)?;
            reduce_segment(sweep, &synth, segment, thin, direction)
        }
    }
}

fn reduce_segment(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    segment: &Curve3Kind,
    thin: Option<ThinPlan>,
    direction: SweepDirection,
) -> Result<Option<BrepSolid>, BrepError> {
    match segment {
        Curve3Kind::Line(line) => sweep_along_line(sweep, profile, *line, thin, direction),
        Curve3Kind::Arc(arc) => sweep_along_arc(sweep, profile, *arc, thin, direction),
        Curve3Kind::Circle(circle) => {
            sweep_along_axis(sweep, profile, circle.center(), circle.normal(), TAU, thin)
        }
    }
}

const FULL_RING_TOLERANCE_RAD: f64 = 1.0e-3;

struct ToolRim {
    center: Point3,
    radius_mm: f64,
    sweep_rad: f64,
}

#[derive(Copy, Clone)]
struct PlainCylinder {
    base: Point3,
    axis: UnitVec3,
    radius_mm: f64,
    height_mm: f64,
}

impl PlainCylinder {
    fn along(self, travel: UnitVec3) -> Self {
        if self.axis.dot(travel) >= 0.0 {
            self
        } else {
            Self {
                base: self.base
                    + self
                        .axis
                        .into_vec(Length::new::<millimeter>(self.height_mm)),
                axis: self.axis.reversed(),
                ..self
            }
        }
    }
}

fn rim_circle(curve: &EdgeCurve3) -> Option<ToolRim> {
    let (center, radius_mm, sweep_rad) = rim_geometry(curve)?;
    Some(ToolRim {
        center,
        radius_mm,
        sweep_rad,
    })
}

fn recognize_plain_cylinder(tool: &BrepSolid) -> Option<PlainCylinder> {
    let (caps, walls): (Vec<&BrepFace>, Vec<&BrepFace>) = tool
        .iter_faces()
        .partition(|face| tool.face_plane_basis(face.id()).is_some());
    let ([wall], [_, _]) = (walls.as_slice(), caps.as_slice()) else {
        return None;
    };
    let rims: Vec<ToolRim> = tool
        .face_boundary_edges(wall.id())
        .into_iter()
        .filter_map(|edge| rim_circle(tool.edges.get(edge)?.curve()))
        .collect();
    let centers: Vec<Point3> = rims.iter().fold(Vec::new(), |mut acc, rim| {
        if !acc
            .iter()
            .any(|seen| (*seen - rim.center).norm_mm() <= PROFILE_TOLERANCE_MM)
        {
            acc.push(rim.center);
        }
        acc
    });
    let [center_a, center_b] = centers.as_slice() else {
        return None;
    };
    let radius_mm = rims.first().map(|rim| rim.radius_mm)?;
    let consistent = rims
        .iter()
        .all(|rim| (rim.radius_mm - radius_mm).abs() <= PROFILE_TOLERANCE_MM);
    let ring_sweep = |center: Point3| {
        rims.iter()
            .filter(|rim| (rim.center - center).norm_mm() <= PROFILE_TOLERANCE_MM)
            .map(|rim| rim.sweep_rad)
            .sum::<f64>()
    };
    let full = (ring_sweep(*center_a) - TAU).abs() <= FULL_RING_TOLERANCE_RAD
        && (ring_sweep(*center_b) - TAU).abs() <= FULL_RING_TOLERANCE_RAD;
    if !consistent || !full {
        return None;
    }
    let span = *center_b - *center_a;
    let axis = span.try_normalize(PROFILE_TOLERANCE).ok()?;
    Some(PlainCylinder {
        base: *center_a,
        axis,
        radius_mm,
        height_mm: span.norm_mm(),
    })
}

fn solid_tool_options_clear(feature: &SweepFeature, guides: &[Vec<Curve3Kind>]) -> bool {
    guides.is_empty()
        && feature.twist.is_none()
        && feature.thin_wall.is_none()
        && matches!(feature.orientation, SweepOrientation::FollowPath)
        && matches!(feature.alignment, SweepAlignment::MinimumTwist)
        && matches!(feature.direction, SweepDirection::DirectionOne)
        && matches!(feature.start_tangency, SweepTangency::None)
        && matches!(feature.end_tangency, SweepTangency::None)
}

fn solid_tool_envelope(
    sweep: FeatureId,
    path: &[Curve3Kind],
    composite: &CompositePath3,
    rails: SweepRails<'_>,
    feature: &SweepFeature,
) -> Result<BrepSolid, BrepError> {
    if let ExtrudeOperation::Boss(_) = feature.operation {
        return Err(invalid_sweep(SweepDefect::SolidToolBoss));
    }
    if composite.has_corner() {
        return Err(invalid_sweep(SweepDefect::SolidToolCorner));
    }
    if !solid_tool_options_clear(feature, rails.guides) {
        return Err(unsupported(TruckGap::SweepSolidEnvelope));
    }
    let tool = rails.tool.ok_or(BrepError::SweepToolUnresolved)?;
    let cylinder =
        recognize_plain_cylinder(tool).ok_or_else(|| unsupported(TruckGap::SweepSolidTool))?;
    let [Curve3Kind::Line(line)] = path else {
        return Err(unsupported(TruckGap::SweepSolidEnvelope));
    };
    let travel = (line.end() - line.start())
        .try_normalize(PROFILE_TOLERANCE)
        .map_err(|_| BrepError::EmptyExtrudeDepth)?;
    if cylinder.axis.dot(travel).abs() < 1.0 - SWEEP_PERP_TOLERANCE {
        return Err(unsupported(TruckGap::SweepSolidEnvelope));
    }
    let oriented = cylinder.along(travel);
    let along = project_onto_axis(oriented.base, oriented.axis, line.start());
    let on_body = (-PROFILE_TOLERANCE_MM..=oriented.height_mm + PROFILE_TOLERANCE_MM)
        .contains(&along)
        && radial_distance(oriented.base, oriented.axis, line.start())
            <= oriented.radius_mm + PROFILE_TOLERANCE_MM;
    if !on_body {
        return Err(invalid_sweep(SweepDefect::SolidToolOffBody));
    }
    let reach = Length::new::<millimeter>(oriented.height_mm + line.length_mm());
    let envelope = Line3::new(
        oriented.base,
        oriented.base + oriented.axis.into_vec(reach),
        PROFILE_TOLERANCE,
    )
    .map_err(|_| BrepError::EmptyExtrudeDepth)?
    .as_kind();
    let diameter = PositiveLength::new(Length::new::<millimeter>(2.0 * oriented.radius_mm))
        .map_err(|_| unsupported(TruckGap::SweepSolidTool))?;
    let synth = circular_profile(diameter, &envelope)?;
    reduce_segment(sweep, &synth, &envelope, None, SweepDirection::DirectionOne)?
        .ok_or_else(|| unsupported(TruckGap::SweepSolidEnvelope))
}

fn invalid_sweep(reason: SweepDefect) -> BrepError {
    BrepError::InvalidSweep { reason }
}

fn direction_spans(
    direction: SweepDirection,
    toward_end: f64,
    toward_start: f64,
    min_span: f64,
) -> Result<(f64, f64), BrepError> {
    let picked = match direction {
        SweepDirection::DirectionOne => (toward_end, 0.0),
        SweepDirection::DirectionTwo => (0.0, toward_start),
        SweepDirection::Bidirectional => (toward_end, toward_start),
    };
    if picked.0 <= min_span && picked.1 <= min_span {
        return Err(invalid_sweep(SweepDefect::EmptySpan));
    }
    Ok(picked)
}

fn profile_centroid(profile: &ExtrudeProfile) -> Point3 {
    let center = profile
        .loops()
        .iter()
        .flat_map(loop_curves)
        .map(|curve| curve.bounding_box())
        .reduce(|bounds, other| bounds.extend_point(other.min()).extend_point(other.max()))
        .map_or(Point2::origin(), |bounds| {
            let (min_x, min_y) = bounds.min().coords_mm();
            let (max_x, max_y) = bounds.max().coords_mm();
            Point2::from_mm(f64::midpoint(min_x, max_x), f64::midpoint(min_y, max_y))
        });
    let (cx, cy) = center.coords_mm();
    profile.plane().point_at_local(cx, cy, 0.0)
}

const PIERCE_SAMPLES: u32 = 1024;
const PIERCE_REFINE_STEPS: u32 = 48;

fn pierce_fraction(
    profile: &ExtrudeProfile,
    composite: &CompositePath3,
) -> Result<Parameter, BrepError> {
    let plane = profile.plane();
    let distance = |t: f64| plane_distance_mm(plane, composite.evaluate(Parameter::new(t)));
    if distance(0.0).abs() <= PROFILE_TOLERANCE_MM {
        return Ok(Parameter::new(0.0));
    }
    let samples: Vec<(f64, f64)> = (0..=PIERCE_SAMPLES)
        .map(|i| {
            let t = f64::from(i) / f64::from(PIERCE_SAMPLES);
            (t, distance(t))
        })
        .collect();
    let crossings = samples.windows(2).filter_map(|pair| {
        let (t0, d0) = pair[0];
        let (t1, d1) = pair[1];
        if d1.abs() <= PROFILE_TOLERANCE_MM {
            Some(t1)
        } else if d0 * d1 < 0.0 {
            let (lo, _, hi) = (0..PIERCE_REFINE_STEPS).fold((t0, d0, t1), |(lo, lo_d, hi), _| {
                let mid = f64::midpoint(lo, hi);
                let mid_d = distance(mid);
                if lo_d * mid_d <= 0.0 {
                    (lo, lo_d, mid)
                } else {
                    (mid, mid_d, hi)
                }
            });
            Some(f64::midpoint(lo, hi))
        } else {
            None
        }
    });
    let centroid = profile_centroid(profile);
    crossings
        .min_by(|a, b| {
            let miss = |t: f64| (composite.evaluate(Parameter::new(t)) - centroid).norm_mm();
            miss(*a).total_cmp(&miss(*b))
        })
        .map(Parameter::new)
        .ok_or_else(|| invalid_sweep(SweepDefect::PathMissesProfile))
}

fn directed_span(
    composite: &CompositePath3,
    direction: SweepDirection,
    pierce: Parameter,
) -> Result<(CompositePath3, Parameter), BrepError> {
    match direction {
        SweepDirection::DirectionOne if pierce.value() <= f64::EPSILON => {
            Ok((composite.clone(), Parameter::new(0.0)))
        }
        SweepDirection::DirectionOne => composite
            .trimmed(pierce, Parameter::new(1.0))
            .map(|path| (path, Parameter::new(0.0)))
            .ok_or_else(|| invalid_sweep(SweepDefect::EmptySpan)),
        SweepDirection::DirectionTwo => composite
            .trimmed(Parameter::new(0.0), pierce)
            .map(|path| (path.reversed(), Parameter::new(0.0)))
            .ok_or_else(|| invalid_sweep(SweepDefect::EmptySpan)),
        SweepDirection::Bidirectional => Ok((composite.clone(), pierce)),
    }
}

fn skin_sweep(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    composite: &CompositePath3,
    rails: SweepRails<'_>,
    feature: &SweepFeature,
    section: SectionProfile,
) -> Result<BrepSolid, BrepError> {
    let synthesized;
    let effective = match section {
        SectionProfile::Sketch => profile,
        SectionProfile::Circular(diameter) => {
            synthesized = circular_profile(diameter, composite)?;
            &synthesized
        }
    };
    if composite.is_closed() {
        return build_closed_sweep(sweep, effective, composite, feature, rails);
    }
    let pierce = pierce_fraction(effective, composite)?;
    let (path, origin) = directed_span(composite, feature.direction, pierce)?;
    let locked = match (feature.alignment, rails.direction) {
        (SweepAlignment::DirectionVector { .. }, Some(axis)) => {
            if !alignment_clear_of_path(&path, axis) {
                return Err(invalid_sweep(SweepDefect::AlignmentAlongPath));
            }
            Some(axis)
        }
        (SweepAlignment::NonePlanar, _) => {
            if !path_is_planar(&path) {
                return Err(invalid_sweep(SweepDefect::PathNotPlanar));
            }
            None
        }
        _ => None,
    };
    let face_aligned = matches!(feature.alignment, SweepAlignment::AllFaces);
    if path.has_corner() {
        if feature.thin_wall.is_some()
            || !rails.guides.is_empty()
            || feature.twist.is_some()
            || locked.is_some()
            || face_aligned
        {
            return Err(unsupported(TruckGap::SweepPathCorner));
        }
        return build_mitered_sweep(sweep, effective, &path, path.evaluate(origin));
    }
    if face_aligned && !path_lies_on_face(&path, rails.bodies) {
        return Err(BrepError::SweepAlignmentUnresolved);
    }
    validate_sweep_fit(effective, &path, origin)?;
    let guide_paths: Vec<CompositePath3> = rails
        .guides
        .iter()
        .map(|segments| CompositePath3::chain(segments).map_err(invalid_sweep))
        .collect::<Result<_, _>>()?;
    let up_fn = |point: Point3| nearest_face_up(rails.bodies, point);
    let plan = SkinPlan {
        stations: StationCount::DEFAULT,
        guide: locked.map_or_else(
            || frame_guide(feature.orientation),
            FrameGuide::DirectionLocked,
        ),
        twist_rad: feature.twist.map_or(0.0, SweepTwist::radians),
        origin,
        anchor: None,
        guides: &guide_paths,
        orientation: feature.orientation,
        tangency: (feature.start_tangency, feature.end_tangency),
        face_up: face_aligned.then_some(&up_fn as &dyn Fn(Point3) -> Option<UnitVec3>),
    };
    match thin_wall_to_plan(feature.thin_wall) {
        None => build_skinned_sweep(sweep, effective, &path, &plan),
        Some(thin) => build_thin_skin(sweep, effective, &path, &plan, thin),
    }
}

fn build_thin_skin(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    path: &CompositePath3,
    plan: &SkinPlan<'_>,
    thin: ThinPlan,
) -> Result<BrepSolid, BrepError> {
    if thin.cap_ends {
        return build_thin_capped_skin(sweep, profile, path, plan, thin);
    }
    let walls = thin_walls(profile.loops(), thin)?;
    let walled = ExtrudeProfile::new(profile.plane(), walls);
    build_skinned_sweep(sweep, &walled, path, plan)
}

fn capped_skin_plain(plan: &SkinPlan<'_>) -> bool {
    plan.twist_rad == 0.0
        && plan.guides.is_empty()
        && plan.face_up.is_none()
        && plan.origin.value().abs() <= f64::EPSILON
        && matches!(plan.guide, FrameGuide::MinimumTwist)
        && matches!(plan.tangency, (SweepTangency::None, SweepTangency::None))
}

fn merge_labelings(mut base: SolidLabeling, extra: SolidLabeling) -> SolidLabeling {
    base.faces.extend(extra.faces);
    base.edges.extend(extra.edges);
    base.vertices.extend(extra.vertices);
    base.closed_curves.extend(extra.closed_curves);
    base.edge_curves.extend(extra.edge_curves);
    base.face_fragments.extend(extra.face_fragments);
    base.edge_fragments.extend(extra.edge_fragments);
    base.vertex_fragments.extend(extra.vertex_fragments);
    base
}

const VOID_FRAGMENT: FragmentId = FragmentId::new(1);

fn fragmented(labeling: SolidLabeling, fragment: FragmentId) -> SolidLabeling {
    SolidLabeling {
        face_fragments: labeling
            .faces
            .keys()
            .map(|&face| (face, fragment))
            .collect(),
        ..labeling
    }
}

fn build_thin_capped_skin(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    path: &CompositePath3,
    plan: &SkinPlan<'_>,
    thin: ThinPlan,
) -> Result<BrepSolid, BrepError> {
    let [single] = profile.loops() else {
        return Err(unsupported(TruckGap::SweepThinCap));
    };
    if matches!(single, ProfileLoop::Chain(_)) || !capped_skin_plain(plan) || !path_is_planar(path)
    {
        return Err(unsupported(TruckGap::SweepThinCap));
    }
    let base = rounded_for_thin(single, thin)?;
    let outer_wall = make_wall(&base, thin.outer_mm, Wall::Outer)?;
    let inner_wall = make_wall(&base, -thin.inner_mm, Wall::Inner)?;
    let cap_mm = thin.outer_mm + thin.inner_mm;

    let outer_profile = ExtrudeProfile::new(profile.plane(), vec![outer_wall]);
    let (outer_shell, outer_labels) = oriented_skin_shell(sweep, &outer_profile, path, plan)?;

    let spine = SweepPath3::new(path, SampleCount::DEFAULT);
    let total_mm = spine.length().get::<millimeter>();
    if total_mm <= 2.0f64.mul_add(cap_mm, EXTRUDE_MIN_DEPTH_MM) {
        let solid = Solid::try_new(vec![outer_shell]).map_err(|_| BrepError::ShellNotClosed)?;
        return assemble_skin(solid, &outer_labels);
    }

    let inner_profile = ExtrudeProfile::new(profile.plane(), vec![inner_wall]);
    let inner_path = path
        .trimmed(
            spine.native_at(ArcFraction::new(cap_mm / total_mm)),
            spine.native_at(ArcFraction::new(1.0 - cap_mm / total_mm)),
        )
        .ok_or(BrepError::EmptyExtrudeDepth)?;
    let seed_anchor = profile
        .plane()
        .project(spine.point_at(spine.fraction_at(plan.origin)));
    let inner_plan = SkinPlan {
        origin: Parameter::new(0.0),
        anchor: Some(seed_anchor),
        stations: plan.stations,
        guide: plan.guide,
        twist_rad: plan.twist_rad,
        guides: plan.guides,
        orientation: plan.orientation,
        tangency: plan.tangency,
        face_up: plan.face_up,
    };
    let (inner_shell, inner_labels) =
        oriented_skin_shell(sweep, &inner_profile, &inner_path, &inner_plan)?;
    let void: Shell = inner_shell.face_iter().map(Face::inverse).collect();

    let solid = Solid::try_new(vec![outer_shell, void]).map_err(|_| BrepError::ShellNotClosed)?;
    let inner_labels = fragmented(inner_labels, VOID_FRAGMENT);
    assemble_skin(solid, &merge_labelings(outer_labels, inner_labels))
}

fn validate_sweep_fit(
    profile: &ExtrudeProfile,
    composite: &CompositePath3,
    origin: Parameter,
) -> Result<(), BrepError> {
    let spine = SweepPath3::new(composite, SampleCount::DEFAULT);
    let min_radius = spine.min_curvature_radius_mm(StationCount::DEFAULT);
    if !min_radius.is_finite() {
        return Ok(());
    }
    let anchor = profile
        .plane()
        .project(spine.point_at(spine.fraction_at(origin)));
    let Some(reach) = profile_reach_mm(profile, anchor) else {
        return Ok(());
    };
    if reach >= min_radius {
        return Err(BrepError::InvalidSweep {
            reason: SweepDefect::PathTooTight,
        });
    }
    Ok(())
}

fn control_hull_reach_mm(sections: &[EdgeSection], anchor: Point2) -> f64 {
    let (ax, ay) = anchor.coords_mm();
    sections
        .iter()
        .flat_map(|section| section.spline.control.iter())
        .map(|point| {
            let (px, py) = point.coords_mm();
            (px - ax).hypot(py - ay)
        })
        .fold(0.0_f64, f64::max)
}

fn point_distance_mm(a: Point2, b: Point2) -> f64 {
    let (ax, ay) = a.coords_mm();
    let (bx, by) = b.coords_mm();
    (bx - ax).hypot(by - ay)
}

fn curve_reach_mm(curve: Curve2Kind, anchor: Point2) -> f64 {
    match curve {
        Curve2Kind::Circle(circle) => {
            point_distance_mm(anchor, circle.center()) + circle.radius_mm()
        }
        Curve2Kind::Arc(arc) => point_distance_mm(anchor, arc.center()) + arc.radius_mm(),
        Curve2Kind::Ellipse(ellipse) => {
            point_distance_mm(anchor, ellipse.center()) + ellipse.semi_major_mm()
        }
        Curve2Kind::Line(line) => [Parameter::new(0.0), Parameter::new(1.0)]
            .into_iter()
            .map(|t| point_distance_mm(anchor, line.evaluate(t)))
            .fold(0.0_f64, f64::max),
        Curve2Kind::Bezier(bezier) => bezier
            .control()
            .into_iter()
            .map(|point| point_distance_mm(anchor, point))
            .fold(0.0_f64, f64::max),
    }
}

fn profile_reach_mm(profile: &ExtrudeProfile, anchor: Point2) -> Option<f64> {
    match profile.loops() {
        [ProfileLoop::Open(edges)] if edges.len() >= 2 => Some(
            edges
                .iter()
                .map(|edge| curve_reach_mm(edge.curve(), anchor))
                .fold(0.0_f64, f64::max),
        ),
        [ProfileLoop::Closed { curve, .. }] => Some(curve_reach_mm(*curve, anchor)),
        _ => None,
    }
}

fn synthetic_circle_entity() -> SketchEntityId {
    SketchEntityId::from(KeyData::from_ffi(1u64 << 32))
}

fn circular_profile<C: Curve3>(
    diameter: PositiveLength,
    curve: &C,
) -> Result<ExtrudeProfile, BrepError> {
    let start = curve.evaluate(Parameter::new(0.0));
    let tangent = curve
        .derivative(Parameter::new(0.0))
        .try_normalize(PROFILE_TOLERANCE)
        .map_err(|_| BrepError::EmptyExtrudeDepth)?;
    let (x_axis, y_axis) = tangent.orthonormal_basis();
    let plane = Plane3::new_unchecked(start, x_axis, y_axis);
    let circle = Circle2::new(Point2::origin(), diameter.get() * 0.5, PROFILE_TOLERANCE)
        .map_err(|_| invalid(ProfileDefect::ZeroArea))?;
    Ok(ExtrudeProfile::new(
        plane,
        vec![ProfileLoop::Closed {
            curve: Curve2Kind::Circle(circle),
            curve_entity: synthetic_circle_entity(),
        }],
    ))
}

fn plane_distance_mm(plane: Plane3, point: Point3) -> f64 {
    let (nx, ny, nz) = plane.normal().components();
    let (dx, dy, dz) = (point - plane.origin()).coords_mm();
    nx * dx + ny * dy + nz * dz
}

fn sweep_along_line(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    line: Line3,
    thin: Option<ThinPlan>,
    direction: SweepDirection,
) -> Result<Option<BrepSolid>, BrepError> {
    let normal = profile.plane().normal();
    let axis = (line.end() - line.start())
        .try_normalize(PROFILE_TOLERANCE)
        .map_err(|_| BrepError::EmptyExtrudeDepth)?;
    let alignment = axis.dot(normal);
    let end_is_forward = if alignment >= 1.0 - SWEEP_PERP_TOLERANCE {
        true
    } else if alignment <= -1.0 + SWEEP_PERP_TOLERANCE {
        false
    } else {
        return Ok(None);
    };
    let total_mm = line.length_mm();
    let pierce_mm = -plane_distance_mm(profile.plane(), line.start()) / alignment;
    if !(-PROFILE_TOLERANCE_MM..=total_mm + PROFILE_TOLERANCE_MM).contains(&pierce_mm) {
        return Err(invalid_sweep(SweepDefect::PathMissesProfile));
    }
    let toward_end = (total_mm - pierce_mm).max(0.0);
    let toward_start = pierce_mm.max(0.0);
    let (end_mm, start_mm) =
        direction_spans(direction, toward_end, toward_start, EXTRUDE_MIN_DEPTH_MM)?;
    let (forward_mm, backward_mm) = if end_is_forward {
        (end_mm, start_mm)
    } else {
        (start_mm, end_mm)
    };
    let step = |distance_mm: f64| {
        (distance_mm > EXTRUDE_MIN_DEPTH_MM).then_some(DirectionPlan {
            distance_mm,
            draft: None,
        })
    };
    let plan = ResolvedSweep {
        base_offset_mm: 0.0,
        forward: step(forward_mm),
        backward: step(backward_mm),
        thin,
    };
    evaluate_extrude_plan(sweep, profile, &plan).map(Some)
}

fn arc_pierce_param(plane: Plane3, arc: Arc3, centroid: Point3) -> Result<f64, BrepError> {
    let (nx, ny, nz) = plane.normal().components();
    let against = |axis: UnitVec3| {
        let (x, y, z) = axis.components();
        nx * x + ny * y + nz * z
    };
    let radial = against(arc.plane().x_axis());
    let transverse = against(arc.plane().y_axis());
    let theta = (-radial).atan2(transverse);
    let sweep = arc.sweep_rad();
    let angle_eps = PROFILE_TOLERANCE_MM / arc.radius_mm().max(PROFILE_TOLERANCE_MM);
    let param_eps = angle_eps / sweep.abs();
    let param_of = |angle: f64| {
        let wound = if sweep >= 0.0 {
            (angle - arc.start_rad()).rem_euclid(TAU)
        } else {
            (arc.start_rad() - angle).rem_euclid(TAU)
        };
        let delta = if wound >= TAU - angle_eps { 0.0 } else { wound };
        let u = delta / sweep.abs();
        (-param_eps..=1.0 + param_eps)
            .contains(&u)
            .then(|| u.clamp(0.0, 1.0))
    };
    [theta, theta + PI]
        .into_iter()
        .filter_map(param_of)
        .min_by(|a, b| {
            let miss = |u: f64| (arc.evaluate(Parameter::new(u)) - centroid).norm_mm();
            miss(*a).total_cmp(&miss(*b))
        })
        .ok_or_else(|| invalid_sweep(SweepDefect::PathMissesProfile))
}

fn sweep_along_arc(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    arc: Arc3,
    thin: Option<ThinPlan>,
    direction: SweepDirection,
) -> Result<Option<BrepSolid>, BrepError> {
    if thin.is_some_and(|thin| thin.cap_ends) {
        return Ok(None);
    }
    let plane = profile.plane();
    let coplanar = plane.normal().dot(arc.normal()).abs() <= SWEEP_PERP_TOLERANCE
        && plane_distance_mm(plane, arc.center()).abs() <= REVOLVE_AXIS_TOLERANCE_MM;
    if !coplanar {
        return Ok(None);
    }
    let pierce = arc_pierce_param(plane, arc, profile_centroid(profile))?;
    let sweep_rad = arc.sweep_rad();
    let toward_end = sweep_rad.abs() * (1.0 - pierce);
    let toward_start = sweep_rad.abs() * pierce;
    let min_span = EXTRUDE_MIN_DEPTH_MM / arc.radius_mm().max(PROFILE_TOLERANCE_MM);
    let (forward_rad, backward_rad) =
        direction_spans(direction, toward_end, toward_start, min_span)?;
    let clamp_span = |span: f64| if span > min_span { span } else { 0.0 };
    let (forward_rad, backward_rad) = (clamp_span(forward_rad), clamp_span(backward_rad));
    let axis_dir = if sweep_rad < 0.0 {
        arc.normal().reversed()
    } else {
        arc.normal()
    };
    let plan = ResolvedRevolve {
        axis: Axis3::new(arc.center(), axis_dir),
        forward_rad,
        backward_rad,
        thin,
    };
    evaluate_revolve(sweep, profile, &plan).map(Some)
}

fn sweep_along_axis(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    center: Point3,
    axis_dir: UnitVec3,
    sweep_rad: f64,
    thin: Option<ThinPlan>,
) -> Result<Option<BrepSolid>, BrepError> {
    let plane = profile.plane();
    let coplanar = plane.normal().dot(axis_dir).abs() <= SWEEP_PERP_TOLERANCE
        && plane_distance_mm(plane, center).abs() <= REVOLVE_AXIS_TOLERANCE_MM;
    if !coplanar {
        return Ok(None);
    }
    let direction = if sweep_rad < 0.0 {
        axis_dir.reversed()
    } else {
        axis_dir
    };
    let plan = ResolvedRevolve {
        axis: Axis3::new(center, direction),
        forward_rad: sweep_rad.abs(),
        backward_rad: 0.0,
        thin,
    };
    evaluate_revolve(sweep, profile, &plan).map(Some)
}

const SKIN_VALIDATION_TOLERANCE_MM: f64 = 1.0e-6;

struct EdgeSection {
    spline: SectionSpline,
    segment: SketchEntityId,
    corner: SketchEntityId,
}

fn profile_sections(profile: &ExtrudeProfile) -> Option<Vec<EdgeSection>> {
    match profile.loops() {
        [ProfileLoop::Open(edges)] if edges.len() >= 2 => Some(
            edges
                .iter()
                .map(|edge| EdgeSection {
                    spline: section_spline(edge.curve()),
                    segment: edge.curve_entity(),
                    corner: edge.corner(),
                })
                .collect(),
        ),
        [
            ProfileLoop::Closed {
                curve,
                curve_entity,
            },
        ] => closed_loop_sections(*curve, *curve_entity),
        _ => None,
    }
}

fn closed_loop_sections(curve: Curve2Kind, entity: SketchEntityId) -> Option<Vec<EdgeSection>> {
    match curve {
        Curve2Kind::Circle(circle) => quarter_sections(entity, |start, sweep| {
            circle_arc_spline(circle, start, sweep)
        }),
        Curve2Kind::Ellipse(ellipse) => quarter_sections(entity, |start, sweep| {
            ellipse_arc_spline(ellipse, start, sweep)
        }),
        Curve2Kind::Line(_) | Curve2Kind::Arc(_) | Curve2Kind::Bezier(_) => None,
    }
}

fn quarter_sections(
    entity: SketchEntityId,
    spline_of: impl Fn(Angle, Angle) -> SectionSpline,
) -> Option<Vec<EdgeSection>> {
    let quarter = TAU / 4.0;
    (0..4u32)
        .map(|index| {
            let (segment, corner) = arc_section_ids(entity, index)?;
            Some(EdgeSection {
                spline: spline_of(
                    Angle::new::<radian>(quarter * f64::from(index)),
                    Angle::new::<radian>(quarter),
                ),
                segment,
                corner,
            })
        })
        .collect()
}

struct LoopSections {
    loop_index: LoopIndex,
    sections: Vec<EdgeSection>,
}

fn loop_sections(
    profile: &ExtrudeProfile,
    resolved: &[ResolvedLoop],
) -> Result<Vec<LoopSections>, BrepError> {
    let multi_loop = || unsupported(TruckGap::SweepMultiLoopProfile);
    if resolved.len() <= 1 {
        let sections = profile_sections(profile).ok_or_else(multi_loop)?;
        return Ok(vec![LoopSections {
            loop_index: LoopIndex::OUTER,
            sections,
        }]);
    }
    resolved
        .iter()
        .map(|resolved_loop| {
            Ok(LoopSections {
                loop_index: resolved_loop.loop_index,
                sections: resolved_sections(resolved_loop).ok_or_else(multi_loop)?,
            })
        })
        .collect()
}

fn resolved_sections(resolved: &ResolvedLoop) -> Option<Vec<EdgeSection>> {
    let forward = match resolved.segments.as_slice() {
        [segment]
            if matches!(
                segment.curve,
                Curve2Kind::Circle(_) | Curve2Kind::Ellipse(_)
            ) =>
        {
            closed_loop_sections(segment.curve, segment.curve_entity)?
        }
        segments if segments.len() >= 2 => segments
            .iter()
            .map(|segment| EdgeSection {
                spline: section_spline(segment.curve),
                segment: segment.curve_entity,
                corner: segment.corner.unwrap_or(segment.curve_entity),
            })
            .collect(),
        _ => return None,
    };
    Some(if resolved.reversed {
        reverse_sections(&forward)
    } else {
        forward
    })
}

fn reverse_sections(sections: &[EdgeSection]) -> Vec<EdgeSection> {
    let count = sections.len();
    (0..count)
        .map(|index| EdgeSection {
            spline: reverse_section(&sections[count - 1 - index].spline),
            segment: sections[count - 1 - index].segment,
            corner: sections[(count - index) % count].corner,
        })
        .collect()
}

struct SkinPlan<'g> {
    stations: StationCount,
    guide: FrameGuide,
    twist_rad: f64,
    origin: Parameter,
    anchor: Option<Point2>,
    guides: &'g [CompositePath3],
    orientation: SweepOrientation,
    tangency: (SweepTangency, SweepTangency),
    face_up: Option<&'g dyn Fn(Point3) -> Option<UnitVec3>>,
}

#[derive(Copy, Clone)]
struct SectionMap {
    xx: f64,
    xy: f64,
    yx: f64,
    yy: f64,
}

impl SectionMap {
    const IDENTITY: Self = Self {
        xx: 1.0,
        xy: 0.0,
        yx: 0.0,
        yy: 1.0,
    };

    fn apply(self, u: f64, v: f64) -> (f64, f64) {
        (
            self.xx.mul_add(u, self.xy * v),
            self.yx.mul_add(u, self.yy * v),
        )
    }
}

struct GuideTrack {
    curve: CompositePath3,
    samples: Vec<(f64, Point3)>,
    cursor: usize,
}

const GUIDE_SAMPLES: u32 = 1024;
const GUIDE_REACH_STEPS: u32 = 256;
const GUIDE_REFINE_STEPS: u32 = 48;
const GUIDE_CONTACT_TOLERANCE_MM: f64 = 1.0e-3;

fn against_plane(origin: Point3, normal: UnitVec3, point: Point3) -> f64 {
    let (nx, ny, nz) = normal.components();
    let (dx, dy, dz) = (point - origin).coords_mm();
    nx * dx + ny * dy + nz * dz
}

impl GuideTrack {
    fn new(curve: &CompositePath3) -> Self {
        let samples = (0..=GUIDE_SAMPLES)
            .map(|i| {
                let t = f64::from(i) / f64::from(GUIDE_SAMPLES);
                (t, curve.evaluate(Parameter::new(t)))
            })
            .collect();
        Self {
            curve: curve.clone(),
            samples,
            cursor: 0,
        }
    }

    fn intersect(&mut self, origin: Point3, normal: UnitVec3) -> Option<Point3> {
        let distance = |point: Point3| against_plane(origin, normal, point);
        let bracket = (0..self.samples.len() - 1)
            .filter(|&i| {
                let d0 = distance(self.samples[i].1);
                let d1 = distance(self.samples[i + 1].1);
                d0 * d1 <= 0.0
                    || d0.abs() <= PROFILE_TOLERANCE_MM
                    || d1.abs() <= PROFILE_TOLERANCE_MM
            })
            .min_by_key(|&i| i.abs_diff(self.cursor))?;
        self.cursor = bracket;
        let (t0, p0) = self.samples[bracket];
        let (t1, _) = self.samples[bracket + 1];
        let d0 = distance(p0);
        let refined = (0..GUIDE_REFINE_STEPS).fold((t0, d0, t1), |(lo, lo_d, hi), _| {
            let mid = f64::midpoint(lo, hi);
            let mid_d = distance(self.curve.evaluate(Parameter::new(mid)));
            if lo_d * mid_d <= 0.0 {
                (lo, lo_d, mid)
            } else {
                (mid, mid_d, hi)
            }
        });
        let t = f64::midpoint(refined.0, refined.2);
        Some(self.curve.evaluate(Parameter::new(t)))
    }
}

fn guide_reach<C: Curve3>(spine: &SweepPath3<C>, guides: &mut [GuideTrack]) -> f64 {
    let station = |fraction: f64| {
        let at = ArcFraction::new(fraction);
        (spine.point_at(at), spine.tangent_at(at))
    };
    let all_hit = |fraction: f64, guides: &mut [GuideTrack]| {
        let (origin, tangent) = station(fraction);
        guides
            .iter_mut()
            .all(|guide| guide.intersect(origin, tangent).is_some())
    };
    let first_miss = (0..=GUIDE_REACH_STEPS)
        .map(|i| f64::from(i) / f64::from(GUIDE_REACH_STEPS))
        .find(|&fraction| !all_hit(fraction, guides));
    let Some(miss) = first_miss else {
        return 1.0;
    };
    if miss == 0.0 {
        return 0.0;
    }
    let step = 1.0 / f64::from(GUIDE_REACH_STEPS);
    let refined = (0..20).fold((miss - step, miss), |(ok, bad), _| {
        let mid = f64::midpoint(ok, bad);
        if all_hit(mid, guides) {
            (mid, bad)
        } else {
            (ok, mid)
        }
    });
    refined.0
}

struct GuideContact {
    track: GuideTrack,
    base: (f64, f64),
}

fn frame_coords(frame: &Frame, point: Point3) -> (f64, f64) {
    let offset = point - frame.origin();
    let along = |axis: UnitVec3| {
        let (x, y, z) = axis.components();
        let (dx, dy, dz) = offset.coords_mm();
        x * dx + y * dy + z * dz
    };
    (along(frame.normal()), along(frame.binormal()))
}

fn contact_on_profile(profile: &ExtrudeProfile, contact: Point2) -> bool {
    let tolerance = Tolerance::new(GUIDE_CONTACT_TOLERANCE_MM);
    profile.loops().iter().flat_map(loop_curves).any(|curve| {
        curve
            .closest_point(contact, tolerance)
            .distance()
            .get::<millimeter>()
            <= GUIDE_CONTACT_TOLERANCE_MM
    })
}

fn guide_contacts(
    profile: &ExtrudeProfile,
    anchor: Point2,
    guides: &[CompositePath3],
) -> Result<Vec<GuideContact>, BrepError> {
    let plane = profile.plane();
    guides
        .iter()
        .map(|guide| {
            let mut track = GuideTrack::new(guide);
            let touch = track
                .intersect(plane.origin(), plane.normal())
                .ok_or_else(|| invalid_sweep(SweepDefect::GuideMissesProfile))?;
            let contact = plane.project(touch);
            if !contact_on_profile(profile, contact) {
                return Err(invalid_sweep(SweepDefect::GuideMissesProfile));
            }
            let (cx, cy) = contact.coords_mm();
            let (ax, ay) = anchor.coords_mm();
            let base = (cx - ax, cy - ay);
            if base.0.hypot(base.1) <= GUIDE_CONTACT_TOLERANCE_MM {
                return Err(invalid_sweep(SweepDefect::GuideSpanDegenerate));
            }
            Ok(GuideContact { track, base })
        })
        .collect()
}

fn one_guide_map(base: (f64, f64), target: (f64, f64), rotate: bool) -> Option<SectionMap> {
    let base_len = base.0.hypot(base.1);
    let target_len = target.0.hypot(target.1);
    let scale = target_len / base_len;
    if scale <= 1.0e-6 {
        return None;
    }
    if !rotate {
        return Some(SectionMap {
            xx: scale,
            xy: 0.0,
            yx: 0.0,
            yy: scale,
        });
    }
    let cos = (base.0 * target.0 + base.1 * target.1) / (base_len * target_len);
    let sin = (base.0 * target.1 - base.1 * target.0) / (base_len * target_len);
    Some(SectionMap {
        xx: scale * cos,
        xy: -scale * sin,
        yx: scale * sin,
        yy: scale * cos,
    })
}

fn two_guide_map(
    first: ((f64, f64), (f64, f64)),
    second: ((f64, f64), (f64, f64)),
) -> Result<Option<SectionMap>, BrepError> {
    let (v0, v1) = first;
    let (w0, w1) = second;
    let det = v0.0.mul_add(w0.1, -(v0.1 * w0.0));
    let norm = v0.0.hypot(v0.1) * w0.0.hypot(w0.1);
    if det.abs() <= 1.0e-9 * norm.max(1.0e-12) {
        return Err(invalid_sweep(SweepDefect::GuideSpanDegenerate));
    }
    let inv = |a: f64, b: f64, c: f64, d: f64| (d / det, -b / det, -c / det, a / det);
    let (i00, i01, i10, i11) = inv(v0.0, w0.0, v0.1, w0.1);
    let map = SectionMap {
        xx: v1.0.mul_add(i00, w1.0 * i10),
        xy: v1.0.mul_add(i01, w1.0 * i11),
        yx: v1.1.mul_add(i00, w1.1 * i10),
        yy: v1.1.mul_add(i01, w1.1 * i11),
    };
    let map_det = map.xx.mul_add(map.yy, -(map.xy * map.yx));
    if map_det.abs() <= 1.0e-6 {
        return Ok(None);
    }
    Ok(Some(map))
}

fn solve_guide_maps(
    frames: &[Frame],
    contacts: &mut [GuideContact],
    orientation: SweepOrientation,
) -> Result<Vec<SectionMap>, BrepError> {
    frames
        .iter()
        .map(|frame| {
            let targets = contacts
                .iter_mut()
                .map(|contact| {
                    contact
                        .track
                        .intersect(frame.origin(), frame.tangent())
                        .map(|point| frame_coords(frame, point))
                })
                .collect::<Option<Vec<_>>>()
                .ok_or_else(|| invalid_sweep(SweepDefect::GuideCollapse))?;
            let map = match (targets.as_slice(), contacts.len()) {
                ([target], 1) => one_guide_map(
                    contacts[0].base,
                    *target,
                    matches!(orientation, SweepOrientation::FollowPathAndFirstGuide),
                ),
                ([first, second], 2) => {
                    two_guide_map((contacts[0].base, *first), (contacts[1].base, *second))?
                }
                _ => Some(SectionMap::IDENTITY),
            };
            map.ok_or_else(|| invalid_sweep(SweepDefect::GuideCollapse))
        })
        .collect()
}

enum RailSolver {
    Free(PathInterpolator),
    Clamped {
        interpolator: ClampedInterpolator,
        spacing: (f64, f64),
        start: Option<UnitVec3>,
        end: Option<UnitVec3>,
    },
}

struct SweepFrames {
    frames: Vec<Frame>,
    maps: Vec<SectionMap>,
    anchor: Point2,
    solver: RailSolver,
}

fn tangency_weight(tangency: (SweepTangency, SweepTangency), along: f64) -> f64 {
    let smoothstep = |x: f64| {
        let clamped = x.clamp(0.0, 1.0);
        clamped * clamped * (3.0 - 2.0 * clamped)
    };
    let toward = |active: SweepTangency, reach: f64| {
        matches!(active, SweepTangency::PathTangent)
            .then(|| smoothstep(reach))
            .unwrap_or(0.0)
    };
    toward(tangency.0, (0.5 - along) / 0.5).max(toward(tangency.1, (along - 0.5) / 0.5))
}

impl SweepFrames {
    fn new(
        profile: &ExtrudeProfile,
        path: &CompositePath3,
        plan: &SkinPlan<'_>,
    ) -> Result<Self, BrepError> {
        let plane = profile.plane();
        let spine = SweepPath3::new(path, SampleCount::DEFAULT);
        let seed_fraction = spine.fraction_at(plan.origin);
        let anchor = plan
            .anchor
            .unwrap_or_else(|| plane.project(spine.point_at(seed_fraction)));
        let mut contacts = guide_contacts(profile, anchor, plan.guides)?;
        let reach = if contacts.is_empty() {
            1.0
        } else {
            let mut tracks: Vec<GuideTrack> = plan.guides.iter().map(GuideTrack::new).collect();
            guide_reach(&spine, &mut tracks)
        };
        if reach <= f64::EPSILON {
            return Err(invalid_sweep(SweepDefect::EmptySpan));
        }
        let (base_fractions, seed_index) = station_fractions(seed_fraction, plan.stations);
        let fractions: Vec<f64> = base_fractions
            .iter()
            .map(|fraction| fraction * reach)
            .collect();
        let native: Vec<Frame> = match plan.face_up {
            Some(up) => fractions
                .iter()
                .map(|&fraction| {
                    let at = ArcFraction::new(fraction);
                    let origin = spine.point_at(at);
                    let tangent = spine.tangent_at(at);
                    let reference =
                        up(origin).unwrap_or_else(|| Frame::seed(origin, tangent, plane).normal());
                    Frame::locked(origin, tangent, reference)
                })
                .collect(),
            None => fractions
                .iter()
                .zip(transport_stations(
                    &spine, plane, &fractions, seed_index, plan.guide,
                ))
                .map(|(&fraction, frame)| {
                    frame.twist(plan.twist_rad * (fraction - seed_fraction.get()))
                })
                .collect(),
        };
        let span = fractions
            .last()
            .zip(fractions.first())
            .map_or(0.0, |(last, first)| last - first);
        let frames: Vec<Frame> = native
            .iter()
            .zip(&fractions)
            .map(|(frame, &fraction)| {
                let along = if span.abs() <= f64::EPSILON {
                    0.0
                } else {
                    (fraction - fractions[0]) / span
                };
                let weight = tangency_weight(plan.tangency, along);
                frame.blend_tangent(spine.tangent_at(ArcFraction::new(fraction)), weight)
            })
            .collect();
        let maps = if contacts.is_empty() {
            vec![SectionMap::IDENTITY; frames.len()]
        } else {
            solve_guide_maps(&frames, &mut contacts, plan.orientation)?
        };
        let centers: Vec<Point3> = frames.iter().map(|frame| frame.origin()).collect();
        let params = chord_params(&centers).ok_or(BrepError::EmptyExtrudeDepth)?;
        let solver = if plan.tangency == (SweepTangency::None, SweepTangency::None) {
            let degree = 3.min(frames.len().saturating_sub(1));
            RailSolver::Free(
                PathInterpolator::new(&params, degree).ok_or(BrepError::EmptyExtrudeDepth)?,
            )
        } else {
            let interpolator =
                ClampedInterpolator::new(&params).ok_or(BrepError::EmptyExtrudeDepth)?;
            let spacing = (
                params[1] - params[0],
                params[params.len() - 1] - params[params.len() - 2],
            );
            let constrained = |tangency: SweepTangency, fraction: Option<&f64>| {
                matches!(tangency, SweepTangency::PathTangent)
                    .then(|| fraction.map(|&fraction| spine.tangent_at(ArcFraction::new(fraction))))
                    .flatten()
            };
            RailSolver::Clamped {
                interpolator,
                spacing,
                start: constrained(plan.tangency.0, fractions.first()),
                end: constrained(plan.tangency.1, fractions.last()),
            }
        };
        Ok(Self {
            frames,
            maps,
            anchor,
            solver,
        })
    }

    fn rail(&self, point: Point2) -> Result<BSpline3, BrepError> {
        let (px, py) = point.coords_mm();
        let (ax, ay) = self.anchor.coords_mm();
        let stations: Vec<Point3> = self
            .frames
            .iter()
            .zip(&self.maps)
            .map(|(frame, map)| {
                let (u, v) = map.apply(px - ax, py - ay);
                frame.section_plane().point_at_local(u, v, 0.0)
            })
            .collect();
        match &self.solver {
            RailSolver::Free(interpolator) => interpolator
                .solve(&stations)
                .ok_or(BrepError::EmptyExtrudeDepth),
            RailSolver::Clamped {
                interpolator,
                spacing,
                start,
                end,
            } => {
                let length: f64 = stations
                    .windows(2)
                    .map(|pair| (pair[1] - pair[0]).norm_mm())
                    .sum();
                let along = |unit: UnitVec3| {
                    let (x, y, z) = unit.components();
                    Vec3::from_mm(x * length, y * length, z * length)
                };
                let start_d =
                    start.map_or_else(|| (stations[1] - stations[0]) * (1.0 / spacing.0), along);
                let end_d = end.map_or_else(
                    || {
                        (stations[stations.len() - 1] - stations[stations.len() - 2])
                            * (1.0 / spacing.1)
                    },
                    along,
                );
                interpolator
                    .solve(&stations, start_d, end_d)
                    .ok_or(BrepError::EmptyExtrudeDepth)
            }
        }
    }
}

fn homogeneous(point: truck_modeling::Point3, weight: f64) -> Vector4 {
    Vector4::new(point.x * weight, point.y * weight, point.z * weight, weight)
}

fn cap_positions(columns: &[BSpline3], at_end: bool) -> Vec<truck_modeling::Point3> {
    columns
        .iter()
        .map(|rail| {
            let control = rail.control();
            let point = if at_end {
                control[control.len() - 1]
            } else {
                control[0]
            };
            point_to_truck(point)
        })
        .collect()
}

fn section_edge(
    from: &Vertex,
    to: &Vertex,
    positions: &[truck_modeling::Point3],
    spline: &SectionSpline,
) -> Edge {
    if spline.degree <= 1 {
        builder::line(from, to)
    } else {
        let control: Vec<Vector4> = positions
            .iter()
            .zip(&spline.weights)
            .map(|(point, &weight)| homogeneous(*point, weight))
            .collect();
        let curve = Curve::NurbsCurve(NurbsCurve::new(BSplineCurve::new(
            KnotVec::from(spline.knots.clone()),
            control,
        )));
        Edge::debug_new(from, to, curve)
    }
}

fn lateral_surface(columns: &[BSpline3], spline: &SectionSpline, path_knots: &KnotVec) -> Surface {
    let net: Vec<Vec<Vector4>> = columns
        .iter()
        .zip(&spline.weights)
        .map(|(rail, &weight)| {
            rail.control()
                .iter()
                .map(|point| homogeneous(point_to_truck(*point), weight))
                .collect()
        })
        .collect();
    let section_knots = KnotVec::from(spline.knots.clone());
    Surface::NurbsSurface(NurbsSurface::new(BSplineSurface::new(
        (section_knots, path_knots.clone()),
        net,
    )))
}

struct LoopShell {
    lateral: Vec<Face>,
    section_start: Vec<Edge>,
    section_end: Vec<Edge>,
    rail_edges: Vec<Edge>,
    starts: Vec<Vertex>,
    ends: Vec<Vertex>,
}

struct SkinShell {
    base: Face,
    top: Face,
    loops: Vec<LoopShell>,
    tolerance: f64,
}

fn build_loop_shell(
    sections: &[EdgeSection],
    frames: &SweepFrames,
) -> Result<(LoopShell, Vec<BSpline3>), BrepError> {
    let count = sections.len();
    let corner_rails: Vec<BSpline3> = sections
        .iter()
        .map(|section| frames.rail(section.spline.control[0]))
        .collect::<Result<Vec<_>, _>>()?;
    let path_knots = KnotVec::from(corner_rails[0].knots().to_vec());
    let along_knots = path_knots.clone();

    let starts: Vec<Vertex> = corner_rails
        .iter()
        .map(|rail| builder::vertex(point_to_truck(rail.control()[0])))
        .collect();
    let ends: Vec<Vertex> = corner_rails
        .iter()
        .map(|rail| {
            let control = rail.control();
            builder::vertex(point_to_truck(control[control.len() - 1]))
        })
        .collect();
    let rail_edges: Vec<Edge> = corner_rails
        .iter()
        .enumerate()
        .map(|(index, rail)| {
            let control: Vec<truck_modeling::Point3> =
                rail.control().iter().copied().map(point_to_truck).collect();
            let curve = Curve::BSplineCurve(BSplineCurve::new(along_knots.clone(), control));
            Edge::debug_new(&starts[index], &ends[index], curve)
        })
        .collect();

    let columns: Vec<Vec<BSpline3>> = sections
        .iter()
        .enumerate()
        .map(|(index, section)| {
            let next = (index + 1) % count;
            let interior = section.spline.control[1..section.spline.control.len() - 1]
                .iter()
                .map(|point| frames.rail(*point))
                .collect::<Result<Vec<_>, _>>()?;
            let mut rails = Vec::with_capacity(interior.len() + 2);
            rails.push(corner_rails[index].clone());
            rails.extend(interior);
            rails.push(corner_rails[next].clone());
            Ok::<Vec<BSpline3>, BrepError>(rails)
        })
        .collect::<Result<Vec<_>, _>>()?;

    let section_start: Vec<Edge> = (0..count)
        .map(|index| {
            let next = (index + 1) % count;
            let positions = cap_positions(&columns[index], false);
            section_edge(
                &starts[index],
                &starts[next],
                &positions,
                &sections[index].spline,
            )
        })
        .collect();
    let section_end: Vec<Edge> = (0..count)
        .map(|index| {
            let next = (index + 1) % count;
            let positions = cap_positions(&columns[index], true);
            section_edge(
                &ends[index],
                &ends[next],
                &positions,
                &sections[index].spline,
            )
        })
        .collect();
    let lateral: Vec<Face> = (0..count)
        .map(|index| {
            let next = (index + 1) % count;
            let surface = lateral_surface(&columns[index], &sections[index].spline, &path_knots);
            let wire: Wire = vec![
                section_start[index].clone(),
                rail_edges[next].clone(),
                section_end[index].inverse(),
                rail_edges[index].inverse(),
            ]
            .into();
            Face::new(vec![wire], surface)
        })
        .collect();
    Ok((
        LoopShell {
            lateral,
            section_start,
            section_end,
            rail_edges,
            starts,
            ends,
        },
        corner_rails,
    ))
}

fn build_skin_shell(loops: &[LoopSections], frames: &SweepFrames) -> Result<SkinShell, BrepError> {
    let built: Vec<(LoopShell, Vec<BSpline3>)> = loops
        .iter()
        .map(|loop_sections| build_loop_shell(&loop_sections.sections, frames))
        .collect::<Result<_, _>>()?;
    let base_wires: Vec<Wire> = built
        .iter()
        .map(|(shell, _)| shell.section_start.clone().into())
        .collect();
    let top_wires: Vec<Wire> = built
        .iter()
        .map(|(shell, _)| shell.section_end.clone().into())
        .collect();
    let base = builder::try_attach_plane(&base_wires)
        .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?;
    let top = builder::try_attach_plane(&top_wires)
        .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?;
    let tolerance = coarse_mesh_tolerance(
        built
            .iter()
            .flat_map(|(_, rails)| rails.iter().flat_map(|rail| rail.control().iter().copied())),
    );
    Ok(SkinShell {
        base,
        top,
        loops: built.into_iter().map(|(shell, _)| shell).collect(),
        tolerance,
    })
}

fn label_skin(sweep: FeatureId, loops: &[LoopSections], parts: &SkinShell) -> SolidLabeling {
    let mut labels = Labels::new(sweep, HashSet::new());
    labels.face(parts.base.id(), FaceRole::StartCap);
    labels.face(parts.top.id(), FaceRole::EndCap);
    loops
        .iter()
        .zip(&parts.loops)
        .for_each(|(loop_sections, shell)| {
            let loop_index = loop_sections.loop_index;
            let sections = &loop_sections.sections;
            shell
                .lateral
                .iter()
                .zip(sections)
                .for_each(|(face, section)| {
                    labels.face(
                        face.id(),
                        FaceRole::Side {
                            loop_index,
                            from: section.segment,
                        },
                    );
                });
            sections.iter().enumerate().for_each(|(index, section)| {
                labels.edge(
                    shell.section_start[index].id(),
                    EdgeRole::StartCapEdge {
                        from: section.segment,
                    },
                );
                labels.edge(
                    shell.section_end[index].id(),
                    EdgeRole::EndCapEdge {
                        from: section.segment,
                    },
                );
                labels.edge(
                    shell.rail_edges[index].id(),
                    EdgeRole::SideEdge {
                        from: section.corner,
                        side: SideKind::Corner,
                    },
                );
                labels.vertex(
                    shell.starts[index].id(),
                    VertexRole::StartCapVertex {
                        from: section.corner,
                        side: SideKind::Corner,
                    },
                );
                labels.vertex(
                    shell.ends[index].id(),
                    VertexRole::EndCapVertex {
                        from: section.corner,
                        side: SideKind::Corner,
                    },
                );
            });
        });
    labels.into_labeling()
}

fn oriented_skin_shell(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    path: &CompositePath3,
    plan: &SkinPlan<'_>,
) -> Result<(Shell, SolidLabeling), BrepError> {
    let resolved = resolve_loops(profile.loops())?;
    let loops = loop_sections(profile, &resolved)?;
    let frames = SweepFrames::new(profile, path, plan)?;
    let parts = build_skin_shell(&loops, &frames)?;
    let labeling = label_skin(sweep, &loops, &parts);

    let mut faces: Vec<Face> = Vec::new();
    faces.push(parts.base.inverse());
    parts
        .loops
        .iter()
        .for_each(|shell| faces.extend(shell.lateral.iter().cloned()));
    faces.push(parts.top.clone());
    let shell: Shell = faces.into();
    let mut solid = Solid::try_new(vec![shell]).map_err(|_| BrepError::ShellNotClosed)?;
    if truck_solid_signed_volume(&solid, parts.tolerance) < 0.0 {
        solid.not();
    }
    let oriented = solid
        .into_boundaries()
        .into_iter()
        .next()
        .ok_or(BrepError::ShellNotClosed)?;
    Ok((oriented, labeling))
}

fn assemble_skin(solid: Solid, labeling: &SolidLabeling) -> Result<BrepSolid, BrepError> {
    let assembled = assemble(solid, labeling)?;
    assembled.validate(Tolerance::new(SKIN_VALIDATION_TOLERANCE_MM))?;
    Ok(assembled)
}

fn build_skinned_sweep(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    path: &CompositePath3,
    plan: &SkinPlan<'_>,
) -> Result<BrepSolid, BrepError> {
    let (shell, labeling) = oriented_skin_shell(sweep, profile, path, plan)?;
    let solid = Solid::try_new(vec![shell]).map_err(|_| BrepError::ShellNotClosed)?;
    assemble_skin(solid, &labeling)
}

fn coarse_mesh_tolerance(points: impl Iterator<Item = Point3>) -> f64 {
    let extent = points
        .map(|point| {
            let (x, y, z) = point.coords_mm();
            x.abs().max(y.abs()).max(z.abs())
        })
        .fold(0.0_f64, f64::max);
    (extent * 1.0e-2).max(SKIN_VALIDATION_TOLERANCE_MM)
}

const ARC_SECTION_SHIFT: u32 = 52;
const ARC_CORNER_BIT: u64 = 1u64 << 55;
const MITER_CORNER_SHIFT: u32 = 20;
const MITER_RING_SHIFT: u32 = 26;
const MITER_RAIL_BIT: u64 = 1u64 << 31;
const MITER_TAG_MASK: u64 = 0xFFF0_0000;
const MITER_MAX_CORNERS: usize = 64;
const MITER_MAX_RINGS: usize = 32;
const WALL_TAG_OUTER: u64 = 1u64 << 62;
const WALL_TAG_INNER: u64 = 1u64 << 61;
const WALL_TAG_CAP_START: u64 = 1u64 << 60;
const WALL_TAG_CAP_END: u64 = 1u64 << 59;
const WALL_TAG_CLOSE: u64 = 1u64 << 58;
const WALL_TAG_FILLET: u64 = 1u64 << 57;
const WALL_TAG_MASK: u64 =
    WALL_TAG_OUTER | WALL_TAG_INNER | WALL_TAG_CAP_START | WALL_TAG_CAP_END | WALL_TAG_CLOSE;

fn arc_section_ids(entity: SketchEntityId, index: u32) -> Option<(SketchEntityId, SketchEntityId)> {
    let raw = entity.data().as_ffi();
    if raw >> ARC_SECTION_SHIFT != 0 {
        return None;
    }
    let base = raw | (u64::from(index + 1) << ARC_SECTION_SHIFT);
    Some((
        SketchEntityId::from(KeyData::from_ffi(base)),
        SketchEntityId::from(KeyData::from_ffi(base | ARC_CORNER_BIT)),
    ))
}

fn miter_id(base: SketchEntityId, rail: bool, ring: u8, corner: u8) -> SketchEntityId {
    let raw = base.data().as_ffi();
    debug_assert!(
        raw & MITER_TAG_MASK == 0,
        "miter tag bits must be free in a section entity key"
    );
    debug_assert!(
        usize::from(ring) < MITER_MAX_RINGS,
        "miter ring index fits its tag field"
    );
    debug_assert!(
        usize::from(corner) < MITER_MAX_CORNERS,
        "miter corner index fits its tag field"
    );
    let tag = (u64::from(ring) << MITER_RING_SHIFT) | (u64::from(corner) << MITER_CORNER_SHIFT);
    let flagged = if rail { tag | MITER_RAIL_BIT } else { tag };
    SketchEntityId::from(KeyData::from_ffi(raw | flagged))
}

fn validate_miter_capacity(sections: &[EdgeSection], segments: usize) -> Result<(), BrepError> {
    let taggable = |entity: SketchEntityId| entity.data().as_ffi() & MITER_TAG_MASK == 0;
    let within = sections.len() <= MITER_MAX_CORNERS
        && segments < MITER_MAX_RINGS
        && sections
            .iter()
            .all(|section| taggable(section.segment) && taggable(section.corner));
    if within {
        Ok(())
    } else {
        Err(unsupported(TruckGap::SweepPathCorner))
    }
}

#[derive(Copy, Clone)]
struct MiterPlane {
    apex: Point3,
    normal: UnitVec3,
    along: UnitVec3,
}

fn corner_too_tight() -> BrepError {
    BrepError::InvalidSweep {
        reason: SweepDefect::CornerTooTight,
    }
}

fn project_to_miter(point: Point3, plane: MiterPlane) -> Point3 {
    let (px, py, pz) = point.coords_mm();
    let (ax, ay, az) = plane.apex.coords_mm();
    let (mx, my, mz) = plane.normal.components();
    let (tx, ty, tz) = plane.along.components();
    let numerator = (px - ax) * mx + (py - ay) * my + (pz - az) * mz;
    let denominator = tx * mx + ty * my + tz * mz;
    debug_assert!(
        denominator > 0.0,
        "a validated miter plane faces its incoming segment"
    );
    let step = -numerator / denominator;
    Point3::from_mm(
        step.mul_add(tx, px),
        step.mul_add(ty, py),
        step.mul_add(tz, pz),
    )
}

struct MiterBoundary {
    corner_vertices: Vec<Vertex>,
    section_edges: Vec<Edge>,
    section_nets: Vec<Vec<truck_modeling::Point3>>,
}

fn build_miter_boundary(
    sections: &[EdgeSection],
    frame: Frame,
    anchor: Point2,
    miter: Option<MiterPlane>,
) -> MiterBoundary {
    let count = sections.len();
    let place = |local: Point2| {
        let perpendicular = frame.place_point(local, anchor);
        match miter {
            None => perpendicular,
            Some(plane) => project_to_miter(perpendicular, plane),
        }
    };
    let corner_points: Vec<Point3> = sections
        .iter()
        .map(|section| place(section.spline.control[0]))
        .collect();
    let corner_vertices: Vec<Vertex> = corner_points
        .iter()
        .map(|point| builder::vertex(point_to_truck(*point)))
        .collect();
    let section_nets: Vec<Vec<truck_modeling::Point3>> = sections
        .iter()
        .enumerate()
        .map(|(index, section)| {
            let next = (index + 1) % count;
            let control = &section.spline.control;
            core::iter::once(corner_points[index])
                .chain(
                    control[1..control.len() - 1]
                        .iter()
                        .map(|&local| place(local)),
                )
                .chain(core::iter::once(corner_points[next]))
                .map(point_to_truck)
                .collect()
        })
        .collect();
    let section_edges: Vec<Edge> = sections
        .iter()
        .enumerate()
        .map(|(index, section)| {
            let next = (index + 1) % count;
            section_edge(
                &corner_vertices[index],
                &corner_vertices[next],
                &section_nets[index],
                &section.spline,
            )
        })
        .collect();
    MiterBoundary {
        corner_vertices,
        section_edges,
        section_nets,
    }
}

fn run_tangent(run: &CompositePath3, at: f64) -> Result<UnitVec3, BrepError> {
    run.derivative(Parameter::new(at))
        .try_normalize(PROFILE_TOLERANCE)
        .map_err(|_| BrepError::EmptyExtrudeDepth)
}

fn corner_miter_plane(
    apex: Point3,
    incoming: UnitVec3,
    outgoing: UnitVec3,
) -> Result<(MiterPlane, f64), BrepError> {
    let (ix, iy, iz) = incoming.components();
    let (ox, oy, oz) = outgoing.components();
    let sum = Vec3::from_mm(ix + ox, iy + oy, iz + oz);
    let diff = Vec3::from_mm(ix - ox, iy - oy, iz - oz);
    let normal = sum
        .try_normalize(PROFILE_TOLERANCE)
        .map_err(|_| corner_too_tight())?;
    let bite_ratio = diff.norm_mm() / sum.norm_mm();
    Ok((
        MiterPlane {
            apex,
            normal,
            along: incoming,
        },
        bite_ratio,
    ))
}

const MITER_RUN_STATIONS: u32 = 24;

fn propagate_run(
    run: &CompositePath3,
    start_frame: Frame,
    end_tangent: UnitVec3,
) -> (Vec<Frame>, Frame) {
    if run.is_polyline() {
        let end = run.evaluate(Parameter::new(1.0));
        return (Vec::new(), start_frame.propagate(end, end_tangent));
    }
    let spine = SweepPath3::new(run, SampleCount::new(64));
    let interior: Vec<Frame> = (1..MITER_RUN_STATIONS)
        .scan(start_frame, |carried, index| {
            let at = ArcFraction::new(f64::from(index) / f64::from(MITER_RUN_STATIONS));
            *carried = carried.propagate(spine.point_at(at), spine.tangent_at(at));
            Some(*carried)
        })
        .collect();
    let last = interior.last().copied().unwrap_or(start_frame);
    let end_frame = last.propagate(run.evaluate(Parameter::new(1.0)), end_tangent);
    (interior, end_frame)
}

fn skin_run(
    sections: &[EdgeSection],
    interior: &[Frame],
    start_frame: Frame,
    end_point: Point3,
    ring_a: &MiterBoundary,
    ring_b: &MiterBoundary,
    anchor: Point2,
) -> Result<(Vec<Edge>, Vec<Face>), BrepError> {
    let count = sections.len();
    let centers: Vec<Point3> = core::iter::once(start_frame.origin())
        .chain(interior.iter().map(|frame| frame.origin()))
        .chain(core::iter::once(end_point))
        .collect();
    let params = chord_params(&centers).ok_or(BrepError::EmptyExtrudeDepth)?;
    let degree = 3.min(centers.len().saturating_sub(1));
    let interpolator =
        PathInterpolator::new(&params, degree).ok_or(BrepError::EmptyExtrudeDepth)?;

    let columns: Vec<Vec<BSpline3>> = sections
        .iter()
        .enumerate()
        .map(|(section_index, section)| {
            (0..section.spline.control.len())
                .map(|control_index| {
                    let start = point_from_truck(ring_a.section_nets[section_index][control_index]);
                    let end = point_from_truck(ring_b.section_nets[section_index][control_index]);
                    let mid = interior.iter().map(|frame| {
                        frame.place_point(section.spline.control[control_index], anchor)
                    });
                    let column: Vec<Point3> = core::iter::once(start)
                        .chain(mid)
                        .chain(core::iter::once(end))
                        .collect();
                    interpolator
                        .solve(&column)
                        .ok_or(BrepError::EmptyExtrudeDepth)
                })
                .collect::<Result<Vec<_>, _>>()
        })
        .collect::<Result<Vec<_>, _>>()?;

    let path_knots = KnotVec::from(columns[0][0].knots().to_vec());
    let rails: Vec<Edge> = (0..count)
        .map(|corner| {
            let corner_rail = &columns[corner][0];
            let control: Vec<truck_modeling::Point3> = corner_rail
                .control()
                .iter()
                .copied()
                .map(point_to_truck)
                .collect();
            let curve = Curve::BSplineCurve(BSplineCurve::new(path_knots.clone(), control));
            Edge::debug_new(
                &ring_a.corner_vertices[corner],
                &ring_b.corner_vertices[corner],
                curve,
            )
        })
        .collect();
    let lateral: Vec<Face> = (0..count)
        .map(|section_index| {
            let next = (section_index + 1) % count;
            let surface = lateral_surface(
                &columns[section_index],
                &sections[section_index].spline,
                &path_knots,
            );
            let wire: Wire = vec![
                ring_a.section_edges[section_index].clone(),
                rails[next].clone(),
                ring_b.section_edges[section_index].inverse(),
                rails[section_index].inverse(),
            ]
            .into();
            Face::new(vec![wire], surface)
        })
        .collect();
    Ok((rails, lateral))
}

fn build_mitered_sweep(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    path: &CompositePath3,
    origin: Point3,
) -> Result<BrepSolid, BrepError> {
    resolve_loops(profile.loops())?;
    let sections = profile_sections(profile).ok_or(unsupported(TruckGap::SweepMultiLoopProfile))?;
    let runs = path.runs();
    let segments = runs.len();
    validate_miter_capacity(&sections, segments)?;
    let plane = profile.plane();
    let anchor = plane.project(origin);
    let extent = control_hull_reach_mm(&sections, anchor);

    let boundary_points: Vec<Point3> = core::iter::once(runs[0].evaluate(Parameter::new(0.0)))
        .chain(runs.iter().map(|run| run.evaluate(Parameter::new(1.0))))
        .collect();
    let start_tangents: Vec<UnitVec3> = runs
        .iter()
        .map(|run| run_tangent(run, 0.0))
        .collect::<Result<_, _>>()?;
    let end_tangents: Vec<UnitVec3> = runs
        .iter()
        .map(|run| run_tangent(run, 1.0))
        .collect::<Result<_, _>>()?;
    let run_lengths: Vec<f64> = (0..segments)
        .map(|run| (boundary_points[run + 1] - boundary_points[run]).norm_mm())
        .collect();

    let bitten: Vec<(MiterPlane, f64)> = (1..segments)
        .map(|corner| {
            let (miter, ratio) = corner_miter_plane(
                boundary_points[corner],
                end_tangents[corner - 1],
                start_tangents[corner],
            )?;
            Ok((miter, extent * ratio))
        })
        .collect::<Result<_, _>>()?;
    let fits = (0..segments).all(|run| {
        let start_bite = run.checked_sub(1).map_or(0.0, |corner| bitten[corner].1);
        let end_bite = bitten.get(run).map_or(0.0, |(_, bite)| *bite);
        start_bite + end_bite < run_lengths[run]
    });
    if !fits {
        return Err(corner_too_tight());
    }
    let planes: Vec<MiterPlane> = bitten.into_iter().map(|(plane, _)| plane).collect();

    let seed_frame = Frame::seed(boundary_points[0], start_tangents[0], plane);
    let mut rings: Vec<MiterBoundary> =
        vec![build_miter_boundary(&sections, seed_frame, anchor, None)];
    let mut run_parts: Vec<(Vec<Edge>, Vec<Face>)> = Vec::with_capacity(segments);
    let mut carried = seed_frame;
    for run in 0..segments {
        let start_frame = carried.reorient(start_tangents[run]);
        let (interior, end_frame) = propagate_run(&runs[run], start_frame, end_tangents[run]);
        let end_miter = (run + 1 < segments).then(|| planes[run]);
        rings.push(build_miter_boundary(
            &sections, end_frame, anchor, end_miter,
        ));
        run_parts.push(skin_run(
            &sections,
            &interior,
            start_frame,
            boundary_points[run + 1],
            &rings[run],
            &rings[run + 1],
            anchor,
        )?);
        carried = end_frame;
    }
    let rails: Vec<Vec<Edge>> = run_parts.iter().map(|(rails, _)| rails.clone()).collect();
    let lateral: Vec<Vec<Face>> = run_parts.iter().map(|(_, faces)| faces.clone()).collect();

    let base = builder::try_attach_plane(&[rings[0].section_edges.clone().into()])
        .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?;
    let top = builder::try_attach_plane(&[rings[segments].section_edges.clone().into()])
        .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?;

    let labeling = label_mitered(sweep, &sections, &rings, &rails, &lateral, &base, &top);

    let mut faces: Vec<Face> = Vec::new();
    faces.push(base.inverse());
    faces.extend(lateral.iter().flat_map(|run| run.iter().cloned()));
    faces.push(top.clone());
    let shell: Shell = faces.into();
    let mut solid = Solid::try_new(vec![shell]).map_err(|_| BrepError::ShellNotClosed)?;
    if truck_solid_signed_volume(
        &solid,
        coarse_mesh_tolerance(boundary_points.iter().copied()),
    ) < 0.0
    {
        solid.not();
    }
    let assembled = assemble(solid, &labeling)?;
    assembled.validate(Tolerance::new(SKIN_VALIDATION_TOLERANCE_MM))?;
    Ok(assembled)
}

const CLOSED_PIECES: u32 = 2;
const CLOSED_PIECE_STATIONS: u32 = 32;

fn build_closed_sweep(
    sweep: FeatureId,
    profile: &ExtrudeProfile,
    composite: &CompositePath3,
    feature: &SweepFeature,
    rails: SweepRails<'_>,
) -> Result<BrepSolid, BrepError> {
    if feature.thin_wall.is_some()
        || !rails.guides.is_empty()
        || feature.twist.is_some()
        || feature.orientation != SweepOrientation::FollowPath
        || !matches!(
            feature.alignment,
            SweepAlignment::MinimumTwist | SweepAlignment::NonePlanar
        )
        || composite.has_corner()
    {
        return Err(unsupported(TruckGap::SweepClosedPath));
    }
    resolve_loops(profile.loops())?;
    let sections = profile_sections(profile).ok_or(unsupported(TruckGap::SweepMultiLoopProfile))?;
    validate_miter_capacity(&sections, usize::try_from(CLOSED_PIECES).unwrap_or(0))?;
    if !path_is_planar(composite) {
        return Err(unsupported(TruckGap::SweepClosedPath));
    }
    let pierce = pierce_fraction(profile, composite)?;
    let anchor = profile.plane().project(composite.evaluate(pierce));

    let spine = SweepPath3::new(composite, SampleCount::DEFAULT);
    let ring_frame = |arc: f64| {
        let at = ArcFraction::new(arc);
        Frame::seed(spine.point_at(at), spine.tangent_at(at), profile.plane())
    };
    let boundary = |piece: u32| f64::from(piece) / f64::from(CLOSED_PIECES);
    let rings: Vec<MiterBoundary> = (0..CLOSED_PIECES)
        .map(|piece| build_miter_boundary(&sections, ring_frame(boundary(piece)), anchor, None))
        .collect();

    let parts: Vec<(Vec<Edge>, Vec<Face>)> = (0..CLOSED_PIECES)
        .map(|piece| {
            let start = boundary(piece);
            let end = boundary(piece + 1);
            let start_frame = ring_frame(start);
            let interior: Vec<Frame> = (1..CLOSED_PIECE_STATIONS)
                .map(|station| {
                    let fraction = f64::from(station) / f64::from(CLOSED_PIECE_STATIONS);
                    ring_frame((end - start).mul_add(fraction, start))
                })
                .collect();
            let end_point = spine.point_at(ArcFraction::new(end.min(1.0)));
            let next = usize::try_from((piece + 1) % CLOSED_PIECES).unwrap_or(0);
            let here = usize::try_from(piece).unwrap_or(0);
            skin_run(
                &sections,
                &interior,
                start_frame,
                end_point,
                &rings[here],
                &rings[next],
                anchor,
            )
        })
        .collect::<Result<_, _>>()?;
    let piece_rails: Vec<Vec<Edge>> = parts.iter().map(|(rails, _)| rails.clone()).collect();
    let lateral: Vec<Vec<Face>> = parts.iter().map(|(_, faces)| faces.clone()).collect();

    let labeling = label_closed(sweep, &sections, &rings, &piece_rails, &lateral);
    let faces: Vec<Face> = lateral.iter().flat_map(|run| run.iter().cloned()).collect();
    let shell: Shell = faces.into();
    let mut solid = Solid::try_new(vec![shell]).map_err(|_| BrepError::ShellNotClosed)?;
    let tolerance = coarse_mesh_tolerance(
        (0..=CLOSED_PIECES).map(|piece| spine.point_at(ArcFraction::new(boundary(piece).min(1.0)))),
    );
    if truck_solid_signed_volume(&solid, tolerance) < 0.0 {
        solid.not();
    }
    let assembled = assemble(solid, &labeling)?;
    assembled.validate(Tolerance::new(SKIN_VALIDATION_TOLERANCE_MM))?;
    Ok(assembled)
}

fn label_closed(
    sweep: FeatureId,
    sections: &[EdgeSection],
    rings: &[MiterBoundary],
    rails: &[Vec<Edge>],
    lateral: &[Vec<Face>],
) -> SolidLabeling {
    let mut labels = Labels::new(sweep, HashSet::new());
    (0u8..).zip(lateral).for_each(|(piece, run)| {
        (0u8..).zip(run).for_each(|(corner, face)| {
            labels.face(
                face.id(),
                FaceRole::Side {
                    loop_index: LoopIndex::OUTER,
                    from: miter_id(sections[usize::from(corner)].segment, false, piece, corner),
                },
            );
        });
    });
    (0u8..).zip(rails).for_each(|(piece, run)| {
        (0u8..).zip(run).for_each(|(corner, rail)| {
            labels.edge(
                rail.id(),
                EdgeRole::SideEdge {
                    from: miter_id(sections[usize::from(corner)].corner, true, piece, corner),
                    side: SideKind::Corner,
                },
            );
        });
    });
    (0u8..).zip(rings).for_each(|(ring, boundary)| {
        (0u8..)
            .zip(&boundary.section_edges)
            .for_each(|(corner, edge)| {
                labels.edge(
                    edge.id(),
                    EdgeRole::SideEdge {
                        from: miter_id(sections[usize::from(corner)].segment, false, ring, corner),
                        side: SideKind::Corner,
                    },
                );
            });
        (0u8..)
            .zip(&boundary.corner_vertices)
            .for_each(|(corner, vertex)| {
                labels.vertex(
                    vertex.id(),
                    VertexRole::StartCapVertex {
                        from: miter_id(sections[usize::from(corner)].corner, false, ring, corner),
                        side: SideKind::Corner,
                    },
                );
            });
    });
    labels.into_labeling()
}

fn label_mitered(
    sweep: FeatureId,
    sections: &[EdgeSection],
    boundaries: &[MiterBoundary],
    rails: &[Vec<Edge>],
    lateral: &[Vec<Face>],
    base: &Face,
    top: &Face,
) -> SolidLabeling {
    let last_ring = boundaries.len() - 1;
    let mut labels = Labels::new(sweep, HashSet::new());
    labels.face(base.id(), FaceRole::StartCap);
    labels.face(top.id(), FaceRole::EndCap);
    (0u8..).zip(lateral).for_each(|(segment, run)| {
        (0u8..).zip(run).for_each(|(corner, face)| {
            labels.face(
                face.id(),
                FaceRole::Side {
                    loop_index: LoopIndex::OUTER,
                    from: miter_id(
                        sections[usize::from(corner)].segment,
                        false,
                        segment,
                        corner,
                    ),
                },
            );
        });
    });
    (0u8..).zip(rails).for_each(|(segment, run)| {
        (0u8..).zip(run).for_each(|(corner, rail)| {
            labels.edge(
                rail.id(),
                EdgeRole::SideEdge {
                    from: miter_id(sections[usize::from(corner)].corner, true, segment, corner),
                    side: SideKind::Corner,
                },
            );
        });
    });
    (0u8..).zip(boundaries).for_each(|(ring, boundary)| {
        (0u8..)
            .zip(&boundary.section_edges)
            .for_each(|(corner, edge)| {
                let from = miter_id(sections[usize::from(corner)].segment, false, ring, corner);
                let role = if ring == 0 {
                    EdgeRole::StartCapEdge { from }
                } else if usize::from(ring) == last_ring {
                    EdgeRole::EndCapEdge { from }
                } else {
                    EdgeRole::SideEdge {
                        from,
                        side: SideKind::Corner,
                    }
                };
                labels.edge(edge.id(), role);
            });
        (0u8..)
            .zip(&boundary.corner_vertices)
            .for_each(|(corner, vertex)| {
                let from = miter_id(sections[usize::from(corner)].corner, false, ring, corner);
                let role = if usize::from(ring) == last_ring {
                    VertexRole::EndCapVertex {
                        from,
                        side: SideKind::Corner,
                    }
                } else {
                    VertexRole::StartCapVertex {
                        from,
                        side: SideKind::Corner,
                    }
                };
                labels.vertex(vertex.id(), role);
            });
    });
    labels.into_labeling()
}

fn segment_is_axial(curve: Curve2Kind, frame: &AxisFrame) -> bool {
    let (lo, hi) = segment_perp_extent(curve, frame.a0, frame.np);
    lo.abs() <= REVOLVE_ATTRIBUTION_TOLERANCE_MM && hi.abs() <= REVOLVE_ATTRIBUTION_TOLERANCE_MM
}

fn pole_chain(resolved: &[ResolvedLoop], frame: &AxisFrame) -> Option<Vec<Segment>> {
    let [only] = resolved else {
        return None;
    };
    let segments = &only.segments;
    let count = segments.len();
    if count < 2 {
        return None;
    }
    let axial: Vec<bool> = segments
        .iter()
        .map(|segment| segment_is_axial(segment.curve, frame))
        .collect();
    let off_count = axial.iter().filter(|on_axis| !**on_axis).count();
    if off_count == 0 || off_count == count {
        return None;
    }
    let last_axial = (0..count).find(|&index| axial[index] && !axial[(index + 1) % count])?;
    let chain: Vec<Segment> = (1..=count)
        .map(|step| (last_axial + step) % count)
        .take_while(|&index| !axial[index])
        .map(|index| segments[index])
        .collect();
    (chain.len() == off_count).then_some(chain)
}

fn build_open_wire(plane: Plane3, subs: &[(Point2, Point2, SubEdge)]) -> Wire {
    let vertices: Vec<Vertex> = core::iter::once(subs[0].0)
        .chain(subs.iter().map(|(_, end, _)| *end))
        .map(|point| builder::vertex(lift(plane, 0.0, point)))
        .collect();
    let edges: Vec<Edge> = subs
        .iter()
        .enumerate()
        .map(|(index, (_, _, shape))| {
            let from = &vertices[index];
            let to = &vertices[index + 1];
            build_sub_edge(plane, 0.0, from, to, *shape)
        })
        .collect();
    edges.into()
}

fn build_pole_revolve(
    revolve: FeatureId,
    plane: Plane3,
    plan: &ResolvedRevolve,
    frame: &AxisFrame,
    resolved: &[ResolvedLoop],
    chain: &[Segment],
) -> Result<BrepSolid, BrepError> {
    let subs: Vec<(Point2, Point2, SubEdge)> = chain
        .iter()
        .flat_map(|segment| sub_edges(segment.curve, false))
        .collect();
    if subs.is_empty() {
        return Err(invalid(ProfileDefect::ZeroArea));
    }
    let wire = build_open_wire(plane, &subs);
    let (ax, ay, az) = plan.axis.direction().components();
    let axis_vec = Vector3::new(ax, ay, az);
    let shell = builder::cone(&wire, axis_vec, Rad(TAU));
    let mut solid = Solid::try_new(vec![shell]).map_err(|_| BrepError::ShellNotClosed)?;
    if truck_solid_signed_volume(&solid, pole_mesh_tolerance(&subs)) < 0.0 {
        solid.not();
    }

    let curves = revolve_curve_table(resolved);
    let corners = revolve_corner_pairs(resolved);
    let labeling = label_revolve_solid(
        &solid,
        revolve,
        frame,
        &curves,
        &corners,
        plan.forward_rad,
        plan.backward_rad,
    );
    let assembled = assemble(solid, &labeling)?;
    assembled.validate(Tolerance::new(REVOLVE_AXIS_TOLERANCE_MM))?;
    Ok(assembled)
}

fn pole_mesh_tolerance(subs: &[(Point2, Point2, SubEdge)]) -> f64 {
    let extent = subs
        .iter()
        .flat_map(|(start, end, _)| [*start, *end])
        .map(|point| {
            let (x, y) = point.coords_mm();
            x.abs().max(y.abs())
        })
        .fold(0.0_f64, f64::max);
    (extent * 1.0e-2).max(REVOLVE_AXIS_TOLERANCE_MM)
}

fn truck_solid_signed_volume(solid: &Solid, tolerance: f64) -> f64 {
    let meshed = solid.robust_triangulation(tolerance);
    meshed
        .boundaries()
        .iter()
        .flat_map(|shell| shell.face_iter())
        .map(|face| {
            face.surface().map_or(0.0, |mut polygon| {
                if !face.orientation() {
                    polygon.invert();
                }
                polygon_signed_volume(&polygon)
            })
        })
        .sum()
}

fn polygon_signed_volume(polygon: &PolygonMesh) -> f64 {
    let positions = polygon.positions();
    polygon
        .tri_faces()
        .iter()
        .map(|tri| {
            let [a, b, c] = *tri;
            tetra_volume(positions[a.pos], positions[b.pos], positions[c.pos])
        })
        .sum()
}

fn tetra_volume(
    p0: truck_modeling::Point3,
    p1: truck_modeling::Point3,
    p2: truck_modeling::Point3,
) -> f64 {
    let cross = (
        p1.y * p2.z - p1.z * p2.y,
        p1.z * p2.x - p1.x * p2.z,
        p1.x * p2.y - p1.y * p2.x,
    );
    (p0.x * cross.0 + p0.y * cross.1 + p0.z * cross.2) / 6.0
}

#[derive(Copy, Clone)]
struct RevolveCurve {
    loop_index: LoopIndex,
    entity: SketchEntityId,
    curve: Curve2Kind,
}

fn revolve_curve_table(resolved: &[ResolvedLoop]) -> Vec<RevolveCurve> {
    resolved
        .iter()
        .flat_map(|resolved_loop| {
            resolved_loop.segments.iter().map(|segment| RevolveCurve {
                loop_index: resolved_loop.loop_index,
                entity: segment.curve_entity,
                curve: segment.curve,
            })
        })
        .collect()
}

struct AxisFrame {
    axis: Axis3,
    a0: (f64, f64),
    ad: (f64, f64),
    np: (f64, f64),
    side: f64,
    ref_radial: (f64, f64, f64),
    touches: bool,
}

impl AxisFrame {
    fn build(plane: Plane3, axis: Axis3, resolved: &[ResolvedLoop]) -> Result<Self, BrepError> {
        let (nx, ny, nz) = plane.normal().components();
        let (dx, dy, dz) = axis.direction().components();
        if (nx * dx + ny * dy + nz * dz).abs() > REVOLVE_AXIS_DOT_TOLERANCE {
            return Err(BrepError::RevolveAxisNotInPlane);
        }
        let (ox, oy, oz) = plane.origin().coords_mm();
        let (px, py, pz) = axis.origin().coords_mm();
        let off = (px - ox, py - oy, pz - oz);
        if (off.0 * nx + off.1 * ny + off.2 * nz).abs() > REVOLVE_AXIS_TOLERANCE_MM {
            return Err(BrepError::RevolveAxisNotInPlane);
        }
        let (xx, xy, xz) = plane.x_axis().components();
        let (yx, yy, yz) = plane.y_axis().components();
        let local = |v: (f64, f64, f64)| {
            (
                v.0 * xx + v.1 * xy + v.2 * xz,
                v.0 * yx + v.1 * yy + v.2 * yz,
            )
        };
        let a0 = local(off);
        let ad_raw = local((dx, dy, dz));
        let len = ad_raw.0.hypot(ad_raw.1);
        if len <= REVOLVE_AXIS_DOT_TOLERANCE {
            return Err(BrepError::RevolveAxisNotInPlane);
        }
        let ad = (ad_raw.0 / len, ad_raw.1 / len);
        let np = (-ad.1, ad.0);

        let (lo, hi) = resolved
            .iter()
            .flat_map(|resolved_loop| resolved_loop.segments.iter())
            .map(|segment| segment_perp_extent(segment.curve, a0, np))
            .fold(
                (f64::INFINITY, f64::NEG_INFINITY),
                |(lo, hi), (seg_lo, seg_hi)| (lo.min(seg_lo), hi.max(seg_hi)),
            );
        if lo < -REVOLVE_ATTRIBUTION_TOLERANCE_MM && hi > REVOLVE_ATTRIBUTION_TOLERANCE_MM {
            return Err(BrepError::RevolveProfileCrossesAxis);
        }
        let side = if hi.abs() >= lo.abs() { 1.0 } else { -1.0 };
        let near = if side > 0.0 { lo } else { -hi };
        let touches = near <= REVOLVE_ATTRIBUTION_TOLERANCE_MM;
        let ref_radial = (
            side * (np.0 * xx + np.1 * yx),
            side * (np.0 * xy + np.1 * yy),
            side * (np.0 * xz + np.1 * yz),
        );
        Ok(Self {
            axis,
            a0,
            ad,
            np,
            side,
            ref_radial,
            touches,
        })
    }

    fn back_project(&self, point: Point3) -> Point2 {
        let (dx, dy, dz) = self.axis.direction().components();
        let (ox, oy, oz) = self.axis.origin().coords_mm();
        let (px, py, pz) = point.coords_mm();
        let rel = (px - ox, py - oy, pz - oz);
        let h = rel.0 * dx + rel.1 * dy + rel.2 * dz;
        let foot = (rel.0 - h * dx, rel.1 - h * dy, rel.2 - h * dz);
        let r = (foot.0 * foot.0 + foot.1 * foot.1 + foot.2 * foot.2).sqrt();
        let u = self.a0.0 + h * self.ad.0 + self.side * r * self.np.0;
        let v = self.a0.1 + h * self.ad.1 + self.side * r * self.np.1;
        Point2::from_mm(u, v)
    }

    fn radius(&self, point: Point3) -> f64 {
        let (dx, dy, dz) = self.axis.direction().components();
        let (ox, oy, oz) = self.axis.origin().coords_mm();
        let (px, py, pz) = point.coords_mm();
        let rel = (px - ox, py - oy, pz - oz);
        let h = rel.0 * dx + rel.1 * dy + rel.2 * dz;
        let foot = (rel.0 - h * dx, rel.1 - h * dy, rel.2 - h * dz);
        (foot.0 * foot.0 + foot.1 * foot.1 + foot.2 * foot.2).sqrt()
    }

    fn angle_of(&self, point: Point3) -> f64 {
        let (dx, dy, dz) = self.axis.direction().components();
        let (ox, oy, oz) = self.axis.origin().coords_mm();
        let (px, py, pz) = point.coords_mm();
        let rel = (px - ox, py - oy, pz - oz);
        let h = rel.0 * dx + rel.1 * dy + rel.2 * dz;
        let radial = (rel.0 - h * dx, rel.1 - h * dy, rel.2 - h * dz);
        let reference = self.ref_radial;
        let dot = radial.0 * reference.0 + radial.1 * reference.1 + radial.2 * reference.2;
        let cross = (
            radial.1 * reference.2 - radial.2 * reference.1,
            radial.2 * reference.0 - radial.0 * reference.2,
            radial.0 * reference.1 - radial.1 * reference.0,
        );
        let signed = cross.0 * dx + cross.1 * dy + cross.2 * dz;
        signed.atan2(dot)
    }

    fn plane_azimuths(&self, plane: Plane3) -> Option<(f64, f64)> {
        let (dx, dy, dz) = self.axis.direction().components();
        let (mx, my, mz) = plane.normal().components();
        if (dx * mx + dy * my + dz * mz).abs() > REVOLVE_AXIS_DOT_TOLERANCE {
            return None;
        }
        let (ox, oy, oz) = self.axis.origin().coords_mm();
        let (px, py, pz) = plane.origin().coords_mm();
        if ((ox - px) * mx + (oy - py) * my + (oz - pz) * mz).abs() > REVOLVE_AXIS_TOLERANCE_MM {
            return None;
        }
        let radial = (dy * mz - dz * my, dz * mx - dx * mz, dx * my - dy * mx);
        let len = (radial.0 * radial.0 + radial.1 * radial.1 + radial.2 * radial.2).sqrt();
        if len <= REVOLVE_AXIS_DOT_TOLERANCE {
            return None;
        }
        let on_plane = Point3::from_mm(ox + radial.0, oy + radial.1, oz + radial.2);
        let azimuth = self.angle_of(on_plane);
        Some((azimuth, azimuth + PI))
    }
}

fn normalize_sweep(angle: f64) -> Option<f64> {
    let wrapped = angle.rem_euclid(TAU);
    if wrapped <= ANGLE_EPS_RAD {
        (TAU - wrapped <= ANGLE_EPS_RAD).then_some(TAU)
    } else {
        Some(wrapped)
    }
}

fn nearest_sweep(sense: f64, first: f64, second: f64) -> Option<f64> {
    [first, second]
        .into_iter()
        .filter_map(|azimuth| normalize_sweep(sense * azimuth))
        .reduce(f64::min)
}

fn revolve_seed_inverted(plane: Plane3, frame: &AxisFrame) -> bool {
    let (nx, ny, nz) = plane.normal().components();
    let (dx, dy, dz) = frame.axis.direction().components();
    let (rx, ry, rz) = frame.ref_radial;
    let cross = (dy * rz - dz * ry, dz * rx - dx * rz, dx * ry - dy * rx);
    nx * cross.0 + ny * cross.1 + nz * cross.2 < 0.0
}

fn build_prismatic(
    extrude: FeatureId,
    loops: &[ProfileLoop],
    plane: Plane3,
    plan: SweepPlan,
) -> Result<BrepSolid, BrepError> {
    let resolved = resolve_loops(loops)?;
    let parents = parent_curves(loops);
    let (solid, labeling) = build_solid(
        plane,
        &resolved,
        plan,
        extrude,
        closed_curves(loops),
        &parents,
    )?;
    assemble(solid, &labeling)
}

fn parent_curves(loops: &[ProfileLoop]) -> HashMap<SketchEntityId, Curve2Kind> {
    loops
        .iter()
        .flat_map(|profile_loop| match profile_loop {
            ProfileLoop::Closed {
                curve,
                curve_entity,
            } => vec![(*curve_entity, *curve)],
            ProfileLoop::Open(edges) | ProfileLoop::Chain(edges) => edges
                .iter()
                .map(|edge| (edge.curve_entity(), edge.curve()))
                .collect(),
        })
        .collect()
}

fn closed_curves(loops: &[ProfileLoop]) -> HashSet<SketchEntityId> {
    loops
        .iter()
        .filter_map(|profile_loop| match profile_loop {
            ProfileLoop::Closed { curve_entity, .. } => Some(*curve_entity),
            ProfileLoop::Open(_) | ProfileLoop::Chain(_) => None,
        })
        .collect()
}

fn invalid(reason: ProfileDefect) -> BrepError {
    BrepError::InvalidProfile { reason }
}

#[derive(Copy, Clone)]
struct SweepPlan {
    depth_mm: f64,
    base_offset_mm: f64,
}

pub struct ExtrudeScene<'a> {
    plane: Plane3,
    attached: bool,
    base_bbox: Option<Aabb3>,
    face_plane: Option<&'a dyn Fn(FaceLabel) -> Option<Plane3>>,
    vertex_point: Option<&'a dyn Fn(VertexLabel) -> Option<Point3>>,
    body_bbox: Option<&'a dyn Fn(FeatureId) -> Option<Aabb3>>,
    next_reach: Option<&'a dyn Fn(f64, f64) -> Option<f64>>,
}

impl<'a> ExtrudeScene<'a> {
    #[must_use]
    pub fn detached(plane: Plane3) -> Self {
        Self {
            plane,
            attached: false,
            base_bbox: None,
            face_plane: None,
            vertex_point: None,
            body_bbox: None,
            next_reach: None,
        }
    }

    #[must_use]
    pub fn new(
        plane: Plane3,
        base_bbox: Option<Aabb3>,
        face_plane: &'a dyn Fn(FaceLabel) -> Option<Plane3>,
        vertex_point: &'a dyn Fn(VertexLabel) -> Option<Point3>,
        body_bbox: &'a dyn Fn(FeatureId) -> Option<Aabb3>,
        next_reach: &'a dyn Fn(f64, f64) -> Option<f64>,
    ) -> Self {
        Self {
            plane,
            attached: true,
            base_bbox,
            face_plane: Some(face_plane),
            vertex_point: Some(vertex_point),
            body_bbox: Some(body_bbox),
            next_reach: Some(next_reach),
        }
    }

    fn signed(&self, point: Point3) -> f64 {
        let (nx, ny, nz) = self.plane.normal().components();
        let (vx, vy, vz) = (point - self.plane.origin()).coords_mm();
        vx * nx + vy * ny + vz * nz
    }

    fn aabb_span(&self, aabb: Aabb3) -> (f64, f64) {
        let (lx, ly, lz) = aabb.min().coords_mm();
        let (hx, hy, hz) = aabb.max().coords_mm();
        (0..8)
            .map(|corner| {
                let x = if corner & 1 == 0 { lx } else { hx };
                let y = if corner & 2 == 0 { ly } else { hy };
                let z = if corner & 4 == 0 { lz } else { hz };
                self.signed(Point3::from_mm(x, y, z))
            })
            .fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), value| {
                (lo.min(value), hi.max(value))
            })
    }

    fn miss(&self, gap: TruckGap) -> BrepError {
        if self.attached {
            BrepError::EndConditionUnresolved
        } else {
            unsupported(gap)
        }
    }

    fn face(&self, face: FaceLabel) -> Result<Plane3, BrepError> {
        self.face_plane
            .and_then(|resolve| resolve(face))
            .ok_or_else(|| self.miss(TruckGap::UpToSurface))
    }

    fn require_parallel_target(&self, target: Plane3, gap: TruckGap) -> Result<(), BrepError> {
        let (nx, ny, nz) = self.plane.normal().components();
        let (mx, my, mz) = target.normal().components();
        let aligned = nx * mx + ny * my + nz * mz;
        (aligned.abs() >= 1.0 - PARALLEL_TARGET_EPS)
            .then_some(())
            .ok_or_else(|| unsupported(gap))
    }

    fn vertex(&self, vertex: VertexLabel) -> Result<Point3, BrepError> {
        self.vertex_point
            .and_then(|resolve| resolve(vertex))
            .ok_or_else(|| self.miss(TruckGap::UpToVertex))
    }

    fn body(&self, body: FeatureId) -> Result<Aabb3, BrepError> {
        self.body_bbox
            .and_then(|resolve| resolve(body))
            .ok_or_else(|| self.miss(TruckGap::UpToBody))
    }

    fn base(&self) -> Result<(f64, f64), BrepError> {
        self.base_bbox
            .map(|aabb| self.aabb_span(aabb))
            .ok_or_else(|| self.miss(TruckGap::ThroughAll))
    }

    fn reach(
        &self,
        condition: ExtrudeEndCondition,
        sign: f64,
        base_offset_mm: f64,
        is_cut: bool,
    ) -> Result<f64, BrepError> {
        match condition {
            ExtrudeEndCondition::Blind { depth } => depth_mm(depth),
            ExtrudeEndCondition::ThroughAll | ExtrudeEndCondition::ThroughAllBoth => {
                let (lo, hi) = self.base()?;
                let boundary = if sign > 0.0 { hi } else { lo };
                let span = (hi - lo).abs().max(THROUGH_ALL_MIN_SPAN_MM);
                let clearance = if is_cut {
                    span * THROUGH_ALL_CLEARANCE_FRACTION
                } else {
                    0.0
                };
                positive(sign * (boundary - base_offset_mm) + clearance)
            }
            ExtrudeEndCondition::UpToVertex { vertex } => {
                positive(sign * (self.signed(self.vertex(vertex)?) - base_offset_mm))
            }
            ExtrudeEndCondition::UpToSurface { surface } => {
                let target = self.face(surface)?;
                self.require_parallel_target(target, TruckGap::UpToSurface)?;
                positive(sign * (self.signed(target.origin()) - base_offset_mm))
            }
            ExtrudeEndCondition::OffsetFromSurface {
                surface,
                offset,
                reverse_offset,
                translate_surface: _,
            } => {
                let target = self.face(surface)?;
                self.require_parallel_target(target, TruckGap::OffsetFromSurface)?;
                let surface_reach = sign * (self.signed(target.origin()) - base_offset_mm);
                let offset_mm = offset.get::<millimeter>();
                let signed_offset = if reverse_offset {
                    -offset_mm
                } else {
                    offset_mm
                };
                positive(surface_reach + signed_offset)
            }
            ExtrudeEndCondition::UpToBody { body } => {
                let (lo, hi) = self.aabb_span(self.body(body)?);
                let near = if sign > 0.0 { lo } else { hi };
                positive(sign * (near - base_offset_mm))
            }
            ExtrudeEndCondition::UpToNext => {
                let reach = self
                    .next_reach
                    .and_then(|next| next(sign, base_offset_mm))
                    .ok_or_else(|| self.miss(TruckGap::UpToNext))?;
                positive(reach)
            }
            ExtrudeEndCondition::MidPlane { .. } => Err(unsupported(TruckGap::ReferenceDirection)),
        }
    }
}

fn positive(distance_mm: f64) -> Result<f64, BrepError> {
    if distance_mm > EXTRUDE_MIN_DEPTH_MM {
        Ok(distance_mm)
    } else {
        Err(BrepError::EndConditionBehindProfile)
    }
}

pub fn resolve_sweep(
    feature: &ExtrudeFeature,
    scene: &ExtrudeScene,
) -> Result<ResolvedSweep, BrepError> {
    match feature.direction {
        ExtrudeDirection::Normal { .. } => {}
        ExtrudeDirection::AlongAxis(_) => return Err(unsupported(TruckGap::AxisDirection)),
        ExtrudeDirection::BetweenReferences { .. } => {
            return Err(unsupported(TruckGap::ReferenceDirection));
        }
    }
    let base_offset_mm = match feature.from {
        FromCondition::SketchPlane => 0.0,
        FromCondition::Offset { distance } => distance.get::<millimeter>(),
        FromCondition::Surface { surface } => scene.signed(scene.face(surface)?.origin()),
        FromCondition::Vertex { vertex } => scene.signed(scene.vertex(vertex)?),
    };
    let reverse = matches!(
        feature.direction,
        ExtrudeDirection::Normal {
            sense: ExtrudeSense::Reverse
        }
    );
    let is_cut = feature.operation.is_cut();
    let into_backward = is_cut ^ reverse;
    let thin = thin_to_plan(feature);

    if matches!(feature.end_condition, ExtrudeEndCondition::ThroughAllBoth) {
        if feature.direction2.is_some() {
            return Err(unsupported(TruckGap::ReferenceDirection));
        }
        let forward_mm =
            scene.reach(ExtrudeEndCondition::ThroughAll, 1.0, base_offset_mm, is_cut)?;
        let backward_mm = scene.reach(
            ExtrudeEndCondition::ThroughAll,
            -1.0,
            base_offset_mm,
            is_cut,
        )?;
        return Ok(ResolvedSweep {
            base_offset_mm,
            forward: Some(DirectionPlan {
                distance_mm: forward_mm,
                draft: feature.draft,
            }),
            backward: Some(DirectionPlan {
                distance_mm: backward_mm,
                draft: feature.draft,
            }),
            thin,
        });
    }

    if let ExtrudeEndCondition::MidPlane { depth } = feature.end_condition {
        if feature.direction2.is_some() {
            return Err(unsupported(TruckGap::ReferenceDirection));
        }
        let half = depth_mm(depth)? / 2.0;
        let arm = Some(DirectionPlan {
            distance_mm: half,
            draft: feature.draft,
        });
        return Ok(ResolvedSweep {
            base_offset_mm,
            forward: arm,
            backward: arm,
            thin,
        });
    }

    let primary_sign = if into_backward { -1.0 } else { 1.0 };
    let primary = DirectionPlan {
        distance_mm: scene.reach(feature.end_condition, primary_sign, base_offset_mm, is_cut)?,
        draft: feature.draft,
    };
    let secondary = feature
        .direction2
        .map(|direction2| {
            Ok::<_, BrepError>(DirectionPlan {
                distance_mm: scene.reach(
                    direction2.end_condition,
                    -primary_sign,
                    base_offset_mm,
                    is_cut,
                )?,
                draft: direction2.draft,
            })
        })
        .transpose()?;
    let (forward, backward) = if into_backward {
        (secondary, Some(primary))
    } else {
        (Some(primary), secondary)
    };
    Ok(ResolvedSweep {
        base_offset_mm,
        forward,
        backward,
        thin,
    })
}

fn thin_to_plan(feature: &ExtrudeFeature) -> Option<crate::extrude::ThinPlan> {
    thin_wall_to_plan(feature.thin_wall)
}

pub(crate) fn thin_wall_to_plan(
    thin: Option<crate::extrude::ThinWall>,
) -> Option<crate::extrude::ThinPlan> {
    use crate::extrude::ThinWallDirection;
    let thin = thin?;
    let t1 = thin.thickness.get().get::<millimeter>();
    let (outer_mm, inner_mm) = match thin.direction {
        ThinWallDirection::Outward => (t1, 0.0),
        ThinWallDirection::Inward => (0.0, t1),
        ThinWallDirection::MidPlane => (t1 / 2.0, t1 / 2.0),
        ThinWallDirection::TwoDirection => {
            let t2 = thin
                .thickness2
                .map_or(t1, |value| value.get().get::<millimeter>());
            (t1, t2)
        }
    };
    Some(crate::extrude::ThinPlan {
        outer_mm,
        inner_mm,
        cap_ends: thin.cap_ends,
        auto_fillet_mm: thin
            .auto_fillet
            .map(|radius| radius.get().get::<millimeter>()),
    })
}

fn span_of(plan: &ResolvedSweep) -> Result<SweepPlan, BrepError> {
    let forward_mm = plan.forward.map_or(0.0, |direction| direction.distance_mm);
    let backward_mm = plan.backward.map_or(0.0, |direction| direction.distance_mm);
    let bottom_mm = plan.base_offset_mm - backward_mm;
    let top_mm = plan.base_offset_mm + forward_mm;
    let depth_mm = top_mm - bottom_mm;
    if depth_mm <= EXTRUDE_MIN_DEPTH_MM {
        return Err(BrepError::EmptyExtrudeDepth);
    }
    Ok(SweepPlan {
        depth_mm,
        base_offset_mm: bottom_mm,
    })
}

fn draft_offset_mm(direction: DirectionPlan) -> f64 {
    direction.draft.map_or(0.0, |draft| {
        direction.distance_mm.abs() * draft.signed_tangent()
    })
}

#[derive(Copy, Clone)]
enum Wall {
    Outer,
    Inner,
    CapStart,
    CapEnd,
    Close,
    Fillet,
}

fn derive_entity(source: SketchEntityId, wall: Wall) -> SketchEntityId {
    let tag = match wall {
        Wall::Outer => WALL_TAG_OUTER,
        Wall::Inner => WALL_TAG_INNER,
        Wall::CapStart => WALL_TAG_CAP_START,
        Wall::CapEnd => WALL_TAG_CAP_END,
        Wall::Close => WALL_TAG_CLOSE,
        Wall::Fillet => WALL_TAG_FILLET,
    };
    let raw = source.data().as_ffi();
    debug_assert!(
        raw & WALL_TAG_MASK == 0,
        "wall tag bits must be free in a real sketch entity key"
    );
    SketchEntityId::from(KeyData::from_ffi(raw | tag))
}

fn offset_error(error: OffsetError, gap: TruckGap) -> BrepError {
    match error {
        OffsetError::Collapsed => invalid(ProfileDefect::SelfIntersectingLoop),
        OffsetError::SharpArcCorner | OffsetError::Degenerate => unsupported(gap),
    }
}

fn loop_curves(profile_loop: &ProfileLoop) -> Vec<Curve2Kind> {
    match profile_loop {
        ProfileLoop::Closed { curve, .. } => vec![*curve],
        ProfileLoop::Open(edges) | ProfileLoop::Chain(edges) => {
            edges.iter().map(|edge| edge.curve()).collect()
        }
    }
}

fn loop_entities(profile_loop: &ProfileLoop) -> Vec<(SketchEntityId, SketchEntityId)> {
    match profile_loop {
        ProfileLoop::Closed { curve_entity, .. } => vec![(*curve_entity, *curve_entity)],
        ProfileLoop::Open(edges) | ProfileLoop::Chain(edges) => edges
            .iter()
            .map(|edge| (edge.curve_entity(), edge.corner()))
            .collect(),
    }
}

fn rebuild_loop(
    curves: &[Curve2Kind],
    entities: &[(SketchEntityId, SketchEntityId)],
    wall: Wall,
) -> ProfileLoop {
    if let ([Curve2Kind::Circle(_)], [(curve_entity, _)]) = (curves, entities) {
        return ProfileLoop::Closed {
            curve: curves[0],
            curve_entity: derive_entity(*curve_entity, wall),
        };
    }
    let edges = curves
        .iter()
        .zip(entities)
        .map(|(curve, (curve_entity, corner))| {
            ProfileEdge::new(
                *curve,
                derive_entity(*curve_entity, wall),
                derive_entity(*corner, wall),
            )
        })
        .collect();
    ProfileLoop::Open(edges)
}

fn make_wall(
    profile_loop: &ProfileLoop,
    distance_mm: f64,
    wall: Wall,
) -> Result<ProfileLoop, BrepError> {
    if distance_mm.abs() < EXTRUDE_MIN_DEPTH_MM {
        return Ok(profile_loop.clone());
    }
    let curves = loop_curves(profile_loop);
    let offset = offset_loop(&curves, distance_mm, PROFILE_TOLERANCE)
        .map_err(|e| offset_error(e, TruckGap::ThinWall))?;
    Ok(rebuild_loop(&offset, &loop_entities(profile_loop), wall))
}

fn rounded_for_thin(profile_loop: &ProfileLoop, thin: ThinPlan) -> Result<ProfileLoop, BrepError> {
    match thin.auto_fillet_mm {
        Some(radius) if radius > PROFILE_TOLERANCE_MM => auto_fillet_loop(profile_loop, radius),
        _ => Ok(profile_loop.clone()),
    }
}

fn thin_walls(loops: &[ProfileLoop], thin: ThinPlan) -> Result<Vec<ProfileLoop>, BrepError> {
    let [single] = loops else {
        return Err(unsupported(TruckGap::ThinWall));
    };
    if let ProfileLoop::Chain(edges) = single {
        return Ok(vec![augment_open_chain_thin(edges, thin)?]);
    }
    let single = rounded_for_thin(single, thin)?;
    let outer = make_wall(&single, thin.outer_mm, Wall::Outer)?;
    let inner = make_wall(&single, -thin.inner_mm, Wall::Inner)?;
    Ok(vec![outer, inner])
}

fn augment_thin(loops: &[ProfileLoop], thin: ThinPlan) -> Result<Vec<ProfileLoop>, BrepError> {
    if thin.cap_ends {
        return Err(unsupported(TruckGap::ThinWall));
    }
    thin_walls(loops, thin)
}

fn fillet_corner(
    prev: Point2,
    vertex: Point2,
    next: Point2,
    radius_mm: f64,
    orientation: f64,
) -> Option<(Point2, Point2, Curve2Kind)> {
    let (px, py) = prev.coords_mm();
    let (vx, vy) = vertex.coords_mm();
    let (nx, ny) = next.coords_mm();
    let incoming = (vx - px, vy - py);
    let outgoing = (nx - vx, ny - vy);
    let len_in = incoming.0.hypot(incoming.1);
    let len_out = outgoing.0.hypot(outgoing.1);
    if len_in < PROFILE_TOLERANCE_MM || len_out < PROFILE_TOLERANCE_MM {
        return None;
    }
    let u = (incoming.0 / len_in, incoming.1 / len_in);
    let w = (outgoing.0 / len_out, outgoing.1 / len_out);
    let cross = u.0 * w.1 - u.1 * w.0;
    let dot = u.0 * w.0 + u.1 * w.1;
    let delta = cross.atan2(dot);
    if delta.abs() < ANGLE_EPS_RAD || (delta > 0.0) != (orientation > 0.0) {
        return None;
    }
    let half = delta.abs() / 2.0;
    let tangent = radius_mm * half.tan();
    if tangent > 0.49 * len_in.min(len_out) {
        return None;
    }
    let bisector = (w.0 - u.0, w.1 - u.1);
    let bisector_len = bisector.0.hypot(bisector.1);
    if bisector_len < PROFILE_TOLERANCE_MM {
        return None;
    }
    let center_distance = radius_mm / half.cos();
    let center = Point2::from_mm(
        vx + center_distance * bisector.0 / bisector_len,
        vy + center_distance * bisector.1 / bisector_len,
    );
    let start_point = Point2::from_mm(vx - tangent * u.0, vy - tangent * u.1);
    let end_point = Point2::from_mm(vx + tangent * w.0, vy + tangent * w.1);
    let (cx, cy) = center.coords_mm();
    let (ax, ay) = start_point.coords_mm();
    let arc = Arc2::new(
        center,
        Length::new::<millimeter>(radius_mm),
        Angle::new::<radian>((ay - cy).atan2(ax - cx)),
        Angle::new::<radian>(delta),
        PROFILE_TOLERANCE,
    )
    .ok()?;
    Some((start_point, end_point, Curve2Kind::Arc(arc)))
}

fn auto_fillet_loop(profile_loop: &ProfileLoop, radius_mm: f64) -> Result<ProfileLoop, BrepError> {
    let ProfileLoop::Open(edges) = profile_loop else {
        return Err(unsupported(TruckGap::ThinWall));
    };
    let lines: Option<Vec<(Point2, Point2)>> = edges
        .iter()
        .map(|edge| match edge.curve() {
            Curve2Kind::Line(line) => Some((line.start(), line.end())),
            Curve2Kind::Arc(_)
            | Curve2Kind::Circle(_)
            | Curve2Kind::Ellipse(_)
            | Curve2Kind::Bezier(_) => None,
        })
        .collect();
    let Some(lines) = lines else {
        return Err(unsupported(TruckGap::ThinWall));
    };
    let count = lines.len();
    if count < 3 {
        return Err(unsupported(TruckGap::ThinWall));
    }
    let polygon: Vec<(f64, f64)> = lines.iter().map(|(start, _)| start.coords_mm()).collect();
    let orientation = if signed_area(&polygon) >= 0.0 {
        1.0
    } else {
        -1.0
    };
    let corners: Vec<Option<(Point2, Point2, Curve2Kind)>> = (0..count)
        .map(|index| {
            fillet_corner(
                lines[index].0,
                lines[index].1,
                lines[(index + 1) % count].1,
                radius_mm,
                orientation,
            )
        })
        .collect();
    let new_edges = (0..count).try_fold(Vec::new(), |mut acc, index| {
        let start =
            corners[(index + count - 1) % count].map_or(lines[index].0, |(_, back, _)| back);
        let end = corners[index].map_or(lines[index].1, |(front, _, _)| front);
        let line = Line2::new(start, end, PROFILE_TOLERANCE)
            .map_err(|_| invalid(ProfileDefect::ZeroArea))?;
        acc.push(ProfileEdge::new(
            Curve2Kind::Line(line),
            edges[index].curve_entity(),
            edges[index].corner(),
        ));
        if let Some((_, _, arc)) = corners[index] {
            let fillet_entity = derive_entity(edges[index].corner(), Wall::Fillet);
            acc.push(ProfileEdge::new(arc, fillet_entity, fillet_entity));
        }
        Ok::<_, BrepError>(acc)
    })?;
    Ok(ProfileLoop::Open(new_edges))
}

fn loop_from_segments(segments: &[Segment]) -> ProfileLoop {
    if let [single] = segments
        && matches!(single.curve, Curve2Kind::Circle(_))
    {
        return ProfileLoop::Closed {
            curve: single.curve,
            curve_entity: single.curve_entity,
        };
    }
    let edges = segments
        .iter()
        .map(|segment| {
            ProfileEdge::new(
                segment.curve,
                segment.curve_entity,
                segment.corner.unwrap_or(segment.curve_entity),
            )
        })
        .collect();
    ProfileLoop::Open(edges)
}

fn region_shell(
    plane: Plane3,
    band: &[ProfileLoop],
    plan: SweepPlan,
    book: &mut Bookkeeping,
) -> Result<Shell, BrepError> {
    let resolved = resolve_loops(band)?;
    let wires: Vec<Wire> = resolved
        .iter()
        .map(|resolved_loop| {
            let steps = loop_steps(&resolved_loop.segments, resolved_loop.reversed);
            build_loop(
                plane,
                plan.base_offset_mm,
                &steps,
                resolved_loop.loop_index,
                book,
            )
        })
        .collect();
    let face = builder::try_attach_plane(&wires)
        .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?;
    let (nx, ny, nz) = plane.normal().components();
    let sweep = Vector3::new(nx * plan.depth_mm, ny * plan.depth_mm, nz * plan.depth_mm);
    let solid = builder::tsweep(&face, sweep);
    solid
        .boundaries()
        .first()
        .cloned()
        .ok_or(BrepError::ShellNotClosed)
}

fn assign_region_fragments(solid: &Solid, labeling: &mut SolidLabeling) {
    solid
        .boundaries()
        .iter()
        .enumerate()
        .for_each(|(index, shell)| {
            let fragment = FragmentId::new(u16::try_from(index + 1).unwrap_or(u16::MAX));
            shell.face_iter().for_each(|face| {
                labeling.face_fragments.insert(face.id(), fragment);
                face.boundaries()
                    .iter()
                    .flat_map(Wire::edge_iter)
                    .for_each(|edge| {
                        labeling.edge_fragments.insert(edge.id(), fragment);
                        labeling
                            .vertex_fragments
                            .insert(edge.front().id(), fragment);
                        labeling.vertex_fragments.insert(edge.back().id(), fragment);
                    });
            });
        });
}

fn build_thin_capped(
    extrude: FeatureId,
    profile_loop: &ProfileLoop,
    plane: Plane3,
    plan: SweepPlan,
    thin: ThinPlan,
) -> Result<BrepSolid, BrepError> {
    let base = rounded_for_thin(profile_loop, thin)?;
    let outer_wall = make_wall(&base, thin.outer_mm, Wall::Outer)?;
    let inner_wall = make_wall(&base, -thin.inner_mm, Wall::Inner)?;
    let cap_mm = thin.outer_mm + thin.inner_mm;
    let mut book = Bookkeeping::default();
    let mut closed: HashSet<SketchEntityId> = HashSet::new();
    closed.extend(closed_curves(core::slice::from_ref(&outer_wall)));
    let mut shells = vec![region_shell(
        plane,
        core::slice::from_ref(&outer_wall),
        plan,
        &mut book,
    )?];
    let cavity_depth = plan.depth_mm - 2.0 * cap_mm;
    if cavity_depth > EXTRUDE_MIN_DEPTH_MM {
        closed.extend(closed_curves(core::slice::from_ref(&inner_wall)));
        let cavity = SweepPlan {
            depth_mm: cavity_depth,
            base_offset_mm: plan.base_offset_mm + cap_mm,
        };
        let void = region_shell(plane, core::slice::from_ref(&inner_wall), cavity, &mut book)?;
        shells.push(void.face_iter().map(Face::inverse).collect());
    }
    let solid = Solid::try_new(shells).map_err(|_| BrepError::ShellNotClosed)?;
    let empty: HashMap<SketchEntityId, Curve2Kind> = HashMap::new();
    let mut labeling = label_solid(&solid, &book, extrude, closed, plane, plan, &empty);
    assign_region_fragments(&solid, &mut labeling);
    assemble(solid, &labeling)
}

fn build_thin_regions(
    extrude: FeatureId,
    loops: &[ProfileLoop],
    plane: Plane3,
    plan: SweepPlan,
    thin: ThinPlan,
) -> Result<BrepSolid, BrepError> {
    let resolved = resolve_loops(loops)?;
    let mut book = Bookkeeping::default();
    let mut shells: Vec<Shell> = Vec::new();
    let mut closed: HashSet<SketchEntityId> = HashSet::new();
    resolved.iter().try_for_each(|resolved_loop| {
        let canonical = rounded_for_thin(&loop_from_segments(&resolved_loop.segments), thin)?;
        let (exterior_mm, interior_mm) = if resolved_loop.loop_index == LoopIndex::OUTER {
            (thin.outer_mm, -thin.inner_mm)
        } else {
            (-thin.outer_mm, thin.inner_mm)
        };
        let wall_out = make_wall(&canonical, exterior_mm, Wall::Outer)?;
        let wall_in = make_wall(&canonical, interior_mm, Wall::Inner)?;
        let band = [wall_out, wall_in];
        closed.extend(closed_curves(&band));
        shells.push(region_shell(plane, &band, plan, &mut book)?);
        Ok::<_, BrepError>(())
    })?;
    let solid = Solid::try_new(shells).map_err(|_| BrepError::ShellNotClosed)?;
    let empty: HashMap<SketchEntityId, Curve2Kind> = HashMap::new();
    let mut labeling = label_solid(&solid, &book, extrude, closed, plane, plan, &empty);
    assign_region_fragments(&solid, &mut labeling);
    assemble(solid, &labeling)
}

fn cap_line(from: Point2, to: Point2) -> Result<Curve2Kind, BrepError> {
    Line2::new(from, to, PROFILE_TOLERANCE)
        .map(Curve2Kind::Line)
        .map_err(|_| invalid(ProfileDefect::ZeroArea))
}

fn close_open_profile(loops: &[ProfileLoop]) -> Result<Option<Vec<ProfileLoop>>, BrepError> {
    let [ProfileLoop::Chain(edges)] = loops else {
        return Ok(None);
    };
    Ok(Some(vec![close_chain(edges)?]))
}

fn close_chain(edges: &[ProfileEdge]) -> Result<ProfileLoop, BrepError> {
    let (Some(first), Some(last)) = (edges.first(), edges.last()) else {
        return Err(invalid(ProfileDefect::ZeroArea));
    };
    let start = first.curve().evaluate(Parameter::new(0.0));
    let end = last.curve().evaluate(Parameter::new(1.0));
    let closing = ProfileEdge::new(
        cap_line(end, start)?,
        derive_entity(last.curve_entity(), Wall::Close),
        derive_entity(first.corner(), Wall::Close),
    );
    let closed = edges
        .iter()
        .copied()
        .chain(core::iter::once(closing))
        .collect();
    Ok(ProfileLoop::Open(closed))
}

fn augment_open_chain_thin(
    edges: &[ProfileEdge],
    thin: ThinPlan,
) -> Result<ProfileLoop, BrepError> {
    if thin
        .auto_fillet_mm
        .is_some_and(|radius| radius > PROFILE_TOLERANCE_MM)
    {
        return Err(unsupported(TruckGap::ThinWall));
    }
    let count = edges.len();
    if count == 0 {
        return Err(invalid(ProfileDefect::ZeroArea));
    }
    let curves: Vec<Curve2Kind> = edges.iter().map(|edge| edge.curve()).collect();
    let outer = offset_open_chain(&curves, thin.outer_mm, PROFILE_TOLERANCE)
        .map_err(|error| offset_error(error, TruckGap::ThinWall))?;
    let inner = offset_open_chain(&curves, -thin.inner_mm, PROFILE_TOLERANCE)
        .map_err(|error| offset_error(error, TruckGap::ThinWall))?;

    let vertex_source = |index: usize| {
        if index < count {
            edges[index].corner()
        } else {
            edges[count - 1].curve_entity()
        }
    };

    let outer_edges = outer.iter().enumerate().map(|(index, curve)| {
        ProfileEdge::new(
            *curve,
            derive_entity(edges[index].curve_entity(), Wall::Outer),
            derive_entity(vertex_source(index), Wall::Outer),
        )
    });

    let cap_end = ProfileEdge::new(
        cap_line(
            outer[count - 1].evaluate(Parameter::new(1.0)),
            inner[count - 1].evaluate(Parameter::new(1.0)),
        )?,
        derive_entity(vertex_source(count), Wall::CapEnd),
        derive_entity(vertex_source(count), Wall::Outer),
    );

    let inner_edges = (0..count)
        .rev()
        .map(|index| {
            let reversed = reverse_curve(inner[index], PROFILE_TOLERANCE)
                .map_err(|error| offset_error(error, TruckGap::ThinWall))?;
            Ok(ProfileEdge::new(
                reversed,
                derive_entity(edges[index].curve_entity(), Wall::Inner),
                derive_entity(vertex_source(index + 1), Wall::Inner),
            ))
        })
        .collect::<Result<Vec<_>, BrepError>>()?;

    let cap_start = ProfileEdge::new(
        cap_line(
            inner[0].evaluate(Parameter::new(0.0)),
            outer[0].evaluate(Parameter::new(0.0)),
        )?,
        derive_entity(vertex_source(0), Wall::CapStart),
        derive_entity(vertex_source(0), Wall::Inner),
    );

    let loop_edges: Vec<ProfileEdge> = outer_edges
        .chain(core::iter::once(cap_end))
        .chain(inner_edges)
        .chain(core::iter::once(cap_start))
        .collect();
    Ok(ProfileLoop::Open(loop_edges))
}

fn drafted_wire(
    resolved_loop: &ResolvedLoop,
    plane: Plane3,
    height_mm: f64,
    offset_mm: f64,
) -> Result<Wire, BrepError> {
    let mut scratch = Bookkeeping::default();
    if offset_mm.abs() < EXTRUDE_MIN_DEPTH_MM {
        let steps = loop_steps(&resolved_loop.segments, resolved_loop.reversed);
        return Ok(build_loop(
            plane,
            height_mm,
            &steps,
            resolved_loop.loop_index,
            &mut scratch,
        ));
    }
    let curves: Vec<Curve2Kind> = resolved_loop.segments.iter().map(|seg| seg.curve).collect();
    let offset = offset_loop(&curves, offset_mm, PROFILE_TOLERANCE)
        .map_err(|e| offset_error(e, TruckGap::Draft))?;
    let offset_segments: Vec<Segment> = offset
        .iter()
        .zip(&resolved_loop.segments)
        .map(|(curve, seg)| Segment {
            curve: *curve,
            curve_entity: seg.curve_entity,
            corner: seg.corner,
        })
        .collect();
    if self_intersects(&offset_segments) {
        return Err(invalid(ProfileDefect::SelfIntersectingLoop));
    }
    let steps = loop_steps(&offset_segments, resolved_loop.reversed);
    Ok(build_loop(
        plane,
        height_mm,
        &steps,
        resolved_loop.loop_index,
        &mut scratch,
    ))
}

fn build_drafted(
    extrude: FeatureId,
    loops: &[ProfileLoop],
    plane: Plane3,
    plan: &ResolvedSweep,
) -> Result<BrepSolid, BrepError> {
    let resolved = resolve_loops(loops)?;
    let base = plan.base_offset_mm;
    let forward_mm = plan.forward.map_or(0.0, |direction| direction.distance_mm);
    let backward_mm = plan.backward.map_or(0.0, |direction| direction.distance_mm);
    let top = base + forward_mm;
    let bottom = base - backward_mm;
    if top - bottom <= EXTRUDE_MIN_DEPTH_MM {
        return Err(BrepError::EmptyExtrudeDepth);
    }
    let mut book = Bookkeeping::default();
    let mut low_wires: Vec<Wire> = Vec::new();
    let mut high_wires: Vec<Wire> = Vec::new();
    let mut side_faces: Vec<truck_modeling::Face> = Vec::new();

    resolved.iter().try_for_each(|resolved_loop| {
        let base_steps = loop_steps(&resolved_loop.segments, resolved_loop.reversed);
        let base_wire = build_loop(
            plane,
            base,
            &base_steps,
            resolved_loop.loop_index,
            &mut book,
        );
        let high_wire = match plan.forward {
            Some(direction) => {
                let end = drafted_wire(resolved_loop, plane, top, draft_offset_mm(direction))?;
                let side = builder::try_wire_homotopy(&base_wire, &end)
                    .map_err(|_| unsupported(TruckGap::Draft))?;
                side_faces.extend(side.face_iter().cloned());
                end
            }
            None => base_wire.clone(),
        };
        let low_wire = match plan.backward {
            Some(direction) => {
                let end = drafted_wire(resolved_loop, plane, bottom, draft_offset_mm(direction))?;
                let side = builder::try_wire_homotopy(&end, &base_wire)
                    .map_err(|_| unsupported(TruckGap::Draft))?;
                side_faces.extend(side.face_iter().cloned());
                end
            }
            None => base_wire.clone(),
        };
        high_wires.push(high_wire);
        low_wires.push(low_wire);
        Ok::<_, BrepError>(())
    })?;

    let low_inverted: Vec<Wire> = low_wires.iter().map(Wire::inverse).collect();
    let low_cap = builder::try_attach_plane(&low_inverted)
        .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?;
    let high_cap = builder::try_attach_plane(&high_wires)
        .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?;

    side_faces.push(low_cap);
    side_faces.push(high_cap);
    let shell: Shell = side_faces.into_iter().collect();
    let solid = Solid::try_new(vec![shell]).map_err(|_| BrepError::ShellNotClosed)?;

    let sweep = SweepPlan {
        depth_mm: top - bottom,
        base_offset_mm: bottom,
    };
    let empty: HashMap<SketchEntityId, Curve2Kind> = HashMap::new();
    let mut labeling = label_solid(
        &solid,
        &book,
        extrude,
        closed_curves(loops),
        plane,
        sweep,
        &empty,
    );
    if plan.forward.is_some() && plan.backward.is_some() {
        assign_band_fragments(&solid, plane, base, &mut labeling);
    }
    assemble(solid, &labeling)
}

const BAND_EPS_MM: f64 = 1.0e-6;

fn plane_offset_mm(plane: Plane3, point: Point3) -> f64 {
    let (nx, ny, nz) = plane.normal().components();
    let (vx, vy, vz) = (point - plane.origin()).coords_mm();
    vx * nx + vy * ny + vz * nz
}

fn band_fragment(delta_mm: f64) -> FragmentId {
    if delta_mm > BAND_EPS_MM {
        FragmentId::new(1)
    } else if delta_mm < -BAND_EPS_MM {
        FragmentId::new(2)
    } else {
        FragmentId::WHOLE
    }
}

fn assign_band_fragments(solid: &Solid, plane: Plane3, base_mm: f64, labeling: &mut SolidLabeling) {
    solid
        .boundaries()
        .iter()
        .flat_map(Shell::face_iter)
        .for_each(|face| {
            let delta: f64 = face
                .boundaries()
                .iter()
                .flat_map(Wire::edge_iter)
                .map(|edge| {
                    plane_offset_mm(plane, point_from_truck(edge.front().point())) - base_mm
                })
                .fold(0.0, |extreme, value| {
                    if value.abs() > extreme.abs() {
                        value
                    } else {
                        extreme
                    }
                });
            labeling
                .face_fragments
                .insert(face.id(), band_fragment(delta));
        });
    solid.edge_iter().for_each(|edge| {
        let delta = plane_offset_mm(plane, truck_edge_midpoint(&edge)) - base_mm;
        labeling
            .edge_fragments
            .insert(edge.id(), band_fragment(delta));
    });
    solid.vertex_iter().for_each(|vertex| {
        let delta = plane_offset_mm(plane, point_from_truck(vertex.point())) - base_mm;
        labeling
            .vertex_fragments
            .insert(vertex.id(), band_fragment(delta));
    });
}

fn unsupported(detail: TruckGap) -> BrepError {
    BrepError::TruckUnsupported { detail }
}

fn depth_mm(depth: PositiveLength) -> Result<f64, BrepError> {
    let value = depth.get().get::<millimeter>();
    if value <= EXTRUDE_MIN_DEPTH_MM {
        Err(BrepError::EmptyExtrudeDepth)
    } else {
        Ok(value)
    }
}

#[derive(Copy, Clone)]
struct Segment {
    curve: Curve2Kind,
    curve_entity: SketchEntityId,
    corner: Option<SketchEntityId>,
}

struct LoopInfo {
    segments: Vec<Segment>,
    polygon: Vec<(f64, f64)>,
    area: f64,
}

struct ResolvedLoop {
    segments: Vec<Segment>,
    reversed: bool,
    loop_index: LoopIndex,
}

fn resolve_loops(loops: &[ProfileLoop]) -> Result<Vec<ResolvedLoop>, BrepError> {
    if loops.is_empty() {
        return Err(invalid(ProfileDefect::ZeroArea));
    }
    let infos: Vec<LoopInfo> = loops.iter().map(analyze_loop).collect::<Result<_, _>>()?;

    let outer = (0..infos.len())
        .max_by(|&a, &b| {
            infos[a]
                .area
                .abs()
                .total_cmp(&infos[b].area.abs())
                .then_with(|| min_entity(&infos[b].segments).cmp(&min_entity(&infos[a].segments)))
        })
        .unwrap_or(0);

    validate_arrangement(&infos, outer)?;

    let mut holes: Vec<usize> = (0..infos.len()).filter(|&i| i != outer).collect();
    holes.sort_by_key(|&i| min_entity(&infos[i].segments));

    let outer_resolved = ResolvedLoop {
        segments: infos[outer].segments.clone(),
        reversed: infos[outer].area < 0.0,
        loop_index: LoopIndex::OUTER,
    };
    let hole_resolved = holes.iter().enumerate().map(|(rank, &index)| ResolvedLoop {
        segments: infos[index].segments.clone(),
        reversed: infos[index].area > 0.0,
        loop_index: LoopIndex::new(u16::try_from(rank + 1).unwrap_or(u16::MAX)),
    });
    Ok(core::iter::once(outer_resolved)
        .chain(hole_resolved)
        .collect())
}

fn analyze_loop(profile_loop: &ProfileLoop) -> Result<LoopInfo, BrepError> {
    let segments = match profile_loop {
        ProfileLoop::Closed {
            curve,
            curve_entity,
        } => {
            if !curve_closed(*curve) {
                return Err(invalid(ProfileDefect::OpenLoop));
            }
            vec![Segment {
                curve: *curve,
                curve_entity: *curve_entity,
                corner: None,
            }]
        }
        ProfileLoop::Open(edges) => {
            if edges.is_empty() || edges.iter().any(|edge| curve_closed(edge.curve())) {
                return Err(invalid(ProfileDefect::OpenLoop));
            }
            let segments: Vec<Segment> = edges
                .iter()
                .map(|edge| Segment {
                    curve: edge.curve(),
                    curve_entity: edge.curve_entity(),
                    corner: Some(edge.corner()),
                })
                .collect();
            if !loop_closed(&segments) {
                return Err(invalid(ProfileDefect::OpenLoop));
            }
            if self_intersects(&segments) {
                return Err(invalid(ProfileDefect::SelfIntersectingLoop));
            }
            segments
        }
        ProfileLoop::Chain(_) => return Err(invalid(ProfileDefect::OpenLoop)),
    };
    let polygon = loop_polygon(&segments);
    let area = signed_area(&polygon);
    if area.abs() < AREA_TOLERANCE_MM2 {
        return Err(invalid(ProfileDefect::ZeroArea));
    }
    Ok(LoopInfo {
        segments,
        polygon,
        area,
    })
}

fn validate_arrangement(infos: &[LoopInfo], outer: usize) -> Result<(), BrepError> {
    let outer_info = &infos[outer];
    let holes: Vec<usize> = (0..infos.len()).filter(|&i| i != outer).collect();

    holes.iter().try_for_each(|&i| {
        let contained = !loops_cross(&infos[i].segments, &outer_info.segments)
            && any_point_inside(&infos[i], outer_info);
        contained
            .then_some(())
            .ok_or(invalid(ProfileDefect::UncontainedLoop))
    })?;

    holes.iter().enumerate().try_for_each(|(rank, &a)| {
        holes[rank + 1..].iter().try_for_each(|&b| {
            let overlaps = loops_cross(&infos[a].segments, &infos[b].segments)
                || any_point_inside(&infos[a], &infos[b])
                || any_point_inside(&infos[b], &infos[a]);
            (!overlaps)
                .then_some(())
                .ok_or(invalid(ProfileDefect::OverlappingLoops))
        })
    })
}

fn any_point_inside(inner: &LoopInfo, container: &LoopInfo) -> bool {
    inner
        .polygon
        .iter()
        .any(|&point| point_in_polygon(point, &container.polygon))
}

fn loops_cross(a: &[Segment], b: &[Segment]) -> bool {
    a.iter().any(|sa| {
        b.iter().any(|sb| {
            !matches!(
                intersect_curves(&sa.curve, &sb.curve, PROFILE_TOLERANCE),
                IntersectionSet::Empty
            )
        })
    })
}

fn self_intersects(segments: &[Segment]) -> bool {
    let count = segments.len();
    (0..count).any(|i| {
        let last = if i == 0 { count - 1 } else { count };
        ((i + 2)..last).any(|j| {
            !matches!(
                intersect_curves(&segments[i].curve, &segments[j].curve, PROFILE_TOLERANCE),
                IntersectionSet::Empty
            )
        })
    })
}

fn curve_closed(curve: Curve2Kind) -> bool {
    let start = curve.evaluate(Parameter::new(0.0));
    let end = curve.evaluate(Parameter::new(1.0));
    (end - start).norm_mm() < PROFILE_TOLERANCE_MM
}

fn min_entity(segments: &[Segment]) -> Option<SketchEntityId> {
    segments.iter().map(|seg| seg.curve_entity).min()
}

fn loop_closed(segments: &[Segment]) -> bool {
    let count = segments.len();
    (0..count).all(|index| {
        let end = segments[index].curve.evaluate(Parameter::new(1.0));
        let next = segments[(index + 1) % count]
            .curve
            .evaluate(Parameter::new(0.0));
        (end - next).norm_mm() < PROFILE_TOLERANCE_MM
    })
}

fn loop_polygon(segments: &[Segment]) -> Vec<(f64, f64)> {
    segments
        .iter()
        .flat_map(|seg| {
            let curve = seg.curve;
            let samples = samples_for(curve);
            (0..samples).map(move |i| {
                let t = f64::from(i) / f64::from(samples);
                curve.evaluate(Parameter::new(t)).coords_mm()
            })
        })
        .collect()
}

fn segment_perp_extent(curve: Curve2Kind, a0: (f64, f64), np: (f64, f64)) -> (f64, f64) {
    let perp = |point: Point2| {
        let (x, y) = point.coords_mm();
        (x - a0.0) * np.0 + (y - a0.1) * np.1
    };
    let start = perp(curve.evaluate(Parameter::new(0.0)));
    let end = perp(curve.evaluate(Parameter::new(1.0)));
    let endpoints = (start.min(end), start.max(end));
    match curve {
        Curve2Kind::Line(_) => endpoints,
        Curve2Kind::Circle(circle) => {
            let center = perp(circle.center());
            let radius = circle.radius_mm();
            (center - radius, center + radius)
        }
        Curve2Kind::Ellipse(ellipse) => {
            let center = perp(ellipse.center());
            let phi = ellipse.rotation_rad();
            let (cp, sp) = (phi.cos(), phi.sin());
            let u_np = cp * np.0 + sp * np.1;
            let v_np = -sp * np.0 + cp * np.1;
            let half = ((ellipse.semi_major_mm() * u_np).powi(2)
                + (ellipse.semi_minor_mm() * v_np).powi(2))
            .sqrt();
            (center - half, center + half)
        }
        Curve2Kind::Bezier(_) => (0..=AREA_SAMPLES)
            .map(|i| perp(curve.evaluate(Parameter::new(f64::from(i) / f64::from(AREA_SAMPLES)))))
            .fold(endpoints, |(lo, hi), value| (lo.min(value), hi.max(value))),
        Curve2Kind::Arc(arc) => {
            let (cx, cy) = arc.center().coords_mm();
            let radius = arc.radius_mm();
            [
                (cx + radius * np.0, cy + radius * np.1),
                (cx - radius * np.0, cy - radius * np.1),
            ]
            .into_iter()
            .map(|(x, y)| Point2::from_mm(x, y))
            .filter(|point| arc.contains_point(*point, REVOLVE_ATTRIBUTION_TOLERANCE))
            .fold(endpoints, |(lo, hi), point| {
                let value = perp(point);
                (lo.min(value), hi.max(value))
            })
        }
    }
}

fn signed_area(points: &[(f64, f64)]) -> f64 {
    let count = points.len();
    points
        .iter()
        .enumerate()
        .map(|(index, &(x0, y0))| {
            let (x1, y1) = points[(index + 1) % count];
            x0 * y1 - x1 * y0
        })
        .sum::<f64>()
        / 2.0
}

fn point_in_polygon(point: (f64, f64), polygon: &[(f64, f64)]) -> bool {
    let (px, py) = point;
    let count = polygon.len();
    (0..count).fold(false, |inside, index| {
        let (xi, yi) = polygon[index];
        let (xj, yj) = polygon[(index + count - 1) % count];
        let crosses = (yi > py) != (yj > py) && px < (xj - xi) * (py - yi) / (yj - yi) + xi;
        inside ^ crosses
    })
}

fn samples_for(curve: Curve2Kind) -> u32 {
    match curve {
        Curve2Kind::Line(_) => 1,
        Curve2Kind::Arc(_)
        | Curve2Kind::Circle(_)
        | Curve2Kind::Ellipse(_)
        | Curve2Kind::Bezier(_) => AREA_SAMPLES,
    }
}

#[derive(Copy, Clone)]
enum SubEdge {
    Line,
    Arc(Point2),
    Bezier([Point2; 2]),
}

struct Step {
    start: Point2,
    shape: SubEdge,
    curve_entity: SketchEntityId,
    vertex_entry: (SideKind, SketchEntityId),
}

fn loop_steps(segments: &[Segment], reversed: bool) -> Vec<Step> {
    let count = segments.len();
    let order: Vec<usize> = if reversed {
        (0..count).rev().collect()
    } else {
        (0..count).collect()
    };
    order
        .into_iter()
        .flat_map(|index| {
            let segment = segments[index];
            let first_entry = if reversed {
                junction_entry(segments, (index + 1) % count)
            } else {
                junction_entry(segments, index)
            };
            sub_edges(segment.curve, reversed)
                .into_iter()
                .enumerate()
                .map(move |(position, sub)| Step {
                    start: sub.0,
                    shape: sub.2,
                    curve_entity: segment.curve_entity,
                    vertex_entry: if position == 0 {
                        first_entry
                    } else {
                        (SideKind::Seam, segment.curve_entity)
                    },
                })
        })
        .collect()
}

fn junction_entry(segments: &[Segment], index: usize) -> (SideKind, SketchEntityId) {
    let segment = segments[index];
    segment
        .corner
        .map_or((SideKind::Seam, segment.curve_entity), |corner| {
            (SideKind::Corner, corner)
        })
}

fn sub_edges(curve: Curve2Kind, reversed: bool) -> Vec<(Point2, Point2, SubEdge)> {
    match curve {
        Curve2Kind::Line(line) => {
            let (start, end) = if reversed {
                (line.end(), line.start())
            } else {
                (line.start(), line.end())
            };
            vec![(start, end, SubEdge::Line)]
        }
        Curve2Kind::Arc(arc) => sub_arcs(curve, arc.sweep_rad().abs(), reversed),
        Curve2Kind::Circle(_) => sub_arcs(curve, TAU, reversed),
        Curve2Kind::Ellipse(ellipse) => sub_ellipse(ellipse, reversed),
        Curve2Kind::Bezier(bezier) => {
            let [c1, c2] = bezier.interior();
            if reversed {
                vec![(bezier.end(), bezier.start(), SubEdge::Bezier([c2, c1]))]
            } else {
                vec![(bezier.start(), bezier.end(), SubEdge::Bezier([c1, c2]))]
            }
        }
    }
}

fn sub_ellipse(
    ellipse: crate::ellipse2::Ellipse2,
    reversed: bool,
) -> Vec<(Point2, Point2, SubEdge)> {
    let bounds = [
        0.0,
        core::f64::consts::FRAC_PI_2,
        core::f64::consts::PI,
        3.0 * core::f64::consts::FRAC_PI_2,
        TAU,
    ];
    let arcs: Vec<(Point2, Point2, SubEdge)> = bounds
        .windows(2)
        .map(|pair| {
            let (start, [c1, c2], end) = ellipse.bezier_arc(pair[0], pair[1]);
            (start, end, SubEdge::Bezier([c1, c2]))
        })
        .collect();
    if reversed {
        arcs.into_iter()
            .rev()
            .map(|(start, end, shape)| match shape {
                SubEdge::Bezier([c1, c2]) => (end, start, SubEdge::Bezier([c2, c1])),
                other => (end, start, other),
            })
            .collect()
    } else {
        arcs
    }
}

fn sub_arcs(
    curve: Curve2Kind,
    sweep_abs_rad: f64,
    reversed: bool,
) -> Vec<(Point2, Point2, SubEdge)> {
    let segments = arc_segments(sweep_abs_rad);
    let knots: Vec<f64> = (0..=segments)
        .map(|i| {
            let forward = f64::from(i) / f64::from(segments);
            if reversed { 1.0 - forward } else { forward }
        })
        .collect();
    knots
        .windows(2)
        .map(|pair| {
            let (t0, t1) = (pair[0], pair[1]);
            let start = curve.evaluate(Parameter::new(t0));
            let end = curve.evaluate(Parameter::new(t1));
            let transit = curve.evaluate(Parameter::new(f64::midpoint(t0, t1)));
            (start, end, SubEdge::Arc(transit))
        })
        .collect()
}

fn arc_segments(sweep_abs_rad: f64) -> u32 {
    (1..=MAX_ARC_SEGMENTS)
        .find(|&segments| sweep_abs_rad / f64::from(segments) <= MAX_SUBARC_RAD)
        .unwrap_or(MAX_ARC_SEGMENTS)
}

#[derive(Default)]
struct Bookkeeping {
    edge_entry: HashMap<EdgeID, (LoopIndex, SketchEntityId)>,
    vertex_entry: HashMap<VertexID, (SideKind, SketchEntityId)>,
}

fn build_solid(
    plane: Plane3,
    resolved: &[ResolvedLoop],
    plan: SweepPlan,
    extrude: FeatureId,
    closed: HashSet<SketchEntityId>,
    parent_curves: &HashMap<SketchEntityId, Curve2Kind>,
) -> Result<(Solid, SolidLabeling), BrepError> {
    let mut book = Bookkeeping::default();
    let wires: Vec<Wire> = resolved
        .iter()
        .map(|resolved_loop| {
            let steps = loop_steps(&resolved_loop.segments, resolved_loop.reversed);
            build_loop(
                plane,
                plan.base_offset_mm,
                &steps,
                resolved_loop.loop_index,
                &mut book,
            )
        })
        .collect();

    let face = builder::try_attach_plane(&wires)
        .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?;
    let (nx, ny, nz) = plane.normal().components();
    let sweep = Vector3::new(nx * plan.depth_mm, ny * plan.depth_mm, nz * plan.depth_mm);
    let solid = builder::tsweep(&face, sweep);
    let labeling = label_solid(&solid, &book, extrude, closed, plane, plan, parent_curves);
    Ok((solid, labeling))
}

fn build_loop(
    plane: Plane3,
    offset_mm: f64,
    steps: &[Step],
    loop_index: LoopIndex,
    book: &mut Bookkeeping,
) -> Wire {
    let count = steps.len();
    let vertices: Vec<Vertex> = steps
        .iter()
        .map(|step| builder::vertex(lift(plane, offset_mm, step.start)))
        .collect();
    vertices.iter().zip(steps).for_each(|(vertex, step)| {
        book.vertex_entry.insert(vertex.id(), step.vertex_entry);
    });
    let edges: Vec<Edge> = (0..count)
        .map(|index| {
            let from = &vertices[index];
            let to = &vertices[(index + 1) % count];
            let step = &steps[index];
            let edge = build_sub_edge(plane, offset_mm, from, to, step.shape);
            book.edge_entry
                .insert(edge.id(), (loop_index, step.curve_entity));
            edge
        })
        .collect();
    edges.into()
}

fn lift(plane: Plane3, offset_mm: f64, point: Point2) -> truck_modeling::Point3 {
    let (u, v) = point.coords_mm();
    let (wx, wy, wz) = plane.point_at_local(u, v, offset_mm).coords_mm();
    truck_modeling::Point3::new(wx, wy, wz)
}

fn build_sub_edge(
    plane: Plane3,
    offset_mm: f64,
    from: &Vertex,
    to: &Vertex,
    shape: SubEdge,
) -> Edge {
    match shape {
        SubEdge::Line => builder::line(from, to),
        SubEdge::Arc(transit) => builder::circle_arc(from, to, lift(plane, offset_mm, transit)),
        SubEdge::Bezier([c1, c2]) => builder::bezier(
            from,
            to,
            vec![lift(plane, offset_mm, c1), lift(plane, offset_mm, c2)],
        ),
    }
}

struct Labels {
    faces: HashMap<FaceID, FaceLabel>,
    edges: HashMap<EdgeID, EdgeLabel>,
    vertices: HashMap<VertexID, VertexLabel>,
    edge_curves: HashMap<EdgeID, EdgeCurve3>,
    feature: FeatureId,
    closed: HashSet<SketchEntityId>,
}

impl Labels {
    fn new(feature: FeatureId, closed: HashSet<SketchEntityId>) -> Self {
        Self {
            faces: HashMap::new(),
            edges: HashMap::new(),
            vertices: HashMap::new(),
            edge_curves: HashMap::new(),
            feature,
            closed,
        }
    }

    fn face(&mut self, id: FaceID, role: FaceRole) {
        self.faces.insert(
            id,
            FaceLabel {
                feature: self.feature,
                role,
            },
        );
    }

    fn edge(&mut self, id: EdgeID, role: EdgeRole) {
        self.edges.insert(
            id,
            EdgeLabel {
                feature: self.feature,
                role,
            },
        );
    }

    fn vertex(&mut self, id: VertexID, role: VertexRole) {
        self.vertices.insert(
            id,
            VertexLabel {
                feature: self.feature,
                role,
            },
        );
    }

    fn edge_curve(&mut self, id: EdgeID, curve: EdgeCurve3) {
        self.edge_curves.insert(id, curve);
    }

    fn into_labeling(self) -> SolidLabeling {
        SolidLabeling {
            faces: self.faces,
            edges: self.edges,
            vertices: self.vertices,
            closed_curves: self.closed,
            edge_curves: self.edge_curves,
            face_fragments: HashMap::new(),
            edge_fragments: HashMap::new(),
            vertex_fragments: HashMap::new(),
        }
    }
}

#[derive(Copy, Clone)]
struct SideFace {
    id: FaceID,
    profile_edge: EdgeID,
    front: VertexID,
    back: VertexID,
}

fn label_solid(
    solid: &Solid,
    book: &Bookkeeping,
    extrude: FeatureId,
    closed: HashSet<SketchEntityId>,
    plane: Plane3,
    plan: SweepPlan,
    parent_curves: &HashMap<SketchEntityId, Curve2Kind>,
) -> SolidLabeling {
    let mut labels = solid
        .boundaries()
        .iter()
        .flat_map(|shell| shell.face_iter())
        .fold(Labels::new(extrude, closed), |mut labels, face| {
            let face_edges: Vec<(EdgeID, VertexID, VertexID)> = face
                .boundaries()
                .iter()
                .flat_map(|wire| {
                    wire.edge_iter()
                        .map(|edge| (edge.id(), edge.front().id(), edge.back().id()))
                })
                .collect();
            let profile: Vec<(EdgeID, VertexID, VertexID)> = face_edges
                .iter()
                .copied()
                .filter(|(edge_id, _, _)| book.edge_entry.contains_key(edge_id))
                .collect();

            match profile.first() {
                None => labels.face(face.id(), FaceRole::EndCap),
                Some(_) if profile.len() == face_edges.len() => {
                    labels.face(face.id(), FaceRole::StartCap);
                }
                Some(&(profile_edge, front, back)) => label_side_face(
                    &mut labels,
                    SideFace {
                        id: face.id(),
                        profile_edge,
                        front,
                        back,
                    },
                    &face_edges,
                    book,
                ),
            }
            labels
        });
    record_cap_curves(&mut labels, plane, plan, parent_curves);
    labels.into_labeling()
}

fn record_cap_curves(
    labels: &mut Labels,
    plane: Plane3,
    plan: SweepPlan,
    parent_curves: &HashMap<SketchEntityId, Curve2Kind>,
) {
    let entries: Vec<(EdgeID, EdgeRole)> = labels
        .edges
        .iter()
        .map(|(id, label)| (*id, label.role))
        .collect();
    entries.into_iter().for_each(|(edge_id, role)| {
        let (offset_mm, curve_entity) = match role {
            EdgeRole::StartCapEdge { from } => (plan.base_offset_mm, from),
            EdgeRole::EndCapEdge { from } => (plan.base_offset_mm + plan.depth_mm, from),
            _ => return,
        };
        let Some(parent) = parent_curves.get(&curve_entity) else {
            return;
        };
        let Some(lifted) = lift_curve2(*parent, plane, offset_mm, PROFILE_TOLERANCE) else {
            return;
        };
        labels.edge_curve(edge_id, lifted);
    });
}

fn label_side_face(
    labels: &mut Labels,
    side: SideFace,
    face_edges: &[(EdgeID, VertexID, VertexID)],
    book: &Bookkeeping,
) {
    let (loop_index, curve_entity) = book.edge_entry[&side.profile_edge];
    let (front_kind, front_entity) = book.vertex_entry[&side.front];
    let (back_kind, back_entity) = book.vertex_entry[&side.back];

    labels.face(
        side.id,
        FaceRole::Side {
            loop_index,
            from: curve_entity,
        },
    );
    labels.edge(
        side.profile_edge,
        EdgeRole::StartCapEdge { from: curve_entity },
    );
    labels.vertex(
        side.front,
        VertexRole::StartCapVertex {
            from: front_entity,
            side: front_kind,
        },
    );
    labels.vertex(
        side.back,
        VertexRole::StartCapVertex {
            from: back_entity,
            side: back_kind,
        },
    );

    face_edges
        .iter()
        .filter(|(edge_id, _, _)| *edge_id != side.profile_edge)
        .for_each(|&(edge_id, edge_front, edge_back)| {
            let touches_front = edge_front == side.front || edge_back == side.front;
            let touches_back = edge_front == side.back || edge_back == side.back;
            match (touches_front, touches_back) {
                (true, false) => {
                    labels.edge(
                        edge_id,
                        EdgeRole::SideEdge {
                            from: front_entity,
                            side: front_kind,
                        },
                    );
                    let top = if edge_front == side.front {
                        edge_back
                    } else {
                        edge_front
                    };
                    labels.vertex(
                        top,
                        VertexRole::EndCapVertex {
                            from: front_entity,
                            side: front_kind,
                        },
                    );
                }
                (false, true) => {
                    labels.edge(
                        edge_id,
                        EdgeRole::SideEdge {
                            from: back_entity,
                            side: back_kind,
                        },
                    );
                    let top = if edge_front == side.back {
                        edge_back
                    } else {
                        edge_front
                    };
                    labels.vertex(
                        top,
                        VertexRole::EndCapVertex {
                            from: back_entity,
                            side: back_kind,
                        },
                    );
                }
                _ => labels.edge(edge_id, EdgeRole::EndCapEdge { from: curve_entity }),
            }
        });
}

fn truck_edge_midpoint(edge: &Edge) -> Point3 {
    let curve = edge.curve();
    let (t0, t1) = curve.range_tuple();
    point_from_truck(curve.subs(f64::midpoint(t0, t1)))
}

fn attribute_face(face: &Face, frame: &AxisFrame, curves: &[RevolveCurve]) -> Option<RevolveCurve> {
    let common = face.boundaries().iter().flat_map(Wire::edge_iter).try_fold(
        None::<Vec<usize>>,
        |common, edge| {
            let query = frame.back_project(truck_edge_midpoint(edge));
            let hits: Vec<usize> = curves
                .iter()
                .enumerate()
                .filter(|(_, candidate)| {
                    candidate
                        .curve
                        .closest_point(query, REVOLVE_ATTRIBUTION_TOLERANCE)
                        .distance()
                        .get::<millimeter>()
                        <= REVOLVE_ATTRIBUTION_TOLERANCE_MM
                })
                .map(|(index, _)| index)
                .collect();
            let next = match common {
                None => hits,
                Some(previous) => previous
                    .into_iter()
                    .filter(|index| hits.contains(index))
                    .collect(),
            };
            if next.is_empty() {
                Err(())
            } else {
                Ok(Some(next))
            }
        },
    );
    let candidates = common.ok().flatten()?;
    candidates
        .into_iter()
        .min_by(|&a, &b| curves[a].entity.cmp(&curves[b].entity))
        .map(|index| curves[index])
}

fn is_cap_face(face: &Face, axis: Axis3) -> bool {
    let Some(basis) = face_plane_basis(face) else {
        return false;
    };
    let plane = Plane3::from(basis);
    let (nx, ny, nz) = plane.normal().components();
    let (dx, dy, dz) = axis.direction().components();
    if (nx * dx + ny * dy + nz * dz).abs() > REVOLVE_AXIS_DOT_TOLERANCE {
        return false;
    }
    let (ox, oy, oz) = plane.origin().coords_mm();
    let (px, py, pz) = axis.origin().coords_mm();
    ((px - ox) * nx + (py - oy) * ny + (pz - oz) * nz).abs() <= REVOLVE_ATTRIBUTION_TOLERANCE_MM
}

fn cap_angle(face: &Face, frame: &AxisFrame) -> f64 {
    face.boundaries()
        .iter()
        .flat_map(Wire::edge_iter)
        .map(|edge| point_from_truck(edge.front().point()))
        .find(|point| frame.radius(*point) > REVOLVE_ATTRIBUTION_TOLERANCE_MM)
        .map_or(0.0, |point| frame.angle_of(point))
}

fn cap_sweep_angle(face: &Face, frame: &AxisFrame, forward_rad: f64, backward_rad: f64) -> f64 {
    let raw = cap_angle(face, frame);
    let mid = (backward_rad - forward_rad) / 2.0;
    raw - TAU * ((raw - mid) / TAU).round()
}

#[derive(Copy, Clone)]
enum FaceClass {
    Cap,
    Side(RevolveCurve),
    Orphan,
}

#[derive(Copy, Clone, PartialEq)]
enum FaceKind {
    Side(SketchEntityId),
    StartCap,
    EndCap,
    Other,
}

type CornerPairs = HashMap<(SketchEntityId, SketchEntityId), SketchEntityId>;

fn pair_key(a: SketchEntityId, b: SketchEntityId) -> (SketchEntityId, SketchEntityId) {
    if a <= b { (a, b) } else { (b, a) }
}

fn revolve_corner_pairs(resolved: &[ResolvedLoop]) -> CornerPairs {
    resolved
        .iter()
        .flat_map(|resolved_loop| {
            let segments = &resolved_loop.segments;
            let count = segments.len();
            (0..count).filter_map(move |index| {
                let current = segments[index];
                let previous = segments[(index + count - 1) % count];
                current.corner.map(|corner| {
                    (
                        pair_key(previous.curve_entity, current.curve_entity),
                        corner,
                    )
                })
            })
        })
        .collect()
}

fn label_revolve_solid(
    solid: &Solid,
    feature: FeatureId,
    frame: &AxisFrame,
    curves: &[RevolveCurve],
    corners: &CornerPairs,
    forward_rad: f64,
    backward_rad: f64,
) -> SolidLabeling {
    let classes = classify_revolve_faces(solid, frame, curves);
    let (faces, kinds) =
        revolve_face_roles(solid, feature, frame, &classes, forward_rad, backward_rad);
    let edge_kinds = face_edge_kinds(solid, &kinds);
    let edge_roles = revolve_edge_roles(solid, &edge_kinds, corners);
    let kept = referenced_vertices(solid, &edge_roles);
    let vertex_kinds = face_vertex_kinds(solid, &kinds);
    SolidLabeling {
        faces,
        edges: edge_roles
            .into_iter()
            .map(|(id, role)| (id, EdgeLabel { feature, role }))
            .collect(),
        vertices: revolve_vertex_labels(solid, feature, &vertex_kinds, corners, &kept),
        closed_curves: HashSet::new(),
        edge_curves: HashMap::new(),
        face_fragments: HashMap::new(),
        edge_fragments: HashMap::new(),
        vertex_fragments: HashMap::new(),
    }
}

fn classify_revolve_faces(
    solid: &Solid,
    frame: &AxisFrame,
    curves: &[RevolveCurve],
) -> HashMap<FaceID, FaceClass> {
    solid
        .boundaries()
        .iter()
        .flat_map(Shell::face_iter)
        .map(|face| {
            let class = if is_cap_face(face, frame.axis) {
                FaceClass::Cap
            } else {
                match attribute_face(face, frame, curves) {
                    Some(curve) => FaceClass::Side(curve),
                    None => FaceClass::Orphan,
                }
            };
            (face.id(), class)
        })
        .collect()
}

fn revolve_face_roles(
    solid: &Solid,
    feature: FeatureId,
    frame: &AxisFrame,
    classes: &HashMap<FaceID, FaceClass>,
    forward_rad: f64,
    backward_rad: f64,
) -> (HashMap<FaceID, FaceLabel>, HashMap<FaceID, FaceKind>) {
    let mut faces: HashMap<FaceID, FaceLabel> = HashMap::new();
    let mut kinds: HashMap<FaceID, FaceKind> = HashMap::new();
    let mut caps: Vec<(FaceID, f64)> = Vec::new();
    let mut orphans: Vec<FaceID> = Vec::new();
    solid
        .boundaries()
        .iter()
        .flat_map(Shell::face_iter)
        .for_each(|face| match classes[&face.id()] {
            FaceClass::Side(curve) => {
                faces.insert(
                    face.id(),
                    FaceLabel {
                        feature,
                        role: FaceRole::Side {
                            loop_index: curve.loop_index,
                            from: curve.entity,
                        },
                    },
                );
                kinds.insert(face.id(), FaceKind::Side(curve.entity));
            }
            FaceClass::Cap => caps.push((
                face.id(),
                cap_sweep_angle(face, frame, forward_rad, backward_rad),
            )),
            FaceClass::Orphan => orphans.push(face.id()),
        });

    caps.sort_by(|a, b| a.1.total_cmp(&b.1));
    let cap_count = caps.len();
    let leftovers: Vec<FaceID> = caps
        .into_iter()
        .enumerate()
        .filter_map(|(rank, (face_id, _))| {
            let (role, kind) = if rank == 0 {
                (FaceRole::EndCap, FaceKind::EndCap)
            } else if rank + 1 == cap_count {
                (FaceRole::StartCap, FaceKind::StartCap)
            } else {
                return Some(face_id);
            };
            faces.insert(face_id, FaceLabel { feature, role });
            kinds.insert(face_id, kind);
            None
        })
        .chain(orphans)
        .collect();
    leftovers
        .into_iter()
        .enumerate()
        .for_each(|(index, face_id)| {
            faces.insert(
                face_id,
                FaceLabel {
                    feature,
                    role: FaceRole::Imported {
                        ordinal: ordinal(index),
                    },
                },
            );
            kinds.insert(face_id, FaceKind::Other);
        });

    (faces, kinds)
}

fn ordinal(index: usize) -> ImportOrdinal {
    ImportOrdinal::new(u32::try_from(index).unwrap_or(u32::MAX))
}

fn distinct_sides(kinds: &[FaceKind]) -> Vec<SketchEntityId> {
    kinds.iter().fold(Vec::new(), |mut sides, kind| {
        if let FaceKind::Side(entity) = kind
            && !sides.contains(entity)
        {
            sides.push(*entity);
        }
        sides
    })
}

fn face_edge_kinds(
    solid: &Solid,
    kinds: &HashMap<FaceID, FaceKind>,
) -> HashMap<EdgeID, Vec<FaceKind>> {
    solid
        .boundaries()
        .iter()
        .flat_map(Shell::face_iter)
        .flat_map(|face| {
            let kind = kinds[&face.id()];
            face.boundaries()
                .iter()
                .flat_map(Wire::edge_iter)
                .map(Edge::id)
                .collect::<Vec<_>>()
                .into_iter()
                .map(move |edge_id| (edge_id, kind))
        })
        .fold(HashMap::new(), |mut acc, (edge_id, kind)| {
            acc.entry(edge_id).or_default().push(kind);
            acc
        })
}

fn face_vertex_kinds(
    solid: &Solid,
    kinds: &HashMap<FaceID, FaceKind>,
) -> HashMap<VertexID, Vec<FaceKind>> {
    solid
        .boundaries()
        .iter()
        .flat_map(Shell::face_iter)
        .flat_map(|face| {
            let kind = kinds[&face.id()];
            face.boundaries()
                .iter()
                .flat_map(Wire::edge_iter)
                .flat_map(|edge| [edge.front().id(), edge.back().id()])
                .collect::<Vec<_>>()
                .into_iter()
                .map(move |vertex_id| (vertex_id, kind))
        })
        .fold(HashMap::new(), |mut acc, (vertex_id, kind)| {
            let seen: &mut Vec<FaceKind> = acc.entry(vertex_id).or_default();
            if !seen.contains(&kind) {
                seen.push(kind);
            }
            acc
        })
}

fn revolve_edge_role(kinds: &[FaceKind], corners: &CornerPairs, index: usize) -> EdgeRole {
    let sides = distinct_sides(kinds);
    let has_start = kinds.contains(&FaceKind::StartCap);
    let has_end = kinds.contains(&FaceKind::EndCap);
    let side_incidences = kinds
        .iter()
        .filter(|kind| matches!(kind, FaceKind::Side(_)))
        .count();
    let imported = EdgeRole::Imported {
        ordinal: ordinal(index),
    };
    match (sides.as_slice(), has_start, has_end) {
        ([entity], false, false) if side_incidences >= 2 => EdgeRole::SideEdge {
            from: *entity,
            side: SideKind::Seam,
        },
        ([entity], true, false) => EdgeRole::StartCapEdge { from: *entity },
        ([entity], false, true) => EdgeRole::EndCapEdge { from: *entity },
        ([a, b], false, false) => {
            corners
                .get(&pair_key(*a, *b))
                .map_or(imported, |corner| EdgeRole::SideEdge {
                    from: *corner,
                    side: SideKind::Corner,
                })
        }
        _ => imported,
    }
}

fn revolve_vertex_role(kinds: &[FaceKind], corners: &CornerPairs, index: usize) -> VertexRole {
    let sides = distinct_sides(kinds);
    let has_start = kinds.contains(&FaceKind::StartCap);
    let has_end = kinds.contains(&FaceKind::EndCap);
    let imported = VertexRole::Imported {
        ordinal: ordinal(index),
    };
    match (sides.as_slice(), has_start, has_end) {
        ([a, b], true, false) => {
            corners
                .get(&pair_key(*a, *b))
                .map_or(imported, |corner| VertexRole::StartCapVertex {
                    from: *corner,
                    side: SideKind::Corner,
                })
        }
        ([a, b], false, true) => {
            corners
                .get(&pair_key(*a, *b))
                .map_or(imported, |corner| VertexRole::EndCapVertex {
                    from: *corner,
                    side: SideKind::Corner,
                })
        }
        _ => imported,
    }
}

fn revolve_edge_roles(
    solid: &Solid,
    edge_kinds: &HashMap<EdgeID, Vec<FaceKind>>,
    corners: &CornerPairs,
) -> HashMap<EdgeID, EdgeRole> {
    let mut seen = HashSet::new();
    solid
        .edge_iter()
        .filter(|edge| seen.insert(edge.id()))
        .enumerate()
        .map(|(index, edge)| {
            let role = edge_kinds.get(&edge.id()).map_or(
                EdgeRole::Imported {
                    ordinal: ordinal(index),
                },
                |kinds| revolve_edge_role(kinds, corners, index),
            );
            (edge.id(), role)
        })
        .collect()
}

fn vertex_order(solid: &Solid) -> HashMap<VertexID, usize> {
    let mut seen = HashSet::new();
    solid
        .vertex_iter()
        .filter(|vertex| seen.insert(vertex.id()))
        .enumerate()
        .map(|(index, vertex)| (vertex.id(), index))
        .collect()
}

fn referenced_vertices(solid: &Solid, edge_roles: &HashMap<EdgeID, EdgeRole>) -> HashSet<VertexID> {
    let order = vertex_order(solid);
    let mut seen = HashSet::new();
    let groups = solid
        .edge_iter()
        .filter(|edge| seen.insert(edge.id()))
        .filter_map(|edge| {
            let role = edge_roles[&edge.id()];
            if matches!(
                role,
                EdgeRole::SideEdge {
                    side: SideKind::Seam,
                    ..
                }
            ) {
                None
            } else {
                Some((role, [edge.front().id(), edge.back().id()]))
            }
        })
        .fold(
            HashMap::<EdgeRole, Vec<[VertexID; 2]>>::new(),
            |mut groups, (role, ends)| {
                groups.entry(role).or_default().push(ends);
                groups
            },
        );
    groups
        .values()
        .flat_map(|segments| group_endpoints(segments, &order))
        .collect()
}

fn group_endpoints(segments: &[[VertexID; 2]], order: &HashMap<VertexID, usize>) -> Vec<VertexID> {
    let counts =
        segments
            .iter()
            .flatten()
            .fold(HashMap::<VertexID, usize>::new(), |mut counts, vertex| {
                *counts.entry(*vertex).or_insert(0) += 1;
                counts
            });
    let odd: Vec<VertexID> = counts
        .iter()
        .filter_map(|(vertex, count)| (count % 2 == 1).then_some(*vertex))
        .collect();
    if odd.is_empty() {
        counts
            .keys()
            .min_by_key(|vertex| order.get(vertex).copied().unwrap_or(usize::MAX))
            .copied()
            .into_iter()
            .collect()
    } else {
        odd
    }
}

fn seam_from(kinds: Option<&Vec<FaceKind>>) -> SketchEntityId {
    kinds
        .and_then(|kinds| distinct_sides(kinds).into_iter().next())
        .unwrap_or_default()
}

fn revolve_vertex_labels(
    solid: &Solid,
    feature: FeatureId,
    vertex_kinds: &HashMap<VertexID, Vec<FaceKind>>,
    corners: &CornerPairs,
    kept: &HashSet<VertexID>,
) -> HashMap<VertexID, VertexLabel> {
    let mut seen = HashSet::new();
    solid
        .vertex_iter()
        .filter(|vertex| seen.insert(vertex.id()))
        .enumerate()
        .map(|(index, vertex)| {
            let kinds = vertex_kinds.get(&vertex.id());
            let role = if kept.contains(&vertex.id()) {
                kinds.map_or(
                    VertexRole::Imported {
                        ordinal: ordinal(index),
                    },
                    |kinds| revolve_vertex_role(kinds, corners, index),
                )
            } else {
                VertexRole::Seam {
                    from: seam_from(kinds),
                }
            };
            (vertex.id(), VertexLabel { feature, role })
        })
        .collect()
}

#[cfg(test)]
mod skin_tests {
    use super::{CompositePath3, FrameGuide, SkinPlan, build_skinned_sweep, profile_sections};

    fn unguided(stations: StationCount, guide: FrameGuide, twist_rad: f64) -> SkinPlan<'static> {
        SkinPlan {
            stations,
            guide,
            twist_rad,
            origin: bone_types::Parameter::new(0.0),
            anchor: None,
            guides: &[],
            orientation: crate::sweep::SweepOrientation::FollowPath,
            tangency: (
                crate::sweep::SweepTangency::None,
                crate::sweep::SweepTangency::None,
            ),
            face_up: None,
        }
    }

    fn one(path: Curve3Kind) -> CompositePath3 {
        match CompositePath3::chain(&[path]) {
            Ok(composite) => composite,
            Err(defect) => panic!("a single segment chains into a path: {defect:?}"),
        }
    }
    use crate::brep::BrepSolid;
    use crate::brep::profile::{ExtrudeProfile, ProfileEdge, ProfileLoop};
    use crate::curve2::Curve2Kind;
    use crate::curve3::Curve3Kind;
    use crate::line2::Line2;
    use crate::line3::Line3;
    use crate::sweep_surface::StationCount;
    use bone_types::{
        AngleTolerance, ChordHeightTolerance, FeatureId, Plane3, Point2, Point3, SketchEntityId,
        Tolerance, UnitVec3,
    };
    use slotmap::SlotMap;

    const TOL: Tolerance = Tolerance::new(1.0e-9);

    struct Ids {
        features: SlotMap<FeatureId, ()>,
        entities: SlotMap<SketchEntityId, ()>,
    }

    impl Ids {
        fn new() -> Self {
            Self {
                features: SlotMap::with_key(),
                entities: SlotMap::with_key(),
            }
        }
    }

    fn rectangle(ids: &mut Ids, width: f64, height: f64) -> ProfileLoop {
        let corners = [
            Point2::from_mm(0.0, 0.0),
            Point2::from_mm(width, 0.0),
            Point2::from_mm(width, height),
            Point2::from_mm(0.0, height),
        ];
        let edges = (0..4)
            .map(|index| {
                let Ok(line) = Line2::new(corners[index], corners[(index + 1) % 4], TOL) else {
                    panic!("distinct corners");
                };
                ProfileEdge::new(
                    Curve2Kind::Line(line),
                    ids.entities.insert(()),
                    ids.entities.insert(()),
                )
            })
            .collect();
        ProfileLoop::Open(edges)
    }

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

    fn mesh_volume(solid: &BrepSolid) -> f64 {
        let mesh = match solid.tessellate(
            ChordHeightTolerance::from_mm(0.001),
            AngleTolerance::from_radians(0.02),
        ) {
            Ok(mesh) => mesh,
            Err(error) => panic!("a valid skin tessellates: {error:?}"),
        };
        mesh.faces()
            .iter()
            .flat_map(|slab| {
                let positions = slab.positions().to_vec();
                slab.triangles().iter().map(move |triangle| {
                    let point = |index: u32| positions[index as usize].coords_mm();
                    let (ax, ay, az) = point(triangle[0]);
                    let (bx, by, bz) = point(triangle[1]);
                    let (cx, cy, cz) = point(triangle[2]);
                    let cross = (by * cz - bz * cy, bz * cx - bx * cz, bx * cy - by * cx);
                    (ax * cross.0 + ay * cross.1 + az * cross.2) / 6.0
                })
            })
            .sum()
    }

    #[test]
    fn profile_sections_reads_a_rectangle() {
        let mut ids = Ids::new();
        let profile = ExtrudeProfile::new(xy_plane(), vec![rectangle(&mut ids, 2.0, 1.0)]);
        let Some(sections) = profile_sections(&profile) else {
            panic!("a rectangle resolves into edge sections");
        };
        assert_eq!(sections.len(), 4);
        assert!(sections.iter().all(|section| section.spline.degree == 1));
    }

    #[test]
    fn skinned_sweep_along_a_line_reproduces_the_prism_volume() {
        let mut ids = Ids::new();
        let profile = ExtrudeProfile::new(xy_plane(), vec![rectangle(&mut ids, 2.0, 1.0)]);
        let Ok(segment) = Line3::new(Point3::origin(), Point3::from_mm(0.0, 0.0, 4.0), TOL) else {
            panic!("distinct endpoints");
        };
        let path = Curve3Kind::Line(segment);
        let sweep = ids.features.insert(());
        let solid = match build_skinned_sweep(
            sweep,
            &profile,
            &one(path),
            &unguided(StationCount::new(8), FrameGuide::MinimumTwist, 0.0),
        ) {
            Ok(solid) => solid,
            Err(error) => panic!("the skin builds a valid solid: {error:?}"),
        };
        let volume = mesh_volume(&solid).abs();
        assert!(
            (volume - 8.0).abs() < 1.0e-3,
            "a 2x1 rectangle skinned along a length-4 line is an 8 mm^3 prism: got {volume}"
        );
    }

    fn half_disc(ids: &mut Ids, radius: f64) -> ProfileLoop {
        let Ok(diameter) = Line2::new(
            Point2::from_mm(-radius, 0.0),
            Point2::from_mm(radius, 0.0),
            TOL,
        ) else {
            panic!("distinct endpoints");
        };
        let Ok(arc) = crate::arc2::Arc2::new(
            Point2::origin(),
            bone_types::Length::new::<bone_types::millimeter>(radius),
            bone_types::Angle::new::<bone_types::radian>(0.0),
            bone_types::Angle::new::<bone_types::radian>(core::f64::consts::PI),
            TOL,
        ) else {
            panic!("valid arc");
        };
        ProfileLoop::Open(vec![
            ProfileEdge::new(
                Curve2Kind::Line(diameter),
                ids.entities.insert(()),
                ids.entities.insert(()),
            ),
            ProfileEdge::new(
                Curve2Kind::Arc(arc),
                ids.entities.insert(()),
                ids.entities.insert(()),
            ),
        ])
    }

    #[test]
    fn skinned_sweep_of_an_arc_profile_matches_the_half_cylinder() {
        let mut ids = Ids::new();
        let profile = ExtrudeProfile::new(xy_plane(), vec![half_disc(&mut ids, 2.0)]);
        let Ok(segment) = Line3::new(Point3::origin(), Point3::from_mm(0.0, 0.0, 5.0), TOL) else {
            panic!("distinct endpoints");
        };
        let path = Curve3Kind::Line(segment);
        let sweep = ids.features.insert(());
        let solid = match build_skinned_sweep(
            sweep,
            &profile,
            &one(path),
            &unguided(StationCount::new(8), FrameGuide::MinimumTwist, 0.0),
        ) {
            Ok(solid) => solid,
            Err(error) => panic!("the arc-profile skin builds a valid solid: {error:?}"),
        };
        let volume = mesh_volume(&solid).abs();
        let expected = core::f64::consts::PI * 2.0 * 2.0 / 2.0 * 5.0;
        assert!(
            (volume - expected).abs() < 0.02,
            "a radius-2 half disc swept 5 mm is a half cylinder of {expected}: got {volume}"
        );
    }

    fn skin_along_line(radius: f64, length: f64, stations: u32) -> BrepSolid {
        let mut ids = Ids::new();
        let profile = ExtrudeProfile::new(xy_plane(), vec![half_disc(&mut ids, radius)]);
        let Ok(segment) = Line3::new(Point3::origin(), Point3::from_mm(0.0, 0.0, length), TOL)
        else {
            panic!("distinct endpoints");
        };
        let path = Curve3Kind::Line(segment);
        let sweep = ids.features.insert(());
        match build_skinned_sweep(
            sweep,
            &profile,
            &one(path),
            &unguided(StationCount::new(stations), FrameGuide::MinimumTwist, 0.0),
        ) {
            Ok(solid) => solid,
            Err(error) => panic!("the skin builds a valid solid: {error:?}"),
        }
    }

    #[test]
    fn skinned_sweep_is_deterministic() {
        let first = skin_along_line(2.0, 5.0, 12);
        let second = skin_along_line(2.0, 5.0, 12);
        assert_eq!(
            first.content_key(),
            second.content_key(),
            "the same skinned sweep builds byte-identically"
        );
    }

    proptest::proptest! {
        #[test]
        fn skinned_sweep_stays_deterministic_and_valid(
            radius in 0.5f64..=4.0f64,
            length in 1.0f64..=8.0f64,
            stations in 4u32..=24u32,
        ) {
            let first = skin_along_line(radius, length, stations);
            let second = skin_along_line(radius, length, stations);
            proptest::prop_assert_eq!(first.content_key(), second.content_key());
            proptest::prop_assert!(first.validate(Tolerance::new(1.0e-6)).is_ok());
        }
    }

    #[test]
    fn skinned_sweep_along_an_arc_matches_the_revolved_volume() {
        let mut ids = Ids::new();
        let xz = {
            let Ok(plane) = Plane3::new(
                Point3::origin(),
                UnitVec3::x_axis(),
                UnitVec3::z_axis(),
                TOL,
            ) else {
                panic!("orthonormal axes");
            };
            plane
        };
        let mut shifted = rectangle(&mut ids, 1.0, 1.0);
        if let ProfileLoop::Open(edges) = &mut shifted {
            *edges = edges
                .iter()
                .map(|edge| {
                    let Curve2Kind::Line(line) = edge.curve() else {
                        panic!("rectangle is lines");
                    };
                    let shift = |p: Point2| Point2::from_mm(p.coords_mm().0 + 2.0, p.coords_mm().1);
                    let Ok(moved) = Line2::new(shift(line.start()), shift(line.end()), TOL) else {
                        panic!("distinct corners");
                    };
                    ProfileEdge::new(Curve2Kind::Line(moved), edge.curve_entity(), edge.corner())
                })
                .collect();
        }
        let profile = ExtrudeProfile::new(xz, vec![shifted]);
        let arc = {
            let xy = xy_plane();
            let Ok(arc) = crate::arc3::Arc3::new(
                xy,
                bone_types::Length::new::<bone_types::millimeter>(2.5),
                bone_types::Angle::new::<bone_types::radian>(0.0),
                bone_types::Angle::new::<bone_types::radian>(core::f64::consts::PI),
                TOL,
            ) else {
                panic!("valid arc");
            };
            Curve3Kind::Arc(arc)
        };
        let sweep = ids.features.insert(());
        let solid = match build_skinned_sweep(
            sweep,
            &profile,
            &one(arc),
            &unguided(StationCount::new(64), FrameGuide::MinimumTwist, 0.0),
        ) {
            Ok(solid) => solid,
            Err(error) => panic!("the arc skin builds a valid solid: {error:?}"),
        };
        let volume = mesh_volume(&solid).abs();
        let expected = 1.0 * core::f64::consts::PI * 2.5;
        assert!(
            (volume - expected).abs() < 0.05,
            "a unit square at radius 2.5 swept a half turn matches Pappus {expected}: got {volume}"
        );
    }

    fn disc(ids: &mut Ids, cx: f64, cy: f64, radius: f64) -> ProfileLoop {
        let Ok(circle) = crate::circle2::Circle2::new(
            Point2::from_mm(cx, cy),
            bone_types::Length::new::<bone_types::millimeter>(radius),
            TOL,
        ) else {
            panic!("positive radius");
        };
        ProfileLoop::Closed {
            curve: Curve2Kind::Circle(circle),
            curve_entity: ids.entities.insert(()),
        }
    }

    #[test]
    fn holed_profile_skins_an_annular_tube_along_an_arc() {
        let mut ids = Ids::new();
        let Ok(xz) = Plane3::new(
            Point3::origin(),
            UnitVec3::x_axis(),
            UnitVec3::z_axis(),
            TOL,
        ) else {
            panic!("orthonormal axes");
        };
        let rho = 2.5;
        let outer_r = 0.5;
        let inner_r = 0.25;
        let profile = ExtrudeProfile::new(
            xz,
            vec![
                disc(&mut ids, rho, 0.0, outer_r),
                disc(&mut ids, rho, 0.0, inner_r),
            ],
        );
        let Ok(arc) = crate::arc3::Arc3::new(
            xy_plane(),
            bone_types::Length::new::<bone_types::millimeter>(rho),
            bone_types::Angle::new::<bone_types::radian>(0.0),
            bone_types::Angle::new::<bone_types::radian>(core::f64::consts::PI),
            TOL,
        ) else {
            panic!("valid arc");
        };
        let sweep = ids.features.insert(());
        let solid = match build_skinned_sweep(
            sweep,
            &profile,
            &one(Curve3Kind::Arc(arc)),
            &unguided(StationCount::new(64), FrameGuide::MinimumTwist, 0.0),
        ) {
            Ok(solid) => solid,
            Err(error) => panic!("the holed skin builds a valid solid: {error:?}"),
        };
        let volume = mesh_volume(&solid).abs();
        let area = core::f64::consts::PI * outer_r.mul_add(outer_r, -(inner_r * inner_r));
        let expected = area * core::f64::consts::PI * rho;
        assert!(
            (volume - expected).abs() < 0.05,
            "an annular tube swept a half turn matches Pappus {expected}: got {volume}"
        );
    }

    fn shifted_half_disc(ids: &mut Ids, radius: f64, dx: f64) -> ProfileLoop {
        let Ok(diameter) = Line2::new(
            Point2::from_mm(dx - radius, 0.0),
            Point2::from_mm(dx + radius, 0.0),
            TOL,
        ) else {
            panic!("distinct endpoints");
        };
        let Ok(arc) = crate::arc2::Arc2::new(
            Point2::from_mm(dx, 0.0),
            bone_types::Length::new::<bone_types::millimeter>(radius),
            bone_types::Angle::new::<bone_types::radian>(0.0),
            bone_types::Angle::new::<bone_types::radian>(core::f64::consts::PI),
            TOL,
        ) else {
            panic!("valid arc");
        };
        ProfileLoop::Open(vec![
            ProfileEdge::new(
                Curve2Kind::Line(diameter),
                ids.entities.insert(()),
                ids.entities.insert(()),
            ),
            ProfileEdge::new(
                Curve2Kind::Arc(arc),
                ids.entities.insert(()),
                ids.entities.insert(()),
            ),
        ])
    }

    fn curved_on_curved_skin(stations: u32) -> BrepSolid {
        let mut ids = Ids::new();
        let Ok(xz) = Plane3::new(
            Point3::origin(),
            UnitVec3::x_axis(),
            UnitVec3::z_axis(),
            TOL,
        ) else {
            panic!("orthonormal axes");
        };
        let profile = ExtrudeProfile::new(xz, vec![shifted_half_disc(&mut ids, 0.4, 2.5)]);
        let Ok(arc) = crate::arc3::Arc3::new(
            xy_plane(),
            bone_types::Length::new::<bone_types::millimeter>(2.5),
            bone_types::Angle::new::<bone_types::radian>(0.0),
            bone_types::Angle::new::<bone_types::radian>(core::f64::consts::PI),
            TOL,
        ) else {
            panic!("valid arc");
        };
        let sweep = ids.features.insert(());
        match build_skinned_sweep(
            sweep,
            &profile,
            &one(Curve3Kind::Arc(arc)),
            &unguided(StationCount::new(stations), FrameGuide::MinimumTwist, 0.0),
        ) {
            Ok(solid) => solid,
            Err(error) => panic!("the curved-section curved-path skin builds: {error:?}"),
        }
    }

    #[test]
    fn skinned_sweep_of_a_curved_section_along_a_curved_path_stays_valid_and_deterministic() {
        let first = curved_on_curved_skin(64);
        let second = curved_on_curved_skin(64);
        assert!(
            first.validate(Tolerance::new(1.0e-6)).is_ok(),
            "a curved section on a curved path is a closed manifold solid"
        );
        assert_eq!(
            first.content_key(),
            second.content_key(),
            "the curved-on-curved skin is deterministic"
        );
        let volume = mesh_volume(&first).abs();
        let area = core::f64::consts::PI * 0.4 * 0.4 / 2.0;
        let expected = area * core::f64::consts::PI * 2.5;
        assert!(
            (volume - expected).abs() < 0.05,
            "a radius-0.4 half disc at radius 2.5 swept a half turn matches Pappus {expected}: got {volume}"
        );
    }
}
