use std::collections::HashMap;
use std::sync::Arc;

use ahash::AHashSet;
use ezpz::Warning;
use indexmap::IndexMap;
use kcl_api::UnitLength;
use kcl_error::SourceRange;
use uuid::Uuid;

use crate::ExecState;
use crate::KclError;
use crate::errors::KclErrorDetails;
use crate::exec::KclValue;
use crate::exec::NumericType;
use crate::exec::Sketch;
use crate::exec::UnitType;
use crate::execution::AbstractSegment;
use crate::execution::Metadata;
use crate::execution::Segment;
use crate::execution::SegmentKind;
use crate::execution::SegmentRepr;
use crate::execution::SketchSurface;
use crate::execution::UnsolvedExpr;
use crate::execution::UnsolvedSegment;
use crate::execution::UnsolvedSegmentKind;
use crate::execution::types::PrimitiveType;
use crate::execution::types::RuntimeType;
use crate::front::Freedom;
use crate::front::Object;
use crate::front::ObjectKind;
use crate::std::args::TyF64;

/// Freedom analysis results from solving a sketch constraint system. The `Vec`
/// is converted to a set to avoid quadratic runtime.
pub(super) struct FreedomAnalysis {
    pub underconstrained: AHashSet<u32>,
    /// Number of constraints that were analyzed. If 0, all variables should be considered Free.
    pub num_constraints: usize,
}

impl FreedomAnalysis {
    pub(super) fn from_ezpz_analysis(analysis: ezpz::FreedomAnalysis, num_constraints: usize) -> Self {
        let underconstrained_vec: Vec<u32> = analysis.into_underconstrained();
        let underconstrained = AHashSet::from_iter(underconstrained_vec.iter().copied());

        FreedomAnalysis {
            underconstrained,
            num_constraints,
        }
    }
}

fn solver_unit(exec_state: &ExecState) -> UnitLength {
    exec_state.length_unit()
}

pub(crate) fn solver_numeric_type(exec_state: &ExecState) -> NumericType {
    NumericType::Known(UnitType::Length(solver_unit(exec_state)))
}

/// When giving input to the solver, all numbers must be given in the same
/// units.
pub(crate) fn normalize_to_solver_distance_unit(
    value: &KclValue,
    source_range: SourceRange,
    exec_state: &mut ExecState,
    description: &str,
) -> Result<KclValue, KclError> {
    let length_ty = RuntimeType::Primitive(PrimitiveType::Number(solver_numeric_type(exec_state)));
    value.coerce(&length_ty, true, exec_state).map_err(|_| {
        KclError::new_semantic(KclErrorDetails::new(
            format!(
                "{} must be a length coercible to the module length unit {}, but found {}",
                description,
                length_ty.human_friendly_type(),
                value.human_friendly_type(),
            ),
            vec![source_range],
        ))
    })
}

/// When giving input to the solver, all numbers must be given in the same
/// units.
pub(crate) fn normalize_to_solver_angle_unit(
    value: &KclValue,
    source_range: SourceRange,
    exec_state: &mut ExecState,
    description: &str,
) -> Result<KclValue, KclError> {
    let angle_ty = RuntimeType::angle();
    value.coerce(&angle_ty, true, exec_state).map_err(|_| {
        KclError::new_semantic(KclErrorDetails::new(
            format!(
                "{} must be coercible to an angle unit {}, but found {}",
                description,
                angle_ty.human_friendly_type(),
                value.human_friendly_type(),
            ),
            vec![source_range],
        ))
    })
}

pub(super) fn substitute_sketch_vars(
    variables: IndexMap<String, KclValue>,
    surface: &SketchSurface,
    sketch_id: Uuid,
    sketch: Option<&Sketch>,
    solve_outcome: &Solved,
    solution_ty: NumericType,
    analysis: Option<&FreedomAnalysis>,
) -> Result<HashMap<String, KclValue>, KclError> {
    let sketch = sketch.cloned().map(Arc::new);
    let mut subbed = HashMap::with_capacity(variables.len());
    for (name, value) in variables {
        let subbed_value = substitute_sketch_var(
            value,
            surface,
            sketch_id,
            sketch.as_ref(),
            solve_outcome,
            solution_ty,
            analysis,
        )?;
        subbed.insert(name, subbed_value);
    }
    Ok(subbed)
}

