use std::f64::consts::TAU;

use indexmap::IndexSet;
use kcl_api::UnitLength;

use crate::execution::types::adjust_length;
use crate::front::Horizontal;
use crate::front::Vertical;
use crate::frontend::api::Number;
use crate::frontend::api::Object;
use crate::frontend::api::ObjectId;
use crate::frontend::api::ObjectKind;
use crate::frontend::sketch::Constraint;
use crate::frontend::sketch::ConstraintSegment;
use crate::frontend::sketch::Segment;
use crate::frontend::sketch::SegmentCtor;
use crate::pretty::NumericSuffix;
use crate::util::MathExt;

#[cfg(test)]
mod tests;

// Epsilon constants for geometric calculations
const EPSILON_PARALLEL: f64 = 1e-10;
const EPSILON_POINT_ON_SEGMENT: f64 = 1e-6;
const EPSILON_COINCIDENT_TERMINATION_SNAP: f64 = 5e-2;

/// Length unit for a numeric suffix (length variants only). Non-length suffixes default to millimeters.
fn suffix_to_unit(suffix: NumericSuffix) -> UnitLength {
    match suffix {
        NumericSuffix::Mm => UnitLength::Millimeters,
        NumericSuffix::Cm => UnitLength::Centimeters,
        NumericSuffix::M => UnitLength::Meters,
        NumericSuffix::Inch => UnitLength::Inches,
        NumericSuffix::Ft => UnitLength::Feet,
        NumericSuffix::Yd => UnitLength::Yards,
        _ => UnitLength::Millimeters,
    }
}

/// Convert a length `Number` to f64 in the target unit. Use when normalizing geometry into a single unit.
fn number_to_unit(n: &Number, target_unit: UnitLength) -> f64 {
    adjust_length(suffix_to_unit(n.units), n.value, target_unit).0
}

/// Convert a length in the given unit to a `Number` in the target suffix.
fn unit_to_number(value: f64, source_unit: UnitLength, target_suffix: NumericSuffix) -> Number {
    let (value, _) = adjust_length(source_unit, value, suffix_to_unit(target_suffix));
    Number {
        value,
        units: target_suffix,
    }
}

/// Convert trim line points from millimeters into the current/default unit.
fn normalize_trim_points_to_unit(points: &[Coords2d], default_unit: UnitLength) -> Vec<Coords2d> {
    points
        .iter()
        .map(|point| Coords2d {
            x: adjust_length(UnitLength::Millimeters, point.x, default_unit).0,
            y: adjust_length(UnitLength::Millimeters, point.y, default_unit).0,
        })
        .collect()
}

/// 2D coordinates in the trim internal unit (current/default length unit).
#[derive(Debug, Clone, Copy)]
pub struct Coords2d {
    pub x: f64,
    pub y: f64,
}

/// Which endpoint of a line segment to get coordinates for
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineEndpoint {
    Start,
    End,
}

/// Which point of an arc segment to get coordinates for
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArcPoint {
    Start,
    End,
    Center,
}

/// Which point of a circle segment to get coordinates for
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CirclePoint {
    Start,
    Center,
}

/// Direction along a segment for finding trim terminations
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrimDirection {
    Left,
    Right,
}

// Manual serde implementation for Coords2d to serialize as [x, y] array
// This matches TypeScript's Coords2d type which is [number, number]

// A trim spawn is the intersection point of the trim line (drawn by the user) and a segment.
// We travel in both directions along the segment from the trim spawn to determine how to implement the trim.

/// Item from advancing to the next trim spawn (intersection), like an iterator item from `Iterator::next()`.
#[derive(Debug, Clone)]
pub enum TrimItem {
    Spawn {
        trim_spawn_seg_id: ObjectId,
        trim_spawn_coords: Coords2d,
        next_index: usize,
    },
    None {
        next_index: usize,
    },
}

/// Trim termination types
///
/// Trim termination is the term used to figure out each end of a segment after a trim spawn has been found.
/// When a trim spawn is found, we travel in both directions to find this termination. It can be:
/// (1) the end of a segment (floating end), (2) an intersection with another segment, or
/// (3) a coincident point where another segment is coincident with the segment we're traveling along.
#[derive(Debug, Clone)]
pub enum TrimTermination {
    SegEndPoint {
        trim_termination_coords: Coords2d,
    },
    Intersection {
        trim_termination_coords: Coords2d,
        intersecting_seg_id: ObjectId,
    },
    TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
        trim_termination_coords: Coords2d,
        intersecting_seg_id: ObjectId,
        other_segment_point_id: ObjectId,
    },
}

/// Trim terminations for both sides
#[derive(Debug, Clone)]
pub struct TrimTerminations {
    pub left_side: TrimTermination,
    pub right_side: TrimTermination,
}

/// Specifies where a constraint should attach when migrating during split operations
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttachToEndpoint {
    Start,
    End,
    Segment,
}

/// Specifies which endpoint of a segment was changed
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EndpointChanged {
    Start,
    End,
}

/// Coincident data for split segment operations
#[derive(Debug, Clone)]
pub struct CoincidentData {
    pub intersecting_seg_id: ObjectId,
    pub intersecting_endpoint_point_id: Option<ObjectId>,
    pub existing_point_segment_constraint_id: Option<ObjectId>,
}

/// Constraint to migrate during split operations
#[derive(Debug, Clone)]
pub struct ConstraintToMigrate {
    pub constraint_id: ObjectId,
    pub other_entity_id: ObjectId,
    /// True if the coincident constraint is between two points (point–point).
    /// False if it is between a point and a line/arc/segment (point-segment coincident).
    pub is_point_point: bool,
    pub attach_to_endpoint: AttachToEndpoint,
}

/// Semantic trim plan produced by analysis/planning before lowering to frontend operations.
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum TrimPlan {
    DeleteSegment {
        segment_id: ObjectId,
    },
    TailCut {
        segment_id: ObjectId,
        endpoint_changed: EndpointChanged,
        ctor: SegmentCtor,
        segment_or_point_to_make_coincident_to: ObjectId,
        intersecting_endpoint_point_id: Option<ObjectId>,
        constraint_ids_to_delete: Vec<ObjectId>,
        additional_edited_segment_ids: Vec<ObjectId>,
    },
    TailCutControlPointSpline {
        segment_id: ObjectId,
        ctor: SegmentCtor,
        constraint_ids_to_delete: Vec<ObjectId>,
    },
    ReplaceCircleWithArc {
        circle_id: ObjectId,
        arc_start_coords: Coords2d,
        arc_end_coords: Coords2d,
        arc_start_termination: Box<TrimTermination>,
        arc_end_termination: Box<TrimTermination>,
    },
    SplitSegment {
        segment_id: ObjectId,
        left_trim_coords: Coords2d,
        right_trim_coords: Coords2d,
        original_end_coords: Coords2d,
        left_side: Box<TrimTermination>,
        right_side: Box<TrimTermination>,
        left_side_coincident_data: CoincidentData,
        right_side_coincident_data: CoincidentData,
        constraints_to_migrate: Vec<ConstraintToMigrate>,
        constraints_to_delete: Vec<ObjectId>,
    },
    SplitControlPointSpline {
        segment_id: ObjectId,
        left_ctor: SegmentCtor,
        right_ctor: SegmentCtor,
        left_side: Box<TrimTermination>,
        right_side: Box<TrimTermination>,
        constraint_ids_to_delete: Vec<ObjectId>,
    },
}

fn lower_trim_plan(plan: &TrimPlan) -> Vec<TrimOperation> {
    match plan {
        TrimPlan::DeleteSegment { segment_id } => vec![TrimOperation::SimpleTrim {
            segment_to_trim_id: *segment_id,
        }],
        TrimPlan::TailCut {
            segment_id,
            endpoint_changed,
            ctor,
            segment_or_point_to_make_coincident_to,
            intersecting_endpoint_point_id,
            constraint_ids_to_delete,
            additional_edited_segment_ids,
        } => {
            let mut ops = vec![
                TrimOperation::EditSegment {
                    segment_id: *segment_id,
                    ctor: ctor.clone(),
                    endpoint_changed: *endpoint_changed,
                    additional_edited_segment_ids: additional_edited_segment_ids.clone(),
                },
                TrimOperation::AddCoincidentConstraint {
                    segment_id: *segment_id,
                    endpoint_changed: *endpoint_changed,
                    segment_or_point_to_make_coincident_to: *segment_or_point_to_make_coincident_to,
                    intersecting_endpoint_point_id: *intersecting_endpoint_point_id,
                },
            ];
            if !constraint_ids_to_delete.is_empty() {
                ops.push(TrimOperation::DeleteConstraints {
                    constraint_ids: constraint_ids_to_delete.clone(),
                });
            }
            ops
        }
        TrimPlan::TailCutControlPointSpline {
            segment_id,
            ctor,
            constraint_ids_to_delete,
        } => {
            let mut ops = vec![TrimOperation::EditControlPointSpline {
                segment_id: *segment_id,
                ctor: ctor.clone(),
            }];
            if !constraint_ids_to_delete.is_empty() {
                ops.push(TrimOperation::DeleteConstraints {
                    constraint_ids: constraint_ids_to_delete.clone(),
                });
            }
            ops
        }
        TrimPlan::ReplaceCircleWithArc {
            circle_id,
            arc_start_coords,
            arc_end_coords,
            arc_start_termination,
            arc_end_termination,
        } => vec![TrimOperation::ReplaceCircleWithArc {
            circle_id: *circle_id,
            arc_start_coords: *arc_start_coords,
            arc_end_coords: *arc_end_coords,
            arc_start_termination: arc_start_termination.clone(),
            arc_end_termination: arc_end_termination.clone(),
        }],
        TrimPlan::SplitSegment {
            segment_id,
            left_trim_coords,
            right_trim_coords,
            original_end_coords,
            left_side,
            right_side,
            left_side_coincident_data,
            right_side_coincident_data,
            constraints_to_migrate,
            constraints_to_delete,
        } => vec![TrimOperation::SplitSegment {
            segment_id: *segment_id,
            left_trim_coords: *left_trim_coords,
            right_trim_coords: *right_trim_coords,
            original_end_coords: *original_end_coords,
            left_side: left_side.clone(),
            right_side: right_side.clone(),
            left_side_coincident_data: left_side_coincident_data.clone(),
            right_side_coincident_data: right_side_coincident_data.clone(),
            constraints_to_migrate: constraints_to_migrate.clone(),
            constraints_to_delete: constraints_to_delete.clone(),
        }],
        TrimPlan::SplitControlPointSpline {
            segment_id,
            left_ctor,
            right_ctor,
            left_side,
            right_side,
            constraint_ids_to_delete,
        } => vec![TrimOperation::SplitControlPointSpline {
            segment_id: *segment_id,
            left_ctor: left_ctor.clone(),
            right_ctor: right_ctor.clone(),
            left_side: left_side.clone(),
            right_side: right_side.clone(),
            constraint_ids_to_delete: constraint_ids_to_delete.clone(),
        }],
    }
}

fn trim_plan_modifies_geometry(plan: &TrimPlan) -> bool {
    matches!(
        plan,
        TrimPlan::DeleteSegment { .. }
            | TrimPlan::TailCut { .. }
            | TrimPlan::TailCutControlPointSpline { .. }
            | TrimPlan::ReplaceCircleWithArc { .. }
            | TrimPlan::SplitSegment { .. }
            | TrimPlan::SplitControlPointSpline { .. }
    )
}

fn rewrite_object_id(id: ObjectId, rewrite_map: &std::collections::HashMap<ObjectId, ObjectId>) -> ObjectId {
    rewrite_map.get(&id).copied().unwrap_or(id)
}

fn rewrite_constraint_segment(
    segment: crate::frontend::sketch::ConstraintSegment,
    rewrite_map: &std::collections::HashMap<ObjectId, ObjectId>,
) -> crate::frontend::sketch::ConstraintSegment {
    match segment {
        crate::frontend::sketch::ConstraintSegment::Segment(id) => {
            crate::frontend::sketch::ConstraintSegment::Segment(rewrite_object_id(id, rewrite_map))
        }
        crate::frontend::sketch::ConstraintSegment::Origin(origin) => {
            crate::frontend::sketch::ConstraintSegment::Origin(origin)
        }
    }
}

fn rewrite_constraint_segments(
    segments: &[crate::frontend::sketch::ConstraintSegment],
    rewrite_map: &std::collections::HashMap<ObjectId, ObjectId>,
) -> Vec<crate::frontend::sketch::ConstraintSegment> {
    segments
        .iter()
        .copied()
        .map(|segment| rewrite_constraint_segment(segment, rewrite_map))
        .collect()
}

fn constraint_segments_reference_any(
    segments: &[crate::frontend::sketch::ConstraintSegment],
    ids: &std::collections::HashSet<ObjectId>,
) -> bool {
    segments.iter().any(|segment| match segment {
        crate::frontend::sketch::ConstraintSegment::Segment(id) => ids.contains(id),
        crate::frontend::sketch::ConstraintSegment::Origin(_) => false,
    })
}

fn rewrite_constraint_with_map(
    constraint: &Constraint,
    rewrite_map: &std::collections::HashMap<ObjectId, ObjectId>,
) -> Option<Constraint> {
    // Keep trim constraint matches exhaustive. New constraints can break trim in
    // unexpected ways; try trimming sketches that use the new constraint and ask
    // Kurt, Max, or a mechanical engineer when the expected behavior is unclear.
    match constraint {
        Constraint::Coincident(coincident) => Some(Constraint::Coincident(crate::frontend::sketch::Coincident {
            segments: rewrite_constraint_segments(&coincident.segments, rewrite_map),
        })),
        Constraint::Distance(distance) => Some(Constraint::Distance(crate::frontend::sketch::Distance {
            points: rewrite_constraint_segments(&distance.points, rewrite_map),
            distance: distance.distance,
            label_position: distance.label_position.clone(),
            source: distance.source.clone(),
        })),
        Constraint::HorizontalDistance(distance) => {
            Some(Constraint::HorizontalDistance(crate::frontend::sketch::Distance {
                points: rewrite_constraint_segments(&distance.points, rewrite_map),
                distance: distance.distance,
                label_position: distance.label_position.clone(),
                source: distance.source.clone(),
            }))
        }
        Constraint::VerticalDistance(distance) => {
            Some(Constraint::VerticalDistance(crate::frontend::sketch::Distance {
                points: rewrite_constraint_segments(&distance.points, rewrite_map),
                distance: distance.distance,
                label_position: distance.label_position.clone(),
                source: distance.source.clone(),
            }))
        }
        Constraint::Radius(radius) => Some(Constraint::Radius(crate::frontend::sketch::Radius {
            arc: rewrite_object_id(radius.arc, rewrite_map),
            radius: radius.radius,
            label_position: radius.label_position.clone(),
            source: radius.source.clone(),
        })),
        Constraint::Diameter(diameter) => Some(Constraint::Diameter(crate::frontend::sketch::Diameter {
            arc: rewrite_object_id(diameter.arc, rewrite_map),
            diameter: diameter.diameter,
            label_position: diameter.label_position.clone(),
            source: diameter.source.clone(),
        })),
        Constraint::EqualRadius(equal_radius) => Some(Constraint::EqualRadius(crate::frontend::sketch::EqualRadius {
            input: equal_radius
                .input
                .iter()
                .map(|id| rewrite_object_id(*id, rewrite_map))
                .collect(),
        })),
        Constraint::Midpoint(midpoint) => Some(Constraint::Midpoint(crate::frontend::sketch::Midpoint {
            point: rewrite_constraint_segment(midpoint.point, rewrite_map),
            segment: rewrite_object_id(midpoint.segment, rewrite_map),
        })),
        Constraint::Tangent(tangent) => Some(Constraint::Tangent(crate::frontend::sketch::Tangent {
            input: tangent
                .input
                .iter()
                .map(|id| rewrite_object_id(*id, rewrite_map))
                .collect(),
        })),
        Constraint::Symmetric(symmetric) => Some(Constraint::Symmetric(crate::frontend::sketch::Symmetric {
            input: symmetric
                .input
                .iter()
                .map(|id| rewrite_object_id(*id, rewrite_map))
                .collect(),
            axis: rewrite_object_id(symmetric.axis, rewrite_map),
        })),
        Constraint::Parallel(parallel) => Some(Constraint::Parallel(crate::frontend::sketch::Parallel {
            lines: parallel
                .lines
                .iter()
                .map(|id| rewrite_object_id(*id, rewrite_map))
                .collect(),
        })),
        Constraint::Perpendicular(perpendicular) => {
            Some(Constraint::Perpendicular(crate::frontend::sketch::Perpendicular {
                lines: perpendicular
                    .lines
                    .iter()
                    .map(|id| rewrite_object_id(*id, rewrite_map))
                    .collect(),
            }))
        }
        Constraint::Horizontal(horizontal) => match horizontal {
            crate::front::Horizontal::Line { line } => {
                Some(Constraint::Horizontal(crate::frontend::sketch::Horizontal::Line {
                    line: rewrite_object_id(*line, rewrite_map),
                }))
            }
            crate::front::Horizontal::Points { points } => Some(Constraint::Horizontal(Horizontal::Points {
                points: points
                    .iter()
                    .map(|point| match point {
                        crate::frontend::sketch::ConstraintSegment::Segment(point) => {
                            crate::frontend::sketch::ConstraintSegment::from(rewrite_object_id(*point, rewrite_map))
                        }
                        crate::frontend::sketch::ConstraintSegment::Origin(origin) => {
                            crate::frontend::sketch::ConstraintSegment::Origin(*origin)
                        }
                    })
                    .collect(),
            })),
        },
        Constraint::Vertical(vertical) => match vertical {
            crate::front::Vertical::Line { line } => {
                Some(Constraint::Vertical(crate::frontend::sketch::Vertical::Line {
                    line: rewrite_object_id(*line, rewrite_map),
                }))
            }
            crate::front::Vertical::Points { points } => Some(Constraint::Vertical(Vertical::Points {
                points: points
                    .iter()
                    .map(|point| match point {
                        crate::frontend::sketch::ConstraintSegment::Segment(point) => {
                            crate::frontend::sketch::ConstraintSegment::from(rewrite_object_id(*point, rewrite_map))
                        }
                        crate::frontend::sketch::ConstraintSegment::Origin(origin) => {
                            crate::frontend::sketch::ConstraintSegment::Origin(*origin)
                        }
                    })
                    .collect(),
            })),
        },
        Constraint::Angle(_) | Constraint::Fixed(_) | Constraint::LinesEqualLength(_) => None,
    }
}

fn point_axis_constraint_references_point(constraint: &Constraint, point_id: ObjectId) -> bool {
    // Keep trim constraint matches exhaustive. New constraints should make an
    // explicit preserve/delete/migrate decision rather than falling through.
    match constraint {
        Constraint::Horizontal(Horizontal::Points { points }) => points.contains(&ConstraintSegment::from(point_id)),
        Constraint::Vertical(Vertical::Points { points }) => points.contains(&ConstraintSegment::from(point_id)),
        Constraint::Angle(_)
        | Constraint::Coincident(_)
        | Constraint::Diameter(_)
        | Constraint::Distance(_)
        | Constraint::EqualRadius(_)
        | Constraint::Fixed(_)
        | Constraint::Horizontal(Horizontal::Line { .. })
        | Constraint::HorizontalDistance(_)
        | Constraint::LinesEqualLength(_)
        | Constraint::Midpoint(_)
        | Constraint::Parallel(_)
        | Constraint::Perpendicular(_)
        | Constraint::Radius(_)
        | Constraint::Symmetric(_)
        | Constraint::Tangent(_)
        | Constraint::Vertical(Vertical::Line { .. })
        | Constraint::VerticalDistance(_) => false,
    }
}

fn owner_or_segment_id(objects: &[Object], segment_id: ObjectId) -> ObjectId {
    if let Some(segment_object) = objects.iter().find(|obj| obj.id == segment_id)
        && let ObjectKind::Segment {
            segment: Segment::Point(point),
        } = &segment_object.kind
        && let Some(owner_id) = point.owner
    {
        owner_id
    } else {
        segment_id
    }
}

fn segment_id_is_or_is_owned_by_curve(objects: &[Object], segment_id: ObjectId) -> bool {
    objects.iter().find(|obj| obj.id == segment_id).is_some_and(|object| {
        let ObjectKind::Segment { segment } = &object.kind else {
            return false;
        };

        match segment {
            Segment::Arc(_) | Segment::Circle(_) => true,
            Segment::Point(point) => point.owner.is_some_and(|owner_id| {
                objects.iter().find(|obj| obj.id == owner_id).is_some_and(|owner| {
                    matches!(
                        owner.kind,
                        ObjectKind::Segment {
                            segment: Segment::Arc(_) | Segment::Circle(_)
                        }
                    )
                })
            }),
            _ => false,
        }
    })
}

fn sketch_segment_ids_for_segment(objects: &[Object], segment_id: ObjectId) -> Vec<ObjectId> {
    objects
        .iter()
        .find_map(|obj| {
            let ObjectKind::Sketch(sketch) = &obj.kind else {
                return None;
            };

            sketch.segments.contains(&segment_id).then(|| sketch.segments.clone())
        })
        .unwrap_or_default()
}

#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum TrimOperation {
    SimpleTrim {
        segment_to_trim_id: ObjectId,
    },
    EditSegment {
        segment_id: ObjectId,
        ctor: SegmentCtor,
        endpoint_changed: EndpointChanged,
        additional_edited_segment_ids: Vec<ObjectId>,
    },
    EditControlPointSpline {
        segment_id: ObjectId,
        ctor: SegmentCtor,
    },
    AddCoincidentConstraint {
        segment_id: ObjectId,
        endpoint_changed: EndpointChanged,
        segment_or_point_to_make_coincident_to: ObjectId,
        intersecting_endpoint_point_id: Option<ObjectId>,
    },
    SplitSegment {
        segment_id: ObjectId,
        left_trim_coords: Coords2d,
        right_trim_coords: Coords2d,
        original_end_coords: Coords2d,
        left_side: Box<TrimTermination>,
        right_side: Box<TrimTermination>,
        left_side_coincident_data: CoincidentData,
        right_side_coincident_data: CoincidentData,
        constraints_to_migrate: Vec<ConstraintToMigrate>,
        constraints_to_delete: Vec<ObjectId>,
    },
    SplitControlPointSpline {
        segment_id: ObjectId,
        left_ctor: SegmentCtor,
        right_ctor: SegmentCtor,
        left_side: Box<TrimTermination>,
        right_side: Box<TrimTermination>,
        constraint_ids_to_delete: Vec<ObjectId>,
    },
    ReplaceCircleWithArc {
        circle_id: ObjectId,
        arc_start_coords: Coords2d,
        arc_end_coords: Coords2d,
        arc_start_termination: Box<TrimTermination>,
        arc_end_termination: Box<TrimTermination>,
    },
    DeleteConstraints {
        constraint_ids: Vec<ObjectId>,
    },
}

/// Helper to check if a point is on a line segment (within epsilon distance)
///
/// Returns the point if it's on the segment, None otherwise.
pub fn is_point_on_line_segment(
    point: Coords2d,
    segment_start: Coords2d,
    segment_end: Coords2d,
    epsilon: f64,
) -> Option<Coords2d> {
    let dx = segment_end.x - segment_start.x;
    let dy = segment_end.y - segment_start.y;
    let segment_length_sq = dx * dx + dy * dy;

    if segment_length_sq < EPSILON_PARALLEL {
        // Segment is degenerate, i.e it's practically a point
        let dist_sq = (point.x - segment_start.x) * (point.x - segment_start.x)
            + (point.y - segment_start.y) * (point.y - segment_start.y);
        if dist_sq <= epsilon * epsilon {
            return Some(point);
        }
        return None;
    }

    let point_dx = point.x - segment_start.x;
    let point_dy = point.y - segment_start.y;
    let projection_param = (point_dx * dx + point_dy * dy) / segment_length_sq;

    // Check if point projects onto the segment
    if !(0.0..=1.0).contains(&projection_param) {
        return None;
    }

    // Calculate the projected point on the segment
    let projected_point = Coords2d {
        x: segment_start.x + projection_param * dx,
        y: segment_start.y + projection_param * dy,
    };

    // Check if the distance from point to projected point is within epsilon
    let dist_dx = point.x - projected_point.x;
    let dist_dy = point.y - projected_point.y;
    let distance_sq = dist_dx * dist_dx + dist_dy * dist_dy;

    if distance_sq <= epsilon * epsilon {
        Some(point)
    } else {
        None
    }
}

/// Helper to calculate intersection point of two line segments
///
/// Returns the intersection point if segments intersect, None otherwise.
pub fn line_segment_intersection(
    line1_start: Coords2d,
    line1_end: Coords2d,
    line2_start: Coords2d,
    line2_end: Coords2d,
    epsilon: f64,
) -> Option<Coords2d> {
    // First check if any endpoints are on the other segment
    if let Some(point) = is_point_on_line_segment(line1_start, line2_start, line2_end, epsilon) {
        return Some(point);
    }

    if let Some(point) = is_point_on_line_segment(line1_end, line2_start, line2_end, epsilon) {
        return Some(point);
    }

    if let Some(point) = is_point_on_line_segment(line2_start, line1_start, line1_end, epsilon) {
        return Some(point);
    }

    if let Some(point) = is_point_on_line_segment(line2_end, line1_start, line1_end, epsilon) {
        return Some(point);
    }

    // Then check for actual line segment intersection
    let x1 = line1_start.x;
    let y1 = line1_start.y;
    let x2 = line1_end.x;
    let y2 = line1_end.y;
    let x3 = line2_start.x;
    let y3 = line2_start.y;
    let x4 = line2_end.x;
    let y4 = line2_end.y;

    let denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
    if denominator.abs() < EPSILON_PARALLEL {
        // Lines are parallel
        return None;
    }

    let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denominator;
    let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denominator;

    // Check if intersection is within both segments
    if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
        let x = x1 + t * (x2 - x1);
        let y = y1 + t * (y2 - y1);
        return Some(Coords2d { x, y });
    }

    None
}

/// Helper to calculate the parametric position of a point on a line segment
///
/// Returns t where t=0 at segmentStart, t=1 at segmentEnd.
/// t can be < 0 or > 1 if the point projects outside the segment.
pub fn project_point_onto_segment(point: Coords2d, segment_start: Coords2d, segment_end: Coords2d) -> f64 {
    let dx = segment_end.x - segment_start.x;
    let dy = segment_end.y - segment_start.y;
    let segment_length_sq = dx * dx + dy * dy;

    if segment_length_sq < EPSILON_PARALLEL {
        // Segment is degenerate
        return 0.0;
    }

    let point_dx = point.x - segment_start.x;
    let point_dy = point.y - segment_start.y;

    (point_dx * dx + point_dy * dy) / segment_length_sq
}

/// Helper to calculate the perpendicular distance from a point to a line segment
///
/// Returns the distance from the point to the closest point on the segment.
pub fn perpendicular_distance_to_segment(point: Coords2d, segment_start: Coords2d, segment_end: Coords2d) -> f64 {
    let dx = segment_end.x - segment_start.x;
    let dy = segment_end.y - segment_start.y;
    let segment_length_sq = dx * dx + dy * dy;

    if segment_length_sq < EPSILON_PARALLEL {
        // Segment is degenerate, return distance to point
        let dist_dx = point.x - segment_start.x;
        let dist_dy = point.y - segment_start.y;
        return (dist_dx * dist_dx + dist_dy * dist_dy).sqrt();
    }

    // Vector from segment start to point
    let point_dx = point.x - segment_start.x;
    let point_dy = point.y - segment_start.y;

    // Project point onto segment
    let t = (point_dx * dx + point_dy * dy) / segment_length_sq;

    // Clamp t to [0, 1] to get closest point on segment
    let clamped_t = t.clamp(0.0, 1.0);
    let closest_point = Coords2d {
        x: segment_start.x + clamped_t * dx,
        y: segment_start.y + clamped_t * dy,
    };

    // Calculate distance
    let dist_dx = point.x - closest_point.x;
    let dist_dy = point.y - closest_point.y;
    (dist_dx * dist_dx + dist_dy * dist_dy).sqrt()
}

