use bone_types::{
    Angle, AngleTolerance, BudgetCeiling, DegreesOfFreedom, DocumentId, EdgeId, FaceId, FeatureId,
    Length, LoopId, NewtonDamping, NewtonStepTolerance, NodeId, Parameter, ParentIndex, Point2,
    Point3, ShellId, SketchDimensionId, SketchEntityId, SketchId, SketchItemId, SketchParameterId,
    SketchPlaneBasis, SketchRelationId, SolidId, SolverResidual, Tolerance, UnitVec2, UnitVec3,
    Vec2, VertexId, WireId, degree, millimeter, radian,
};
use slotmap::Key;

#[test]
fn id_debug_surface() {
    let surface = format!(
        "FaceId::null = {:?}\n\
         EdgeId::null = {:?}\n\
         VertexId::null = {:?}\n\
         ShellId::null = {:?}\n\
         SolidId::null = {:?}\n\
         LoopId::null = {:?}\n\
         WireId::null = {:?}\n\
         FeatureId::null = {:?}\n\
         NodeId::null = {:?}\n\
         DocumentId::null = {:?}\n\
         SketchId::null = {:?}\n\
         SketchEntityId::null = {:?}\n\
         SketchRelationId::null = {:?}\n\
         SketchDimensionId::null = {:?}\n\
         SketchParameterId::null = {:?}\n\
         FaceId::default = {:?}\n\
         FaceId::null.is_null = {}\n\
         EdgeId::null.is_null = {}\n\
         SketchEntityId::null.is_null = {}",
        FaceId::null(),
        EdgeId::null(),
        VertexId::null(),
        ShellId::null(),
        SolidId::null(),
        LoopId::null(),
        WireId::null(),
        FeatureId::null(),
        NodeId::null(),
        DocumentId::null(),
        SketchId::null(),
        SketchEntityId::null(),
        SketchRelationId::null(),
        SketchDimensionId::null(),
        SketchParameterId::null(),
        FaceId::default(),
        FaceId::null().is_null(),
        EdgeId::null().is_null(),
        SketchEntityId::null().is_null(),
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn dimensioned_surface() {
    use uom::si::length::meter;
    let l = Length::new::<millimeter>(12.5);
    let a = Angle::new::<degree>(180.0);
    let t = Tolerance::new(1e-6);
    let at_direct = AngleTolerance::from_radians(1e-4);
    let at_scaled = AngleTolerance::from_arc_length(t, Length::new::<millimeter>(10.0));
    let at_zero = AngleTolerance::ZERO;
    let surface = format!(
        "length_mm_in={mm_in}\n\
         length_mm_out={mm_out}\n\
         length_m_out={m_out}\n\
         angle_deg_in=180\n\
         angle_deg_out={deg_out}\n\
         angle_rad_out={rad_out:.16}\n\
         tolerance_value={tol_val}\n\
         tolerance_display={tol_disp}\n\
         angle_tol_direct_rad={atd_rad}\n\
         angle_tol_direct_display={atd_disp}\n\
         angle_tol_scaled_rad={ats_rad}\n\
         angle_tol_zero_rad={atz_rad}",
        mm_in = 12.5,
        mm_out = l.get::<millimeter>(),
        m_out = l.get::<meter>(),
        deg_out = a.get::<degree>(),
        rad_out = a.get::<radian>(),
        tol_val = t.value(),
        tol_disp = t,
        atd_rad = at_direct.radians(),
        atd_disp = at_direct,
        ats_rad = at_scaled.radians(),
        atz_rad = at_zero.radians(),
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn space_surface() {
    let p2 = Point2::from_mm(1.5, -2.5);
    let v2 = Vec2::from_mm(3.0, 4.0);
    let p3 = Point3::from_mm(1.0, 2.0, 3.0);
    let u2x = UnitVec2::x_axis();
    let u2y = UnitVec2::y_axis();
    let ux = UnitVec3::x_axis();
    let uy = UnitVec3::y_axis();
    let uz = UnitVec3::z_axis();
    let Ok(basis) = SketchPlaneBasis::new(p3, ux, uy, Tolerance::new(1e-9)) else {
        panic!("x and y unit axes are orthogonal");
    };
    let diff = Point2::from_mm(10.0, 7.0) - Point2::from_mm(1.0, 2.0);

    let surface = format!(
        "point2_debug   = {p2:?}\n\
         point2_display = {p2}\n\
         point2_x_mm    = {p2x}\n\
         point2_y_mm    = {p2y}\n\
         vec2_debug     = {v2:?}\n\
         vec2_display   = {v2}\n\
         vec2_x_mm      = {v2x}\n\
         vec2_y_mm      = {v2y}\n\
         point2_sub_mm  = ({dx}, {dy})\n\
         vec2_perp_ccw  = {vperp}\n\
         vec2_norm_mm   = {vnorm}\n\
         unit2_x_debug  = {u2x:?}\n\
         unit2_x_display= {u2x}\n\
         unit2_y_display= {u2y}\n\
         unit2_perp_y   = ({u2px}, {u2py})\n\
         point3_debug   = {p3:?}\n\
         point3_display = {p3}\n\
         point3_x_mm    = {p3x}\n\
         point3_y_mm    = {p3y}\n\
         point3_z_mm    = {p3z}\n\
         unit_x_debug   = {ux:?}\n\
         unit_x_display = {ux}\n\
         unit_y_display = {uy}\n\
         unit_z_display = {uz}\n\
         basis_debug    = {basis:?}\n\
         basis_display  = {basis}\n\
         basis_normal   = {normal}",
        p2x = p2.x().get::<millimeter>(),
        p2y = p2.y().get::<millimeter>(),
        v2x = v2.x().get::<millimeter>(),
        v2y = v2.y().get::<millimeter>(),
        dx = diff.x().get::<millimeter>(),
        dy = diff.y().get::<millimeter>(),
        vperp = v2.perp_ccw(),
        vnorm = v2.norm_mm(),
        u2px = u2x.perp_ccw().components().0,
        u2py = u2x.perp_ccw().components().1,
        p3x = p3.x().get::<millimeter>(),
        p3y = p3.y().get::<millimeter>(),
        p3z = p3.z().get::<millimeter>(),
        normal = basis.normal(),
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn solver_surface() {
    let p = Parameter::new(0.25);
    let d = DegreesOfFreedom::new(3);
    let r = SolverResidual::new(1.5e-6);
    let damp = NewtonDamping::DEFAULT;
    let step_tol = NewtonStepTolerance::DEFAULT;
    let parent = ParentIndex::new(4);
    let budget = BudgetCeiling::FRAME_16MS;
    let item_rel = SketchItemId::Relation(SketchRelationId::default());
    let item_dim = SketchItemId::Dimension(SketchDimensionId::default());
    let item_ent = SketchItemId::Entity(SketchEntityId::default());
    let surface = format!(
        "parameter_debug   = {p:?}\n\
         parameter_display = {p}\n\
         parameter_value   = {pv}\n\
         dof_debug         = {d:?}\n\
         dof_display       = {d}\n\
         dof_value         = {dv}\n\
         residual_debug    = {r:?}\n\
         residual_display  = {r}\n\
         residual_value    = {rv}\n\
         damping_display   = {damp}\n\
         damping_value     = {damp_v}\n\
         step_tol_display  = {step_tol}\n\
         step_tol_value    = {step_tol_v}\n\
         parent_debug      = {parent:?}\n\
         parent_display    = {parent}\n\
         parent_value      = {parent_v}\n\
         budget_debug      = {budget:?}\n\
         budget_display    = {budget}\n\
         budget_duration   = {budget_d:?}\n\
         item_relation     = {item_rel}\n\
         item_dimension    = {item_dim}\n\
         item_entity       = {item_ent}",
        pv = p.value(),
        dv = d.value(),
        rv = r.value(),
        damp_v = damp.value(),
        step_tol_v = step_tol.value(),
        parent_v = parent.value(),
        budget_d = budget.duration(),
    );
    insta::assert_snapshot!(surface);
}