fn substitute_sketch_var(
    value: KclValue,
    surface: &SketchSurface,
    sketch_id: Uuid,
    sketch: Option<&Arc<Sketch>>,
    solve_outcome: &Solved,
    solution_ty: NumericType,
    analysis: Option<&FreedomAnalysis>,
) -> Result<KclValue, KclError> {
    match value {
        KclValue::Uuid { .. } => Ok(value),
        KclValue::Bool { .. } => Ok(value),
        KclValue::Number { .. } => Ok(value),
        KclValue::String { .. } => Ok(value),
        KclValue::SketchVar { value: var } => {
            let Some(solution) = solve_outcome.final_values.get(var.id.0) else {
                let message = format!("No solution for sketch variable with id {}", var.id.0);
                debug_assert!(false, "{}", &message);
                return Err(KclError::new_internal(KclErrorDetails::new(
                    message,
                    var.meta.into_iter().map(|m| m.source_range).collect(),
                )));
            };
            Ok(KclValue::Number {
                value: *solution,
                ty: solution_ty,
                meta: var.meta.clone(),
            })
        }
        KclValue::SketchConstraint { .. } => {
            debug_assert!(false, "Sketch constraints should not appear in substituted values");
            Ok(value)
        }
        KclValue::Tuple { value, meta } => {
            let subbed = value
                .into_iter()
                .map(|v| substitute_sketch_var(v, surface, sketch_id, sketch, solve_outcome, solution_ty, analysis))
                .collect::<Result<Vec<_>, KclError>>()?;
            Ok(KclValue::Tuple { value: subbed, meta })
        }
        KclValue::HomArray { value, ty } => {
            let subbed = value
                .into_iter()
                .map(|v| substitute_sketch_var(v, surface, sketch_id, sketch, solve_outcome, solution_ty, analysis))
                .collect::<Result<Vec<_>, KclError>>()?;
            Ok(KclValue::HomArray { value: subbed, ty })
        }
        KclValue::Object {
            value,
            constrainable,
            object_kind,
            meta,
        } => {
            let subbed = value
                .into_iter()
                .map(|(k, v)| {
                    substitute_sketch_var(v, surface, sketch_id, sketch, solve_outcome, solution_ty, analysis)
                        .map(|v| (k, v))
                })
                .collect::<Result<HashMap<_, _>, KclError>>()?;
            Ok(KclValue::Object {
                value: subbed,
                constrainable,
                object_kind,
                meta,
            })
        }
        KclValue::TagIdentifier(_) => Ok(value),
        KclValue::TagDeclarator(_) => Ok(value),
        KclValue::GdtAnnotation { .. } => Ok(value),
        KclValue::Plane { .. } => Ok(value),
        KclValue::Face { .. } => Ok(value),
        KclValue::Segment {
            value: abstract_segment,
        } => match abstract_segment.repr {
            SegmentRepr::Unsolved { segment } => {
                let subbed = substitute_sketch_var_in_segment(
                    *segment,
                    surface,
                    sketch_id,
                    sketch.cloned(),
                    solve_outcome,
                    solution_ty,
                    analysis,
                )?;
                Ok(KclValue::Segment {
                    value: Box::new(AbstractSegment {
                        repr: SegmentRepr::Solved {
                            segment: Box::new(subbed),
                        },
                        meta: abstract_segment.meta,
                    }),
                })
            }
            SegmentRepr::Solved { .. } => Ok(KclValue::Segment {
                value: abstract_segment,
            }),
        },
        KclValue::Sketch { .. } => Ok(value),
        KclValue::Solid { .. } => Ok(value),
        KclValue::Helix { .. } => Ok(value),
        KclValue::ImportedGeometry(_) => Ok(value),
        KclValue::Function { .. } => Ok(value),
        KclValue::Module { .. } => Ok(value),
        KclValue::Type { .. } => Ok(value),
        KclValue::KclNone { .. } => Ok(value),
        KclValue::BoundedEdge { .. } => Ok(value),
    }
}