/// Helper to check if a point is on an arc segment (CCW from start to end)
///
/// Returns true if the point is on the arc, false otherwise.
pub fn is_point_on_arc(point: Coords2d, center: Coords2d, start: Coords2d, end: Coords2d, epsilon: f64) -> bool {
    // Calculate radius
    let radius = ((start.x - center.x) * (start.x - center.x) + (start.y - center.y) * (start.y - center.y)).sqrt();

    // Check if point is on the circle (within epsilon)
    let dist_from_center =
        ((point.x - center.x) * (point.x - center.x) + (point.y - center.y) * (point.y - center.y)).sqrt();
    if (dist_from_center - radius).abs() > epsilon {
        return false;
    }

    // Calculate angles
    let start_angle = libm::atan2(start.y - center.y, start.x - center.x);
    let end_angle = libm::atan2(end.y - center.y, end.x - center.x);
    let point_angle = libm::atan2(point.y - center.y, point.x - center.x);

    // Normalize angles to [0, 2π]
    let normalize_angle = |angle: f64| -> f64 {
        if !angle.is_finite() {
            return angle;
        }
        let mut normalized = angle;
        while normalized < 0.0 {
            normalized += TAU;
        }
        while normalized >= TAU {
            normalized -= TAU;
        }
        normalized
    };

    let normalized_start = normalize_angle(start_angle);
    let normalized_end = normalize_angle(end_angle);
    let normalized_point = normalize_angle(point_angle);

    // Check if point is on the arc going CCW from start to end
    // Since arcs always travel CCW, we need to check if the point angle
    // is between start and end when going CCW
    if normalized_start < normalized_end {
        // No wrap around
        normalized_point >= normalized_start && normalized_point <= normalized_end
    } else {
        // Wrap around (e.g., start at 350°, end at 10°)
        normalized_point >= normalized_start || normalized_point <= normalized_end
    }
}

/// Helper to calculate intersections between a line segment and an arc.
///
/// Returns intersections sorted by the line segment parametric position.
pub fn line_arc_intersections(
    line_start: Coords2d,
    line_end: Coords2d,
    arc_center: Coords2d,
    arc_start: Coords2d,
    arc_end: Coords2d,
    epsilon: f64,
) -> Vec<(f64, Coords2d)> {
    // Calculate radius
    let radius = ((arc_start.x - arc_center.x) * (arc_start.x - arc_center.x)
        + (arc_start.y - arc_center.y) * (arc_start.y - arc_center.y))
        .sqrt();

    // Translate line to origin (center at 0,0)
    let translated_line_start = Coords2d {
        x: line_start.x - arc_center.x,
        y: line_start.y - arc_center.y,
    };
    let translated_line_end = Coords2d {
        x: line_end.x - arc_center.x,
        y: line_end.y - arc_center.y,
    };

    // Line equation: p = lineStart + t * (lineEnd - lineStart)
    let dx = translated_line_end.x - translated_line_start.x;
    let dy = translated_line_end.y - translated_line_start.y;

    // Circle equation: x² + y² = r²
    // Substitute line equation into circle equation
    // (x0 + t*dx)² + (y0 + t*dy)² = r²
    // Expand: x0² + 2*x0*t*dx + t²*dx² + y0² + 2*y0*t*dy + t²*dy² = r²
    // Rearrange: t²*(dx² + dy²) + 2*t*(x0*dx + y0*dy) + (x0² + y0² - r²) = 0

    let a = dx * dx + dy * dy;
    let b = 2.0 * (translated_line_start.x * dx + translated_line_start.y * dy);
    let c = translated_line_start.x * translated_line_start.x + translated_line_start.y * translated_line_start.y
        - radius * radius;

    let discriminant = b * b - 4.0 * a * c;

    if discriminant < 0.0 {
        // No intersection
        return Vec::new();
    }

    if a.abs() < EPSILON_PARALLEL {
        // Line segment is degenerate
        let dist_from_center = (translated_line_start.x * translated_line_start.x
            + translated_line_start.y * translated_line_start.y)
            .sqrt();
        if (dist_from_center - radius).abs() <= epsilon {
            // Point is on circle, check if it's on the arc
            let point = line_start;
            if is_point_on_arc(point, arc_center, arc_start, arc_end, epsilon) {
                return vec![(0.0, point)];
            }
        }
        return Vec::new();
    }

    let sqrt_discriminant = discriminant.sqrt();
    let t1 = (-b - sqrt_discriminant) / (2.0 * a);
    let t2 = (-b + sqrt_discriminant) / (2.0 * a);

    // Check both intersection points
    let mut candidates: Vec<(f64, Coords2d)> = Vec::new();
    if (0.0..=1.0).contains(&t1) {
        let point = Coords2d {
            x: line_start.x + t1 * (line_end.x - line_start.x),
            y: line_start.y + t1 * (line_end.y - line_start.y),
        };
        candidates.push((t1, point));
    }
    if (0.0..=1.0).contains(&t2) && (t2 - t1).abs() > epsilon {
        let point = Coords2d {
            x: line_start.x + t2 * (line_end.x - line_start.x),
            y: line_start.y + t2 * (line_end.y - line_start.y),
        };
        candidates.push((t2, point));
    }

    candidates.retain(|(_, point)| is_point_on_arc(*point, arc_center, arc_start, arc_end, epsilon));
    candidates.sort_by(|(a_t, _), (b_t, _)| a_t.partial_cmp(b_t).unwrap_or(std::cmp::Ordering::Equal));
    candidates
}

/// Helper to calculate intersection between a line segment and an arc.
///
/// Returns the first intersection point if found, None otherwise.
pub fn line_arc_intersection(
    line_start: Coords2d,
    line_end: Coords2d,
    arc_center: Coords2d,
    arc_start: Coords2d,
    arc_end: Coords2d,
    epsilon: f64,
) -> Option<Coords2d> {
    line_arc_intersections(line_start, line_end, arc_center, arc_start, arc_end, epsilon)
        .into_iter()
        .map(|(_, point)| point)
        .next()
}

/// Helper to calculate intersection points between a line segment and a circle.
///
/// Returns intersections as `(t, point)` where `t` is the line parametric position:
/// `point = line_start + t * (line_end - line_start)`, with `0 <= t <= 1`.
pub fn line_circle_intersections(
    line_start: Coords2d,
    line_end: Coords2d,
    circle_center: Coords2d,
    radius: f64,
    epsilon: f64,
) -> Vec<(f64, Coords2d)> {
    // Translate line to origin (center at 0,0)
    let translated_line_start = Coords2d {
        x: line_start.x - circle_center.x,
        y: line_start.y - circle_center.y,
    };
    let translated_line_end = Coords2d {
        x: line_end.x - circle_center.x,
        y: line_end.y - circle_center.y,
    };

    let dx = translated_line_end.x - translated_line_start.x;
    let dy = translated_line_end.y - translated_line_start.y;
    let a = dx * dx + dy * dy;
    let b = 2.0 * (translated_line_start.x * dx + translated_line_start.y * dy);
    let c = translated_line_start.x * translated_line_start.x + translated_line_start.y * translated_line_start.y
        - radius * radius;

    if a.abs() < EPSILON_PARALLEL {
        return Vec::new();
    }

    let discriminant = b * b - 4.0 * a * c;
    if discriminant < 0.0 {
        return Vec::new();
    }

    let sqrt_discriminant = discriminant.sqrt();
    let mut intersections = Vec::new();

    let t1 = (-b - sqrt_discriminant) / (2.0 * a);
    if (0.0..=1.0).contains(&t1) {
        intersections.push((
            t1,
            Coords2d {
                x: line_start.x + t1 * (line_end.x - line_start.x),
                y: line_start.y + t1 * (line_end.y - line_start.y),
            },
        ));
    }

    let t2 = (-b + sqrt_discriminant) / (2.0 * a);
    if (0.0..=1.0).contains(&t2) && (t2 - t1).abs() > epsilon {
        intersections.push((
            t2,
            Coords2d {
                x: line_start.x + t2 * (line_end.x - line_start.x),
                y: line_start.y + t2 * (line_end.y - line_start.y),
            },
        ));
    }

    intersections.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
    intersections
}

/// Parametric position of a point on a circle, measured CCW from the circle start point.
///
/// Returns `t` in `[0, 1)` where:
/// - `t = 0` at circle start
/// - increasing `t` moves CCW
pub fn project_point_onto_circle(point: Coords2d, center: Coords2d, start: Coords2d) -> f64 {
    let normalize_angle = |angle: f64| -> f64 {
        if !angle.is_finite() {
            return angle;
        }
        let mut normalized = angle;
        while normalized < 0.0 {
            normalized += TAU;
        }
        while normalized >= TAU {
            normalized -= TAU;
        }
        normalized
    };

    let start_angle = normalize_angle(libm::atan2(start.y - center.y, start.x - center.x));
    let point_angle = normalize_angle(libm::atan2(point.y - center.y, point.x - center.x));
    let delta_ccw = (point_angle - start_angle).rem_euclid(TAU);
    delta_ccw / TAU
}

fn is_point_on_circle(point: Coords2d, center: Coords2d, radius: f64, epsilon: f64) -> bool {
    let dist = ((point.x - center.x) * (point.x - center.x) + (point.y - center.y) * (point.y - center.y)).sqrt();
    (dist - radius).abs() <= epsilon
}

/// Helper to calculate the parametric position of a point on an arc
/// Returns t where t=0 at start, t=1 at end, based on CCW angle
pub fn project_point_onto_arc(point: Coords2d, arc_center: Coords2d, arc_start: Coords2d, arc_end: Coords2d) -> f64 {
    // Calculate angles
    let start_angle = libm::atan2(arc_start.y - arc_center.y, arc_start.x - arc_center.x);
    let end_angle = libm::atan2(arc_end.y - arc_center.y, arc_end.x - arc_center.x);
    let point_angle = libm::atan2(point.y - arc_center.y, point.x - arc_center.x);

    // Normalize angles to [0, 2π]
    let normalize_angle = |angle: f64| -> f64 {
        if !angle.is_finite() {
            return angle;
        }
        let mut normalized = angle;
        while normalized < 0.0 {
            normalized += TAU;
        }
        while normalized >= TAU {
            normalized -= TAU;
        }
        normalized
    };

    let normalized_start = normalize_angle(start_angle);
    let normalized_end = normalize_angle(end_angle);
    let normalized_point = normalize_angle(point_angle);

    // Calculate arc length (CCW)
    let arc_length = if normalized_start < normalized_end {
        normalized_end - normalized_start
    } else {
        // Wrap around
        TAU - normalized_start + normalized_end
    };

    if arc_length < EPSILON_PARALLEL {
        // Arc is degenerate (full circle or very small)
        return 0.0;
    }

    // Calculate point's position along arc (CCW from start)
    let point_arc_length = if normalized_start < normalized_end {
        if normalized_point >= normalized_start && normalized_point <= normalized_end {
            normalized_point - normalized_start
        } else {
            // Point is not on the arc, return closest endpoint
            let dist_to_start = libm::fmin(
                (normalized_point - normalized_start).abs(),
                TAU - (normalized_point - normalized_start).abs(),
            );
            let dist_to_end = libm::fmin(
                (normalized_point - normalized_end).abs(),
                TAU - (normalized_point - normalized_end).abs(),
            );
            return if dist_to_start < dist_to_end { 0.0 } else { 1.0 };
        }
    } else {
        // Wrap around case
        if normalized_point >= normalized_start || normalized_point <= normalized_end {
            if normalized_point >= normalized_start {
                normalized_point - normalized_start
            } else {
                TAU - normalized_start + normalized_point
            }
        } else {
            // Point is not on the arc
            let dist_to_start = libm::fmin(
                (normalized_point - normalized_start).abs(),
                TAU - (normalized_point - normalized_start).abs(),
            );
            let dist_to_end = libm::fmin(
                (normalized_point - normalized_end).abs(),
                TAU - (normalized_point - normalized_end).abs(),
            );
            return if dist_to_start < dist_to_end { 0.0 } else { 1.0 };
        }
    };

    // Return parametric position
    point_arc_length / arc_length
}

/// Helper to calculate all intersections between two arcs (via circle-circle intersection).
///
/// Returns all valid points that lie on both arcs (0, 1, or 2).
pub fn arc_arc_intersections(
    arc1_center: Coords2d,
    arc1_start: Coords2d,
    arc1_end: Coords2d,
    arc2_center: Coords2d,
    arc2_start: Coords2d,
    arc2_end: Coords2d,
    epsilon: f64,
) -> Vec<Coords2d> {
    // Calculate radii
    let r1 = ((arc1_start.x - arc1_center.x) * (arc1_start.x - arc1_center.x)
        + (arc1_start.y - arc1_center.y) * (arc1_start.y - arc1_center.y))
        .sqrt();
    let r2 = ((arc2_start.x - arc2_center.x) * (arc2_start.x - arc2_center.x)
        + (arc2_start.y - arc2_center.y) * (arc2_start.y - arc2_center.y))
        .sqrt();

    // Distance between centers
    let dx = arc2_center.x - arc1_center.x;
    let dy = arc2_center.y - arc1_center.y;
    let d = (dx * dx + dy * dy).sqrt();

    // Check if circles intersect
    if d > r1 + r2 + epsilon || d < (r1 - r2).abs() - epsilon {
        // No intersection
        return Vec::new();
    }

    // Check for degenerate cases
    if d < EPSILON_PARALLEL {
        // Concentric circles - no intersection (or infinite if same radius, but we treat as none)
        return Vec::new();
    }

    // Calculate intersection points
    // Using the formula from: https://mathworld.wolfram.com/Circle-CircleIntersection.html
    let a = (r1 * r1 - r2 * r2 + d * d) / (2.0 * d);
    let h_sq = r1 * r1 - a * a;

    // If h_sq is negative, no intersection
    if h_sq < 0.0 {
        return Vec::new();
    }

    let h = h_sq.sqrt();

    // If h is NaN, no intersection
    if h.is_nan() {
        return Vec::new();
    }

    // Unit vector from arc1Center to arc2Center
    let ux = dx / d;
    let uy = dy / d;

    // Perpendicular vector (rotated 90 degrees)
    let px = -uy;
    let py = ux;

    // Midpoint on the line connecting centers
    let mid_point = Coords2d {
        x: arc1_center.x + a * ux,
        y: arc1_center.y + a * uy,
    };

    // Two intersection points
    let intersection1 = Coords2d {
        x: mid_point.x + h * px,
        y: mid_point.y + h * py,
    };
    let intersection2 = Coords2d {
        x: mid_point.x - h * px,
        y: mid_point.y - h * py,
    };

    // Check which intersection point(s) are on both arcs
    let mut candidates: Vec<Coords2d> = Vec::new();

    if is_point_on_arc(intersection1, arc1_center, arc1_start, arc1_end, epsilon)
        && is_point_on_arc(intersection1, arc2_center, arc2_start, arc2_end, epsilon)
    {
        candidates.push(intersection1);
    }

    if (intersection1.x - intersection2.x).abs() > epsilon || (intersection1.y - intersection2.y).abs() > epsilon {
        // Only check second point if it's different from the first
        if is_point_on_arc(intersection2, arc1_center, arc1_start, arc1_end, epsilon)
            && is_point_on_arc(intersection2, arc2_center, arc2_start, arc2_end, epsilon)
        {
            candidates.push(intersection2);
        }
    }

    candidates
}

/// Helper to calculate one intersection between two arcs (if any).
///
/// This is kept for compatibility with existing call sites/tests that expect
/// a single optional intersection.
pub fn arc_arc_intersection(
    arc1_center: Coords2d,
    arc1_start: Coords2d,
    arc1_end: Coords2d,
    arc2_center: Coords2d,
    arc2_start: Coords2d,
    arc2_end: Coords2d,
    epsilon: f64,
) -> Option<Coords2d> {
    arc_arc_intersections(
        arc1_center,
        arc1_start,
        arc1_end,
        arc2_center,
        arc2_start,
        arc2_end,
        epsilon,
    )
    .first()
    .copied()
}

/// Helper to calculate intersections between a full circle and an arc.
///
/// Returns all valid intersection points on the arc (0, 1, or 2).
pub fn circle_arc_intersections(
    circle_center: Coords2d,
    circle_radius: f64,
    arc_center: Coords2d,
    arc_start: Coords2d,
    arc_end: Coords2d,
    epsilon: f64,
) -> Vec<Coords2d> {
    let r1 = circle_radius;
    let r2 = ((arc_start.x - arc_center.x) * (arc_start.x - arc_center.x)
        + (arc_start.y - arc_center.y) * (arc_start.y - arc_center.y))
        .sqrt();

    let dx = arc_center.x - circle_center.x;
    let dy = arc_center.y - circle_center.y;
    let d = (dx * dx + dy * dy).sqrt();

    if d > r1 + r2 + epsilon || d < (r1 - r2).abs() - epsilon || d < EPSILON_PARALLEL {
        return Vec::new();
    }

    let a = (r1 * r1 - r2 * r2 + d * d) / (2.0 * d);
    let h_sq = r1 * r1 - a * a;
    if h_sq < 0.0 {
        return Vec::new();
    }
    let h = h_sq.sqrt();
    if h.is_nan() {
        return Vec::new();
    }

    let ux = dx / d;
    let uy = dy / d;
    let px = -uy;
    let py = ux;
    let mid_point = Coords2d {
        x: circle_center.x + a * ux,
        y: circle_center.y + a * uy,
    };

    let intersection1 = Coords2d {
        x: mid_point.x + h * px,
        y: mid_point.y + h * py,
    };
    let intersection2 = Coords2d {
        x: mid_point.x - h * px,
        y: mid_point.y - h * py,
    };

    let mut intersections = Vec::new();
    if is_point_on_arc(intersection1, arc_center, arc_start, arc_end, epsilon) {
        intersections.push(intersection1);
    }
    if ((intersection1.x - intersection2.x).abs() > epsilon || (intersection1.y - intersection2.y).abs() > epsilon)
        && is_point_on_arc(intersection2, arc_center, arc_start, arc_end, epsilon)
    {
        intersections.push(intersection2);
    }
    intersections
}

/// Helper to calculate intersections between two full circles.
///
/// Returns 0, 1 (tangent), or 2 intersection points.
pub fn circle_circle_intersections(
    circle1_center: Coords2d,
    circle1_radius: f64,
    circle2_center: Coords2d,
    circle2_radius: f64,
    epsilon: f64,
) -> Vec<Coords2d> {
    let dx = circle2_center.x - circle1_center.x;
    let dy = circle2_center.y - circle1_center.y;
    let d = (dx * dx + dy * dy).sqrt();

    if d > circle1_radius + circle2_radius + epsilon
        || d < (circle1_radius - circle2_radius).abs() - epsilon
        || d < EPSILON_PARALLEL
    {
        return Vec::new();
    }

    let a = (circle1_radius * circle1_radius - circle2_radius * circle2_radius + d * d) / (2.0 * d);
    let h_sq = circle1_radius * circle1_radius - a * a;
    if h_sq < 0.0 {
        return Vec::new();
    }

    let h = if h_sq <= epsilon { 0.0 } else { h_sq.sqrt() };
    if h.is_nan() {
        return Vec::new();
    }

    let ux = dx / d;
    let uy = dy / d;
    let px = -uy;
    let py = ux;

    let mid_point = Coords2d {
        x: circle1_center.x + a * ux,
        y: circle1_center.y + a * uy,
    };

    let intersection1 = Coords2d {
        x: mid_point.x + h * px,
        y: mid_point.y + h * py,
    };
    let intersection2 = Coords2d {
        x: mid_point.x - h * px,
        y: mid_point.y - h * py,
    };

    let mut intersections = vec![intersection1];
    if (intersection1.x - intersection2.x).abs() > epsilon || (intersection1.y - intersection2.y).abs() > epsilon {
        intersections.push(intersection2);
    }
    intersections
}

/// Helper to extract coordinates from a point object in JSON format
// Native type helper - get point coordinates from ObjectId
fn get_point_coords_from_native(objects: &[Object], point_id: ObjectId, default_unit: UnitLength) -> Option<Coords2d> {
    let point_obj = objects.get(point_id.0)?;

    // Check if it's a Point segment
    let ObjectKind::Segment { segment } = &point_obj.kind else {
        return None;
    };

    let Segment::Point(point) = segment else {
        return None;
    };

    // Extract position coordinates in the trim internal unit
    Some(Coords2d {
        x: number_to_unit(&point.position.x, default_unit),
        y: number_to_unit(&point.position.y, default_unit),
    })
}

// Legacy JSON helper (will be removed)
/// Helper to get point coordinates from a Line segment by looking up the point object (native types)
pub fn get_position_coords_for_line(
    segment_obj: &Object,
    which: LineEndpoint,
    objects: &[Object],
    default_unit: UnitLength,
) -> Option<Coords2d> {
    let ObjectKind::Segment { segment } = &segment_obj.kind else {
        return None;
    };

    let Segment::Line(line) = segment else {
        return None;
    };

    // Get the point ID from the segment
    let point_id = match which {
        LineEndpoint::Start => line.start,
        LineEndpoint::End => line.end,
    };

    get_point_coords_from_native(objects, point_id, default_unit)
}

/// Helper to check if a point is coincident with a segment (line or arc) via constraints (native types)
fn is_point_coincident_with_segment_native(point_id: ObjectId, segment_id: ObjectId, objects: &[Object]) -> bool {
    // Find coincident constraints
    for obj in objects {
        let ObjectKind::Constraint { constraint } = &obj.kind else {
            continue;
        };

        let Constraint::Coincident(coincident) = constraint else {
            continue;
        };

        // Check if both pointId and segmentId are in the segments array
        let has_point = coincident.contains_segment(point_id);
        let has_segment = coincident.contains_segment(segment_id);

        if has_point && has_segment {
            return true;
        }
    }
    false
}

/// Helper to get point coordinates from an Arc segment by looking up the point object (native types)
pub fn get_position_coords_from_arc(
    segment_obj: &Object,
    which: ArcPoint,
    objects: &[Object],
    default_unit: UnitLength,
) -> Option<Coords2d> {
    let ObjectKind::Segment { segment } = &segment_obj.kind else {
        return None;
    };

    let Segment::Arc(arc) = segment else {
        return None;
    };

    // Get the point ID from the segment
    let point_id = match which {
        ArcPoint::Start => arc.start,
        ArcPoint::End => arc.end,
        ArcPoint::Center => arc.center,
    };

    get_point_coords_from_native(objects, point_id, default_unit)
}

/// Helper to get point coordinates from a Circle segment by looking up the point object (native types)
pub fn get_position_coords_from_circle(
    segment_obj: &Object,
    which: CirclePoint,
    objects: &[Object],
    default_unit: UnitLength,
) -> Option<Coords2d> {
    let ObjectKind::Segment { segment } = &segment_obj.kind else {
        return None;
    };

    let Segment::Circle(circle) = segment else {
        return None;
    };

    let point_id = match which {
        CirclePoint::Start => circle.start,
        CirclePoint::Center => circle.center,
    };

    get_point_coords_from_native(objects, point_id, default_unit)
}

/// Internal normalized curve kind used by trim.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CurveKind {
    Line,
    Circular,
    Spline,
}

/// Internal curve domain used by trim.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CurveDomain {
    Open,
    Closed,
}

/// Internal normalized curve representation loaded from a scene segment.
#[derive(Debug, Clone)]
struct SampledCurvePoint {
    parameter: f64,
    point: Coords2d,
}

#[derive(Debug, Clone)]
struct CurveHandle {
    segment_id: ObjectId,
    kind: CurveKind,
    domain: CurveDomain,
    start: Coords2d,
    end: Coords2d,
    center: Option<Coords2d>,
    radius: Option<f64>,
    sampled_points: Option<Vec<SampledCurvePoint>>,
}

impl CurveHandle {
    fn project_for_trim(&self, point: Coords2d) -> Result<f64, String> {
        match (self.kind, self.domain) {
            (CurveKind::Line, CurveDomain::Open) => Ok(project_point_onto_segment(point, self.start, self.end)),
            (CurveKind::Circular, CurveDomain::Open) => {
                let center = self
                    .center
                    .ok_or_else(|| format!("Curve {} missing center for arc projection", self.segment_id.0))?;
                Ok(project_point_onto_arc(point, center, self.start, self.end))
            }
            (CurveKind::Circular, CurveDomain::Closed) => {
                let center = self
                    .center
                    .ok_or_else(|| format!("Curve {} missing center for circle projection", self.segment_id.0))?;
                Ok(project_point_onto_circle(point, center, self.start))
            }
            (CurveKind::Line, CurveDomain::Closed) => Err(format!(
                "Invalid curve state: line {} cannot be closed",
                self.segment_id.0
            )),
            (CurveKind::Spline, CurveDomain::Open) => project_point_onto_sampled_curve(
                self.sampled_points.as_deref().ok_or_else(|| {
                    format!(
                        "Curve {} missing sampled points for spline projection",
                        self.segment_id.0
                    )
                })?,
                point,
            ),
            (CurveKind::Spline, CurveDomain::Closed) => Err(format!(
                "Invalid curve state: spline {} cannot be closed",
                self.segment_id.0
            )),
        }
    }
}

const CONTROL_POINT_SPLINE_TRIM_SAMPLES_PER_SPAN: usize = 32;

fn build_open_uniform_knot_vector(control_count: usize, degree: usize) -> Vec<f64> {
    let span_count = control_count.saturating_sub(degree);
    let mut knots = vec![0.0; degree + 1];
    if span_count > 1 {
        for value in 1..span_count {
            knots.push(value as f64);
        }
    }
    knots.extend(std::iter::repeat_n(span_count as f64, degree + 1));
    knots
}

fn find_knot_span(parameter: f64, degree: usize, knots: &[f64], control_count: usize) -> usize {
    let n = control_count - 1;
    if parameter >= knots[n + 1] {
        return n;
    }
    if parameter <= knots[degree] {
        return degree;
    }

    let mut low = degree;
    let mut high = n + 1;
    let mut mid = (low + high) / 2;
    while parameter < knots[mid] || parameter >= knots[mid + 1] {
        if parameter < knots[mid] {
            high = mid;
        } else {
            low = mid;
        }
        mid = (low + high) / 2;
    }
    mid
}

fn de_boor_point(parameter: f64, degree: usize, knots: &[f64], controls: &[Coords2d]) -> Coords2d {
    let span = find_knot_span(parameter, degree, knots, controls.len());
    let mut points = (0..=degree).map(|j| controls[span - degree + j]).collect::<Vec<_>>();

    for r in 1..=degree {
        for j in (r..=degree).rev() {
            let knot_index = span - degree + j;
            let denominator = knots[knot_index + degree + 1 - r] - knots[knot_index];
            let alpha = if denominator.abs() <= f64::EPSILON {
                0.0
            } else {
                (parameter - knots[knot_index]) / denominator
            };
            points[j] = Coords2d {
                x: (1.0 - alpha) * points[j - 1].x + alpha * points[j].x,
                y: (1.0 - alpha) * points[j - 1].y + alpha * points[j].y,
            };
        }
    }

    points[degree]
}

fn sample_control_point_spline_for_trim(controls: &[Coords2d], degree: usize) -> Vec<SampledCurvePoint> {
    let knots = build_open_uniform_knot_vector(controls.len(), degree);
    let span_count = controls.len().saturating_sub(degree);
    let mut samples = Vec::with_capacity(span_count * CONTROL_POINT_SPLINE_TRIM_SAMPLES_PER_SPAN + 1);
    samples.push(SampledCurvePoint {
        parameter: 0.0,
        point: controls[0],
    });

    for span_index in 0..span_count {
        let start = span_index as f64;
        let end = (span_index + 1) as f64;
        let is_last_span = span_index + 1 == span_count;
        let max_step = if is_last_span {
            CONTROL_POINT_SPLINE_TRIM_SAMPLES_PER_SPAN
        } else {
            CONTROL_POINT_SPLINE_TRIM_SAMPLES_PER_SPAN - 1
        };

        for step in 1..=max_step {
            let t = step as f64 / CONTROL_POINT_SPLINE_TRIM_SAMPLES_PER_SPAN as f64;
            let parameter = if is_last_span && step == CONTROL_POINT_SPLINE_TRIM_SAMPLES_PER_SPAN {
                end
            } else {
                start + t * (end - start)
            };
            samples.push(SampledCurvePoint {
                parameter,
                point: de_boor_point(parameter, degree, &knots, controls),
            });
        }
    }

    samples
}

fn project_point_onto_sampled_curve(samples: &[SampledCurvePoint], point: Coords2d) -> Result<f64, String> {
    if samples.len() < 2 {
        return Err("Need at least two sampled points to project onto spline".to_string());
    }

    let mut best_parameter = samples[0].parameter;
    let mut best_distance_sq = f64::INFINITY;
    for window in samples.windows(2) {
        let start = window[0].point;
        let end = window[1].point;
        let dx = end.x - start.x;
        let dy = end.y - start.y;
        let segment_length_sq = dx * dx + dy * dy;
        let local_t = if segment_length_sq <= f64::EPSILON {
            0.0
        } else {
            (((point.x - start.x) * dx + (point.y - start.y) * dy) / segment_length_sq).clamp(0.0, 1.0)
        };
        let projected = Coords2d {
            x: start.x + local_t * dx,
            y: start.y + local_t * dy,
        };
        let distance_sq = (point.x - projected.x).powi(2) + (point.y - projected.y).powi(2);
        if distance_sq < best_distance_sq {
            best_distance_sq = distance_sq;
            best_parameter = window[0].parameter + local_t * (window[1].parameter - window[0].parameter);
        }
    }

    Ok(best_parameter)
}

/// Load a normalized curve handle from a segment object.
fn is_control_point_spline_owned_helper_line(segment_obj: &Object, objects: &[Object]) -> bool {
    let ObjectKind::Segment { segment } = &segment_obj.kind else {
        return false;
    };
    let Segment::Line(line) = segment else {
        return false;
    };
    let Some(owner_id) = line.owner else {
        return false;
    };
    objects.iter().find(|obj| obj.id == owner_id).is_some_and(|owner_obj| {
        matches!(
            &owner_obj.kind,
            ObjectKind::Segment {
                segment: Segment::ControlPointSpline(_)
            }
        )
    })
}

fn get_control_point_spline_controls(
    segment_obj: &Object,
    objects: &[Object],
    default_unit: UnitLength,
) -> Result<Vec<(ObjectId, Coords2d)>, String> {
    let ObjectKind::Segment {
        segment: Segment::ControlPointSpline(spline),
    } = &segment_obj.kind
    else {
        return Err(format!("Segment {} is not a control point spline", segment_obj.id.0));
    };

    spline
        .controls
        .iter()
        .map(|control_id| {
            let point_obj = objects.iter().find(|obj| obj.id == *control_id).ok_or_else(|| {
                format!(
                    "Control point {} not found for spline {}",
                    control_id.0, segment_obj.id.0
                )
            })?;
            let ObjectKind::Segment {
                segment: Segment::Point(point),
            } = &point_obj.kind
            else {
                return Err(format!(
                    "Control point {} for spline {} is not a point",
                    control_id.0, segment_obj.id.0
                ));
            };
            Ok((
                *control_id,
                Coords2d {
                    x: number_to_unit(&point.position.x, default_unit),
                    y: number_to_unit(&point.position.y, default_unit),
                },
            ))
        })
        .collect()
}

fn load_curve_handle(
    segment_obj: &Object,
    objects: &[Object],
    default_unit: UnitLength,
) -> Result<CurveHandle, String> {
    if is_control_point_spline_owned_helper_line(segment_obj, objects) {
        return Err(format!(
            "Control point spline helper line {} cannot be used as a trim curve",
            segment_obj.id.0
        ));
    }

    let ObjectKind::Segment { segment } = &segment_obj.kind else {
        return Err("Object is not a segment".to_owned());
    };

    match segment {
        Segment::Line(_) => {
            let start = get_position_coords_for_line(segment_obj, LineEndpoint::Start, objects, default_unit)
                .ok_or_else(|| format!("Could not get line start for segment {}", segment_obj.id.0))?;
            let end = get_position_coords_for_line(segment_obj, LineEndpoint::End, objects, default_unit)
                .ok_or_else(|| format!("Could not get line end for segment {}", segment_obj.id.0))?;
            Ok(CurveHandle {
                segment_id: segment_obj.id,
                kind: CurveKind::Line,
                domain: CurveDomain::Open,
                start,
                end,
                center: None,
                radius: None,
                sampled_points: None,
            })
        }
        Segment::Arc(_) => {
            let start = get_position_coords_from_arc(segment_obj, ArcPoint::Start, objects, default_unit)
                .ok_or_else(|| format!("Could not get arc start for segment {}", segment_obj.id.0))?;
            let end = get_position_coords_from_arc(segment_obj, ArcPoint::End, objects, default_unit)
                .ok_or_else(|| format!("Could not get arc end for segment {}", segment_obj.id.0))?;
            let center = get_position_coords_from_arc(segment_obj, ArcPoint::Center, objects, default_unit)
                .ok_or_else(|| format!("Could not get arc center for segment {}", segment_obj.id.0))?;
            let radius =
                ((start.x - center.x) * (start.x - center.x) + (start.y - center.y) * (start.y - center.y)).sqrt();
            Ok(CurveHandle {
                segment_id: segment_obj.id,
                kind: CurveKind::Circular,
                domain: CurveDomain::Open,
                start,
                end,
                center: Some(center),
                radius: Some(radius),
                sampled_points: None,
            })
        }
        Segment::Circle(_) => {
            let start = get_position_coords_from_circle(segment_obj, CirclePoint::Start, objects, default_unit)
                .ok_or_else(|| format!("Could not get circle start for segment {}", segment_obj.id.0))?;
            let center = get_position_coords_from_circle(segment_obj, CirclePoint::Center, objects, default_unit)
                .ok_or_else(|| format!("Could not get circle center for segment {}", segment_obj.id.0))?;
            let radius =
                ((start.x - center.x) * (start.x - center.x) + (start.y - center.y) * (start.y - center.y)).sqrt();
            Ok(CurveHandle {
                segment_id: segment_obj.id,
                kind: CurveKind::Circular,
                domain: CurveDomain::Closed,
                start,
                // Closed curves have no true "end"; keep current trim semantics by mirroring start.
                end: start,
                center: Some(center),
                radius: Some(radius),
                sampled_points: None,
            })
        }
        Segment::Point(_) => Err(format!(
            "Point segment {} cannot be used as trim curve",
            segment_obj.id.0
        )),
        Segment::ControlPointSpline(spline) => {
            let controls = get_control_point_spline_controls(segment_obj, objects, default_unit)?;
            let sampled_points = sample_control_point_spline_for_trim(
                &controls.iter().map(|(_, point)| *point).collect::<Vec<_>>(),
                spline.degree as usize,
            );
            let start = controls
                .first()
                .map(|(_, point)| *point)
                .ok_or_else(|| format!("Spline {} has no control points", segment_obj.id.0))?;
            let end = controls
                .last()
                .map(|(_, point)| *point)
                .ok_or_else(|| format!("Spline {} has no control points", segment_obj.id.0))?;
            Ok(CurveHandle {
                segment_id: segment_obj.id,
                kind: CurveKind::Spline,
                domain: CurveDomain::Open,
                start,
                end,
                center: None,
                radius: None,
                sampled_points: Some(sampled_points),
            })
        }
    }
}

fn project_point_onto_curve(curve: &CurveHandle, point: Coords2d) -> Result<f64, String> {
    curve.project_for_trim(point)
}

fn curve_contains_point(curve: &CurveHandle, point: Coords2d, epsilon: f64) -> bool {
    match (curve.kind, curve.domain) {
        (CurveKind::Line, CurveDomain::Open) => {
            let t = project_point_onto_segment(point, curve.start, curve.end);
            (0.0..=1.0).contains(&t) && perpendicular_distance_to_segment(point, curve.start, curve.end) <= epsilon
        }
        (CurveKind::Circular, CurveDomain::Open) => curve
            .center
            .is_some_and(|center| is_point_on_arc(point, center, curve.start, curve.end, epsilon)),
        (CurveKind::Circular, CurveDomain::Closed) => curve.center.is_some_and(|center| {
            let radius = curve.radius.unwrap_or_else(|| {
                ((curve.start.x - center.x).squared() + (curve.start.y - center.y).squared()).sqrt()
            });
            is_point_on_circle(point, center, radius, epsilon)
        }),
        (CurveKind::Line, CurveDomain::Closed) => false,
        (CurveKind::Spline, CurveDomain::Open) => {
            project_point_onto_sampled_curve(curve.sampled_points.as_deref().unwrap_or(&[]), point)
                .ok()
                .and_then(|parameter| {
                    curve.sampled_points.as_ref().map(|samples| {
                        let nearest = samples
                            .windows(2)
                            .map(|window| {
                                let start = window[0].point;
                                let end = window[1].point;
                                let dx = end.x - start.x;
                                let dy = end.y - start.y;
                                let segment_length_sq = dx * dx + dy * dy;
                                let local_t = if segment_length_sq <= f64::EPSILON {
                                    0.0
                                } else {
                                    ((parameter - window[0].parameter) / (window[1].parameter - window[0].parameter))
                                        .clamp(0.0, 1.0)
                                };
                                let projected = Coords2d {
                                    x: start.x + local_t * dx,
                                    y: start.y + local_t * dy,
                                };
                                ((point.x - projected.x).powi(2) + (point.y - projected.y).powi(2)).sqrt()
                            })
                            .fold(f64::INFINITY, libm::fmin);
                        nearest <= epsilon
                    })
                })
                .unwrap_or(false)
        }
        (CurveKind::Spline, CurveDomain::Closed) => false,
    }
}

fn curve_line_segment_intersections(
    curve: &CurveHandle,
    line_start: Coords2d,
    line_end: Coords2d,
    epsilon: f64,
) -> Vec<(f64, Coords2d)> {
    match (curve.kind, curve.domain) {
        (CurveKind::Line, CurveDomain::Open) => {
            line_segment_intersection(line_start, line_end, curve.start, curve.end, epsilon)
                .map(|intersection| {
                    (
                        project_point_onto_segment(intersection, line_start, line_end),
                        intersection,
                    )
                })
                .into_iter()
                .collect()
        }
        (CurveKind::Circular, CurveDomain::Open) => curve
            .center
            .map(|center| line_arc_intersections(line_start, line_end, center, curve.start, curve.end, epsilon))
            .unwrap_or_default(),
        (CurveKind::Circular, CurveDomain::Closed) => {
            let Some(center) = curve.center else {
                return Vec::new();
            };
            let radius = curve.radius.unwrap_or_else(|| {
                ((curve.start.x - center.x).squared() + (curve.start.y - center.y).squared()).sqrt()
            });
            line_circle_intersections(line_start, line_end, center, radius, epsilon)
        }
        (CurveKind::Line, CurveDomain::Closed) => Vec::new(),
        (CurveKind::Spline, CurveDomain::Open) => {
            let Some(samples) = curve.sampled_points.as_ref() else {
                return Vec::new();
            };
            let mut intersections = Vec::new();
            for window in samples.windows(2) {
                if let Some(intersection) =
                    line_segment_intersection(line_start, line_end, window[0].point, window[1].point, epsilon)
                {
                    let t = project_point_onto_segment(intersection, line_start, line_end);
                    intersections.push((t, intersection));
                }
            }
            intersections
        }
        (CurveKind::Spline, CurveDomain::Closed) => Vec::new(),
    }
}

fn curve_polyline_intersections(curve: &CurveHandle, polyline: &[Coords2d], epsilon: f64) -> Vec<(Coords2d, usize)> {
    let mut intersections = Vec::new();

    for i in 0..polyline.len().saturating_sub(1) {
        let p1 = polyline[i];
        let p2 = polyline[i + 1];
        for (_, intersection) in curve_line_segment_intersections(curve, p1, p2, epsilon) {
            intersections.push((intersection, i));
        }
    }

    intersections
}

fn curve_curve_intersections(curve: &CurveHandle, other: &CurveHandle, epsilon: f64) -> Vec<Coords2d> {
    match (curve.kind, curve.domain, other.kind, other.domain) {
        (CurveKind::Line, CurveDomain::Open, CurveKind::Line, CurveDomain::Open) => {
            line_segment_intersection(curve.start, curve.end, other.start, other.end, epsilon)
                .into_iter()
                .collect()
        }
        (CurveKind::Line, CurveDomain::Open, CurveKind::Circular, CurveDomain::Open) => other
            .center
            .map(|other_center| {
                line_arc_intersections(curve.start, curve.end, other_center, other.start, other.end, epsilon)
                    .into_iter()
                    .map(|(_, point)| point)
                    .collect()
            })
            .unwrap_or_default(),
        (CurveKind::Line, CurveDomain::Open, CurveKind::Circular, CurveDomain::Closed) => {
            let Some(other_center) = other.center else {
                return Vec::new();
            };
            let other_radius = other.radius.unwrap_or_else(|| {
                ((other.start.x - other_center.x).squared() + (other.start.y - other_center.y).squared()).sqrt()
            });
            line_circle_intersections(curve.start, curve.end, other_center, other_radius, epsilon)
                .into_iter()
                .map(|(_, point)| point)
                .collect()
        }
        (CurveKind::Circular, CurveDomain::Open, CurveKind::Line, CurveDomain::Open) => curve
            .center
            .map(|curve_center| {
                line_arc_intersections(other.start, other.end, curve_center, curve.start, curve.end, epsilon)
                    .into_iter()
                    .map(|(_, point)| point)
                    .collect()
            })
            .unwrap_or_default(),
        (CurveKind::Circular, CurveDomain::Open, CurveKind::Circular, CurveDomain::Open) => {
            let (Some(curve_center), Some(other_center)) = (curve.center, other.center) else {
                return Vec::new();
            };
            arc_arc_intersections(
                curve_center,
                curve.start,
                curve.end,
                other_center,
                other.start,
                other.end,
                epsilon,
            )
        }
        (CurveKind::Circular, CurveDomain::Open, CurveKind::Circular, CurveDomain::Closed) => {
            let (Some(curve_center), Some(other_center)) = (curve.center, other.center) else {
                return Vec::new();
            };
            let other_radius = other.radius.unwrap_or_else(|| {
                ((other.start.x - other_center.x).squared() + (other.start.y - other_center.y).squared()).sqrt()
            });
            circle_arc_intersections(
                other_center,
                other_radius,
                curve_center,
                curve.start,
                curve.end,
                epsilon,
            )
        }
        (CurveKind::Circular, CurveDomain::Closed, CurveKind::Line, CurveDomain::Open) => {
            let Some(curve_center) = curve.center else {
                return Vec::new();
            };
            let curve_radius = curve.radius.unwrap_or_else(|| {
                ((curve.start.x - curve_center.x).squared() + (curve.start.y - curve_center.y).squared()).sqrt()
            });
            line_circle_intersections(other.start, other.end, curve_center, curve_radius, epsilon)
                .into_iter()
                .map(|(_, point)| point)
                .collect()
        }
        (CurveKind::Circular, CurveDomain::Closed, CurveKind::Circular, CurveDomain::Open) => {
            let (Some(curve_center), Some(other_center)) = (curve.center, other.center) else {
                return Vec::new();
            };
            let curve_radius = curve.radius.unwrap_or_else(|| {
                ((curve.start.x - curve_center.x).squared() + (curve.start.y - curve_center.y).squared()).sqrt()
            });
            circle_arc_intersections(
                curve_center,
                curve_radius,
                other_center,
                other.start,
                other.end,
                epsilon,
            )
        }
        (CurveKind::Circular, CurveDomain::Closed, CurveKind::Circular, CurveDomain::Closed) => {
            let (Some(curve_center), Some(other_center)) = (curve.center, other.center) else {
                return Vec::new();
            };
            let curve_radius = curve.radius.unwrap_or_else(|| {
                ((curve.start.x - curve_center.x).squared() + (curve.start.y - curve_center.y).squared()).sqrt()
            });
            let other_radius = other.radius.unwrap_or_else(|| {
                ((other.start.x - other_center.x).squared() + (other.start.y - other_center.y).squared()).sqrt()
            });
            circle_circle_intersections(curve_center, curve_radius, other_center, other_radius, epsilon)
        }
        (CurveKind::Spline, CurveDomain::Open, _, _) => sampled_curve_curve_intersections(curve, other, epsilon),
        (_, _, CurveKind::Spline, CurveDomain::Open) => sampled_curve_curve_intersections(other, curve, epsilon),
        _ => Vec::new(),
    }
}

fn sampled_curve_curve_intersections(sampled_curve: &CurveHandle, other: &CurveHandle, epsilon: f64) -> Vec<Coords2d> {
    let Some(samples) = sampled_curve.sampled_points.as_ref() else {
        return Vec::new();
    };
    let mut intersections = Vec::new();

    for window in samples.windows(2) {
        let start = window[0].point;
        let end = window[1].point;
        match (other.kind, other.domain) {
            (CurveKind::Line, CurveDomain::Open) => {
                if let Some(intersection) = line_segment_intersection(start, end, other.start, other.end, epsilon) {
                    intersections.push(intersection);
                }
            }
            (CurveKind::Circular, CurveDomain::Open) => {
                if let Some(center) = other.center
                    && let Some(intersection) =
                        line_arc_intersection(start, end, center, other.start, other.end, epsilon)
                {
                    intersections.push(intersection);
                }
            }
            (CurveKind::Circular, CurveDomain::Closed) => {
                if let Some(center) = other.center {
                    let radius = other.radius.unwrap_or_else(|| {
                        ((other.start.x - center.x).powi(2) + (other.start.y - center.y).powi(2)).sqrt()
                    });
                    intersections.extend(
                        line_circle_intersections(start, end, center, radius, epsilon)
                            .into_iter()
                            .map(|(_, point)| point),
                    );
                }
            }
            (CurveKind::Spline, CurveDomain::Open) => {
                let Some(other_samples) = other.sampled_points.as_ref() else {
                    continue;
                };
                for other_window in other_samples.windows(2) {
                    if let Some(intersection) =
                        line_segment_intersection(start, end, other_window[0].point, other_window[1].point, epsilon)
                    {
                        intersections.push(intersection);
                    }
                }
            }
            _ => {}
        }
    }

    intersections
}

fn segment_endpoint_points(
    segment_obj: &Object,
    objects: &[Object],
    default_unit: UnitLength,
) -> Vec<(ObjectId, Coords2d)> {
    let ObjectKind::Segment { segment } = &segment_obj.kind else {
        return Vec::new();
    };

    match segment {
        Segment::Line(line) => {
            if is_control_point_spline_owned_helper_line(segment_obj, objects) {
                return Vec::new();
            }
            let mut points = Vec::new();
            if let Some(start) = get_position_coords_for_line(segment_obj, LineEndpoint::Start, objects, default_unit) {
                points.push((line.start, start));
            }
            if let Some(end) = get_position_coords_for_line(segment_obj, LineEndpoint::End, objects, default_unit) {
                points.push((line.end, end));
            }
            points
        }
        Segment::Arc(arc) => {
            let mut points = Vec::new();
            if let Some(start) = get_position_coords_from_arc(segment_obj, ArcPoint::Start, objects, default_unit) {
                points.push((arc.start, start));
            }
            if let Some(end) = get_position_coords_from_arc(segment_obj, ArcPoint::End, objects, default_unit) {
                points.push((arc.end, end));
            }
            points
        }
        Segment::ControlPointSpline(spline) => {
            let mut points = Vec::new();
            if let Ok(controls) = get_control_point_spline_controls(segment_obj, objects, default_unit) {
                if let Some((control_id, point)) = controls.first() {
                    points.push((*control_id, *point));
                }
                if let Some((control_id, point)) = controls.last()
                    && Some(*control_id) != points.first().map(|(id, _)| *id)
                {
                    points.push((*control_id, *point));
                }
            } else if !spline.controls.is_empty() {
                return Vec::new();
            }
            points
        }
        _ => Vec::new(),
    }
}

/// Find the next trim spawn (intersection) between trim line and scene segments
///
/// When a user draws a trim line, we loop over each pairs of points of the trim line,
/// until we find an intersection, this intersection is called the trim spawn (to differentiate from
/// segment-segment intersections which are also important for trimming).
/// Below the dashes are segments and the periods are points on the trim line.
///
/// ```
///          /
///         /
///        /    .
/// ------/-------x--------
///      /       .       
///     /       .       
///    /           .   
/// ```
///
/// When we find a trim spawn we stop looping but save the index as we process each trim spawn one at a time.
/// The loop that processes each spawn one at a time is managed by `execute_trim_loop` (or `execute_trim_loop_with_context`).
///
/// Loops through polyline segments starting from startIndex and checks for intersections
/// with all scene segments (both Line and Arc). Returns the first intersection found.
///
/// **Units:** Trim line points are expected in millimeters at the API boundary. Callers should
/// normalize points to the current/default length unit before calling this function (the
/// trim loop does this for you). Segment positions read from `objects` are converted to that same
/// unit internally.
pub fn get_next_trim_spawn(
    points: &[Coords2d],
    start_index: usize,
    objects: &[Object],
    default_unit: UnitLength,
) -> TrimItem {
    let scene_curves: Vec<CurveHandle> = objects
        .iter()
        .filter_map(|obj| load_curve_handle(obj, objects, default_unit).ok())
        .collect();

    // Loop through polyline segments starting from startIndex
    for i in start_index..points.len().saturating_sub(1) {
        let p1 = points[i];
        let p2 = points[i + 1];

        // Check this polyline segment against all scene segments
        for curve in &scene_curves {
            let intersections = curve_line_segment_intersections(curve, p1, p2, EPSILON_POINT_ON_SEGMENT);
            if let Some((_, intersection)) = intersections.first() {
                return TrimItem::Spawn {
                    trim_spawn_seg_id: curve.segment_id,
                    trim_spawn_coords: *intersection,
                    next_index: i,
                };
            }
        }
    }

    // No intersection found
    TrimItem::None {
        next_index: points.len().saturating_sub(1),
    }
}

/**
 * For the trim spawn segment and the intersection point on that segment,
 * finds the "trim terminations" in both directions (left and right from the intersection point).
 * A trim termination is the point where trimming should stop in each direction.
 *
 * The function searches for candidates in each direction and selects the closest one,
 * with the following priority when distances are equal: coincident > intersection > endpoint.
 *
 * ## segEndPoint: The segment's own endpoint
 *
 *   ========0
 * OR
 *   ========0
 *            \
 *             \
 *
 *  Returns this when:
 *  - No other candidates are found between the intersection point and the segment end
 *  - An intersection is found at the segment's own endpoint (even if due to numerical precision)
 *  - An intersection is found at another segment's endpoint (without a coincident constraint)
 *  - The closest candidate is the segment's own endpoint
 *
 * ## intersection: Intersection with another segment's body
 *            /
 *           /
 *  ========X=====
 *         /
 *        /
 *
 *  Returns this when:
 *  - A geometric intersection is found with another segment's body (not at an endpoint)
 *  - The intersection is not at our own segment's endpoint
 *  - The intersection is not at the other segment's endpoint (which would be segEndPoint)
 *
 * ## trimSpawnSegmentCoincidentWithAnotherSegmentPoint: Another segment's endpoint coincident with our segment
 *
 *  ========0=====
 *         /
 *        /
 *
 *  Returns this when:
 *  - Another segment's endpoint has a coincident constraint with our trim spawn segment
 *  - The endpoint's perpendicular distance to our segment is within epsilon
 *  - The endpoint is geometrically on our segment (between start and end)
 *  - This takes priority over intersections when distances are equal (within epsilon)
 *
 * ## Fallback
 *  If no candidates are found in a direction, defaults to "segEndPoint".
 * */
/// Find trim terminations for both sides of a trim spawn
///
/// For the trim spawn segment and the intersection point on that segment,
/// finds the "trim terminations" in both directions (left and right from the intersection point).
/// A trim termination is the point where trimming should stop in each direction.
pub fn get_trim_spawn_terminations(
    trim_spawn_seg_id: ObjectId,
    trim_spawn_coords: &[Coords2d],
    objects: &[Object],
    default_unit: UnitLength,
) -> Result<TrimTerminations, String> {
    // Find the trim spawn segment
    let trim_spawn_seg = objects.iter().find(|obj| obj.id == trim_spawn_seg_id);

    let trim_spawn_seg = match trim_spawn_seg {
        Some(seg) => seg,
        None => {
            return Err(format!("Trim spawn segment {} not found", trim_spawn_seg_id.0));
        }
    };

    let trim_curve = load_curve_handle(trim_spawn_seg, objects, default_unit).map_err(|e| {
        format!(
            "Failed to load trim spawn segment {} as normalized curve: {}",
            trim_spawn_seg_id.0, e
        )
    })?;

    // Find intersection point between polyline and trim spawn segment
    // trimSpawnCoords is a polyline, so we check each segment
    // We need to find ALL intersections and use a consistent one to avoid
    // different results for different trim lines in the same area
    let all_intersections = curve_polyline_intersections(&trim_curve, trim_spawn_coords, EPSILON_POINT_ON_SEGMENT);

    // Use the intersection that's closest to the middle of the polyline
    // This ensures consistent results regardless of which segment intersects first
    let intersection_point = if all_intersections.is_empty() {
        return Err("Could not find intersection point between polyline and trim spawn segment".to_string());
    } else {
        // Find the middle of the polyline
        let mid_index = (trim_spawn_coords.len() - 1) / 2;
        let mid_point = trim_spawn_coords[mid_index];

        // Find the intersection closest to the middle
        let mut min_dist = f64::INFINITY;
        let mut closest_intersection = all_intersections[0].0;

        for (intersection, _) in &all_intersections {
            let dist = ((intersection.x - mid_point.x) * (intersection.x - mid_point.x)
                + (intersection.y - mid_point.y) * (intersection.y - mid_point.y))
                .sqrt();
            if dist < min_dist {
                min_dist = dist;
                closest_intersection = *intersection;
            }
        }

        closest_intersection
    };

    // Project intersection point onto segment to get parametric position
    let intersection_t = project_point_onto_curve(&trim_curve, intersection_point)?;

    // Find terminations on both sides
    let left_termination = find_termination_in_direction(
        trim_spawn_seg,
        &trim_curve,
        intersection_t,
        TrimDirection::Left,
        objects,
        default_unit,
    )?;

    let right_termination = find_termination_in_direction(
        trim_spawn_seg,
        &trim_curve,
        intersection_t,
        TrimDirection::Right,
        objects,
        default_unit,
    )?;

    Ok(TrimTerminations {
        left_side: left_termination,
        right_side: right_termination,
    })
}