pub(super) fn substitute_sketch_var_in_segment(
    segment: UnsolvedSegment,
    surface: &SketchSurface,
    sketch_id: Uuid,
    sketch: Option<Arc<Sketch>>,
    solve_outcome: &Solved,
    solution_ty: NumericType,
    analysis: Option<&FreedomAnalysis>,
) -> Result<Segment, KclError> {
    let srs = segment.meta.iter().map(|m| m.source_range).collect::<Vec<_>>();
    match &segment.kind {
        UnsolvedSegmentKind::Point { position, ctor } => {
            let (position_x, position_x_freedom) =
                substitute_sketch_var_in_unsolved_expr(&position[0], solve_outcome, solution_ty, analysis, &srs)?;
            let (position_y, position_y_freedom) =
                substitute_sketch_var_in_unsolved_expr(&position[1], solve_outcome, solution_ty, analysis, &srs)?;
            let position = [position_x, position_y];
            Ok(Segment {
                id: segment.id,
                object_id: segment.object_id,
                kind: SegmentKind::Point {
                    position,
                    ctor: ctor.clone(),
                    freedom: point_freedom(position_x_freedom, position_y_freedom),
                },
                surface: surface.clone(),
                sketch_id,
                sketch,
                tag: segment.tag,
                node_path: segment.node_path,
                meta: segment.meta,
            })
        }
        UnsolvedSegmentKind::Line {
            start,
            end,
            ctor,
            start_object_id,
            end_object_id,
            construction,
        } => {
            let (start_x, start_x_freedom) =
                substitute_sketch_var_in_unsolved_expr(&start[0], solve_outcome, solution_ty, analysis, &srs)?;
            let (start_y, start_y_freedom) =
                substitute_sketch_var_in_unsolved_expr(&start[1], solve_outcome, solution_ty, analysis, &srs)?;
            let (end_x, end_x_freedom) =
                substitute_sketch_var_in_unsolved_expr(&end[0], solve_outcome, solution_ty, analysis, &srs)?;
            let (end_y, end_y_freedom) =
                substitute_sketch_var_in_unsolved_expr(&end[1], solve_outcome, solution_ty, analysis, &srs)?;
            let start = [start_x, start_y];
            let end = [end_x, end_y];
            Ok(Segment {
                id: segment.id,
                object_id: segment.object_id,
                kind: SegmentKind::Line {
                    start,
                    end,
                    ctor: ctor.clone(),
                    start_object_id: *start_object_id,
                    end_object_id: *end_object_id,
                    start_freedom: point_freedom(start_x_freedom, start_y_freedom),
                    end_freedom: point_freedom(end_x_freedom, end_y_freedom),
                    construction: *construction,
                },
                surface: surface.clone(),
                sketch_id,
                sketch,
                tag: segment.tag,
                node_path: segment.node_path,
                meta: segment.meta,
            })
        }
        UnsolvedSegmentKind::Arc {
            start,
            end,
            center,
            ctor,
            start_object_id,
            end_object_id,
            center_object_id,
            construction,
        } => {
            let (start_x, start_x_freedom) =
                substitute_sketch_var_in_unsolved_expr(&start[0], solve_outcome, solution_ty, analysis, &srs)?;
            let (start_y, start_y_freedom) =
                substitute_sketch_var_in_unsolved_expr(&start[1], solve_outcome, solution_ty, analysis, &srs)?;
            let (end_x, end_x_freedom) =
                substitute_sketch_var_in_unsolved_expr(&end[0], solve_outcome, solution_ty, analysis, &srs)?;
            let (end_y, end_y_freedom) =
                substitute_sketch_var_in_unsolved_expr(&end[1], solve_outcome, solution_ty, analysis, &srs)?;
            let (center_x, center_x_freedom) =
                substitute_sketch_var_in_unsolved_expr(&center[0], solve_outcome, solution_ty, analysis, &srs)?;
            let (center_y, center_y_freedom) =
                substitute_sketch_var_in_unsolved_expr(&center[1], solve_outcome, solution_ty, analysis, &srs)?;
            let start = [start_x, start_y];
            let end = [end_x, end_y];
            let center = [center_x, center_y];
            Ok(Segment {
                id: segment.id,
                object_id: segment.object_id,
                kind: SegmentKind::Arc {
                    start,
                    end,
                    center,
                    ctor: ctor.clone(),
                    start_object_id: *start_object_id,
                    end_object_id: *end_object_id,
                    center_object_id: *center_object_id,
                    start_freedom: point_freedom(start_x_freedom, start_y_freedom),
                    end_freedom: point_freedom(end_x_freedom, end_y_freedom),
                    center_freedom: point_freedom(center_x_freedom, center_y_freedom),
                    construction: *construction,
                },
                surface: surface.clone(),
                sketch_id,
                sketch,
                tag: segment.tag,
                node_path: segment.node_path,
                meta: segment.meta,
            })
        }
        UnsolvedSegmentKind::Circle {
            start,
            center,
            ctor,
            start_object_id,
            center_object_id,
            construction,
        } => {
            let (start_x, start_x_freedom) =
                substitute_sketch_var_in_unsolved_expr(&start[0], solve_outcome, solution_ty, analysis, &srs)?;
            let (start_y, start_y_freedom) =
                substitute_sketch_var_in_unsolved_expr(&start[1], solve_outcome, solution_ty, analysis, &srs)?;
            let (center_x, center_x_freedom) =
                substitute_sketch_var_in_unsolved_expr(&center[0], solve_outcome, solution_ty, analysis, &srs)?;
            let (center_y, center_y_freedom) =
                substitute_sketch_var_in_unsolved_expr(&center[1], solve_outcome, solution_ty, analysis, &srs)?;
            let start = [start_x, start_y];
            let center = [center_x, center_y];
            Ok(Segment {
                id: segment.id,
                object_id: segment.object_id,
                kind: SegmentKind::Circle {
                    start,
                    center,
                    ctor: ctor.clone(),
                    start_object_id: *start_object_id,
                    center_object_id: *center_object_id,
                    start_freedom: point_freedom(start_x_freedom, start_y_freedom),
                    center_freedom: point_freedom(center_x_freedom, center_y_freedom),
                    construction: *construction,
                },
                surface: surface.clone(),
                sketch_id,
                sketch,
                tag: segment.tag,
                node_path: segment.node_path,
                meta: segment.meta,
            })
        }
        UnsolvedSegmentKind::ControlPointSpline {
            controls,
            ctor,
            control_object_ids,
            control_polygon_edge_object_ids,
            degree,
            construction,
        } => {
            let mut solved_controls = Vec::with_capacity(controls.len());
            let mut control_freedoms = Vec::with_capacity(controls.len());
            for control in controls {
                let (x, x_freedom) =
                    substitute_sketch_var_in_unsolved_expr(&control[0], solve_outcome, solution_ty, analysis, &srs)?;
                let (y, y_freedom) =
                    substitute_sketch_var_in_unsolved_expr(&control[1], solve_outcome, solution_ty, analysis, &srs)?;
                solved_controls.push([x, y]);
                control_freedoms.push(point_freedom(x_freedom, y_freedom));
            }

            Ok(Segment {
                id: segment.id,
                object_id: segment.object_id,
                kind: SegmentKind::ControlPointSpline {
                    controls: solved_controls,
                    ctor: ctor.clone(),
                    control_object_ids: control_object_ids.clone(),
                    control_polygon_edge_object_ids: control_polygon_edge_object_ids.clone(),
                    control_freedoms,
                    degree: *degree,
                    construction: *construction,
                },
                surface: surface.clone(),
                sketch_id,
                sketch,
                tag: segment.tag,
                node_path: segment.node_path,
                meta: segment.meta,
            })
        }
    }
}