/// Helper to find trim termination in a given direction from the intersection point
///
/// This is called by `get_trim_spawn_terminations` for each direction (left and right).
/// It searches for candidates in the specified direction and selects the closest one,
/// with the following priority when distances are equal: coincident > intersection > endpoint.
///
/// ## segEndPoint: The segment's own endpoint
///
/// ```
///   ========0
/// OR
///   ========0
///            \
///             \
/// ```
///
/// Returns this when:
/// - No other candidates are found between the intersection point and the segment end
/// - An intersection is found at the segment's own endpoint (even if due to numerical precision)
/// - An intersection is found at another segment's endpoint (without a coincident constraint)
/// - The closest candidate is the segment's own endpoint
///
/// ## intersection: Intersection with another segment's body
/// ```
///            /
///           /
///  ========X=====
///         /
///        /
/// ```
///
/// Returns this when:
/// - A geometric intersection is found with another segment's body (not at an endpoint)
/// - The intersection is not at our own segment's endpoint
/// - The intersection is not at the other segment's endpoint (which would be segEndPoint)
///
/// ## trimSpawnSegmentCoincidentWithAnotherSegmentPoint: Another segment's endpoint coincident with our segment
///
/// ```
///  ========0=====
///         /
///        /
/// ```
///
/// Returns this when:
/// - Another segment's endpoint has a coincident constraint with our trim spawn segment
/// - The endpoint's perpendicular distance to our segment is within epsilon
/// - The endpoint is geometrically on our segment (between start and end)
/// - This takes priority over intersections when distances are equal (within epsilon)
///
/// ## Fallback
/// If no candidates are found in a direction, defaults to "segEndPoint".
fn find_termination_in_direction(
    trim_spawn_seg: &Object,
    trim_curve: &CurveHandle,
    intersection_t: f64,
    direction: TrimDirection,
    objects: &[Object],
    default_unit: UnitLength,
) -> Result<TrimTermination, String> {
    // Use native types
    let ObjectKind::Segment { segment } = &trim_spawn_seg.kind else {
        return Err("Trim spawn segment is not a segment".to_string());
    };

    // Collect all candidate points: intersections, coincident points, and endpoints
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
    enum CandidateType {
        Intersection,
        Coincident,
        Endpoint,
    }

    #[derive(Debug, Clone)]
    struct Candidate {
        t: f64,
        point: Coords2d,
        candidate_type: CandidateType,
        segment_id: Option<ObjectId>,
        point_id: Option<ObjectId>,
    }

    let mut candidates: Vec<Candidate> = Vec::new();

    // Add segment endpoints using native types
    match segment {
        Segment::Line(line) => {
            candidates.push(Candidate {
                t: 0.0,
                point: trim_curve.start,
                candidate_type: CandidateType::Endpoint,
                segment_id: None,
                point_id: Some(line.start),
            });
            candidates.push(Candidate {
                t: 1.0,
                point: trim_curve.end,
                candidate_type: CandidateType::Endpoint,
                segment_id: None,
                point_id: Some(line.end),
            });
        }
        Segment::Arc(arc) => {
            // For arcs, endpoints are at t=0 and t=1 conceptually
            candidates.push(Candidate {
                t: 0.0,
                point: trim_curve.start,
                candidate_type: CandidateType::Endpoint,
                segment_id: None,
                point_id: Some(arc.start),
            });
            candidates.push(Candidate {
                t: 1.0,
                point: trim_curve.end,
                candidate_type: CandidateType::Endpoint,
                segment_id: None,
                point_id: Some(arc.end),
            });
        }
        Segment::Circle(_) => {
            // Circles have no endpoints for trim termination purposes.
        }
        Segment::ControlPointSpline(spline) => {
            let end_t = trim_curve
                .sampled_points
                .as_ref()
                .and_then(|samples| samples.last())
                .map(|sample| sample.parameter)
                .unwrap_or_else(|| spline.controls.len().saturating_sub(1) as f64);
            candidates.push(Candidate {
                t: 0.0,
                point: trim_curve.start,
                candidate_type: CandidateType::Endpoint,
                segment_id: None,
                point_id: spline.controls.first().copied(),
            });
            candidates.push(Candidate {
                t: end_t,
                point: trim_curve.end,
                candidate_type: CandidateType::Endpoint,
                segment_id: None,
                point_id: spline.controls.last().copied(),
            });
        }
        _ => {}
    }

    // Get trim spawn segment ID for comparison
    let trim_spawn_seg_id = trim_spawn_seg.id;

    // Find intersections and coincident endpoint candidates against all other segments.
    for other_seg in objects.iter() {
        let other_id = other_seg.id;
        if other_id == trim_spawn_seg_id {
            continue;
        }

        if let Ok(other_curve) = load_curve_handle(other_seg, objects, default_unit) {
            for intersection in curve_curve_intersections(trim_curve, &other_curve, EPSILON_POINT_ON_SEGMENT) {
                let Ok(t) = project_point_onto_curve(trim_curve, intersection) else {
                    continue;
                };
                candidates.push(Candidate {
                    t,
                    point: intersection,
                    candidate_type: CandidateType::Intersection,
                    segment_id: Some(other_id),
                    point_id: None,
                });
            }
        }

        for (other_point_id, other_point) in segment_endpoint_points(other_seg, objects, default_unit) {
            if !is_point_coincident_with_segment_native(other_point_id, trim_spawn_seg_id, objects) {
                continue;
            }
            if !curve_contains_point(trim_curve, other_point, EPSILON_POINT_ON_SEGMENT) {
                continue;
            }
            let Ok(t) = project_point_onto_curve(trim_curve, other_point) else {
                continue;
            };
            candidates.push(Candidate {
                t,
                point: other_point,
                candidate_type: CandidateType::Coincident,
                segment_id: Some(other_id),
                point_id: Some(other_point_id),
            });
        }
    }

    let is_circle_segment = trim_curve.domain == CurveDomain::Closed;

    // Filter candidates to exclude the intersection point itself and those on the wrong side.
    // Use a slightly larger epsilon to account for numerical precision variations.
    let intersection_epsilon = EPSILON_POINT_ON_SEGMENT * 10.0; // 0.0001mm
    let direction_distance = |candidate_t: f64| -> f64 {
        if is_circle_segment {
            match direction {
                TrimDirection::Left => (intersection_t - candidate_t).rem_euclid(1.0),
                TrimDirection::Right => (candidate_t - intersection_t).rem_euclid(1.0),
            }
        } else {
            (candidate_t - intersection_t).abs()
        }
    };
    let filtered_candidates: Vec<Candidate> = candidates
        .into_iter()
        .filter(|candidate| {
            let dist_from_intersection = if is_circle_segment {
                let ccw = (candidate.t - intersection_t).rem_euclid(1.0);
                let cw = (intersection_t - candidate.t).rem_euclid(1.0);
                libm::fmin(ccw, cw)
            } else {
                (candidate.t - intersection_t).abs()
            };
            if dist_from_intersection < intersection_epsilon {
                return false; // Too close to intersection point
            }

            if is_circle_segment {
                direction_distance(candidate.t) > intersection_epsilon
            } else {
                match direction {
                    TrimDirection::Left => candidate.t < intersection_t,
                    TrimDirection::Right => candidate.t > intersection_t,
                }
            }
        })
        .collect();

    // Sort candidates by distance from intersection (closest first).
    // When distances are equal, prioritize: coincident > intersection > endpoint.
    // Also allow constrained coincident endpoints to win over nearby geometric
    // intersections so arc endpoints coincident with line segments terminate at
    // the authored endpoint instead of a tiny adjacent arc-body intersection.
    let mut sorted_candidates = filtered_candidates;
    sorted_candidates.sort_by(|a, b| {
        let dist_a = direction_distance(a.t);
        let dist_b = direction_distance(b.t);
        let dist_diff = dist_a - dist_b;
        let coincident_snap_applies = dist_diff.abs() <= EPSILON_COINCIDENT_TERMINATION_SNAP
            && (a.candidate_type == CandidateType::Coincident || b.candidate_type == CandidateType::Coincident);
        if dist_diff.abs() > EPSILON_POINT_ON_SEGMENT && !coincident_snap_applies {
            dist_diff.partial_cmp(&0.0).unwrap_or(std::cmp::Ordering::Equal)
        } else {
            // Distances are effectively equal - prioritize by type
            let type_priority = |candidate_type: CandidateType| -> i32 {
                match candidate_type {
                    CandidateType::Coincident => 0,
                    CandidateType::Intersection => 1,
                    CandidateType::Endpoint => 2,
                }
            };
            type_priority(a.candidate_type).cmp(&type_priority(b.candidate_type))
        }
    });

    // Find the first valid trim termination
    let closest_candidate = match sorted_candidates.first() {
        Some(c) => c,
        None => {
            if is_circle_segment {
                return Err("No trim termination candidate found for circle".to_string());
            }
            // No trim termination found, default to segment endpoint
            let endpoint = match direction {
                TrimDirection::Left => trim_curve.start,
                TrimDirection::Right => trim_curve.end,
            };
            return Ok(TrimTermination::SegEndPoint {
                trim_termination_coords: endpoint,
            });
        }
    };

    // Check if the closest candidate is an intersection that is actually another segment's endpoint
    // According to test case: if another segment's endpoint is on our segment (even without coincident constraint),
    // we should return segEndPoint, not intersection
    if !is_circle_segment
        && closest_candidate.candidate_type == CandidateType::Intersection
        && let Some(seg_id) = closest_candidate.segment_id
    {
        let intersecting_seg = objects.iter().find(|obj| obj.id == seg_id);

        if let Some(intersecting_seg) = intersecting_seg {
            // Use a larger epsilon for checking if intersection is at another segment's endpoint
            let endpoint_epsilon = EPSILON_POINT_ON_SEGMENT * 1000.0; // 0.001mm
            let is_other_seg_endpoint = segment_endpoint_points(intersecting_seg, objects, default_unit)
                .into_iter()
                .any(|(_, endpoint)| {
                    let dist_to_endpoint = ((closest_candidate.point.x - endpoint.x).squared()
                        + (closest_candidate.point.y - endpoint.y).squared())
                    .sqrt();
                    dist_to_endpoint < endpoint_epsilon
                });

            // If the intersection point is another segment's endpoint (even without coincident constraint),
            // return segEndPoint instead of intersection
            if is_other_seg_endpoint {
                let endpoint = match direction {
                    TrimDirection::Left => trim_curve.start,
                    TrimDirection::Right => trim_curve.end,
                };
                return Ok(TrimTermination::SegEndPoint {
                    trim_termination_coords: endpoint,
                });
            }
        }

        // Also check if intersection is at our arc's endpoint
        let endpoint_t = match direction {
            TrimDirection::Left => 0.0,
            TrimDirection::Right => 1.0,
        };
        let endpoint = match direction {
            TrimDirection::Left => trim_curve.start,
            TrimDirection::Right => trim_curve.end,
        };
        let dist_to_endpoint_param = (closest_candidate.t - endpoint_t).abs();
        let dist_to_endpoint_coords = ((closest_candidate.point.x - endpoint.x)
            * (closest_candidate.point.x - endpoint.x)
            + (closest_candidate.point.y - endpoint.y) * (closest_candidate.point.y - endpoint.y))
            .sqrt();

        let is_at_endpoint =
            dist_to_endpoint_param < EPSILON_POINT_ON_SEGMENT || dist_to_endpoint_coords < EPSILON_POINT_ON_SEGMENT;

        if is_at_endpoint {
            // Intersection is at our endpoint -> segEndPoint
            return Ok(TrimTermination::SegEndPoint {
                trim_termination_coords: endpoint,
            });
        }
    }

    // Check if the closest candidate is an intersection at an endpoint
    let endpoint_t_for_return = match direction {
        TrimDirection::Left => 0.0,
        TrimDirection::Right => 1.0,
    };
    if !is_circle_segment && closest_candidate.candidate_type == CandidateType::Intersection {
        let dist_to_endpoint = (closest_candidate.t - endpoint_t_for_return).abs();
        if dist_to_endpoint < EPSILON_POINT_ON_SEGMENT {
            // Intersection is at endpoint - check if there's a coincident constraint
            // or if it's just a numerical precision issue
            let endpoint = match direction {
                TrimDirection::Left => trim_curve.start,
                TrimDirection::Right => trim_curve.end,
            };
            return Ok(TrimTermination::SegEndPoint {
                trim_termination_coords: endpoint,
            });
        }
    }

    // Check if the closest candidate is an endpoint at the trim spawn segment's endpoint
    let endpoint = match direction {
        TrimDirection::Left => trim_curve.start,
        TrimDirection::Right => trim_curve.end,
    };
    if !is_circle_segment && closest_candidate.candidate_type == CandidateType::Endpoint {
        let dist_to_endpoint = (closest_candidate.t - endpoint_t_for_return).abs();
        if dist_to_endpoint < EPSILON_POINT_ON_SEGMENT {
            // This is our own endpoint, return it
            return Ok(TrimTermination::SegEndPoint {
                trim_termination_coords: endpoint,
            });
        }
    }

    // Return appropriate termination type
    if closest_candidate.candidate_type == CandidateType::Coincident {
        // Even if at endpoint, return coincident type because it's a constraint-based termination
        Ok(TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
            trim_termination_coords: closest_candidate.point,
            intersecting_seg_id: closest_candidate
                .segment_id
                .ok_or_else(|| "Missing segment_id for coincident".to_string())?,
            other_segment_point_id: closest_candidate
                .point_id
                .ok_or_else(|| "Missing point_id for coincident".to_string())?,
        })
    } else if closest_candidate.candidate_type == CandidateType::Intersection {
        Ok(TrimTermination::Intersection {
            trim_termination_coords: closest_candidate.point,
            intersecting_seg_id: closest_candidate
                .segment_id
                .ok_or_else(|| "Missing segment_id for intersection".to_string())?,
        })
    } else {
        if is_circle_segment {
            return Err("Circle trim termination unexpectedly resolved to endpoint".to_string());
        }
        // endpoint
        Ok(TrimTermination::SegEndPoint {
            trim_termination_coords: closest_candidate.point,
        })
    }
}

/// Execute the core trim loop.
/// This function handles the iteration through trim points, finding intersections,
/// and determining strategies. It calls the provided callback to execute operations.
///
/// The callback receives:
/// - The strategy (list of operations to execute)
/// - The current scene graph delta
///
/// The callback should return:
/// - The updated scene graph delta after executing operations
#[cfg(test)]
#[allow(dead_code)]
pub(crate) async fn execute_trim_loop<F, Fut>(
    points: &[Coords2d],
    default_unit: UnitLength,
    initial_scene_graph_delta: crate::frontend::api::SceneGraphDelta,
    mut execute_operations: F,
) -> Result<(crate::frontend::api::SourceDelta, crate::frontend::api::SceneGraphDelta), String>
where
    F: FnMut(Vec<TrimOperation>, crate::frontend::api::SceneGraphDelta) -> Fut,
    Fut: std::future::Future<
            Output = Result<(crate::frontend::api::SourceDelta, crate::frontend::api::SceneGraphDelta), String>,
        >,
{
    // Trim line points are expected in millimeters and normalized to the current unit here.
    let normalized_points = normalize_trim_points_to_unit(points, default_unit);
    let points = normalized_points.as_slice();

    let mut start_index = 0;
    let max_iterations = 1000;
    let mut iteration_count = 0;
    let mut last_result: Option<(crate::frontend::api::SourceDelta, crate::frontend::api::SceneGraphDelta)> = Some((
        crate::frontend::api::SourceDelta { text: String::new() },
        initial_scene_graph_delta.clone(),
    ));
    let mut invalidates_ids = false;
    let mut current_scene_graph_delta = initial_scene_graph_delta;
    let circle_delete_fallback_strategy =
        |error: &str, segment_id: ObjectId, scene_objects: &[Object]| -> Option<Vec<TrimOperation>> {
            if !error.contains("No trim termination candidate found for circle") {
                return None;
            }
            let is_circle = scene_objects
                .iter()
                .find(|obj| obj.id == segment_id)
                .is_some_and(|obj| {
                    matches!(
                        obj.kind,
                        ObjectKind::Segment {
                            segment: Segment::Circle(_)
                        }
                    )
                });
            if is_circle {
                Some(vec![TrimOperation::SimpleTrim {
                    segment_to_trim_id: segment_id,
                }])
            } else {
                None
            }
        };

    while start_index < points.len().saturating_sub(1) && iteration_count < max_iterations {
        iteration_count += 1;

        // Get next trim result
        let next_trim_spawn = get_next_trim_spawn(
            points,
            start_index,
            &current_scene_graph_delta.new_graph.objects,
            default_unit,
        );

        match &next_trim_spawn {
            TrimItem::None { next_index } => {
                let old_start_index = start_index;
                start_index = *next_index;

                // Fail-safe: if start_index didn't advance, force it to advance
                if start_index <= old_start_index {
                    start_index = old_start_index + 1;
                }

                // Early exit if we've reached the end
                if start_index >= points.len().saturating_sub(1) {
                    break;
                }
                continue;
            }
            TrimItem::Spawn {
                trim_spawn_seg_id,
                trim_spawn_coords,
                next_index,
                ..
            } => {
                // Get terminations
                let terminations = match get_trim_spawn_terminations(
                    *trim_spawn_seg_id,
                    points,
                    &current_scene_graph_delta.new_graph.objects,
                    default_unit,
                ) {
                    Ok(terms) => terms,
                    Err(e) => {
                        crate::logln!("Error getting trim spawn terminations: {}", e);
                        if let Some(strategy) = circle_delete_fallback_strategy(
                            &e,
                            *trim_spawn_seg_id,
                            &current_scene_graph_delta.new_graph.objects,
                        ) {
                            match execute_operations(strategy, current_scene_graph_delta.clone()).await {
                                Ok((source_delta, scene_graph_delta)) => {
                                    last_result = Some((source_delta, scene_graph_delta.clone()));
                                    invalidates_ids = invalidates_ids || scene_graph_delta.invalidates_ids;
                                    current_scene_graph_delta = scene_graph_delta;
                                }
                                Err(exec_err) => {
                                    crate::logln!(
                                        "Error executing circle-delete fallback trim operation: {}",
                                        exec_err
                                    );
                                }
                            }

                            let old_start_index = start_index;
                            start_index = *next_index;
                            if start_index <= old_start_index {
                                start_index = old_start_index + 1;
                            }
                            continue;
                        }

                        let old_start_index = start_index;
                        start_index = *next_index;
                        if start_index <= old_start_index {
                            start_index = old_start_index + 1;
                        }
                        continue;
                    }
                };

                // Get trim strategy
                let trim_spawn_segment = current_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == *trim_spawn_seg_id)
                    .ok_or_else(|| format!("Trim spawn segment {} not found", trim_spawn_seg_id.0))?;

                let plan = match build_trim_plan(
                    *trim_spawn_seg_id,
                    *trim_spawn_coords,
                    trim_spawn_segment,
                    &terminations.left_side,
                    &terminations.right_side,
                    &current_scene_graph_delta.new_graph.objects,
                    default_unit,
                ) {
                    Ok(plan) => plan,
                    Err(e) => {
                        crate::logln!("Error determining trim strategy: {}", e);
                        let old_start_index = start_index;
                        start_index = *next_index;
                        if start_index <= old_start_index {
                            start_index = old_start_index + 1;
                        }
                        continue;
                    }
                };
                let strategy = lower_trim_plan(&plan);

                // Keep processing the same trim polyline segment after geometry-changing ops.
                // This allows a single stroke to trim multiple intersected segments.
                let mut geometry_was_modified = false;

                // Execute operations via callback
                match execute_operations(strategy, current_scene_graph_delta.clone()).await {
                    Ok((source_delta, scene_graph_delta)) => {
                        last_result = Some((source_delta, scene_graph_delta.clone()));
                        invalidates_ids = invalidates_ids || scene_graph_delta.invalidates_ids;
                        current_scene_graph_delta = scene_graph_delta;
                        geometry_was_modified = trim_plan_modifies_geometry(&plan);
                    }
                    Err(e) => {
                        crate::logln!("Error executing trim operations: {}", e);
                        // Continue to next segment
                    }
                }

                // Move to next segment
                let old_start_index = start_index;
                start_index = *next_index;

                // Fail-safe: if start_index didn't advance, force it to advance
                if start_index <= old_start_index && !geometry_was_modified {
                    start_index = old_start_index + 1;
                }
            }
        }
    }

    if iteration_count >= max_iterations {
        return Err(format!("Reached max iterations ({})", max_iterations));
    }

    // Return the last result
    last_result.ok_or_else(|| "No trim operations were executed".to_string())
}

/// Result of executing trim flow
#[cfg(test)]
#[derive(Debug, Clone)]
pub struct TrimFlowResult {
    pub kcl_code: String,
    pub invalidates_ids: bool,
}

/// Execute a complete trim flow from KCL code to KCL code.
/// This is a high-level function that sets up the frontend state and executes the trim loop.
///
/// This function:
/// 1. Parses the input KCL code
/// 2. Sets up ExecutorContext and FrontendState
/// 3. Executes the initial code to get the scene graph
/// 4. Runs the trim loop using `execute_trim_loop`
/// 5. Returns the resulting KCL code
///
/// This is designed for testing and simple use cases. For more complex scenarios
/// (like WASM with batching), use `execute_trim_loop` directly with a custom callback.
///
/// Note: This function is only available for non-WASM builds (tests) and uses
/// a mock executor context so tests can run without an engine token.
#[cfg(all(not(target_arch = "wasm32"), test))]
pub(crate) async fn execute_trim_flow(
    kcl_code: &str,
    trim_points: &[Coords2d],
    sketch_id: ObjectId,
) -> Result<TrimFlowResult, String> {
    use crate::ExecutorContext;
    use crate::Program;
    use crate::execution::MockConfig;
    use crate::frontend::FrontendState;
    use crate::frontend::api::Version;

    // Parse KCL code
    let parse_result = Program::parse(kcl_code).map_err(|e| format!("Failed to parse KCL: {}", e))?;
    let (program_opt, errors) = parse_result;
    if !errors.is_empty() {
        return Err(format!("Failed to parse KCL: {:?}", errors));
    }
    let program = program_opt.ok_or_else(|| "No AST produced".to_string())?;

    let mock_ctx = ExecutorContext::new_mock(None).await;

    // Use a guard to ensure context is closed even on error
    let result = async {
        let mut frontend = FrontendState::new();

        // Set the program
        frontend.program = program.clone();

        let exec_outcome = mock_ctx
            .run_mock(&program, &MockConfig::default())
            .await
            .map_err(|e| format!("Failed to execute program: {}", e.error.message()))?;

        let exec_outcome = frontend.update_state_after_exec(exec_outcome, false);
        let mut initial_scene_graph = frontend.scene_graph.clone();

        // If scene graph is empty, try to get objects from exec_outcome.scene_objects
        if initial_scene_graph.objects.is_empty() && !exec_outcome.scene_objects.is_empty() {
            initial_scene_graph.objects = exec_outcome.scene_objects.clone();
        }

        // Get the sketch ID from the scene graph
        // First try sketch_mode, then try to find a sketch object, then fall back to provided sketch_id
        let actual_sketch_id = if let Some(sketch_mode) = initial_scene_graph.sketch_mode {
            sketch_mode
        } else {
            // Try to find a sketch object in the scene graph
            initial_scene_graph
                .objects
                .iter()
                .find(|obj| matches!(obj.kind, crate::frontend::api::ObjectKind::Sketch { .. }))
                .map(|obj| obj.id)
                .unwrap_or(sketch_id) // Fall back to provided sketch_id
        };

        let version = Version(0);
        let initial_scene_graph_delta = crate::frontend::api::SceneGraphDelta {
            new_graph: initial_scene_graph,
            new_objects: vec![],
            invalidates_ids: false,
            exec_outcome,
        };

        // Execute the trim loop with a callback that executes operations using SketchApi
        // We need to use a different approach since we can't easily capture mutable references in closures
        // Instead, we'll use a helper that takes the necessary parameters
        // Use mock_ctx for operations (SketchApi methods require mock context)
        let (source_delta, scene_graph_delta) = execute_trim_loop_with_context(
            trim_points,
            initial_scene_graph_delta,
            &mut frontend,
            &mock_ctx,
            version,
            actual_sketch_id,
        )
        .await?;

        // Return the source delta text - this should contain the full updated KCL code
        // If it's empty, that means no operations were executed, which is an error
        if source_delta.text.is_empty() {
            return Err("No trim operations were executed - source delta is empty".to_string());
        }

        Ok(TrimFlowResult {
            kcl_code: source_delta.text,
            invalidates_ids: scene_graph_delta.invalidates_ids,
        })
    }
    .await;

    // Clean up context regardless of success or failure
    mock_ctx.close().await;

    result
}

fn normalize_scene_graph_delta_for_internal_trim(
    frontend: &crate::frontend::FrontendState,
    scene_graph_delta: &mut crate::frontend::api::SceneGraphDelta,
) {
    scene_graph_delta.new_graph = frontend.scene_graph().clone();
}

/// Execute the trim loop with a context struct that provides access to FrontendState.
/// This is a convenience wrapper that inlines the loop to avoid borrow checker issues with closures.
/// The core loop logic is duplicated here, but this allows direct access to frontend and ctx.
///
/// Trim line points are expected in millimeters and are normalized to the current/default unit.
pub async fn execute_trim_loop_with_context(
    points: &[Coords2d],
    initial_scene_graph_delta: crate::frontend::api::SceneGraphDelta,
    frontend: &mut crate::frontend::FrontendState,
    ctx: &crate::ExecutorContext,
    version: crate::frontend::api::Version,
    sketch_id: ObjectId,
) -> Result<(crate::frontend::api::SourceDelta, crate::frontend::api::SceneGraphDelta), String> {
    // Trim line points are expected in millimeters and normalized to the current unit here.
    let default_unit = frontend.default_length_unit();
    let normalized_points = normalize_trim_points_to_unit(points, default_unit);

    // We inline the loop logic here to avoid borrow checker issues with closures capturing mutable references
    // This duplicates the loop from execute_trim_loop, but allows us to access frontend and ctx directly
    let mut current_scene_graph_delta = initial_scene_graph_delta.clone();
    let mut last_result: Option<(crate::frontend::api::SourceDelta, crate::frontend::api::SceneGraphDelta)> = Some((
        crate::frontend::api::SourceDelta { text: String::new() },
        initial_scene_graph_delta.clone(),
    ));
    let mut invalidates_ids = false;
    let mut start_index = 0;
    let max_iterations = 1000;
    let mut iteration_count = 0;
    let circle_delete_fallback_strategy =
        |error: &str, segment_id: ObjectId, scene_objects: &[Object]| -> Option<Vec<TrimOperation>> {
            if !error.contains("No trim termination candidate found for circle") {
                return None;
            }
            let is_circle = scene_objects
                .iter()
                .find(|obj| obj.id == segment_id)
                .is_some_and(|obj| {
                    matches!(
                        obj.kind,
                        ObjectKind::Segment {
                            segment: Segment::Circle(_)
                        }
                    )
                });
            if is_circle {
                Some(vec![TrimOperation::SimpleTrim {
                    segment_to_trim_id: segment_id,
                }])
            } else {
                None
            }
        };

    let points = normalized_points.as_slice();

    while start_index < points.len().saturating_sub(1) && iteration_count < max_iterations {
        iteration_count += 1;

        // Get next trim result
        let next_trim_spawn = get_next_trim_spawn(
            points,
            start_index,
            &current_scene_graph_delta.new_graph.objects,
            default_unit,
        );

        match &next_trim_spawn {
            TrimItem::None { next_index } => {
                let old_start_index = start_index;
                start_index = *next_index;
                if start_index <= old_start_index {
                    start_index = old_start_index + 1;
                }
                if start_index >= points.len().saturating_sub(1) {
                    break;
                }
                continue;
            }
            TrimItem::Spawn {
                trim_spawn_seg_id,
                trim_spawn_coords,
                next_index,
                ..
            } => {
                // Get terminations
                let terminations = match get_trim_spawn_terminations(
                    *trim_spawn_seg_id,
                    points,
                    &current_scene_graph_delta.new_graph.objects,
                    default_unit,
                ) {
                    Ok(terms) => terms,
                    Err(e) => {
                        crate::logln!("Error getting trim spawn terminations: {}", e);
                        if let Some(strategy) = circle_delete_fallback_strategy(
                            &e,
                            *trim_spawn_seg_id,
                            &current_scene_graph_delta.new_graph.objects,
                        ) {
                            match execute_trim_operations_simple(
                                strategy.clone(),
                                &current_scene_graph_delta,
                                frontend,
                                ctx,
                                version,
                                sketch_id,
                            )
                            .await
                            {
                                Ok((source_delta, mut scene_graph_delta)) => {
                                    normalize_scene_graph_delta_for_internal_trim(frontend, &mut scene_graph_delta);
                                    invalidates_ids = invalidates_ids || scene_graph_delta.invalidates_ids;
                                    last_result = Some((source_delta, scene_graph_delta.clone()));
                                    current_scene_graph_delta = scene_graph_delta;
                                }
                                Err(exec_err) => {
                                    crate::logln!(
                                        "Error executing circle-delete fallback trim operation: {}",
                                        exec_err
                                    );
                                }
                            }

                            let old_start_index = start_index;
                            start_index = *next_index;
                            if start_index <= old_start_index {
                                start_index = old_start_index + 1;
                            }
                            continue;
                        }

                        let old_start_index = start_index;
                        start_index = *next_index;
                        if start_index <= old_start_index {
                            start_index = old_start_index + 1;
                        }
                        continue;
                    }
                };

                // Get trim strategy
                let trim_spawn_segment = current_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == *trim_spawn_seg_id)
                    .ok_or_else(|| format!("Trim spawn segment {} not found", trim_spawn_seg_id.0))?;

                let plan = match build_trim_plan(
                    *trim_spawn_seg_id,
                    *trim_spawn_coords,
                    trim_spawn_segment,
                    &terminations.left_side,
                    &terminations.right_side,
                    &current_scene_graph_delta.new_graph.objects,
                    default_unit,
                ) {
                    Ok(plan) => plan,
                    Err(e) => {
                        crate::logln!("Error determining trim strategy: {}", e);
                        let old_start_index = start_index;
                        start_index = *next_index;
                        if start_index <= old_start_index {
                            start_index = old_start_index + 1;
                        }
                        continue;
                    }
                };
                let strategy = lower_trim_plan(&plan);

                // Keep processing the same trim polyline segment after geometry-changing ops.
                // This allows a single stroke to trim multiple intersected segments.
                let mut geometry_was_modified = false;

                // Execute operations
                match execute_trim_operations_simple(
                    strategy.clone(),
                    &current_scene_graph_delta,
                    frontend,
                    ctx,
                    version,
                    sketch_id,
                )
                .await
                {
                    Ok((source_delta, mut scene_graph_delta)) => {
                        normalize_scene_graph_delta_for_internal_trim(frontend, &mut scene_graph_delta);
                        invalidates_ids = invalidates_ids || scene_graph_delta.invalidates_ids;
                        last_result = Some((source_delta, scene_graph_delta.clone()));
                        current_scene_graph_delta = scene_graph_delta;
                        geometry_was_modified = trim_plan_modifies_geometry(&plan);
                    }
                    Err(e) => {
                        crate::logln!("Error executing trim operations: {}", e);
                    }
                }

                // Move to next segment
                let old_start_index = start_index;
                start_index = *next_index;
                if start_index <= old_start_index && !geometry_was_modified {
                    start_index = old_start_index + 1;
                }
            }
        }
    }

    if iteration_count >= max_iterations {
        return Err(format!("Reached max iterations ({})", max_iterations));
    }

    let (source_delta, mut scene_graph_delta) =
        last_result.ok_or_else(|| "No trim operations were executed".to_string())?;
    // Set invalidates_ids if any operation invalidated IDs
    scene_graph_delta.invalidates_ids = invalidates_ids;
    Ok((source_delta, scene_graph_delta))
}