fn substitute_sketch_var_in_unsolved_expr(
    unsolved_expr: &UnsolvedExpr,
    solve_outcome: &Solved,
    solution_ty: NumericType,
    analysis: Option<&FreedomAnalysis>,
    source_ranges: &[SourceRange],
) -> Result<(TyF64, Option<Freedom>), KclError> {
    match unsolved_expr {
        UnsolvedExpr::Known(n) => Ok((n.clone(), Some(Freedom::Fixed))),
        UnsolvedExpr::Unknown(var_id) => {
            let Some(solution) = solve_outcome.final_values.get(var_id.0) else {
                let message = format!("No solution for sketch variable with id {}", var_id.0);
                debug_assert!(false, "{}", &message);
                return Err(KclError::new_internal(KclErrorDetails::new(
                    message,
                    source_ranges.to_vec(),
                )));
            };
            let freedom = if solve_outcome.variables_in_conflicts.contains(&(var_id.0 as ezpz::Id)) {
                Some(Freedom::Conflict)
            } else if let Some(analysis) = analysis {
                let solver_var_id = var_id.to_constraint_id(source_ranges.first().copied().unwrap_or_default())?;
                // Special case: if there are 0 constraints, ALL variables are underconstrained (Free)
                let is_underconstrained = if analysis.num_constraints == 0 {
                    true
                } else {
                    analysis.underconstrained.contains(&solver_var_id)
                };
                if is_underconstrained {
                    Some(Freedom::Free)
                } else {
                    Some(Freedom::Fixed)
                }
            } else {
                // We didn't do the freedom analysis, so we don't know.
                None
            };
            Ok((TyF64::new(*solution, solution_ty), freedom))
        }
    }
}

pub(crate) struct Solved {
    /// Each variable's final value.
    pub(crate) final_values: Vec<f64>,
    /// How many iterations of Newton's method were required?
    #[expect(dead_code, reason = "ezpz provides this info, but we aren't using it yet")]
    pub(crate) iterations: usize,
    /// Anything that went wrong either in problem definition or during solving it.
    pub(crate) warnings: Vec<Warning>,
    /// What is the lowest priority that got solved?
    /// 0 is the highest priority. Larger numbers are lower priority.
    #[expect(dead_code, reason = "ezpz provides this info, but we aren't using it yet")]
    pub(crate) priority_solved: u32,
    /// Variables involved in unsatisfied constraints (for conflict detection)
    pub(crate) variables_in_conflicts: AHashSet<ezpz::Id>,
    /// Did the solver converge on a solution?
    pub(crate) converged: bool,
}

impl Solved {
    /// Create a Solved from a ezpz::SolveOutcome, building the set of variables
    /// involved in unsatisfied constraints by examining the original constraints.
    /// Only marks variables from required constraints (not optional) as conflicted.
    pub(crate) fn from_ezpz_outcome(
        value: ezpz::SolveOutcome,
        constraints: &[ezpz::Constraint],
        num_required_constraints: usize,
    ) -> Self {
        // Build a set of variables involved in unsatisfied constraints
        // Only include required constraints (not optional ones like from dragging)
        let mut variables_in_conflicts = AHashSet::new();
        for &constraint_idx in value.unsatisfied() {
            // Only mark as conflicted if it's a required constraint, not an optional one
            if constraint_idx < num_required_constraints
                && let Some(constraint) = constraints.get(constraint_idx)
            {
                constraint.extend_associated_variable_ids(&mut variables_in_conflicts);
            }
        }

        Self {
            final_values: value.final_values().to_owned(),
            iterations: value.iterations(),
            warnings: value.warnings().to_owned(),
            priority_solved: value.priority_solved(),
            variables_in_conflicts,
            converged: value.converged(),
        }
    }
}

/// Create the freedom for a 2D point by merging two optional `Freedom` values.
/// None represents unknown. If both are Some, merges them using
/// [`Freedom::merge`]. If one is None, returns the other *only if* it's not
/// `Fixed`. We don't want to communicate that a point is well-constrained if we
/// don't actually know.
fn point_freedom(x: Option<Freedom>, y: Option<Freedom>) -> Option<Freedom> {
    match (x, y) {
        (Some(x), Some(y)) => Some(x.merge(y)),
        (Some(f), None) | (None, Some(f)) => match f {
            Freedom::Fixed => None,
            Freedom::Conflict | Freedom::Free => Some(f),
        },
        (None, None) => None,
    }
}