/// Determine the trim strategy based on the terminations found on both sides
///
/// Once we have the termination of both sides, we have all the information we need to come up with a trim strategy.
/// In the below x is the trim spawn.
///
/// ## When both sides are the end of a segment
///
/// ```
/// o - -----x - -----o
/// ```
///
/// This is the simplest and we just delete the segment. This includes when the ends of the segment have
/// coincident constraints, as the delete API cascade deletes these constraints
///
/// ## When one side is the end of the segment and the other side is either an intersection or has another segment endpoint coincident with it
///
/// ```
///        /
/// -------/---x--o
///      /
/// ```
/// OR
/// ```
/// ----o---x---o
///    /
///   /
/// ```
///
/// In both of these cases, we need to edit one end of the segment to be the location of the
/// intersection/coincident point of this other segment though:
/// - If it's an intersection, we need to create a point-segment coincident constraint
/// ```
///        /
/// -------o
///      /
/// ```
/// - If it's a coincident endpoint, we need to create a point-point coincident constraint
///
/// ```
/// ----o
///    /
///   /
/// ```
///
/// ## When both sides are either intersections or coincident endpoints
///
/// ```
///        /
/// -------/---x----o------
///      /         |
/// ```
///
/// We need to split the segment in two, which basically means editing the existing segment to be one side
/// of the split, and adding a new segment for the other side of the split. And then there is lots of
/// complications around how to migrate constraints applied to each side of the segment, to list a couple
/// of considerations:
/// - Coincident constraints on either side need to be migrated to the correct side
/// - Angle based constraints (parallel, perpendicular, horizontal, vertical), need to be applied to both sides of the trim
/// - If the segment getting split is an arc, and there's a constraints applied to an arc's center, this should be applied to both arcs after they are split.
fn segment_ctor_units(ctor: &SegmentCtor) -> NumericSuffix {
    match ctor {
        SegmentCtor::Line(line_ctor) => match &line_ctor.start.x {
            crate::frontend::api::Expr::Var(v) | crate::frontend::api::Expr::Number(v) => v.units,
            _ => NumericSuffix::Mm,
        },
        SegmentCtor::Arc(arc_ctor) => match &arc_ctor.start.x {
            crate::frontend::api::Expr::Var(v) | crate::frontend::api::Expr::Number(v) => v.units,
            _ => NumericSuffix::Mm,
        },
        SegmentCtor::Circle(circle_ctor) => match &circle_ctor.start.x {
            crate::frontend::api::Expr::Var(v) | crate::frontend::api::Expr::Number(v) => v.units,
            _ => NumericSuffix::Mm,
        },
        SegmentCtor::ControlPointSpline(spline_ctor) => spline_ctor
            .points
            .first()
            .and_then(|point| match &point.x {
                crate::frontend::api::Expr::Var(v) | crate::frontend::api::Expr::Number(v) => Some(v.units),
                _ => None,
            })
            .unwrap_or(NumericSuffix::Mm),
        SegmentCtor::Point(point_ctor) => match &point_ctor.position.x {
            crate::frontend::api::Expr::Var(v) | crate::frontend::api::Expr::Number(v) => v.units,
            _ => NumericSuffix::Mm,
        },
    }
}

fn coords_to_expr_point(
    coords: Coords2d,
    default_unit: UnitLength,
    units: NumericSuffix,
) -> crate::frontend::sketch::Point2d<crate::frontend::api::Expr> {
    crate::frontend::sketch::Point2d {
        x: crate::frontend::api::Expr::Var(unit_to_number(coords.x, default_unit, units)),
        y: crate::frontend::api::Expr::Var(unit_to_number(coords.y, default_unit, units)),
    }
}

fn resample_control_point_spline_interval(
    controls: &[Coords2d],
    degree: usize,
    start_parameter: f64,
    end_parameter: f64,
    control_count: usize,
) -> Vec<Coords2d> {
    let knots = build_open_uniform_knot_vector(controls.len(), degree);
    (0..control_count)
        .map(|index| {
            let ratio = if control_count <= 1 {
                0.0
            } else {
                index as f64 / (control_count - 1) as f64
            };
            let parameter = start_parameter + ratio * (end_parameter - start_parameter);
            de_boor_point(parameter, degree, &knots, controls)
        })
        .collect()
}

fn build_trimmed_control_point_spline_ctor(
    trim_spawn_segment: &Object,
    objects: &[Object],
    default_unit: UnitLength,
    start_parameter: f64,
    end_parameter: f64,
) -> Result<SegmentCtor, String> {
    let ObjectKind::Segment {
        segment: Segment::ControlPointSpline(spline),
    } = &trim_spawn_segment.kind
    else {
        return Err("Trim spawn segment is not a control point spline".to_string());
    };
    let SegmentCtor::ControlPointSpline(spline_ctor) = &spline.ctor else {
        return Err("Control point spline segment is missing a control point spline ctor".to_string());
    };
    let controls = get_control_point_spline_controls(trim_spawn_segment, objects, default_unit)?
        .into_iter()
        .map(|(_, point)| point)
        .collect::<Vec<_>>();
    let units = segment_ctor_units(&spline.ctor);
    let resampled = resample_control_point_spline_interval(
        &controls,
        spline.degree as usize,
        start_parameter,
        end_parameter,
        spline.controls.len(),
    );
    Ok(SegmentCtor::ControlPointSpline(
        crate::frontend::sketch::ControlPointSplineCtor {
            points: resampled
                .into_iter()
                .map(|coords| coords_to_expr_point(coords, default_unit, units))
                .collect(),
            construction: spline_ctor.construction,
        },
    ))
}

fn spline_constraint_ids_to_delete(
    spline: &crate::frontend::sketch::ControlPointSpline,
    trimmed_endpoint_id: Option<ObjectId>,
    objects: &[Object],
) -> Vec<ObjectId> {
    let internal_control_ids: std::collections::HashSet<ObjectId> = spline
        .controls
        .iter()
        .copied()
        .skip(1)
        .take(spline.controls.len().saturating_sub(2))
        .collect();
    let spline_control_ids: std::collections::HashSet<ObjectId> = spline.controls.iter().copied().collect();
    let mut deletions = IndexSet::new();

    for obj in objects {
        let ObjectKind::Constraint { constraint } = &obj.kind else {
            continue;
        };
        match constraint {
            Constraint::Coincident(coincident) => {
                let ids: Vec<ObjectId> = coincident.segment_ids().collect();
                if ids.iter().any(|id| internal_control_ids.contains(id))
                    || trimmed_endpoint_id.is_some_and(|endpoint_id| ids.contains(&endpoint_id))
                {
                    deletions.insert(obj.id);
                }
            }
            Constraint::Distance(distance)
            | Constraint::HorizontalDistance(distance)
            | Constraint::VerticalDistance(distance)
                if distance.point_ids().any(|id| spline_control_ids.contains(&id)) =>
            {
                deletions.insert(obj.id);
            }
            Constraint::Horizontal(Horizontal::Points { points })
            | Constraint::Vertical(Vertical::Points { points })
                if points.iter().any(
                    |point| matches!(point, ConstraintSegment::Segment(id) if spline_control_ids.contains(id)),
                ) =>
            {
                deletions.insert(obj.id);
            }
            Constraint::Fixed(fixed)
                if fixed
                    .points
                    .iter()
                    .any(|fixed_point| spline_control_ids.contains(&fixed_point.point)) =>
            {
                deletions.insert(obj.id);
            }
            _ => {}
        }
    }

    deletions.into_iter().collect()
}