pub(super) fn create_segment_scene_objects(
    segments: &[Segment],
    sketch_block_range: SourceRange,
    exec_state: &mut ExecState,
) -> Result<Vec<Object>, KclError> {
    let mut scene_objects = Vec::with_capacity(segments.len());
    for segment in segments {
        let source = Metadata::to_source_ref(&segment.meta, segment.node_path.clone());

        match &segment.kind {
            SegmentKind::Point {
                position,
                ctor,
                freedom,
            } => {
                let final_freedom = freedom.unwrap_or(Freedom::Free);
                let point2d = TyF64::to_point2d(position).map_err(|_| {
                    KclError::new_internal(KclErrorDetails::new(
                        format!("Error converting start point runtime type to API value: {:?}", position),
                        vec![sketch_block_range],
                    ))
                })?;
                let artifact_id = exec_state.next_artifact_id();
                let point_object = Object {
                    id: segment.object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Point(crate::front::Point {
                            position: point2d.clone(),
                            ctor: Some(crate::front::PointCtor {
                                position: ctor.position.clone(),
                            }),
                            owner: None,
                            freedom: final_freedom,
                            constraints: Vec::new(),
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id,
                    source: source.clone(),
                };
                scene_objects.push(point_object);
            }
            SegmentKind::Line {
                start,
                end,
                ctor,
                start_object_id,
                end_object_id,
                start_freedom,
                end_freedom,
                construction,
            } => {
                let start_final_freedom = start_freedom.unwrap_or(Freedom::Free);
                let end_final_freedom = end_freedom.unwrap_or(Freedom::Free);
                let start_point2d = TyF64::to_point2d(start).map_err(|_| {
                    KclError::new_internal(KclErrorDetails::new(
                        format!("Error converting start point runtime type to API value: {:?}", start),
                        vec![sketch_block_range],
                    ))
                })?;
                let start_artifact_id = exec_state.next_artifact_id();
                let start_point_object = Object {
                    id: *start_object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Point(crate::front::Point {
                            position: start_point2d.clone(),
                            ctor: None,
                            owner: Some(segment.object_id),
                            freedom: start_final_freedom,
                            constraints: Vec::new(),
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: start_artifact_id,
                    source: source.clone(),
                };
                let start_point_object_id = start_point_object.id;
                scene_objects.push(start_point_object);

                let end_point2d = TyF64::to_point2d(end).map_err(|_| {
                    KclError::new_internal(KclErrorDetails::new(
                        format!("Error converting end point runtime type to API value: {:?}", end),
                        vec![sketch_block_range],
                    ))
                })?;
                let end_artifact_id = exec_state.next_artifact_id();
                let end_point_object = Object {
                    id: *end_object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Point(crate::front::Point {
                            position: end_point2d.clone(),
                            ctor: None,
                            owner: Some(segment.object_id),
                            freedom: end_final_freedom,
                            constraints: Vec::new(),
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: end_artifact_id,
                    source: source.clone(),
                };
                let end_point_object_id = end_point_object.id;
                scene_objects.push(end_point_object);

                let line_artifact_id = exec_state.next_artifact_id();
                let segment_object = Object {
                    id: segment.object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Line(crate::front::Line {
                            start: start_point_object_id,
                            end: end_point_object_id,
                            owner: None,
                            ctor: crate::front::SegmentCtor::Line(ctor.as_ref().clone()),
                            ctor_applicable: true,
                            construction: *construction,
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: line_artifact_id,
                    source,
                };
                scene_objects.push(segment_object);
            }
            SegmentKind::Arc {
                start,
                end,
                center,
                ctor,
                start_object_id,
                end_object_id,
                center_object_id,
                start_freedom,
                end_freedom,
                center_freedom,
                construction,
            } => {
                let start_final_freedom = start_freedom.unwrap_or(Freedom::Free);
                let end_final_freedom = end_freedom.unwrap_or(Freedom::Free);
                let center_final_freedom = center_freedom.unwrap_or(Freedom::Free);
                let start_point2d = TyF64::to_point2d(start).map_err(|_| {
                    KclError::new_internal(KclErrorDetails::new(
                        format!("Error converting start point runtime type to API value: {:?}", start),
                        vec![sketch_block_range],
                    ))
                })?;
                let start_artifact_id = exec_state.next_artifact_id();
                let start_point_object = Object {
                    id: *start_object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Point(crate::front::Point {
                            position: start_point2d.clone(),
                            ctor: None,
                            owner: Some(segment.object_id),
                            freedom: start_final_freedom,
                            constraints: Vec::new(),
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: start_artifact_id,
                    source: source.clone(),
                };
                let start_point_object_id = start_point_object.id;
                scene_objects.push(start_point_object);

                let end_point2d = TyF64::to_point2d(end).map_err(|_| {
                    KclError::new_internal(KclErrorDetails::new(
                        format!("Error converting end point runtime type to API value: {:?}", end),
                        vec![sketch_block_range],
                    ))
                })?;
                let end_artifact_id = exec_state.next_artifact_id();
                let end_point_object = Object {
                    id: *end_object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Point(crate::front::Point {
                            position: end_point2d.clone(),
                            ctor: None,
                            owner: Some(segment.object_id),
                            freedom: end_final_freedom,
                            constraints: Vec::new(),
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: end_artifact_id,
                    source: source.clone(),
                };
                let end_point_object_id = end_point_object.id;
                scene_objects.push(end_point_object);

                let center_point2d = TyF64::to_point2d(center).map_err(|_| {
                    KclError::new_internal(KclErrorDetails::new(
                        format!("Error converting center point runtime type to API value: {:?}", center),
                        vec![sketch_block_range],
                    ))
                })?;
                let center_artifact_id = exec_state.next_artifact_id();
                let center_point_object = Object {
                    id: *center_object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Point(crate::front::Point {
                            position: center_point2d.clone(),
                            ctor: None,
                            owner: Some(segment.object_id),
                            freedom: center_final_freedom,
                            constraints: Vec::new(),
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: center_artifact_id,
                    source: source.clone(),
                };
                let center_point_object_id = center_point_object.id;
                scene_objects.push(center_point_object);

                let arc_artifact_id = exec_state.next_artifact_id();
                let segment_object = Object {
                    id: segment.object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Arc(crate::front::Arc {
                            start: start_point_object_id,
                            end: end_point_object_id,
                            center: center_point_object_id,
                            ctor: crate::front::SegmentCtor::Arc(ctor.as_ref().clone()),
                            ctor_applicable: true,
                            construction: *construction,
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: arc_artifact_id,
                    source,
                };
                scene_objects.push(segment_object);
            }
            SegmentKind::Circle {
                start,
                center,
                ctor,
                start_object_id,
                center_object_id,
                start_freedom,
                center_freedom,
                construction,
            } => {
                let start_final_freedom = start_freedom.unwrap_or(Freedom::Free);
                let center_final_freedom = center_freedom.unwrap_or(Freedom::Free);
                let start_point2d = TyF64::to_point2d(start).map_err(|_| {
                    KclError::new_internal(KclErrorDetails::new(
                        format!("Error converting start point runtime type to API value: {:?}", start),
                        vec![sketch_block_range],
                    ))
                })?;
                let start_artifact_id = exec_state.next_artifact_id();
                let start_point_object = Object {
                    id: *start_object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Point(crate::front::Point {
                            position: start_point2d.clone(),
                            ctor: None,
                            owner: Some(segment.object_id),
                            freedom: start_final_freedom,
                            constraints: Vec::new(),
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: start_artifact_id,
                    source: source.clone(),
                };
                let start_point_object_id = start_point_object.id;
                scene_objects.push(start_point_object);

                let center_point2d = TyF64::to_point2d(center).map_err(|_| {
                    KclError::new_internal(KclErrorDetails::new(
                        format!("Error converting center point runtime type to API value: {:?}", center),
                        vec![sketch_block_range],
                    ))
                })?;
                let center_artifact_id = exec_state.next_artifact_id();
                let center_point_object = Object {
                    id: *center_object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Point(crate::front::Point {
                            position: center_point2d.clone(),
                            ctor: None,
                            owner: Some(segment.object_id),
                            freedom: center_final_freedom,
                            constraints: Vec::new(),
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: center_artifact_id,
                    source: source.clone(),
                };
                let center_point_object_id = center_point_object.id;
                scene_objects.push(center_point_object);

                let circle_artifact_id = exec_state.next_artifact_id();
                let segment_object = Object {
                    id: segment.object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::Circle(crate::front::Circle {
                            start: start_point_object_id,
                            center: center_point_object_id,
                            ctor: crate::front::SegmentCtor::Circle(ctor.as_ref().clone()),
                            ctor_applicable: true,
                            construction: *construction,
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: circle_artifact_id,
                    source,
                };
                scene_objects.push(segment_object);
            }
            SegmentKind::ControlPointSpline {
                controls,
                ctor,
                control_object_ids,
                control_polygon_edge_object_ids,
                control_freedoms,
                degree,
                construction,
            } => {
                let mut control_point_object_ids = Vec::with_capacity(controls.len());

                for ((control, control_object_id), control_freedom) in controls
                    .iter()
                    .zip(control_object_ids.iter())
                    .zip(control_freedoms.iter())
                {
                    let control_point2d = TyF64::to_point2d(control).map_err(|_| {
                        KclError::new_internal(KclErrorDetails::new(
                            format!(
                                "Error converting control point runtime type to API value: {:?}",
                                control
                            ),
                            vec![sketch_block_range],
                        ))
                    })?;
                    let artifact_id = exec_state.next_artifact_id();
                    let point_object = Object {
                        id: *control_object_id,
                        kind: ObjectKind::Segment {
                            segment: crate::front::Segment::Point(crate::front::Point {
                                position: control_point2d,
                                ctor: None,
                                owner: Some(segment.object_id),
                                freedom: control_freedom.unwrap_or(Freedom::Free),
                                constraints: Vec::new(),
                            }),
                        },
                        label: Default::default(),
                        comments: Default::default(),
                        artifact_id,
                        source: source.clone(),
                    };
                    control_point_object_ids.push(point_object.id);
                    scene_objects.push(point_object);
                }

                for (index, edge_object_id) in control_polygon_edge_object_ids.iter().enumerate() {
                    let edge_artifact_id = exec_state.next_artifact_id();
                    let edge_object = Object {
                        id: *edge_object_id,
                        kind: ObjectKind::Segment {
                            segment: crate::front::Segment::Line(crate::front::Line {
                                start: control_point_object_ids[index],
                                end: control_point_object_ids[index + 1],
                                owner: Some(segment.object_id),
                                ctor: crate::front::SegmentCtor::Line(crate::front::LineCtor {
                                    start: ctor.points[index].clone(),
                                    end: ctor.points[index + 1].clone(),
                                    construction: Some(*construction),
                                }),
                                ctor_applicable: false,
                                construction: *construction,
                            }),
                        },
                        label: Default::default(),
                        comments: Default::default(),
                        artifact_id: edge_artifact_id,
                        source: source.clone(),
                    };
                    scene_objects.push(edge_object);
                }

                let artifact_id = exec_state.next_artifact_id();
                let segment_object = Object {
                    id: segment.object_id,
                    kind: ObjectKind::Segment {
                        segment: crate::front::Segment::ControlPointSpline(crate::front::ControlPointSpline {
                            controls: control_point_object_ids,
                            degree: *degree,
                            ctor: crate::front::SegmentCtor::ControlPointSpline(ctor.as_ref().clone()),
                            ctor_applicable: true,
                            construction: *construction,
                        }),
                    },
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id,
                    source,
                };
                scene_objects.push(segment_object);
            }
        }
    }
    Ok(scene_objects)
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use indexmap::IndexMap;
    use kcl_api::UnitLength;
    use uuid::Uuid;

    use super::*;
    use crate::execution::ArtifactId;
    use crate::execution::BasePath;
    use crate::execution::GeoMeta;
    use crate::execution::Plane;
    use crate::execution::PlaneInfo;
    use crate::execution::PlaneKind;
    use crate::execution::ProfileClosed;
    use crate::execution::types::NumericTypeExt;
    use crate::front::Expr;
    use crate::front::LineCtor;
    use crate::front::Number;
    use crate::front::ObjectId;
    use crate::front::Point2d;
    use crate::std::sketch::PlaneData;

    fn test_point(x: f64, y: f64) -> Point2d<Expr> {
        Point2d {
            x: Expr::Var(Number::from((x, UnitLength::Millimeters))),
            y: Expr::Var(Number::from((y, UnitLength::Millimeters))),
        }
    }

    fn test_surface() -> SketchSurface {
        let id = Uuid::new_v4();
        SketchSurface::Plane(Box::new(Plane {
            id,
            artifact_id: ArtifactId::new(id),
            object_id: None,
            kind: PlaneKind::XY,
            info: PlaneInfo::try_from(PlaneData::XY).unwrap(),
            meta: vec![],
        }))
    }

    fn test_sketch(surface: SketchSurface) -> Sketch {
        let id = Uuid::new_v4();
        let base = BasePath {
            from: [0.0, 0.0],
            to: [0.0, 0.0],
            units: UnitLength::Millimeters,
            tag: None,
            geo_meta: GeoMeta {
                id,
                metadata: Metadata {
                    source_range: SourceRange::default(),
                },
            },
        };
        Sketch {
            id,
            paths: vec![],
            inner_paths: vec![],
            on: surface,
            start: base,
            tags: Default::default(),
            artifact_id: ArtifactId::new(id),
            original_id: id,
            origin_sketch_id: None,
            mirror: None,
            clone: None,
            synthetic_jump_path_ids: vec![],
            units: UnitLength::Millimeters,
            meta: vec![],
            is_closed: ProfileClosed::No,
        }
    }

    fn test_line_value(object_id_seed: usize) -> KclValue {
        let point = |x, y| {
            [
                UnsolvedExpr::Known(TyF64::new(x, NumericType::mm())),
                UnsolvedExpr::Known(TyF64::new(y, NumericType::mm())),
            ]
        };
        let segment = UnsolvedSegment {
            id: Uuid::new_v4(),
            object_id: ObjectId(object_id_seed),
            kind: UnsolvedSegmentKind::Line {
                start: point(0.0, 0.0),
                end: point(1.0, 0.0),
                ctor: Box::new(LineCtor {
                    start: test_point(0.0, 0.0),
                    end: test_point(1.0, 0.0),
                    construction: None,
                }),
                start_object_id: ObjectId(object_id_seed + 1),
                end_object_id: ObjectId(object_id_seed + 2),
                construction: false,
            },
            tag: None,
            node_path: None,
            meta: vec![],
        };

        KclValue::Segment {
            value: Box::new(AbstractSegment {
                repr: SegmentRepr::Unsolved {
                    segment: Box::new(segment),
                },
                meta: vec![],
            }),
        }
    }

    fn segment_sketch(value: &KclValue) -> &Arc<Sketch> {
        let KclValue::Segment { value } = value else {
            panic!("expected segment");
        };
        let SegmentRepr::Solved { segment } = &value.repr else {
            panic!("expected solved segment");
        };
        segment.sketch.as_ref().expect("expected segment sketch")
    }

    #[test]
    fn substitute_sketch_vars_shares_one_sketch_across_segments() {
        let surface = test_surface();
        let sketch = test_sketch(surface.clone());
        let mut variables = IndexMap::new();
        variables.insert("line1".to_owned(), test_line_value(1));
        variables.insert("line2".to_owned(), test_line_value(4));
        let solved = Solved {
            final_values: vec![],
            iterations: 0,
            warnings: vec![],
            priority_solved: 0,
            variables_in_conflicts: AHashSet::new(),
            converged: true,
        };

        let substituted = substitute_sketch_vars(
            variables,
            &surface,
            sketch.id,
            Some(&sketch),
            &solved,
            NumericType::mm(),
            None,
        )
        .unwrap();

        let line1_sketch = segment_sketch(&substituted["line1"]);
        let line2_sketch = segment_sketch(&substituted["line2"]);
        assert!(Arc::ptr_eq(line1_sketch, line2_sketch));
        assert_eq!(line1_sketch.id, sketch.id);
    }
}