pub(crate) fn build_trim_plan(
    trim_spawn_id: ObjectId,
    trim_spawn_coords: Coords2d,
    trim_spawn_segment: &Object,
    left_side: &TrimTermination,
    right_side: &TrimTermination,
    objects: &[Object],
    default_unit: UnitLength,
) -> Result<TrimPlan, String> {
    // Simple trim: both sides are endpoints
    if matches!(left_side, TrimTermination::SegEndPoint { .. })
        && matches!(right_side, TrimTermination::SegEndPoint { .. })
    {
        return Ok(TrimPlan::DeleteSegment {
            segment_id: trim_spawn_id,
        });
    }

    // Helper to check if a side is an intersection or coincident
    let is_intersect_or_coincident = |side: &TrimTermination| -> bool {
        matches!(
            side,
            TrimTermination::Intersection { .. }
                | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint { .. }
        )
    };

    let left_side_needs_tail_cut = is_intersect_or_coincident(left_side) && !is_intersect_or_coincident(right_side);
    let right_side_needs_tail_cut = is_intersect_or_coincident(right_side) && !is_intersect_or_coincident(left_side);

    // Validate trim spawn segment using native types
    let ObjectKind::Segment { segment } = &trim_spawn_segment.kind else {
        return Err("Trim spawn segment is not a segment".to_string());
    };

    let (_segment_type, ctor) = match segment {
        Segment::Line(line) => ("Line", &line.ctor),
        Segment::Arc(arc) => ("Arc", &arc.ctor),
        Segment::Circle(circle) => ("Circle", &circle.ctor),
        Segment::ControlPointSpline(spline) => ("ControlPointSpline", &spline.ctor),
        _ => {
            return Err("Trim spawn segment is not a Line, Arc, Circle, or Control Point Spline".to_string());
        }
    };

    // Extract units from the existing ctor's first point.
    let units = segment_ctor_units(ctor);

    // Helper to find distance constraints that reference a segment (via owned points)
    let find_distance_constraints_for_segment = |segment_id: ObjectId| -> Vec<ObjectId> {
        let mut constraint_ids = Vec::new();
        for obj in objects {
            let ObjectKind::Constraint { constraint } = &obj.kind else {
                continue;
            };

            let Constraint::Distance(distance) = constraint else {
                continue;
            };

            // Only delete distance constraints where BOTH points are owned by this segment.
            // Distance constraints that reference points on other segments should be preserved,
            // as they define relationships between this segment and other geometry that remain valid
            // even when this segment is trimmed. Only constraints that measure distances between
            // points on the same segment (e.g., segment length constraints) should be deleted.
            let points_owned_by_segment: Vec<bool> = distance
                .point_ids()
                .map(|point_id| {
                    if let Some(point_obj) = objects.iter().find(|o| o.id == point_id)
                        && let ObjectKind::Segment { segment } = &point_obj.kind
                        && let Segment::Point(point) = segment
                        && let Some(owner_id) = point.owner
                    {
                        return owner_id == segment_id;
                    }
                    false
                })
                .collect();

            // Only include if ALL points are owned by this segment
            if points_owned_by_segment.len() == 2 && points_owned_by_segment.iter().all(|&owned| owned) {
                constraint_ids.push(obj.id);
            }
        }
        constraint_ids
    };

    // Helper to find existing point-segment coincident constraint (using native types)
    let find_existing_point_segment_coincident =
        |trim_seg_id: ObjectId, intersecting_seg_id: ObjectId| -> CoincidentData {
            // If the intersecting id itself is a point, try a fast lookup using it directly
            let lookup_by_point_id = |point_id: ObjectId| -> Option<CoincidentData> {
                for obj in objects {
                    let ObjectKind::Constraint { constraint } = &obj.kind else {
                        continue;
                    };

                    let Constraint::Coincident(coincident) = constraint else {
                        continue;
                    };

                    let involves_trim_seg = coincident.segment_ids().any(|id| id == trim_seg_id || id == point_id);
                    let involves_point = coincident.contains_segment(point_id);

                    if involves_trim_seg && involves_point {
                        return Some(CoincidentData {
                            intersecting_seg_id,
                            intersecting_endpoint_point_id: Some(point_id),
                            existing_point_segment_constraint_id: Some(obj.id),
                        });
                    }
                }
                None
            };

            // Collect trim endpoints using native types
            let trim_seg = objects.iter().find(|obj| obj.id == trim_seg_id);

            let mut trim_endpoint_ids: Vec<ObjectId> = Vec::new();
            if let Some(seg) = trim_seg
                && let ObjectKind::Segment { segment } = &seg.kind
            {
                match segment {
                    Segment::Line(line) => {
                        trim_endpoint_ids.push(line.start);
                        trim_endpoint_ids.push(line.end);
                    }
                    Segment::Arc(arc) => {
                        trim_endpoint_ids.push(arc.start);
                        trim_endpoint_ids.push(arc.end);
                    }
                    Segment::ControlPointSpline(spline) => {
                        if let Some(start) = spline.controls.first() {
                            trim_endpoint_ids.push(*start);
                        }
                        if let Some(end) = spline.controls.last() {
                            trim_endpoint_ids.push(*end);
                        }
                    }
                    _ => {}
                }
            }

            let intersecting_obj = objects.iter().find(|obj| obj.id == intersecting_seg_id);

            if let Some(obj) = intersecting_obj
                && let ObjectKind::Segment { segment } = &obj.kind
                && let Segment::Point(_) = segment
                && let Some(found) = lookup_by_point_id(intersecting_seg_id)
            {
                return found;
            }

            // Collect intersecting endpoint IDs using native types
            let mut intersecting_endpoint_ids: Vec<ObjectId> = Vec::new();
            if let Some(obj) = intersecting_obj
                && let ObjectKind::Segment { segment } = &obj.kind
            {
                match segment {
                    Segment::Line(line) => {
                        intersecting_endpoint_ids.push(line.start);
                        intersecting_endpoint_ids.push(line.end);
                    }
                    Segment::Arc(arc) => {
                        intersecting_endpoint_ids.push(arc.start);
                        intersecting_endpoint_ids.push(arc.end);
                    }
                    Segment::ControlPointSpline(spline) => {
                        if let Some(start) = spline.controls.first() {
                            intersecting_endpoint_ids.push(*start);
                        }
                        if let Some(end) = spline.controls.last() {
                            intersecting_endpoint_ids.push(*end);
                        }
                    }
                    _ => {}
                }
            }

            // Also include the intersecting_seg_id itself (it might already be a point id)
            intersecting_endpoint_ids.push(intersecting_seg_id);

            // Search for constraints involving trim segment (or trim endpoints) and intersecting endpoints/points
            for obj in objects {
                let ObjectKind::Constraint { constraint } = &obj.kind else {
                    continue;
                };

                let Constraint::Coincident(coincident) = constraint else {
                    continue;
                };

                let constraint_segment_ids: Vec<ObjectId> = coincident.get_segments();

                // Check if constraint involves the trim segment itself OR any trim endpoint
                let involves_trim_seg = constraint_segment_ids.contains(&trim_seg_id)
                    || trim_endpoint_ids.iter().any(|&id| constraint_segment_ids.contains(&id));

                if !involves_trim_seg {
                    continue;
                }

                // Check if any intersecting endpoint/point is involved
                if let Some(&intersecting_endpoint_id) = intersecting_endpoint_ids
                    .iter()
                    .find(|&&id| constraint_segment_ids.contains(&id))
                {
                    return CoincidentData {
                        intersecting_seg_id,
                        intersecting_endpoint_point_id: Some(intersecting_endpoint_id),
                        existing_point_segment_constraint_id: Some(obj.id),
                    };
                }
            }

            // No existing constraint found
            CoincidentData {
                intersecting_seg_id,
                intersecting_endpoint_point_id: None,
                existing_point_segment_constraint_id: None,
            }
        };

    // Helper to find point-segment coincident constraints on an endpoint (using native types)
    let find_point_segment_coincident_constraints = |endpoint_point_id: ObjectId| -> Vec<serde_json::Value> {
        let mut constraints: Vec<serde_json::Value> = Vec::new();
        for obj in objects {
            let ObjectKind::Constraint { constraint } = &obj.kind else {
                continue;
            };

            let Constraint::Coincident(coincident) = constraint else {
                continue;
            };

            // Check if this constraint involves the endpoint
            if !coincident.contains_segment(endpoint_point_id) {
                continue;
            }

            // Find the other entity
            let other_segment_id = coincident.segment_ids().find(|&seg_id| seg_id != endpoint_point_id);

            if let Some(other_id) = other_segment_id
                && let Some(other_obj) = objects.iter().find(|o| o.id == other_id)
            {
                // Check if other is a segment (not a point)
                if matches!(&other_obj.kind, ObjectKind::Segment { segment } if !matches!(segment, Segment::Point(_))) {
                    constraints.push(serde_json::json!({
                        "constraintId": obj.id.0,
                        "segmentOrPointId": other_id.0,
                    }));
                }
            }
        }
        constraints
    };

    // Helper to find point-point coincident constraints on an endpoint (using native types)
    // Returns constraint IDs
    let find_point_point_coincident_constraints = |endpoint_point_id: ObjectId| -> Vec<ObjectId> {
        let mut constraint_ids = Vec::new();
        for obj in objects {
            let ObjectKind::Constraint { constraint } = &obj.kind else {
                continue;
            };

            let Constraint::Coincident(coincident) = constraint else {
                continue;
            };

            // Check if this constraint involves the endpoint
            if !coincident.contains_segment(endpoint_point_id) {
                continue;
            }

            // Check if this is a point-point constraint (all segments are points)
            let is_point_point = coincident.segment_ids().all(|seg_id| {
                if let Some(seg_obj) = objects.iter().find(|o| o.id == seg_id) {
                    matches!(&seg_obj.kind, ObjectKind::Segment { segment } if matches!(segment, Segment::Point(_)))
                } else {
                    false
                }
            });

            if is_point_point {
                constraint_ids.push(obj.id);
            }
        }
        constraint_ids
    };

    // Helper to find point-segment coincident constraints on an endpoint (using native types)
    // Returns constraint IDs
    let find_point_segment_coincident_constraint_ids = |endpoint_point_id: ObjectId| -> Vec<ObjectId> {
        let mut constraint_ids = Vec::new();
        for obj in objects {
            let ObjectKind::Constraint { constraint } = &obj.kind else {
                continue;
            };

            let Constraint::Coincident(coincident) = constraint else {
                continue;
            };

            // Check if this constraint involves the endpoint
            if !coincident.contains_segment(endpoint_point_id) {
                continue;
            }

            // Find the other entity
            let other_segment_id = coincident.segment_ids().find(|&seg_id| seg_id != endpoint_point_id);

            if let Some(other_id) = other_segment_id
                && let Some(other_obj) = objects.iter().find(|o| o.id == other_id)
            {
                // Check if other is a segment (not a point) - this is a point-segment constraint
                if matches!(&other_obj.kind, ObjectKind::Segment { segment } if !matches!(segment, Segment::Point(_))) {
                    constraint_ids.push(obj.id);
                }
            }
        }
        constraint_ids
    };

    let find_midpoint_constraints_for_segment = |segment_id: ObjectId| -> Vec<ObjectId> {
        objects
            .iter()
            .filter_map(|obj| {
                let ObjectKind::Constraint { constraint } = &obj.kind else {
                    return None;
                };

                let Constraint::Midpoint(midpoint) = constraint else {
                    return None;
                };

                (midpoint.segment == segment_id).then_some(obj.id)
            })
            .collect()
    };

    // Cut tail: one side intersects, one is endpoint
    if left_side_needs_tail_cut || right_side_needs_tail_cut {
        let side = if left_side_needs_tail_cut {
            left_side
        } else {
            right_side
        };

        let intersection_coords = match side {
            TrimTermination::Intersection {
                trim_termination_coords,
                ..
            }
            | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                trim_termination_coords,
                ..
            } => *trim_termination_coords,
            TrimTermination::SegEndPoint { .. } => {
                return Err("Logic error: side should not be segEndPoint here".to_string());
            }
        };

        let endpoint_to_change = if left_side_needs_tail_cut {
            EndpointChanged::End
        } else {
            EndpointChanged::Start
        };

        let intersecting_seg_id = match side {
            TrimTermination::Intersection {
                intersecting_seg_id, ..
            }
            | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                intersecting_seg_id, ..
            } => *intersecting_seg_id,
            TrimTermination::SegEndPoint { .. } => {
                return Err("Logic error".to_string());
            }
        };

        let mut coincident_data = if matches!(
            side,
            TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint { .. }
        ) {
            let point_id = match side {
                TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                    other_segment_point_id, ..
                } => *other_segment_point_id,
                _ => return Err("Logic error".to_string()),
            };
            let mut data = find_existing_point_segment_coincident(trim_spawn_id, intersecting_seg_id);
            data.intersecting_endpoint_point_id = Some(point_id);
            data
        } else {
            find_existing_point_segment_coincident(trim_spawn_id, intersecting_seg_id)
        };

        if matches!(side, TrimTermination::Intersection { .. })
            && let Some(point_id) = coincident_data.intersecting_endpoint_point_id
        {
            let endpoint_is_at_intersection = get_point_coords_from_native(objects, point_id, default_unit)
                .is_some_and(|point_coords| {
                    ((point_coords.x - intersection_coords.x).squared()
                        + (point_coords.y - intersection_coords.y).squared())
                    .sqrt()
                        <= EPSILON_POINT_ON_SEGMENT * 1000.0
                });

            if !endpoint_is_at_intersection {
                coincident_data.existing_point_segment_constraint_id = None;
                coincident_data.intersecting_endpoint_point_id = None;
            }
        }

        // Find the endpoint that will be trimmed using native types
        let trim_seg = objects.iter().find(|obj| obj.id == trim_spawn_id);

        let endpoint_point_id = if let Some(seg) = trim_seg {
            let ObjectKind::Segment { segment } = &seg.kind else {
                return Err("Trim spawn segment is not a segment".to_string());
            };
            match segment {
                Segment::Line(line) => {
                    if endpoint_to_change == EndpointChanged::Start {
                        Some(line.start)
                    } else {
                        Some(line.end)
                    }
                }
                Segment::Arc(arc) => {
                    if endpoint_to_change == EndpointChanged::Start {
                        Some(arc.start)
                    } else {
                        Some(arc.end)
                    }
                }
                Segment::ControlPointSpline(spline) => {
                    if endpoint_to_change == EndpointChanged::Start {
                        spline.controls.first().copied()
                    } else {
                        spline.controls.last().copied()
                    }
                }
                _ => None,
            }
        } else {
            None
        };

        if let (Some(endpoint_id), Some(existing_constraint_id)) =
            (endpoint_point_id, coincident_data.existing_point_segment_constraint_id)
        {
            let constraint_involves_trimmed_endpoint = objects
                .iter()
                .find(|obj| obj.id == existing_constraint_id)
                .and_then(|obj| match &obj.kind {
                    ObjectKind::Constraint {
                        constraint: Constraint::Coincident(coincident),
                    } => Some(coincident.contains_segment(endpoint_id) || coincident.contains_segment(trim_spawn_id)),
                    _ => None,
                })
                .unwrap_or(false);

            if !constraint_involves_trimmed_endpoint {
                coincident_data.existing_point_segment_constraint_id = None;
                coincident_data.intersecting_endpoint_point_id = None;
            }
        }

        // Find point-point and point-segment constraints to delete
        let coincident_end_constraint_to_delete_ids = if let Some(point_id) = endpoint_point_id {
            let mut constraint_ids = find_point_point_coincident_constraints(point_id);
            // Also find point-segment constraints where the point is the endpoint being trimmed
            constraint_ids.extend(find_point_segment_coincident_constraint_ids(point_id));
            constraint_ids
        } else {
            Vec::new()
        };

        let point_axis_constraint_ids_to_delete = if let Some(point_id) = endpoint_point_id {
            objects
                .iter()
                .filter_map(|obj| {
                    let ObjectKind::Constraint { constraint } = &obj.kind else {
                        return None;
                    };

                    point_axis_constraint_references_point(constraint, point_id).then_some(obj.id)
                })
                .collect::<Vec<_>>()
        } else {
            Vec::new()
        };

        if let Segment::ControlPointSpline(spline) = segment {
            let trim_curve = load_curve_handle(trim_spawn_segment, objects, default_unit)?;
            let intersection_parameter = project_point_onto_curve(&trim_curve, intersection_coords)?;
            let end_parameter = trim_curve
                .sampled_points
                .as_ref()
                .and_then(|samples| samples.last())
                .map(|sample| sample.parameter)
                .unwrap_or_else(|| spline.controls.len().saturating_sub(1) as f64);
            let (keep_start_parameter, keep_end_parameter) = if endpoint_to_change == EndpointChanged::End {
                (0.0, intersection_parameter)
            } else {
                (intersection_parameter, end_parameter)
            };
            let new_ctor = build_trimmed_control_point_spline_ctor(
                trim_spawn_segment,
                objects,
                default_unit,
                keep_start_parameter,
                keep_end_parameter,
            )?;

            let mut all_constraint_ids_to_delete = spline_constraint_ids_to_delete(spline, endpoint_point_id, objects);
            all_constraint_ids_to_delete.extend(coincident_end_constraint_to_delete_ids);
            all_constraint_ids_to_delete.extend(point_axis_constraint_ids_to_delete);
            all_constraint_ids_to_delete.extend(find_distance_constraints_for_segment(trim_spawn_id));
            all_constraint_ids_to_delete.sort_unstable();
            all_constraint_ids_to_delete.dedup();

            return Ok(TrimPlan::TailCutControlPointSpline {
                segment_id: trim_spawn_id,
                ctor: new_ctor,
                constraint_ids_to_delete: all_constraint_ids_to_delete,
            });
        }

        // Edit the segment - create new ctor with updated endpoint
        let new_ctor = match ctor {
            SegmentCtor::Line(line_ctor) => {
                // Convert to segment units only; rounding happens at final conversion to output if needed.
                let new_point = crate::frontend::sketch::Point2d {
                    x: crate::frontend::api::Expr::Var(unit_to_number(intersection_coords.x, default_unit, units)),
                    y: crate::frontend::api::Expr::Var(unit_to_number(intersection_coords.y, default_unit, units)),
                };
                if endpoint_to_change == EndpointChanged::Start {
                    SegmentCtor::Line(crate::frontend::sketch::LineCtor {
                        start: new_point,
                        end: line_ctor.end.clone(),
                        construction: line_ctor.construction,
                    })
                } else {
                    SegmentCtor::Line(crate::frontend::sketch::LineCtor {
                        start: line_ctor.start.clone(),
                        end: new_point,
                        construction: line_ctor.construction,
                    })
                }
            }
            SegmentCtor::Arc(arc_ctor) => {
                // Convert to segment units only; rounding happens at final conversion to output if needed.
                let new_point = crate::frontend::sketch::Point2d {
                    x: crate::frontend::api::Expr::Var(unit_to_number(intersection_coords.x, default_unit, units)),
                    y: crate::frontend::api::Expr::Var(unit_to_number(intersection_coords.y, default_unit, units)),
                };
                if endpoint_to_change == EndpointChanged::Start {
                    SegmentCtor::Arc(crate::frontend::sketch::ArcCtor {
                        start: new_point,
                        end: arc_ctor.end.clone(),
                        center: arc_ctor.center.clone(),
                        construction: arc_ctor.construction,
                    })
                } else {
                    SegmentCtor::Arc(crate::frontend::sketch::ArcCtor {
                        start: arc_ctor.start.clone(),
                        end: new_point,
                        center: arc_ctor.center.clone(),
                        construction: arc_ctor.construction,
                    })
                }
            }
            _ => {
                return Err("Unsupported segment type for edit".to_string());
            }
        };

        // Delete old constraints
        let mut all_constraint_ids_to_delete: Vec<ObjectId> = Vec::new();
        if let Some(constraint_id) = coincident_data.existing_point_segment_constraint_id {
            all_constraint_ids_to_delete.push(constraint_id);
        }
        all_constraint_ids_to_delete.extend(coincident_end_constraint_to_delete_ids);
        all_constraint_ids_to_delete.extend(point_axis_constraint_ids_to_delete);
        all_constraint_ids_to_delete.extend(find_midpoint_constraints_for_segment(trim_spawn_id));

        // Delete distance constraints that reference this segment
        // When trimming an endpoint, the distance constraint no longer makes sense
        let distance_constraint_ids = find_distance_constraints_for_segment(trim_spawn_id);
        all_constraint_ids_to_delete.extend(distance_constraint_ids);

        let coincident_target_id = coincident_data
            .intersecting_endpoint_point_id
            .unwrap_or(intersecting_seg_id);
        let adds_curved_segment_coincident = endpoint_point_id
            .is_some_and(|point_id| segment_id_is_or_is_owned_by_curve(objects, point_id))
            || segment_id_is_or_is_owned_by_curve(objects, coincident_target_id);
        let has_midpoint_deletions = all_constraint_ids_to_delete.iter().any(|constraint_id| {
            objects
                .iter()
                .find(|obj| obj.id == *constraint_id)
                .is_some_and(|object| {
                    matches!(
                        object.kind,
                        ObjectKind::Constraint {
                            constraint: Constraint::Midpoint(_)
                        }
                    )
                })
        });

        let mut additional_edited_segment_ids = IndexSet::new();
        if has_midpoint_deletions || (adds_curved_segment_coincident && all_constraint_ids_to_delete.is_empty()) {
            additional_edited_segment_ids.extend(sketch_segment_ids_for_segment(objects, trim_spawn_id));
        }

        if adds_curved_segment_coincident {
            for constraint_id in &all_constraint_ids_to_delete {
                let Some(constraint_object) = objects.iter().find(|obj| obj.id == *constraint_id) else {
                    continue;
                };
                let ObjectKind::Constraint {
                    constraint: Constraint::Coincident(coincident),
                } = &constraint_object.kind
                else {
                    continue;
                };

                additional_edited_segment_ids.extend(
                    coincident
                        .segment_ids()
                        .map(|segment_id| owner_or_segment_id(objects, segment_id)),
                );
            }
        }

        return Ok(TrimPlan::TailCut {
            segment_id: trim_spawn_id,
            endpoint_changed: endpoint_to_change,
            ctor: new_ctor,
            segment_or_point_to_make_coincident_to: intersecting_seg_id,
            intersecting_endpoint_point_id: coincident_data.intersecting_endpoint_point_id,
            constraint_ids_to_delete: all_constraint_ids_to_delete,
            additional_edited_segment_ids: additional_edited_segment_ids.into_iter().collect(),
        });
    }

    // Circle trim: both sides must terminate on intersections/coincident points.
    // A circle cannot be "split" into two circles; it is converted into a single arc.
    if matches!(segment, Segment::Circle(_)) {
        let left_side_intersects = is_intersect_or_coincident(left_side);
        let right_side_intersects = is_intersect_or_coincident(right_side);
        if !(left_side_intersects && right_side_intersects) {
            return Err(format!(
                "Unsupported circle trim termination combination: left={:?} right={:?}",
                left_side, right_side
            ));
        }

        let left_trim_coords = match left_side {
            TrimTermination::SegEndPoint {
                trim_termination_coords,
            }
            | TrimTermination::Intersection {
                trim_termination_coords,
                ..
            }
            | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                trim_termination_coords,
                ..
            } => *trim_termination_coords,
        };
        let right_trim_coords = match right_side {
            TrimTermination::SegEndPoint {
                trim_termination_coords,
            }
            | TrimTermination::Intersection {
                trim_termination_coords,
                ..
            }
            | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                trim_termination_coords,
                ..
            } => *trim_termination_coords,
        };

        // If both sides resolve to essentially the same trim point (e.g., tangent-only hit),
        // deleting the circle matches expected trim behavior better than creating a zero-length arc.
        let trim_points_coincident = ((left_trim_coords.x - right_trim_coords.x)
            * (left_trim_coords.x - right_trim_coords.x)
            + (left_trim_coords.y - right_trim_coords.y) * (left_trim_coords.y - right_trim_coords.y))
            .sqrt()
            <= EPSILON_POINT_ON_SEGMENT * 10.0;
        if trim_points_coincident {
            return Ok(TrimPlan::DeleteSegment {
                segment_id: trim_spawn_id,
            });
        }

        let circle_center_coords =
            get_position_coords_from_circle(trim_spawn_segment, CirclePoint::Center, objects, default_unit)
                .ok_or_else(|| {
                    format!(
                        "Could not get center coordinates for circle segment {}",
                        trim_spawn_id.0
                    )
                })?;

        // The trim removes the side containing the trim spawn. Keep the opposite side.
        let spawn_on_left_to_right = is_point_on_arc(
            trim_spawn_coords,
            circle_center_coords,
            left_trim_coords,
            right_trim_coords,
            EPSILON_POINT_ON_SEGMENT,
        );
        let (arc_start_coords, arc_end_coords, arc_start_termination, arc_end_termination) = if spawn_on_left_to_right {
            (
                right_trim_coords,
                left_trim_coords,
                Box::new(right_side.clone()),
                Box::new(left_side.clone()),
            )
        } else {
            (
                left_trim_coords,
                right_trim_coords,
                Box::new(left_side.clone()),
                Box::new(right_side.clone()),
            )
        };

        return Ok(TrimPlan::ReplaceCircleWithArc {
            circle_id: trim_spawn_id,
            arc_start_coords,
            arc_end_coords,
            arc_start_termination,
            arc_end_termination,
        });
    }

    // Split segment: both sides intersect
    let left_side_intersects = is_intersect_or_coincident(left_side);
    let right_side_intersects = is_intersect_or_coincident(right_side);

    if left_side_intersects && right_side_intersects {
        // This is the most complex case - split segment
        // Get coincident data for both sides
        let left_intersecting_seg_id = match left_side {
            TrimTermination::Intersection {
                intersecting_seg_id, ..
            }
            | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                intersecting_seg_id, ..
            } => *intersecting_seg_id,
            TrimTermination::SegEndPoint { .. } => {
                return Err("Logic error: left side should not be segEndPoint".to_string());
            }
        };

        let right_intersecting_seg_id = match right_side {
            TrimTermination::Intersection {
                intersecting_seg_id, ..
            }
            | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                intersecting_seg_id, ..
            } => *intersecting_seg_id,
            TrimTermination::SegEndPoint { .. } => {
                return Err("Logic error: right side should not be segEndPoint".to_string());
            }
        };

        let left_coincident_data = if matches!(
            left_side,
            TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint { .. }
        ) {
            let point_id = match left_side {
                TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                    other_segment_point_id, ..
                } => *other_segment_point_id,
                _ => return Err("Logic error".to_string()),
            };
            let mut data = find_existing_point_segment_coincident(trim_spawn_id, left_intersecting_seg_id);
            data.intersecting_endpoint_point_id = Some(point_id);
            data
        } else {
            find_existing_point_segment_coincident(trim_spawn_id, left_intersecting_seg_id)
        };

        let right_coincident_data = if matches!(
            right_side,
            TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint { .. }
        ) {
            let point_id = match right_side {
                TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                    other_segment_point_id, ..
                } => *other_segment_point_id,
                _ => return Err("Logic error".to_string()),
            };
            let mut data = find_existing_point_segment_coincident(trim_spawn_id, right_intersecting_seg_id);
            data.intersecting_endpoint_point_id = Some(point_id);
            data
        } else {
            find_existing_point_segment_coincident(trim_spawn_id, right_intersecting_seg_id)
        };

        if let Segment::ControlPointSpline(spline) = segment {
            let trim_curve = load_curve_handle(trim_spawn_segment, objects, default_unit)?;
            let end_parameter = trim_curve
                .sampled_points
                .as_ref()
                .and_then(|samples| samples.last())
                .map(|sample| sample.parameter)
                .unwrap_or_else(|| spline.controls.len().saturating_sub(1) as f64);
            let left_trim_coords = match left_side {
                TrimTermination::SegEndPoint {
                    trim_termination_coords,
                }
                | TrimTermination::Intersection {
                    trim_termination_coords,
                    ..
                }
                | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                    trim_termination_coords,
                    ..
                } => *trim_termination_coords,
            };
            let right_trim_coords = match right_side {
                TrimTermination::SegEndPoint {
                    trim_termination_coords,
                }
                | TrimTermination::Intersection {
                    trim_termination_coords,
                    ..
                }
                | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                    trim_termination_coords,
                    ..
                } => *trim_termination_coords,
            };
            let left_trim_parameter = project_point_onto_curve(&trim_curve, left_trim_coords)?;
            let right_trim_parameter = project_point_onto_curve(&trim_curve, right_trim_coords)?;

            if (right_trim_parameter - left_trim_parameter).abs() < EPSILON_POINT_ON_SEGMENT {
                return Err("Split trim on spline collapsed to the same parameter on both sides".to_string());
            }

            let left_ctor = build_trimmed_control_point_spline_ctor(
                trim_spawn_segment,
                objects,
                default_unit,
                0.0,
                left_trim_parameter,
            )?;
            let right_ctor = build_trimmed_control_point_spline_ctor(
                trim_spawn_segment,
                objects,
                default_unit,
                right_trim_parameter,
                end_parameter,
            )?;

            let mut constraint_ids_to_delete =
                spline_constraint_ids_to_delete(spline, spline.controls.last().copied(), objects);
            for obj in objects {
                let ObjectKind::Constraint { constraint } = &obj.kind else {
                    continue;
                };
                match constraint {
                    Constraint::Coincident(coincident)
                        if spline
                            .controls
                            .last()
                            .is_some_and(|end_id| coincident.contains_segment(*end_id)) =>
                    {
                        constraint_ids_to_delete.push(obj.id);
                    }
                    Constraint::Tangent(tangent) if tangent.input.contains(&trim_spawn_id) => {
                        constraint_ids_to_delete.push(obj.id);
                    }
                    _ => {}
                }
            }
            constraint_ids_to_delete.sort_unstable();
            constraint_ids_to_delete.dedup();

            return Ok(TrimPlan::SplitControlPointSpline {
                segment_id: trim_spawn_id,
                left_ctor,
                right_ctor,
                left_side: Box::new(left_side.clone()),
                right_side: Box::new(right_side.clone()),
                constraint_ids_to_delete,
            });
        }

        // Find the endpoints of the segment being split using native types
        let (original_start_point_id, original_end_point_id) = match segment {
            Segment::Line(line) => (Some(line.start), Some(line.end)),
            Segment::Arc(arc) => (Some(arc.start), Some(arc.end)),
            _ => (None, None),
        };

        // Get the original end point coordinates before editing using native types
        let original_end_point_coords = match segment {
            Segment::Line(_) => {
                get_position_coords_for_line(trim_spawn_segment, LineEndpoint::End, objects, default_unit)
            }
            Segment::Arc(_) => get_position_coords_from_arc(trim_spawn_segment, ArcPoint::End, objects, default_unit),
            _ => None,
        };

        let Some(original_end_coords) = original_end_point_coords else {
            return Err(
                "Could not get original end point coordinates before editing - this is required for split trim"
                    .to_string(),
            );
        };

        // Calculate trim coordinates for both sides
        let left_trim_coords = match left_side {
            TrimTermination::SegEndPoint {
                trim_termination_coords,
            }
            | TrimTermination::Intersection {
                trim_termination_coords,
                ..
            }
            | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                trim_termination_coords,
                ..
            } => *trim_termination_coords,
        };

        let right_trim_coords = match right_side {
            TrimTermination::SegEndPoint {
                trim_termination_coords,
            }
            | TrimTermination::Intersection {
                trim_termination_coords,
                ..
            }
            | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                trim_termination_coords,
                ..
            } => *trim_termination_coords,
        };

        // Check if the split point is at the original end point
        let dist_to_original_end = ((right_trim_coords.x - original_end_coords.x)
            * (right_trim_coords.x - original_end_coords.x)
            + (right_trim_coords.y - original_end_coords.y) * (right_trim_coords.y - original_end_coords.y))
            .sqrt();
        if dist_to_original_end < EPSILON_POINT_ON_SEGMENT {
            return Err(
                "Split point is at original end point - this should be handled as cutTail, not split".to_string(),
            );
        }

        // For now, implement a simplified version that creates the split operation
        // The full constraint migration logic is very complex and can be refined during testing
        let mut constraints_to_migrate: Vec<ConstraintToMigrate> = Vec::new();
        let mut constraints_to_delete_set: IndexSet<ObjectId> = IndexSet::new();

        // Add existing point-segment constraints from terminations to delete list
        if let Some(constraint_id) = left_coincident_data.existing_point_segment_constraint_id {
            constraints_to_delete_set.insert(constraint_id);
        }
        if let Some(constraint_id) = right_coincident_data.existing_point_segment_constraint_id {
            constraints_to_delete_set.insert(constraint_id);
        }

        if let Some(end_id) = original_end_point_id {
            for obj in objects {
                let ObjectKind::Constraint { constraint } = &obj.kind else {
                    continue;
                };

                if point_axis_constraint_references_point(constraint, end_id) {
                    constraints_to_delete_set.insert(obj.id);
                }
            }
        }

        // Find point-point constraints on end endpoint to migrate
        if let Some(end_id) = original_end_point_id {
            let end_point_point_constraint_ids = find_point_point_coincident_constraints(end_id);
            for constraint_id in end_point_point_constraint_ids {
                // Identify the other point in the coincident constraint
                let other_point_id_opt = objects.iter().find_map(|obj| {
                    if obj.id != constraint_id {
                        return None;
                    }
                    let ObjectKind::Constraint { constraint } = &obj.kind else {
                        return None;
                    };
                    let Constraint::Coincident(coincident) = constraint else {
                        return None;
                    };
                    coincident.segment_ids().find(|&seg_id| seg_id != end_id)
                });

                if let Some(other_point_id) = other_point_id_opt {
                    constraints_to_delete_set.insert(constraint_id);
                    // Migrate as point-point constraint to the new end endpoint
                    constraints_to_migrate.push(ConstraintToMigrate {
                        constraint_id,
                        other_entity_id: other_point_id,
                        is_point_point: true,
                        attach_to_endpoint: AttachToEndpoint::End,
                    });
                }
            }
        }

        // Find point-segment constraints on end endpoint to migrate
        if let Some(end_id) = original_end_point_id {
            let end_point_segment_constraints = find_point_segment_coincident_constraints(end_id);
            for constraint_json in end_point_segment_constraints {
                if let Some(constraint_id_usize) = constraint_json
                    .get("constraintId")
                    .and_then(|v| v.as_u64())
                    .map(|id| id as usize)
                {
                    let constraint_id = ObjectId(constraint_id_usize);
                    constraints_to_delete_set.insert(constraint_id);
                    // Add to migrate list (simplified)
                    if let Some(other_id_usize) = constraint_json
                        .get("segmentOrPointId")
                        .and_then(|v| v.as_u64())
                        .map(|id| id as usize)
                    {
                        constraints_to_migrate.push(ConstraintToMigrate {
                            constraint_id,
                            other_entity_id: ObjectId(other_id_usize),
                            is_point_point: false,
                            attach_to_endpoint: AttachToEndpoint::End,
                        });
                    }
                }
            }
        }

        // Find point-segment constraints where the point is geometrically at the original end point
        // These should migrate to [newSegmentEndPointId, pointId] (point-point), not [pointId, newSegmentId] (point-segment)
        // We need to find these by checking all point-segment constraints involving the segment ID
        // and checking if the point is at the original end point
        if let Some(end_id) = original_end_point_id {
            for obj in objects {
                let ObjectKind::Constraint { constraint } = &obj.kind else {
                    continue;
                };

                let Constraint::Coincident(coincident) = constraint else {
                    continue;
                };

                // Only consider constraints that involve the segment ID but NOT the endpoint IDs directly
                // Note: We want to find constraints like [pointId, segmentId] where pointId is a point
                // that happens to be at the endpoint geometrically, but the constraint doesn't reference
                // the endpoint ID directly
                if !coincident.contains_segment(trim_spawn_id) {
                    continue;
                }
                // Skip constraints that involve endpoint IDs directly (those are handled by endpoint constraint migration)
                // But we still want to find constraints where a point (not an endpoint ID) is at the endpoint
                if let (Some(start_id), Some(end_id_val)) = (original_start_point_id, Some(end_id))
                    && coincident.segment_ids().any(|id| id == start_id || id == end_id_val)
                {
                    continue; // Skip constraints that involve endpoint IDs directly
                }

                // Find the other entity (should be a point)
                let other_id = coincident.segment_ids().find(|&seg_id| seg_id != trim_spawn_id);

                if let Some(other_id) = other_id {
                    // Check if the other entity is a point
                    if let Some(other_obj) = objects.iter().find(|o| o.id == other_id) {
                        let ObjectKind::Segment { segment: other_segment } = &other_obj.kind else {
                            continue;
                        };

                        let Segment::Point(point) = other_segment else {
                            continue;
                        };

                        // Get point coordinates in the trim internal unit
                        let point_coords = Coords2d {
                            x: number_to_unit(&point.position.x, default_unit),
                            y: number_to_unit(&point.position.y, default_unit),
                        };

                        // Check if point is at original end point (geometrically)
                        // Use post-solve coordinates for original end point if available, otherwise use the coordinates we have
                        let original_end_point_post_solve_coords = if let Some(end_id) = original_end_point_id {
                            if let Some(end_point_obj) = objects.iter().find(|o| o.id == end_id) {
                                if let ObjectKind::Segment {
                                    segment: Segment::Point(end_point),
                                } = &end_point_obj.kind
                                {
                                    Some(Coords2d {
                                        x: number_to_unit(&end_point.position.x, default_unit),
                                        y: number_to_unit(&end_point.position.y, default_unit),
                                    })
                                } else {
                                    None
                                }
                            } else {
                                None
                            }
                        } else {
                            None
                        };

                        let reference_coords = original_end_point_post_solve_coords.unwrap_or(original_end_coords);
                        let dist_to_original_end = ((point_coords.x - reference_coords.x)
                            * (point_coords.x - reference_coords.x)
                            + (point_coords.y - reference_coords.y) * (point_coords.y - reference_coords.y))
                            .sqrt();

                        if dist_to_original_end < EPSILON_POINT_ON_SEGMENT {
                            // Point is at the original end point - migrate as point-point constraint
                            // Check if there's already a point-point constraint between this point and the original end point
                            let has_point_point_constraint = find_point_point_coincident_constraints(end_id)
                                .iter()
                                .any(|&constraint_id| {
                                    if let Some(constraint_obj) = objects.iter().find(|o| o.id == constraint_id) {
                                        if let ObjectKind::Constraint {
                                            constraint: Constraint::Coincident(coincident),
                                        } = &constraint_obj.kind
                                        {
                                            coincident.contains_segment(other_id)
                                        } else {
                                            false
                                        }
                                    } else {
                                        false
                                    }
                                });

                            if !has_point_point_constraint {
                                // No existing point-point constraint - migrate as point-point constraint
                                constraints_to_migrate.push(ConstraintToMigrate {
                                    constraint_id: obj.id,
                                    other_entity_id: other_id,
                                    is_point_point: true, // Convert to point-point constraint
                                    attach_to_endpoint: AttachToEndpoint::End, // Attach to new segment's end
                                });
                            }
                            // Always delete the old point-segment constraint (whether we migrate or not)
                            constraints_to_delete_set.insert(obj.id);
                        }
                    }
                }
            }
        }

        // Find point-segment constraints on the segment body (not at endpoints)
        // These are constraints [pointId, segmentId] where the point is on the segment body
        // They should be migrated to [pointId, newSegmentId] if the point is after the split point
        let split_point = right_trim_coords; // Use right trim coords as split point
        let segment_start_coords = match segment {
            Segment::Line(_) => {
                get_position_coords_for_line(trim_spawn_segment, LineEndpoint::Start, objects, default_unit)
            }
            Segment::Arc(_) => get_position_coords_from_arc(trim_spawn_segment, ArcPoint::Start, objects, default_unit),
            _ => None,
        };
        let segment_end_coords = match segment {
            Segment::Line(_) => {
                get_position_coords_for_line(trim_spawn_segment, LineEndpoint::End, objects, default_unit)
            }
            Segment::Arc(_) => get_position_coords_from_arc(trim_spawn_segment, ArcPoint::End, objects, default_unit),
            _ => None,
        };
        let segment_center_coords = match segment {
            Segment::Line(_) => None,
            Segment::Arc(_) => {
                get_position_coords_from_arc(trim_spawn_segment, ArcPoint::Center, objects, default_unit)
            }
            _ => None,
        };

        if let (Some(start_coords), Some(end_coords)) = (segment_start_coords, segment_end_coords) {
            // Calculate split point parametric position
            let split_point_t_opt = match segment {
                Segment::Line(_) => Some(project_point_onto_segment(split_point, start_coords, end_coords)),
                Segment::Arc(_) => segment_center_coords
                    .map(|center| project_point_onto_arc(split_point, center, start_coords, end_coords)),
                _ => None,
            };

            if let Some(split_point_t) = split_point_t_opt {
                // Find all coincident constraints involving the segment
                for obj in objects {
                    let ObjectKind::Constraint { constraint } = &obj.kind else {
                        continue;
                    };

                    let Constraint::Coincident(coincident) = constraint else {
                        continue;
                    };

                    // Check if constraint involves the segment being split
                    if !coincident.contains_segment(trim_spawn_id) {
                        continue;
                    }

                    // Skip if constraint also involves endpoint IDs directly (those are handled separately)
                    if let (Some(start_id), Some(end_id)) = (original_start_point_id, original_end_point_id)
                        && coincident.segment_ids().any(|id| id == start_id || id == end_id)
                    {
                        continue;
                    }

                    // Find the other entity in the constraint
                    let other_id = coincident.segment_ids().find(|&seg_id| seg_id != trim_spawn_id);

                    if let Some(other_id) = other_id {
                        // Check if the other entity is a point
                        if let Some(other_obj) = objects.iter().find(|o| o.id == other_id) {
                            let ObjectKind::Segment { segment: other_segment } = &other_obj.kind else {
                                continue;
                            };

                            let Segment::Point(point) = other_segment else {
                                continue;
                            };

                            // Get point coordinates in the trim internal unit
                            let point_coords = Coords2d {
                                x: number_to_unit(&point.position.x, default_unit),
                                y: number_to_unit(&point.position.y, default_unit),
                            };

                            // Project the point onto the segment to get its parametric position
                            let point_t = match segment {
                                Segment::Line(_) => project_point_onto_segment(point_coords, start_coords, end_coords),
                                Segment::Arc(_) => {
                                    if let Some(center) = segment_center_coords {
                                        project_point_onto_arc(point_coords, center, start_coords, end_coords)
                                    } else {
                                        continue; // Skip this constraint if no center
                                    }
                                }
                                _ => continue, // Skip non-line/arc segments
                            };

                            // Check if point is at the original end point (skip if so - already handled above)
                            // Use post-solve coordinates for original end point if available
                            let original_end_point_post_solve_coords = if let Some(end_id) = original_end_point_id {
                                if let Some(end_point_obj) = objects.iter().find(|o| o.id == end_id) {
                                    if let ObjectKind::Segment {
                                        segment: Segment::Point(end_point),
                                    } = &end_point_obj.kind
                                    {
                                        Some(Coords2d {
                                            x: number_to_unit(&end_point.position.x, default_unit),
                                            y: number_to_unit(&end_point.position.y, default_unit),
                                        })
                                    } else {
                                        None
                                    }
                                } else {
                                    None
                                }
                            } else {
                                None
                            };

                            let reference_coords = original_end_point_post_solve_coords.unwrap_or(original_end_coords);
                            let dist_to_original_end = ((point_coords.x - reference_coords.x)
                                * (point_coords.x - reference_coords.x)
                                + (point_coords.y - reference_coords.y) * (point_coords.y - reference_coords.y))
                                .sqrt();

                            if dist_to_original_end < EPSILON_POINT_ON_SEGMENT {
                                // This should have been handled in the first loop, but if we find it here,
                                // make sure it's deleted (it might have been missed due to filtering)
                                // Also check if we should migrate it as point-point constraint
                                let has_point_point_constraint = if let Some(end_id) = original_end_point_id {
                                    find_point_point_coincident_constraints(end_id)
                                        .iter()
                                        .any(|&constraint_id| {
                                            if let Some(constraint_obj) = objects.iter().find(|o| o.id == constraint_id)
                                            {
                                                if let ObjectKind::Constraint {
                                                    constraint: Constraint::Coincident(coincident),
                                                } = &constraint_obj.kind
                                                {
                                                    coincident.contains_segment(other_id)
                                                } else {
                                                    false
                                                }
                                            } else {
                                                false
                                            }
                                        })
                                } else {
                                    false
                                };

                                if !has_point_point_constraint {
                                    // No existing point-point constraint - migrate as point-point constraint
                                    constraints_to_migrate.push(ConstraintToMigrate {
                                        constraint_id: obj.id,
                                        other_entity_id: other_id,
                                        is_point_point: true, // Convert to point-point constraint
                                        attach_to_endpoint: AttachToEndpoint::End, // Attach to new segment's end
                                    });
                                }
                                // Always delete the old point-segment constraint
                                constraints_to_delete_set.insert(obj.id);
                                continue; // Already handled as point-point constraint migration above
                            }

                            // Check if point is at the current start endpoint (skip if so - handled separately)
                            let dist_to_start = ((point_coords.x - start_coords.x) * (point_coords.x - start_coords.x)
                                + (point_coords.y - start_coords.y) * (point_coords.y - start_coords.y))
                                .sqrt();
                            let is_at_start = (point_t - 0.0).abs() < EPSILON_POINT_ON_SEGMENT
                                || dist_to_start < EPSILON_POINT_ON_SEGMENT;

                            if is_at_start {
                                continue; // Handled by endpoint constraint migration
                            }

                            // Check if point is at the split point (don't migrate - would pull halves together)
                            let dist_to_split = (point_t - split_point_t).abs();
                            if dist_to_split < EPSILON_POINT_ON_SEGMENT * 100.0 {
                                continue; // Too close to split point
                            }

                            // If point is after split point (closer to end), migrate to new segment
                            if point_t > split_point_t {
                                constraints_to_migrate.push(ConstraintToMigrate {
                                    constraint_id: obj.id,
                                    other_entity_id: other_id,
                                    is_point_point: false, // Keep as point-segment, but replace the segment
                                    attach_to_endpoint: AttachToEndpoint::Segment, // Replace old segment with new segment
                                });
                                constraints_to_delete_set.insert(obj.id);
                            }
                        }
                    }
                }
            } // End of if let Some(split_point_t)
        } // End of if let (Some(start_coords), Some(end_coords))

        // Find distance constraints that reference the segment being split
        // These need to be deleted and re-added with new endpoints after split
        // BUT: For arcs, we need to exclude distance constraints that reference the center point
        // (those will be migrated separately in the execution code)
        let distance_constraint_ids_for_split = find_distance_constraints_for_segment(trim_spawn_id);

        // Get the center point ID if this is an arc, so we can exclude center point constraints
        let arc_center_point_id: Option<ObjectId> = match segment {
            Segment::Arc(arc) => Some(arc.center),
            _ => None,
        };

        for constraint_id in distance_constraint_ids_for_split {
            // Skip if this is a center point constraint for an arc (will be migrated separately)
            if let Some(center_id) = arc_center_point_id {
                // Check if this constraint references the center point
                if let Some(constraint_obj) = objects.iter().find(|o| o.id == constraint_id)
                    && let ObjectKind::Constraint { constraint } = &constraint_obj.kind
                    && let Constraint::Distance(distance) = constraint
                    && distance.contains_point(center_id)
                {
                    // This is a center point constraint - skip deletion, it will be migrated
                    continue;
                }
            }

            constraints_to_delete_set.insert(constraint_id);
        }

        // Midpoint constraints become stale after trim changes the owning
        // segment's extent, so delete them instead of migrating them.
        for obj in objects {
            let ObjectKind::Constraint { constraint } = &obj.kind else {
                continue;
            };

            let Constraint::Midpoint(midpoint) = constraint else {
                continue;
            };

            let references_trimmed_segment = midpoint.segment == trim_spawn_id;
            let references_trimmed_endpoint = match midpoint.point {
                ConstraintSegment::Segment(point_id) => {
                    original_start_point_id.is_some_and(|id| point_id == id)
                        || original_end_point_id.is_some_and(|id| point_id == id)
                }
                ConstraintSegment::Origin(_) => false,
            };

            if references_trimmed_segment || references_trimmed_endpoint {
                constraints_to_delete_set.insert(obj.id);
            }
        }

        // Find angle constraints (Parallel, Perpendicular, Horizontal, Vertical) that reference the segment being split
        // Note: We don't delete these - they still apply to the original (trimmed) segment
        // We'll add new constraints for the new segment in the execution code

        // Catch-all: Find any remaining point-segment constraints involving the segment
        // that we might have missed (e.g., due to coordinate precision issues)
        // This ensures we don't leave orphaned constraints
        for obj in objects {
            let ObjectKind::Constraint { constraint } = &obj.kind else {
                continue;
            };

            let Constraint::Coincident(coincident) = constraint else {
                continue;
            };

            // Only consider constraints that involve the segment ID
            if !coincident.contains_segment(trim_spawn_id) {
                continue;
            }

            // Skip if already marked for deletion
            if constraints_to_delete_set.contains(&obj.id) {
                continue;
            }

            // Skip if this constraint involves an endpoint directly (handled separately)
            // BUT: if the other entity is a point that's at the original end point geometrically,
            // we still want to handle it here even if it's not the same point object
            // So we'll check this after we verify the other entity is a point and check its coordinates

            // Find the other entity (should be a point)
            let other_id = coincident.segment_ids().find(|&seg_id| seg_id != trim_spawn_id);

            if let Some(other_id) = other_id {
                // Check if the other entity is a point
                if let Some(other_obj) = objects.iter().find(|o| o.id == other_id) {
                    let ObjectKind::Segment { segment: other_segment } = &other_obj.kind else {
                        continue;
                    };

                    let Segment::Point(point) = other_segment else {
                        continue;
                    };

                    // Skip if this constraint involves an endpoint directly (handled separately)
                    // BUT: if the point is at the original end point geometrically, we still want to handle it
                    let _is_endpoint_constraint =
                        if let (Some(start_id), Some(end_id)) = (original_start_point_id, original_end_point_id) {
                            coincident.segment_ids().any(|id| id == start_id || id == end_id)
                        } else {
                            false
                        };

                    // Get point coordinates in the trim internal unit
                    let point_coords = Coords2d {
                        x: number_to_unit(&point.position.x, default_unit),
                        y: number_to_unit(&point.position.y, default_unit),
                    };

                    // Check if point is at original end point (with relaxed tolerance for catch-all)
                    let original_end_point_post_solve_coords = if let Some(end_id) = original_end_point_id {
                        if let Some(end_point_obj) = objects.iter().find(|o| o.id == end_id) {
                            if let ObjectKind::Segment {
                                segment: Segment::Point(end_point),
                            } = &end_point_obj.kind
                            {
                                Some(Coords2d {
                                    x: number_to_unit(&end_point.position.x, default_unit),
                                    y: number_to_unit(&end_point.position.y, default_unit),
                                })
                            } else {
                                None
                            }
                        } else {
                            None
                        }
                    } else {
                        None
                    };

                    let reference_coords = original_end_point_post_solve_coords.unwrap_or(original_end_coords);
                    let dist_to_original_end = ((point_coords.x - reference_coords.x)
                        * (point_coords.x - reference_coords.x)
                        + (point_coords.y - reference_coords.y) * (point_coords.y - reference_coords.y))
                        .sqrt();

                    // Use a slightly more relaxed tolerance for catch-all to catch edge cases
                    // Also handle endpoint constraints that might have been missed
                    let is_at_original_end = dist_to_original_end < EPSILON_POINT_ON_SEGMENT * 2.0;

                    if is_at_original_end {
                        // Point is at or very close to original end point - delete the constraint
                        // Check if we should migrate it as point-point constraint
                        let has_point_point_constraint = if let Some(end_id) = original_end_point_id {
                            find_point_point_coincident_constraints(end_id)
                                .iter()
                                .any(|&constraint_id| {
                                    if let Some(constraint_obj) = objects.iter().find(|o| o.id == constraint_id) {
                                        if let ObjectKind::Constraint {
                                            constraint: Constraint::Coincident(coincident),
                                        } = &constraint_obj.kind
                                        {
                                            coincident.contains_segment(other_id)
                                        } else {
                                            false
                                        }
                                    } else {
                                        false
                                    }
                                })
                        } else {
                            false
                        };

                        if !has_point_point_constraint {
                            // No existing point-point constraint - migrate as point-point constraint
                            constraints_to_migrate.push(ConstraintToMigrate {
                                constraint_id: obj.id,
                                other_entity_id: other_id,
                                is_point_point: true, // Convert to point-point constraint
                                attach_to_endpoint: AttachToEndpoint::End, // Attach to new segment's end
                            });
                        }
                        // Always delete the old point-segment constraint
                        constraints_to_delete_set.insert(obj.id);
                    }
                }
            }
        }

        // Create split segment operation
        let constraints_to_delete: Vec<ObjectId> = constraints_to_delete_set.iter().copied().collect();
        let plan = TrimPlan::SplitSegment {
            segment_id: trim_spawn_id,
            left_trim_coords,
            right_trim_coords,
            original_end_coords,
            left_side: Box::new(left_side.clone()),
            right_side: Box::new(right_side.clone()),
            left_side_coincident_data: CoincidentData {
                intersecting_seg_id: left_intersecting_seg_id,
                intersecting_endpoint_point_id: left_coincident_data.intersecting_endpoint_point_id,
                existing_point_segment_constraint_id: left_coincident_data.existing_point_segment_constraint_id,
            },
            right_side_coincident_data: CoincidentData {
                intersecting_seg_id: right_intersecting_seg_id,
                intersecting_endpoint_point_id: right_coincident_data.intersecting_endpoint_point_id,
                existing_point_segment_constraint_id: right_coincident_data.existing_point_segment_constraint_id,
            },
            constraints_to_migrate,
            constraints_to_delete,
        };

        return Ok(plan);
    }

    // Only three strategy cases should exist: simple trim (endpoint/endpoint),
    // tail cut (intersection+endpoint), or split (intersection+intersection).
    // If we get here, trim termination pairing was unexpected or a new variant
    // was added without updating the strategy mapping.
    Err(format!(
        "Unsupported trim termination combination: left={:?} right={:?}",
        left_side, right_side
    ))
}

/// Execute the trim operations determined by the trim strategy
///
/// Once we have a trim strategy, it then needs to be executed. This function is separate just to keep
/// one phase just collecting info (`build_trim_plan` + `lower_trim_plan`), and the other actually mutating things.
///
/// This function takes the list of trim operations from `lower_trim_plan` and executes them, which may include:
/// - Deleting segments (SimpleTrim)
/// - Editing segment endpoints (EditSegment)
/// - Adding coincident constraints (AddCoincidentConstraint)
/// - Splitting segments (SplitSegment)
/// - Migrating constraints (MigrateConstraint)
pub(crate) async fn execute_trim_operations_simple(
    strategy: Vec<TrimOperation>,
    current_scene_graph_delta: &crate::frontend::api::SceneGraphDelta,
    frontend: &mut crate::frontend::FrontendState,
    ctx: &crate::ExecutorContext,
    version: crate::frontend::api::Version,
    sketch_id: ObjectId,
) -> Result<(crate::frontend::api::SourceDelta, crate::frontend::api::SceneGraphDelta), String> {
    use crate::frontend::SketchApi;
    use crate::frontend::sketch::Constraint;
    use crate::frontend::sketch::ExistingSegmentCtor;
    use crate::frontend::sketch::SegmentCtor;

    let default_unit = frontend.default_length_unit();

    let mut op_index = 0;
    let mut last_result: Option<(crate::frontend::api::SourceDelta, crate::frontend::api::SceneGraphDelta)> = None;
    let mut invalidates_ids = false;

    while op_index < strategy.len() {
        let mut consumed_ops = 1;
        let operation_result = match &strategy[op_index] {
            TrimOperation::SimpleTrim { segment_to_trim_id } => {
                // Delete the segment
                frontend
                    .delete_objects(
                        ctx,
                        version,
                        sketch_id,
                        Vec::new(),                // constraint_ids
                        vec![*segment_to_trim_id], // segment_ids
                    )
                    .await
                    .map_err(|e| format!("Failed to delete segment: {}", e.error.message()))
            }
            TrimOperation::EditSegment {
                segment_id,
                ctor,
                endpoint_changed,
                additional_edited_segment_ids,
            } => {
                // Try to batch tail-cut sequence: EditSegment + AddCoincidentConstraint (+ DeleteConstraints)
                // This matches the batching logic in kcl-wasm-lib/src/api.rs
                if op_index + 1 < strategy.len() {
                    if let TrimOperation::AddCoincidentConstraint {
                        segment_id: coincident_seg_id,
                        endpoint_changed: coincident_endpoint_changed,
                        segment_or_point_to_make_coincident_to,
                        intersecting_endpoint_point_id,
                    } = &strategy[op_index + 1]
                    {
                        if segment_id == coincident_seg_id && endpoint_changed == coincident_endpoint_changed {
                            // This is a tail-cut sequence - batch it!
                            let mut delete_constraint_ids: Vec<ObjectId> = Vec::new();
                            consumed_ops = 2;

                            if op_index + 2 < strategy.len()
                                && let TrimOperation::DeleteConstraints { constraint_ids } = &strategy[op_index + 2]
                            {
                                delete_constraint_ids = constraint_ids.to_vec();
                                consumed_ops = 3;
                            }

                            // Use ctor directly
                            let segment_ctor = ctor.clone();

                            // Get endpoint point id from current scene graph (IDs stay the same after edit)
                            let edited_segment = current_scene_graph_delta
                                .new_graph
                                .objects
                                .iter()
                                .find(|obj| obj.id == *segment_id)
                                .ok_or_else(|| format!("Failed to find segment {} for tail-cut batch", segment_id.0))?;

                            let endpoint_point_id = match &edited_segment.kind {
                                crate::frontend::api::ObjectKind::Segment { segment } => match segment {
                                    crate::frontend::sketch::Segment::Line(line) => {
                                        if *endpoint_changed == EndpointChanged::Start {
                                            line.start
                                        } else {
                                            line.end
                                        }
                                    }
                                    crate::frontend::sketch::Segment::Arc(arc) => {
                                        if *endpoint_changed == EndpointChanged::Start {
                                            arc.start
                                        } else {
                                            arc.end
                                        }
                                    }
                                    _ => {
                                        return Err("Unsupported segment type for tail-cut batch".to_string());
                                    }
                                },
                                _ => {
                                    return Err("Edited object is not a segment (tail-cut batch)".to_string());
                                }
                            };

                            let coincident_segments = if let Some(point_id) = intersecting_endpoint_point_id {
                                vec![endpoint_point_id.into(), (*point_id).into()]
                            } else {
                                vec![
                                    endpoint_point_id.into(),
                                    (*segment_or_point_to_make_coincident_to).into(),
                                ]
                            };

                            let constraint = Constraint::Coincident(crate::frontend::sketch::Coincident {
                                segments: coincident_segments,
                            });

                            let segment_to_edit = ExistingSegmentCtor {
                                id: *segment_id,
                                ctor: segment_ctor,
                            };

                            // Batch the operations - this is the key optimization!
                            // Note: consumed_ops is set above (2 or 3), and we'll use it after the match
                            frontend
                                .batch_tail_cut_operations(
                                    ctx,
                                    version,
                                    sketch_id,
                                    vec![segment_to_edit],
                                    vec![constraint],
                                    delete_constraint_ids,
                                    additional_edited_segment_ids.clone(),
                                )
                                .await
                                .map_err(|e| format!("Failed to batch tail-cut operations: {}", e.error.message()))
                        } else {
                            // Not same segment/endpoint - execute EditSegment normally
                            let segment_to_edit = ExistingSegmentCtor {
                                id: *segment_id,
                                ctor: ctor.clone(),
                            };

                            frontend
                                .edit_segments(ctx, version, sketch_id, vec![segment_to_edit])
                                .await
                                .map_err(|e| format!("Failed to edit segment: {}", e.error.message()))
                        }
                    } else {
                        // Not followed by AddCoincidentConstraint - execute EditSegment normally
                        let segment_to_edit = ExistingSegmentCtor {
                            id: *segment_id,
                            ctor: ctor.clone(),
                        };

                        frontend
                            .edit_segments(ctx, version, sketch_id, vec![segment_to_edit])
                            .await
                            .map_err(|e| format!("Failed to edit segment: {}", e.error.message()))
                    }
                } else {
                    // No following op to batch with - execute EditSegment normally
                    let segment_to_edit = ExistingSegmentCtor {
                        id: *segment_id,
                        ctor: ctor.clone(),
                    };

                    frontend
                        .edit_segments(ctx, version, sketch_id, vec![segment_to_edit])
                        .await
                        .map_err(|e| format!("Failed to edit segment: {}", e.error.message()))
                }
            }
            TrimOperation::EditControlPointSpline { segment_id, ctor } => {
                let segment_to_edit = ExistingSegmentCtor {
                    id: *segment_id,
                    ctor: ctor.clone(),
                };

                frontend
                    .edit_segments(ctx, version, sketch_id, vec![segment_to_edit])
                    .await
                    .map_err(|e| format!("Failed to edit control point spline: {}", e.error.message()))
            }
            TrimOperation::AddCoincidentConstraint {
                segment_id,
                endpoint_changed,
                segment_or_point_to_make_coincident_to,
                intersecting_endpoint_point_id,
            } => {
                // Find the edited segment to get the endpoint point ID
                let edited_segment = current_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == *segment_id)
                    .ok_or_else(|| format!("Failed to find edited segment {}", segment_id.0))?;

                // Get the endpoint ID after editing
                let new_segment_endpoint_point_id = match &edited_segment.kind {
                    crate::frontend::api::ObjectKind::Segment { segment } => match segment {
                        crate::frontend::sketch::Segment::Line(line) => {
                            if *endpoint_changed == EndpointChanged::Start {
                                line.start
                            } else {
                                line.end
                            }
                        }
                        crate::frontend::sketch::Segment::Arc(arc) => {
                            if *endpoint_changed == EndpointChanged::Start {
                                arc.start
                            } else {
                                arc.end
                            }
                        }
                        crate::frontend::sketch::Segment::ControlPointSpline(spline) => {
                            if *endpoint_changed == EndpointChanged::Start {
                                spline
                                    .controls
                                    .first()
                                    .copied()
                                    .ok_or_else(|| "Edited spline has no start control point".to_string())?
                            } else {
                                spline
                                    .controls
                                    .last()
                                    .copied()
                                    .ok_or_else(|| "Edited spline has no end control point".to_string())?
                            }
                        }
                        _ => {
                            return Err("Unsupported segment type for addCoincidentConstraint".to_string());
                        }
                    },
                    _ => {
                        return Err("Edited object is not a segment".to_string());
                    }
                };

                // Determine coincident segments
                let coincident_segments = if let Some(point_id) = intersecting_endpoint_point_id {
                    vec![new_segment_endpoint_point_id.into(), (*point_id).into()]
                } else {
                    vec![
                        new_segment_endpoint_point_id.into(),
                        (*segment_or_point_to_make_coincident_to).into(),
                    ]
                };

                let constraint = Constraint::Coincident(crate::frontend::sketch::Coincident {
                    segments: coincident_segments,
                });

                frontend
                    .add_constraint(ctx, version, sketch_id, constraint)
                    .await
                    .map_err(|e| format!("Failed to add constraint: {}", e.error.message()))
            }
            TrimOperation::DeleteConstraints { constraint_ids } => {
                // Delete constraints
                let constraint_object_ids: Vec<ObjectId> = constraint_ids.to_vec();

                frontend
                    .delete_objects(
                        ctx,
                        version,
                        sketch_id,
                        constraint_object_ids,
                        Vec::new(), // segment_ids
                    )
                    .await
                    .map_err(|e| format!("Failed to delete constraints: {}", e.error.message()))
            }
            TrimOperation::ReplaceCircleWithArc {
                circle_id,
                arc_start_coords,
                arc_end_coords,
                arc_start_termination,
                arc_end_termination,
            } => {
                // Replace a circle with a single arc and re-attach coincident constraints.
                let original_circle = current_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == *circle_id)
                    .ok_or_else(|| format!("Failed to find original circle {}", circle_id.0))?;

                let (original_circle_start_id, original_circle_center_id, circle_ctor) = match &original_circle.kind {
                    crate::frontend::api::ObjectKind::Segment { segment } => match segment {
                        crate::frontend::sketch::Segment::Circle(circle) => match &circle.ctor {
                            SegmentCtor::Circle(circle_ctor) => (circle.start, circle.center, circle_ctor.clone()),
                            _ => return Err("Circle does not have a Circle ctor".to_string()),
                        },
                        _ => return Err("Original segment is not a circle".to_string()),
                    },
                    _ => return Err("Original object is not a segment".to_string()),
                };

                let units = match &circle_ctor.start.x {
                    crate::frontend::api::Expr::Var(v) | crate::frontend::api::Expr::Number(v) => v.units,
                    _ => crate::pretty::NumericSuffix::Mm,
                };

                let coords_to_point_expr = |coords: Coords2d| crate::frontend::sketch::Point2d {
                    x: crate::frontend::api::Expr::Var(unit_to_number(coords.x, default_unit, units)),
                    y: crate::frontend::api::Expr::Var(unit_to_number(coords.y, default_unit, units)),
                };

                let arc_ctor = SegmentCtor::Arc(crate::frontend::sketch::ArcCtor {
                    start: coords_to_point_expr(*arc_start_coords),
                    end: coords_to_point_expr(*arc_end_coords),
                    center: circle_ctor.center.clone(),
                    construction: circle_ctor.construction,
                });

                let (_add_source_delta, add_scene_graph_delta) = frontend
                    .add_segment(ctx, version, sketch_id, arc_ctor, None)
                    .await
                    .map_err(|e| format!("Failed to add arc while replacing circle: {}", e.error.message()))?;
                invalidates_ids = invalidates_ids || add_scene_graph_delta.invalidates_ids;

                let new_arc_id = *add_scene_graph_delta
                    .new_objects
                    .iter()
                    .find(|&id| {
                        add_scene_graph_delta
                            .new_graph
                            .objects
                            .iter()
                            .find(|o| o.id == *id)
                            .is_some_and(|obj| {
                                matches!(
                                    &obj.kind,
                                    crate::frontend::api::ObjectKind::Segment { segment }
                                        if matches!(segment, crate::frontend::sketch::Segment::Arc(_))
                                )
                            })
                    })
                    .ok_or_else(|| "Failed to find newly created arc segment".to_string())?;

                let new_arc_obj = add_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == new_arc_id)
                    .ok_or_else(|| format!("New arc segment not found {}", new_arc_id.0))?;
                let (new_arc_start_id, new_arc_end_id, new_arc_center_id) = match &new_arc_obj.kind {
                    crate::frontend::api::ObjectKind::Segment { segment } => match segment {
                        crate::frontend::sketch::Segment::Arc(arc) => (arc.start, arc.end, arc.center),
                        _ => return Err("New segment is not an arc".to_string()),
                    },
                    _ => return Err("New arc object is not a segment".to_string()),
                };

                let constraint_segments_for =
                    |arc_endpoint_id: ObjectId,
                     term: &TrimTermination|
                     -> Result<Vec<crate::frontend::sketch::ConstraintSegment>, String> {
                        match term {
                            TrimTermination::Intersection {
                                intersecting_seg_id, ..
                            } => Ok(vec![arc_endpoint_id.into(), (*intersecting_seg_id).into()]),
                            TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                                other_segment_point_id,
                                ..
                            } => Ok(vec![arc_endpoint_id.into(), (*other_segment_point_id).into()]),
                            TrimTermination::SegEndPoint { .. } => {
                                Err("Circle replacement endpoint cannot terminate at seg endpoint".to_string())
                            }
                        }
                    };

                let start_constraint = Constraint::Coincident(crate::frontend::sketch::Coincident {
                    segments: constraint_segments_for(new_arc_start_id, arc_start_termination)?,
                });
                let (_c1_source_delta, c1_scene_graph_delta) = frontend
                    .add_constraint(ctx, version, sketch_id, start_constraint)
                    .await
                    .map_err(|e| format!("Failed to add start coincident on replaced arc: {}", e.error.message()))?;
                invalidates_ids = invalidates_ids || c1_scene_graph_delta.invalidates_ids;

                let end_constraint = Constraint::Coincident(crate::frontend::sketch::Coincident {
                    segments: constraint_segments_for(new_arc_end_id, arc_end_termination)?,
                });
                let (_c2_source_delta, c2_scene_graph_delta) = frontend
                    .add_constraint(ctx, version, sketch_id, end_constraint)
                    .await
                    .map_err(|e| format!("Failed to add end coincident on replaced arc: {}", e.error.message()))?;
                invalidates_ids = invalidates_ids || c2_scene_graph_delta.invalidates_ids;

                let mut termination_point_ids: Vec<ObjectId> = Vec::new();
                for term in [arc_start_termination, arc_end_termination] {
                    if let TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                        other_segment_point_id,
                        ..
                    } = term.as_ref()
                    {
                        termination_point_ids.push(*other_segment_point_id);
                    }
                }

                // Migrate constraints that reference the original circle segment or points.
                // This preserves authored constraints (e.g. radius/tangent/coincident) when
                // a trim converts a circle into an arc.
                let rewrite_map = std::collections::HashMap::from([
                    (*circle_id, new_arc_id),
                    (original_circle_center_id, new_arc_center_id),
                    (original_circle_start_id, new_arc_start_id),
                ]);
                let rewrite_ids: std::collections::HashSet<ObjectId> = rewrite_map.keys().copied().collect();

                let mut migrated_constraints: Vec<Constraint> = Vec::new();
                for obj in &current_scene_graph_delta.new_graph.objects {
                    let crate::frontend::api::ObjectKind::Constraint { constraint } = &obj.kind else {
                        continue;
                    };

                    // Keep this exhaustive so new constraints must declare how
                    // circle-to-arc trim should migrate or ignore them.
                    match constraint {
                        Constraint::Coincident(coincident) => {
                            if !constraint_segments_reference_any(&coincident.segments, &rewrite_ids) {
                                continue;
                            }

                            // If the original coincident is circle<->point for a point that is
                            // already used as a trim termination, endpoint coincident constraints
                            // already preserve that relationship.
                            if coincident.contains_segment(*circle_id)
                                && coincident
                                    .segment_ids()
                                    .filter(|id| *id != *circle_id)
                                    .any(|id| termination_point_ids.contains(&id))
                            {
                                continue;
                            }

                            let Some(Constraint::Coincident(migrated_coincident)) =
                                rewrite_constraint_with_map(constraint, &rewrite_map)
                            else {
                                continue;
                            };

                            // Skip redundant migration when a previous point-segment circle
                            // coincident would become point-segment arc coincident at an arc
                            // endpoint that is already handled by explicit endpoint constraints.
                            let migrated_ids: Vec<ObjectId> = migrated_coincident
                                .segments
                                .iter()
                                .filter_map(|segment| match segment {
                                    crate::frontend::sketch::ConstraintSegment::Segment(id) => Some(*id),
                                    crate::frontend::sketch::ConstraintSegment::Origin(_) => None,
                                })
                                .collect();
                            if migrated_ids.contains(&new_arc_id)
                                && (migrated_ids.contains(&new_arc_start_id) || migrated_ids.contains(&new_arc_end_id))
                            {
                                continue;
                            }

                            migrated_constraints.push(Constraint::Coincident(migrated_coincident));
                        }
                        Constraint::Distance(distance) => {
                            if !constraint_segments_reference_any(&distance.points, &rewrite_ids) {
                                continue;
                            }
                            if let Some(migrated) = rewrite_constraint_with_map(constraint, &rewrite_map) {
                                migrated_constraints.push(migrated);
                            }
                        }
                        Constraint::HorizontalDistance(distance) => {
                            if !constraint_segments_reference_any(&distance.points, &rewrite_ids) {
                                continue;
                            }
                            if let Some(migrated) = rewrite_constraint_with_map(constraint, &rewrite_map) {
                                migrated_constraints.push(migrated);
                            }
                        }
                        Constraint::VerticalDistance(distance) => {
                            if !constraint_segments_reference_any(&distance.points, &rewrite_ids) {
                                continue;
                            }
                            if let Some(migrated) = rewrite_constraint_with_map(constraint, &rewrite_map) {
                                migrated_constraints.push(migrated);
                            }
                        }
                        Constraint::Radius(radius) => {
                            if radius.arc == *circle_id
                                && let Some(migrated) = rewrite_constraint_with_map(constraint, &rewrite_map)
                            {
                                migrated_constraints.push(migrated);
                            }
                        }
                        Constraint::Diameter(diameter) => {
                            if diameter.arc == *circle_id
                                && let Some(migrated) = rewrite_constraint_with_map(constraint, &rewrite_map)
                            {
                                migrated_constraints.push(migrated);
                            }
                        }
                        Constraint::EqualRadius(equal_radius) => {
                            if equal_radius.input.contains(circle_id)
                                && let Some(migrated) = rewrite_constraint_with_map(constraint, &rewrite_map)
                            {
                                migrated_constraints.push(migrated);
                            }
                        }
                        Constraint::Tangent(tangent) => {
                            if tangent.input.contains(circle_id)
                                && let Some(migrated) = rewrite_constraint_with_map(constraint, &rewrite_map)
                            {
                                migrated_constraints.push(migrated);
                            }
                        }
                        Constraint::Angle(_)
                        | Constraint::Fixed(_)
                        | Constraint::Horizontal(_)
                        | Constraint::LinesEqualLength(_)
                        | Constraint::Midpoint(_)
                        | Constraint::Parallel(_)
                        | Constraint::Perpendicular(_)
                        | Constraint::Symmetric(_)
                        | Constraint::Vertical(_) => {}
                    }
                }

                for constraint in migrated_constraints {
                    let (_source_delta, migrated_scene_graph_delta) = frontend
                        .add_constraint(ctx, version, sketch_id, constraint)
                        .await
                        .map_err(|e| format!("Failed to migrate circle constraint to arc: {}", e.error.message()))?;
                    invalidates_ids = invalidates_ids || migrated_scene_graph_delta.invalidates_ids;
                }

                frontend
                    .delete_objects(ctx, version, sketch_id, Vec::new(), vec![*circle_id])
                    .await
                    .map_err(|e| format!("Failed to delete circle after arc replacement: {}", e.error.message()))
            }
            TrimOperation::SplitSegment {
                segment_id,
                left_trim_coords,
                right_trim_coords,
                original_end_coords,
                left_side,
                right_side,
                constraints_to_migrate,
                constraints_to_delete,
                ..
            } => {
                // SplitSegment is a complex multi-step operation
                // Ported from kcl-wasm-lib/src/api.rs execute_trim function

                // Step 1: Find and validate original segment
                let original_segment = current_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == *segment_id)
                    .ok_or_else(|| format!("Failed to find original segment {}", segment_id.0))?;

                // Extract point IDs from original segment
                let (original_segment_start_point_id, original_segment_end_point_id, original_segment_center_point_id) =
                    match &original_segment.kind {
                        crate::frontend::api::ObjectKind::Segment { segment } => match segment {
                            crate::frontend::sketch::Segment::Line(line) => (Some(line.start), Some(line.end), None),
                            crate::frontend::sketch::Segment::Arc(arc) => {
                                (Some(arc.start), Some(arc.end), Some(arc.center))
                            }
                            _ => (None, None, None),
                        },
                        _ => (None, None, None),
                    };

                // Store center point constraints to migrate BEFORE edit_segments modifies the scene graph
                let mut center_point_constraints_to_migrate: Vec<(Constraint, ObjectId)> = Vec::new();
                if let Some(original_center_id) = original_segment_center_point_id {
                    for obj in &current_scene_graph_delta.new_graph.objects {
                        let crate::frontend::api::ObjectKind::Constraint { constraint } = &obj.kind else {
                            continue;
                        };

                        // Find coincident constraints that reference the original center point
                        if let Constraint::Coincident(coincident) = constraint
                            && coincident.contains_segment(original_center_id)
                        {
                            center_point_constraints_to_migrate.push((constraint.clone(), original_center_id));
                        }

                        // Find distance constraints that reference the original center point
                        if let Constraint::Distance(distance) = constraint
                            && distance.contains_point(original_center_id)
                        {
                            center_point_constraints_to_migrate.push((constraint.clone(), original_center_id));
                        }
                    }
                }

                // Extract segment and ctor
                let (_segment_type, original_ctor) = match &original_segment.kind {
                    crate::frontend::api::ObjectKind::Segment { segment } => match segment {
                        crate::frontend::sketch::Segment::Line(line) => ("Line", line.ctor.clone()),
                        crate::frontend::sketch::Segment::Arc(arc) => ("Arc", arc.ctor.clone()),
                        _ => {
                            return Err("Original segment is not a Line or Arc".to_string());
                        }
                    },
                    _ => {
                        return Err("Original object is not a segment".to_string());
                    }
                };

                // Extract units from the existing ctor
                let units = match &original_ctor {
                    SegmentCtor::Line(line_ctor) => match &line_ctor.start.x {
                        crate::frontend::api::Expr::Var(v) | crate::frontend::api::Expr::Number(v) => v.units,
                        _ => crate::pretty::NumericSuffix::Mm,
                    },
                    SegmentCtor::Arc(arc_ctor) => match &arc_ctor.start.x {
                        crate::frontend::api::Expr::Var(v) | crate::frontend::api::Expr::Number(v) => v.units,
                        _ => crate::pretty::NumericSuffix::Mm,
                    },
                    _ => crate::pretty::NumericSuffix::Mm,
                };

                // Helper to convert Coords2d (current trim unit) to Point2d in segment units.
                // No rounding here; rounding happens at final conversion to output if needed.
                let coords_to_point =
                    |coords: Coords2d| -> crate::frontend::sketch::Point2d<crate::frontend::api::Number> {
                        crate::frontend::sketch::Point2d {
                            x: unit_to_number(coords.x, default_unit, units),
                            y: unit_to_number(coords.y, default_unit, units),
                        }
                    };

                // Convert Point2d<Number> to Point2d<Expr> for SegmentCtor
                let point_to_expr = |point: crate::frontend::sketch::Point2d<crate::frontend::api::Number>| -> crate::frontend::sketch::Point2d<crate::frontend::api::Expr> {
                    crate::frontend::sketch::Point2d {
                        x: crate::frontend::api::Expr::Var(point.x),
                        y: crate::frontend::api::Expr::Var(point.y),
                    }
                };

                // Step 2: Create new segment (right side) first to get its IDs
                let new_segment_ctor = match &original_ctor {
                    SegmentCtor::Line(line_ctor) => SegmentCtor::Line(crate::frontend::sketch::LineCtor {
                        start: point_to_expr(coords_to_point(*right_trim_coords)),
                        end: point_to_expr(coords_to_point(*original_end_coords)),
                        construction: line_ctor.construction,
                    }),
                    SegmentCtor::Arc(arc_ctor) => SegmentCtor::Arc(crate::frontend::sketch::ArcCtor {
                        start: point_to_expr(coords_to_point(*right_trim_coords)),
                        end: point_to_expr(coords_to_point(*original_end_coords)),
                        center: arc_ctor.center.clone(),
                        construction: arc_ctor.construction,
                    }),
                    _ => {
                        return Err("Unsupported segment type for new segment".to_string());
                    }
                };

                let (_add_source_delta, add_scene_graph_delta) = frontend
                    .add_segment(ctx, version, sketch_id, new_segment_ctor, None)
                    .await
                    .map_err(|e| format!("Failed to add new segment: {}", e.error.message()))?;

                // Step 3: Find the newly created segment
                let new_segment_id = *add_scene_graph_delta
                    .new_objects
                    .iter()
                    .find(|&id| {
                        if let Some(obj) = add_scene_graph_delta.new_graph.objects.iter().find(|o| o.id == *id) {
                            matches!(
                                &obj.kind,
                                crate::frontend::api::ObjectKind::Segment { segment }
                                    if matches!(segment, crate::frontend::sketch::Segment::Line(_) | crate::frontend::sketch::Segment::Arc(_))
                            )
                        } else {
                            false
                        }
                    })
                    .ok_or_else(|| "Failed to find newly created segment".to_string())?;

                let new_segment = add_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|o| o.id == new_segment_id)
                    .ok_or_else(|| format!("New segment not found with id {}", new_segment_id.0))?;

                // Extract endpoint IDs
                let (new_segment_start_point_id, new_segment_end_point_id, new_segment_center_point_id) =
                    match &new_segment.kind {
                        crate::frontend::api::ObjectKind::Segment { segment } => match segment {
                            crate::frontend::sketch::Segment::Line(line) => (line.start, line.end, None),
                            crate::frontend::sketch::Segment::Arc(arc) => (arc.start, arc.end, Some(arc.center)),
                            _ => {
                                return Err("New segment is not a Line or Arc".to_string());
                            }
                        },
                        _ => {
                            return Err("New segment is not a segment".to_string());
                        }
                    };

                // Step 4: Edit the original segment (trim left side)
                let edited_ctor = match &original_ctor {
                    SegmentCtor::Line(line_ctor) => SegmentCtor::Line(crate::frontend::sketch::LineCtor {
                        start: line_ctor.start.clone(),
                        end: point_to_expr(coords_to_point(*left_trim_coords)),
                        construction: line_ctor.construction,
                    }),
                    SegmentCtor::Arc(arc_ctor) => SegmentCtor::Arc(crate::frontend::sketch::ArcCtor {
                        start: arc_ctor.start.clone(),
                        end: point_to_expr(coords_to_point(*left_trim_coords)),
                        center: arc_ctor.center.clone(),
                        construction: arc_ctor.construction,
                    }),
                    _ => {
                        return Err("Unsupported segment type for split".to_string());
                    }
                };

                let (_edit_source_delta, edit_scene_graph_delta) = frontend
                    .edit_segments(
                        ctx,
                        version,
                        sketch_id,
                        vec![ExistingSegmentCtor {
                            id: *segment_id,
                            ctor: edited_ctor,
                        }],
                    )
                    .await
                    .map_err(|e| format!("Failed to edit segment: {}", e.error.message()))?;
                // Track invalidates_ids from edit_segments call
                invalidates_ids = invalidates_ids || edit_scene_graph_delta.invalidates_ids;

                // Get left endpoint ID from edited segment
                let edited_segment = edit_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == *segment_id)
                    .ok_or_else(|| format!("Failed to find edited segment {}", segment_id.0))?;

                let left_side_endpoint_point_id = match &edited_segment.kind {
                    crate::frontend::api::ObjectKind::Segment { segment } => match segment {
                        crate::frontend::sketch::Segment::Line(line) => line.end,
                        crate::frontend::sketch::Segment::Arc(arc) => arc.end,
                        _ => {
                            return Err("Edited segment is not a Line or Arc".to_string());
                        }
                    },
                    _ => {
                        return Err("Edited segment is not a segment".to_string());
                    }
                };

                // Step 5: Prepare constraints for batch
                let mut batch_constraints = Vec::new();

                // Left constraint
                let left_intersecting_seg_id = match &**left_side {
                    TrimTermination::Intersection {
                        intersecting_seg_id, ..
                    }
                    | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                        intersecting_seg_id, ..
                    } => *intersecting_seg_id,
                    _ => {
                        return Err("Left side is not an intersection or coincident".to_string());
                    }
                };
                let left_coincident_segments = match &**left_side {
                    TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                        other_segment_point_id,
                        ..
                    } => {
                        vec![left_side_endpoint_point_id.into(), (*other_segment_point_id).into()]
                    }
                    _ => {
                        vec![left_side_endpoint_point_id.into(), left_intersecting_seg_id.into()]
                    }
                };
                batch_constraints.push(Constraint::Coincident(crate::frontend::sketch::Coincident {
                    segments: left_coincident_segments,
                }));

                // Right constraint - need to check if intersection is at endpoint
                let right_intersecting_seg_id = match &**right_side {
                    TrimTermination::Intersection {
                        intersecting_seg_id, ..
                    }
                    | TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                        intersecting_seg_id, ..
                    } => *intersecting_seg_id,
                    _ => {
                        return Err("Right side is not an intersection or coincident".to_string());
                    }
                };

                let mut intersection_point_id: Option<ObjectId> = None;
                if matches!(&**right_side, TrimTermination::Intersection { .. }) {
                    let intersecting_seg = edit_scene_graph_delta
                        .new_graph
                        .objects
                        .iter()
                        .find(|obj| obj.id == right_intersecting_seg_id);

                    if let Some(seg) = intersecting_seg {
                        let endpoint_epsilon = 1e-3; // In current trim unit
                        let right_trim_coords_value = *right_trim_coords;

                        if let crate::frontend::api::ObjectKind::Segment { segment } = &seg.kind {
                            match segment {
                                crate::frontend::sketch::Segment::Line(_) => {
                                    if let (Some(start_coords), Some(end_coords)) = (
                                        crate::frontend::trim::get_position_coords_for_line(
                                            seg,
                                            crate::frontend::trim::LineEndpoint::Start,
                                            &edit_scene_graph_delta.new_graph.objects,
                                            default_unit,
                                        ),
                                        crate::frontend::trim::get_position_coords_for_line(
                                            seg,
                                            crate::frontend::trim::LineEndpoint::End,
                                            &edit_scene_graph_delta.new_graph.objects,
                                            default_unit,
                                        ),
                                    ) {
                                        let dist_to_start = ((right_trim_coords_value.x - start_coords.x)
                                            * (right_trim_coords_value.x - start_coords.x)
                                            + (right_trim_coords_value.y - start_coords.y)
                                                * (right_trim_coords_value.y - start_coords.y))
                                            .sqrt();
                                        if dist_to_start < endpoint_epsilon {
                                            if let crate::frontend::sketch::Segment::Line(line) = segment {
                                                intersection_point_id = Some(line.start);
                                            }
                                        } else {
                                            let dist_to_end = ((right_trim_coords_value.x - end_coords.x)
                                                * (right_trim_coords_value.x - end_coords.x)
                                                + (right_trim_coords_value.y - end_coords.y)
                                                    * (right_trim_coords_value.y - end_coords.y))
                                                .sqrt();
                                            if dist_to_end < endpoint_epsilon
                                                && let crate::frontend::sketch::Segment::Line(line) = segment
                                            {
                                                intersection_point_id = Some(line.end);
                                            }
                                        }
                                    }
                                }
                                crate::frontend::sketch::Segment::Arc(_) => {
                                    if let (Some(start_coords), Some(end_coords)) = (
                                        crate::frontend::trim::get_position_coords_from_arc(
                                            seg,
                                            crate::frontend::trim::ArcPoint::Start,
                                            &edit_scene_graph_delta.new_graph.objects,
                                            default_unit,
                                        ),
                                        crate::frontend::trim::get_position_coords_from_arc(
                                            seg,
                                            crate::frontend::trim::ArcPoint::End,
                                            &edit_scene_graph_delta.new_graph.objects,
                                            default_unit,
                                        ),
                                    ) {
                                        let dist_to_start = ((right_trim_coords_value.x - start_coords.x)
                                            * (right_trim_coords_value.x - start_coords.x)
                                            + (right_trim_coords_value.y - start_coords.y)
                                                * (right_trim_coords_value.y - start_coords.y))
                                            .sqrt();
                                        if dist_to_start < endpoint_epsilon {
                                            if let crate::frontend::sketch::Segment::Arc(arc) = segment {
                                                intersection_point_id = Some(arc.start);
                                            }
                                        } else {
                                            let dist_to_end = ((right_trim_coords_value.x - end_coords.x)
                                                * (right_trim_coords_value.x - end_coords.x)
                                                + (right_trim_coords_value.y - end_coords.y)
                                                    * (right_trim_coords_value.y - end_coords.y))
                                                .sqrt();
                                            if dist_to_end < endpoint_epsilon
                                                && let crate::frontend::sketch::Segment::Arc(arc) = segment
                                            {
                                                intersection_point_id = Some(arc.end);
                                            }
                                        }
                                    }
                                }
                                _ => {}
                            }
                        }
                    }
                }

                let right_coincident_segments = if let Some(point_id) = intersection_point_id {
                    vec![new_segment_start_point_id.into(), point_id.into()]
                } else if let TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                    other_segment_point_id,
                    ..
                } = &**right_side
                {
                    vec![new_segment_start_point_id.into(), (*other_segment_point_id).into()]
                } else {
                    vec![new_segment_start_point_id.into(), right_intersecting_seg_id.into()]
                };
                batch_constraints.push(Constraint::Coincident(crate::frontend::sketch::Coincident {
                    segments: right_coincident_segments,
                }));

                // Migrate constraints
                let mut points_constrained_to_new_segment_start = std::collections::HashSet::new();
                let mut points_constrained_to_new_segment_end = std::collections::HashSet::new();

                if let TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                    other_segment_point_id,
                    ..
                } = &**right_side
                {
                    points_constrained_to_new_segment_start.insert(other_segment_point_id);
                }

                for constraint_to_migrate in constraints_to_migrate.iter() {
                    if constraint_to_migrate.attach_to_endpoint == AttachToEndpoint::End
                        && constraint_to_migrate.is_point_point
                    {
                        points_constrained_to_new_segment_end.insert(constraint_to_migrate.other_entity_id);
                    }
                }

                for constraint_to_migrate in constraints_to_migrate.iter() {
                    // Skip migrating point-segment constraints if the point is already constrained
                    if constraint_to_migrate.attach_to_endpoint == AttachToEndpoint::Segment
                        && (points_constrained_to_new_segment_start.contains(&constraint_to_migrate.other_entity_id)
                            || points_constrained_to_new_segment_end.contains(&constraint_to_migrate.other_entity_id))
                    {
                        continue; // Skip redundant constraint
                    }

                    let constraint_segments = if constraint_to_migrate.attach_to_endpoint == AttachToEndpoint::Segment {
                        vec![constraint_to_migrate.other_entity_id.into(), new_segment_id.into()]
                    } else {
                        let target_endpoint_id = if constraint_to_migrate.attach_to_endpoint == AttachToEndpoint::Start
                        {
                            new_segment_start_point_id
                        } else {
                            new_segment_end_point_id
                        };
                        vec![target_endpoint_id.into(), constraint_to_migrate.other_entity_id.into()]
                    };
                    batch_constraints.push(Constraint::Coincident(crate::frontend::sketch::Coincident {
                        segments: constraint_segments,
                    }));
                }

                // Find distance constraints that reference both endpoints of the original segment
                let mut distance_constraints_to_re_add: Vec<(
                    crate::frontend::api::Number,
                    Option<crate::frontend::sketch::Point2d<crate::frontend::api::Number>>,
                    crate::frontend::sketch::ConstraintSource,
                )> = Vec::new();
                if let (Some(original_start_id), Some(original_end_id)) =
                    (original_segment_start_point_id, original_segment_end_point_id)
                {
                    for obj in &edit_scene_graph_delta.new_graph.objects {
                        let crate::frontend::api::ObjectKind::Constraint { constraint } = &obj.kind else {
                            continue;
                        };

                        let Constraint::Distance(distance) = constraint else {
                            continue;
                        };

                        let references_start = distance.contains_point(original_start_id);
                        let references_end = distance.contains_point(original_end_id);

                        if references_start && references_end {
                            distance_constraints_to_re_add.push((
                                distance.distance,
                                distance.label_position.clone(),
                                distance.source.clone(),
                            ));
                        }
                    }
                }

                // Re-add distance constraints
                if let Some(original_start_id) = original_segment_start_point_id {
                    for (distance_value, label_position, source) in distance_constraints_to_re_add {
                        batch_constraints.push(Constraint::Distance(crate::frontend::sketch::Distance {
                            points: vec![original_start_id.into(), new_segment_end_point_id.into()],
                            distance: distance_value,
                            label_position,
                            source,
                        }));
                    }
                }

                // Migrate center point constraints for arcs
                if let Some(new_center_id) = new_segment_center_point_id {
                    for (constraint, original_center_id) in center_point_constraints_to_migrate {
                        let center_rewrite_map = std::collections::HashMap::from([(original_center_id, new_center_id)]);
                        if let Some(rewritten) = rewrite_constraint_with_map(&constraint, &center_rewrite_map)
                            && matches!(rewritten, Constraint::Coincident(_) | Constraint::Distance(_))
                        {
                            batch_constraints.push(rewritten);
                        }
                    }
                }

                // Re-add angle constraints (Parallel, Perpendicular, Horizontal, Vertical)
                let mut angle_rewrite_map = std::collections::HashMap::from([(*segment_id, new_segment_id)]);
                if let Some(original_end_id) = original_segment_end_point_id {
                    angle_rewrite_map.insert(original_end_id, new_segment_end_point_id);
                }
                for obj in &edit_scene_graph_delta.new_graph.objects {
                    let crate::frontend::api::ObjectKind::Constraint { constraint } = &obj.kind else {
                        continue;
                    };

                    // Keep this exhaustive so new constraints must declare
                    // whether split trim should migrate them to the new segment.
                    let should_migrate = match constraint {
                        Constraint::Parallel(parallel) => parallel.lines.contains(segment_id),
                        Constraint::Perpendicular(perpendicular) => perpendicular.lines.contains(segment_id),
                        Constraint::Horizontal(Horizontal::Line { line }) => line == segment_id,
                        Constraint::Horizontal(Horizontal::Points { points }) => original_segment_end_point_id
                            .is_some_and(|end_id| points.contains(&ConstraintSegment::from(end_id))),
                        Constraint::Vertical(Vertical::Line { line }) => line == segment_id,
                        Constraint::Vertical(Vertical::Points { points }) => original_segment_end_point_id
                            .is_some_and(|end_id| points.contains(&ConstraintSegment::from(end_id))),
                        Constraint::Angle(_)
                        | Constraint::Coincident(_)
                        | Constraint::Diameter(_)
                        | Constraint::Distance(_)
                        | Constraint::EqualRadius(_)
                        | Constraint::Fixed(_)
                        | Constraint::HorizontalDistance(_)
                        | Constraint::LinesEqualLength(_)
                        | Constraint::Midpoint(_)
                        | Constraint::Radius(_)
                        | Constraint::Symmetric(_)
                        | Constraint::Tangent(_)
                        | Constraint::VerticalDistance(_) => false,
                    };

                    if should_migrate
                        && let Some(migrated_constraint) = rewrite_constraint_with_map(constraint, &angle_rewrite_map)
                        && matches!(
                            migrated_constraint,
                            Constraint::Parallel(_)
                                | Constraint::Perpendicular(_)
                                | Constraint::Horizontal(_)
                                | Constraint::Vertical(_)
                        )
                    {
                        batch_constraints.push(migrated_constraint);
                    }
                }

                // Step 6: Batch all remaining operations
                let constraint_object_ids: Vec<ObjectId> = constraints_to_delete.to_vec();

                let batch_result = frontend
                    .batch_split_segment_operations(
                        ctx,
                        version,
                        sketch_id,
                        Vec::new(), // edit_segments already done
                        batch_constraints,
                        constraint_object_ids,
                        crate::frontend::sketch::NewSegmentInfo {
                            segment_id: new_segment_id,
                            start_point_id: new_segment_start_point_id,
                            end_point_id: new_segment_end_point_id,
                            center_point_id: new_segment_center_point_id,
                        },
                    )
                    .await
                    .map_err(|e| format!("Failed to batch split segment operations: {}", e.error.message()));
                // Track invalidates_ids from batch_split_segment_operations call
                if let Ok((_, ref batch_delta)) = batch_result {
                    invalidates_ids = invalidates_ids || batch_delta.invalidates_ids;
                }
                batch_result
            }
            TrimOperation::SplitControlPointSpline {
                segment_id,
                left_ctor,
                right_ctor,
                left_side,
                right_side,
                constraint_ids_to_delete,
            } => {
                let original_segment = current_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == *segment_id)
                    .ok_or_else(|| format!("Failed to find original control point spline {}", segment_id.0))?;

                let (_original_start_id, original_end_id) = match &original_segment.kind {
                    crate::frontend::api::ObjectKind::Segment {
                        segment: crate::frontend::sketch::Segment::ControlPointSpline(spline),
                    } => (
                        spline
                            .controls
                            .first()
                            .copied()
                            .ok_or_else(|| format!("Spline {} has no start control point", segment_id.0))?,
                        spline
                            .controls
                            .last()
                            .copied()
                            .ok_or_else(|| format!("Spline {} has no end control point", segment_id.0))?,
                    ),
                    _ => return Err("Original segment is not a control point spline".to_string()),
                };

                let (_add_source_delta, add_scene_graph_delta) = frontend
                    .add_segment(ctx, version, sketch_id, right_ctor.clone(), None)
                    .await
                    .map_err(|e| format!("Failed to add split spline segment: {}", e.error.message()))?;
                invalidates_ids = invalidates_ids || add_scene_graph_delta.invalidates_ids;

                let new_right_segment_id = *add_scene_graph_delta
                    .new_objects
                    .iter()
                    .find(|&&id| {
                        add_scene_graph_delta
                            .new_graph
                            .objects
                            .iter()
                            .find(|obj| obj.id == id)
                            .is_some_and(|obj| {
                                matches!(
                                    obj.kind,
                                    crate::frontend::api::ObjectKind::Segment {
                                        segment: crate::frontend::sketch::Segment::ControlPointSpline(_)
                                    }
                                )
                            })
                    })
                    .ok_or_else(|| "Failed to find newly created split spline segment".to_string())?;

                let new_right_segment = add_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == new_right_segment_id)
                    .ok_or_else(|| format!("New split spline {} not found", new_right_segment_id.0))?;
                let (new_right_start_id, new_right_end_id) = match &new_right_segment.kind {
                    crate::frontend::api::ObjectKind::Segment {
                        segment: crate::frontend::sketch::Segment::ControlPointSpline(spline),
                    } => (
                        spline.controls.first().copied().ok_or_else(|| {
                            format!("New split spline {} has no start control point", new_right_segment_id.0)
                        })?,
                        spline.controls.last().copied().ok_or_else(|| {
                            format!("New split spline {} has no end control point", new_right_segment_id.0)
                        })?,
                    ),
                    _ => return Err("New split segment is not a control point spline".to_string()),
                };

                let (_edit_source_delta, edit_scene_graph_delta) = frontend
                    .edit_segments(
                        ctx,
                        version,
                        sketch_id,
                        vec![ExistingSegmentCtor {
                            id: *segment_id,
                            ctor: left_ctor.clone(),
                        }],
                    )
                    .await
                    .map_err(|e| format!("Failed to edit original split spline: {}", e.error.message()))?;
                invalidates_ids = invalidates_ids || edit_scene_graph_delta.invalidates_ids;

                let edited_left_segment = edit_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .find(|obj| obj.id == *segment_id)
                    .ok_or_else(|| format!("Edited split spline {} not found", segment_id.0))?;
                let edited_left_end_id = match &edited_left_segment.kind {
                    crate::frontend::api::ObjectKind::Segment {
                        segment: crate::frontend::sketch::Segment::ControlPointSpline(spline),
                    } => spline
                        .controls
                        .last()
                        .copied()
                        .ok_or_else(|| format!("Edited split spline {} has no end control point", segment_id.0))?,
                    _ => return Err("Edited split segment is not a control point spline".to_string()),
                };

                let constraint_segments_for =
                    |endpoint_id: ObjectId,
                     term: &TrimTermination|
                     -> Result<Vec<crate::frontend::sketch::ConstraintSegment>, String> {
                        match term {
                            TrimTermination::Intersection {
                                intersecting_seg_id, ..
                            } => Ok(vec![endpoint_id.into(), (*intersecting_seg_id).into()]),
                            TrimTermination::TrimSpawnSegmentCoincidentWithAnotherSegmentPoint {
                                other_segment_point_id,
                                ..
                            } => Ok(vec![endpoint_id.into(), (*other_segment_point_id).into()]),
                            TrimTermination::SegEndPoint { .. } => {
                                Err("Split spline termination cannot be a segment endpoint".to_string())
                            }
                        }
                    };

                let (_left_source_delta, left_scene_graph_delta) = frontend
                    .add_constraint(
                        ctx,
                        version,
                        sketch_id,
                        Constraint::Coincident(crate::frontend::sketch::Coincident {
                            segments: constraint_segments_for(edited_left_end_id, left_side)?,
                        }),
                    )
                    .await
                    .map_err(|e| format!("Failed to add left split spline coincident: {}", e.error.message()))?;
                invalidates_ids = invalidates_ids || left_scene_graph_delta.invalidates_ids;

                let (_right_source_delta, right_scene_graph_delta) = frontend
                    .add_constraint(
                        ctx,
                        version,
                        sketch_id,
                        Constraint::Coincident(crate::frontend::sketch::Coincident {
                            segments: constraint_segments_for(new_right_start_id, right_side)?,
                        }),
                    )
                    .await
                    .map_err(|e| format!("Failed to add right split spline coincident: {}", e.error.message()))?;
                invalidates_ids = invalidates_ids || right_scene_graph_delta.invalidates_ids;

                let original_end_owner_ids: std::collections::HashSet<ObjectId> = current_scene_graph_delta
                    .new_graph
                    .objects
                    .iter()
                    .filter_map(|obj| match &obj.kind {
                        crate::frontend::api::ObjectKind::Constraint {
                            constraint: Constraint::Coincident(coincident),
                        } if coincident.contains_segment(original_end_id) => coincident.segment_ids().find_map(|id| {
                            if id == original_end_id {
                                None
                            } else {
                                current_scene_graph_delta
                                    .new_graph
                                    .objects
                                    .iter()
                                    .find(|candidate| candidate.id == id)
                                    .and_then(|candidate| match &candidate.kind {
                                        crate::frontend::api::ObjectKind::Segment {
                                            segment: crate::frontend::sketch::Segment::Point(point),
                                        } => point.owner,
                                        _ => Some(id),
                                    })
                            }
                        }),
                        _ => None,
                    })
                    .collect();

                for obj in &current_scene_graph_delta.new_graph.objects {
                    let crate::frontend::api::ObjectKind::Constraint { constraint } = &obj.kind else {
                        continue;
                    };
                    if !constraint_ids_to_delete.contains(&obj.id) {
                        continue;
                    }

                    match constraint {
                        Constraint::Coincident(coincident) if coincident.contains_segment(original_end_id) => {
                            let migrated_segments = coincident
                                .segments
                                .iter()
                                .map(|segment| match segment {
                                    crate::frontend::sketch::ConstraintSegment::Segment(id)
                                        if *id == original_end_id =>
                                    {
                                        crate::frontend::sketch::ConstraintSegment::Segment(new_right_end_id)
                                    }
                                    _ => *segment,
                                })
                                .collect::<Vec<_>>();
                            let (_source_delta, migrated_scene_graph_delta) = frontend
                                .add_constraint(
                                    ctx,
                                    version,
                                    sketch_id,
                                    Constraint::Coincident(crate::frontend::sketch::Coincident {
                                        segments: migrated_segments,
                                    }),
                                )
                                .await
                                .map_err(|e| {
                                    format!("Failed to migrate split spline coincident: {}", e.error.message())
                                })?;
                            invalidates_ids = invalidates_ids || migrated_scene_graph_delta.invalidates_ids;
                        }
                        Constraint::Tangent(tangent) if tangent.input.contains(segment_id) => {
                            let other_ids = tangent
                                .input
                                .iter()
                                .copied()
                                .filter(|id| *id != *segment_id)
                                .collect::<Vec<_>>();
                            if other_ids.iter().any(|id| original_end_owner_ids.contains(id)) {
                                let (_source_delta, migrated_scene_graph_delta) = frontend
                                    .add_constraint(
                                        ctx,
                                        version,
                                        sketch_id,
                                        Constraint::Tangent(crate::frontend::sketch::Tangent {
                                            input: tangent
                                                .input
                                                .iter()
                                                .map(|id| if *id == *segment_id { new_right_segment_id } else { *id })
                                                .collect(),
                                        }),
                                    )
                                    .await
                                    .map_err(|e| {
                                        format!("Failed to migrate split spline tangent: {}", e.error.message())
                                    })?;
                                invalidates_ids = invalidates_ids || migrated_scene_graph_delta.invalidates_ids;
                            }
                        }
                        _ => {}
                    }
                }

                frontend
                    .delete_objects(ctx, version, sketch_id, constraint_ids_to_delete.clone(), Vec::new())
                    .await
                    .map_err(|e| format!("Failed to delete split spline constraints: {}", e.error.message()))
            }
        };

        match operation_result {
            Ok((source_delta, mut scene_graph_delta)) => {
                normalize_scene_graph_delta_for_internal_trim(frontend, &mut scene_graph_delta);
                // Track invalidates_ids from each operation result
                invalidates_ids = invalidates_ids || scene_graph_delta.invalidates_ids;
                last_result = Some((source_delta, scene_graph_delta.clone()));
            }
            Err(e) => {
                crate::logln!("Error executing trim operation {}: {}", op_index, e);
                // Continue to next operation
            }
        }

        op_index += consumed_ops;
    }

    let (source_delta, mut scene_graph_delta) =
        last_result.ok_or_else(|| "No operations were executed successfully".to_string())?;
    // Set invalidates_ids if any operation invalidated IDs
    scene_graph_delta.invalidates_ids = invalidates_ids;
    Ok((source_delta, scene_graph_delta))
}
