use bone_types::{
    Aabb3, Angle, AngleTolerance, AxisAngle, BodyId, BrepEdgeId, BrepFaceId, BrepLoopId,
    BrepShellId, BrepVertexId, BudgetCeiling, Camera3, ChordHeightTolerance, DegreesOfFreedom,
    DisplayMode, DocumentId, EdgeFingerprint, EdgeId, EdgeLabel, EdgeRole, EntityRef, ExtrudeId,
    FaceFingerprint, FaceId, FaceLabel, FaceRef, FaceRole, FeatureGeneration, FeatureId,
    ImportOrdinal, Length, LoopId, LoopIndex, MatchScore, NewtonDamping, NewtonStepTolerance,
    NodeId, OrbitState, OrientedBox3, Parameter, ParentIndex, Plane3, Point2, Point3, Projection,
    RebuildError, RebuildStatus, RebuildWarning, Resolution, RollbackMarker, ShadingModel, ShellId,
    SideKind, SketchDimensionId, SketchEntityId, SketchId, SketchItemId, SketchParameterId,
    SketchPlaneBasis, SketchRelationId, SolidId, SolverResidual, StandardView, StepEntityId,
    StepFileHeader, StepFileName, StepOrganization, StepOriginatingSystem, StepSchema,
    SuppressionState, Tolerance, UnitVec2, UnitVec3, Vec2, Vec3, VertexFingerprint, VertexId,
    VertexLabel, VertexRole, WireId, ZoomFactor, 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\
         ExtrudeId::null = {:?}\n\
         BodyId::null = {:?}\n\
         BrepShellId::null = {:?}\n\
         BrepFaceId::null = {:?}\n\
         BrepEdgeId::null = {:?}\n\
         BrepVertexId::null = {:?}\n\
         BrepLoopId::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(),
        ExtrudeId::null(),
        BodyId::null(),
        BrepShellId::null(),
        BrepFaceId::null(),
        BrepEdgeId::null(),
        BrepVertexId::null(),
        BrepLoopId::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);
}

#[test]
fn space3_surface() {
    let v3 = Vec3::from_mm(3.0, 0.0, 4.0);
    let unit = UnitVec3::z_axis();
    let bb = Aabb3::from_corners(
        Point3::from_mm(2.0, -1.0, 5.0),
        Point3::from_mm(-3.0, 4.0, 0.0),
    );
    let chord = ChordHeightTolerance::from_mm(0.01);
    let cross_z = Vec3::from_mm(1.0, 0.0, 0.0)
        .cross(Vec3::from_mm(0.0, 1.0, 0.0))
        .z()
        .get::<millimeter>();
    let surface = format!(
        "vec3_debug      = {v3:?}\n\
         vec3_display    = {v3}\n\
         vec3_norm_mm    = {vn}\n\
         vec3_cross_z    = {cross_z}\n\
         unit3_reversed  = {urev}\n\
         unit3_into_vec5 = {uvec}\n\
         aabb_debug      = {bb:?}\n\
         aabb_display    = {bb}\n\
         aabb_center     = {center}\n\
         aabb_extent     = {extent}\n\
         chord_display   = {chord}\n\
         chord_mm        = {chmm}",
        vn = v3.norm_mm(),
        urev = unit.reversed(),
        uvec = unit.into_vec(Length::new::<millimeter>(5.0)),
        center = bb.center(),
        extent = bb.extent(),
        chmm = chord.millimeters(),
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn geometry3_surface() {
    let Ok(plane) = Plane3::new(
        Point3::from_mm(1.0, 0.0, 0.0),
        UnitVec3::x_axis(),
        UnitVec3::y_axis(),
        Tolerance::new(1e-9),
    ) else {
        panic!("orthonormal axes");
    };
    let axis_angle = AxisAngle::new(UnitVec3::z_axis(), Angle::new::<radian>(0.5));
    let obox = OrientedBox3::from_aabb(Aabb3::from_corners(
        Point3::from_mm(-2.0, -4.0, 0.0),
        Point3::from_mm(2.0, 4.0, 10.0),
    ));
    let surface = format!(
        "plane_debug     = {plane:?}\n\
         plane_display   = {plane}\n\
         plane_normal    = {pn}\n\
         axisangle_debug = {axis_angle:?}\n\
         axisangle_disp  = {axis_angle}\n\
         obox_debug      = {obox:?}\n\
         obox_display    = {obox}\n\
         obox_center     = {oc}\n\
         obox_half       = {oh}",
        pn = plane.normal(),
        oc = obox.center(),
        oh = obox.half_extents(),
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn label_surface() {
    let feature = FeatureId::default();
    let from = SketchEntityId::default();
    let face_start = FaceLabel {
        feature,
        role: FaceRole::StartCap,
    };
    let face_side = FaceLabel {
        feature,
        role: FaceRole::Side {
            loop_index: LoopIndex::OUTER,
            from,
        },
    };
    let face_imp = FaceLabel {
        feature,
        role: FaceRole::Imported {
            ordinal: ImportOrdinal::new(7),
        },
    };
    let edge_side = EdgeLabel {
        feature,
        role: EdgeRole::SideEdge {
            from,
            side: SideKind::Seam,
        },
    };
    let vert_end = VertexLabel {
        feature,
        role: VertexRole::EndCapVertex {
            from,
            side: SideKind::Corner,
        },
    };
    let surface = format!(
        "loop_index_disp = {li}\n\
         loop_outer_disp = {lo}\n\
         import_ordinal  = {io}\n\
         side_kind_corner= {skc}\n\
         side_kind_seam  = {sks}\n\
         face_start_disp = {face_start}\n\
         face_start_dbg  = {face_start:?}\n\
         face_side_disp  = {face_side}\n\
         face_imp_disp   = {face_imp}\n\
         edge_side_disp  = {edge_side}\n\
         vertex_end_disp = {vert_end}",
        li = LoopIndex::new(2),
        lo = LoopIndex::OUTER,
        io = ImportOrdinal::new(7),
        skc = SideKind::Corner,
        sks = SideKind::Seam,
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn camera_surface() {
    let Ok(ortho) = Projection::orthographic(Length::new::<millimeter>(25.0)) else {
        panic!("25 mm half-height is positive");
    };
    let Ok(persp) = Projection::perspective(
        Angle::new::<degree>(60.0),
        Length::new::<millimeter>(1.0),
        Length::new::<millimeter>(1000.0),
    ) else {
        panic!("valid perspective frustum");
    };
    let Ok(camera) = Camera3::new(
        Point3::from_mm(0.0, 0.0, 10.0),
        Point3::origin(),
        UnitVec3::y_axis(),
        persp,
    ) else {
        panic!("eye and target are 10 mm apart");
    };
    let Ok(zoom) = ZoomFactor::new(2.5) else {
        panic!("positive zoom");
    };
    let orbit_id = OrbitState::identity();
    let Ok(orbit_rot) = OrbitState::new(
        AxisAngle::new(
            UnitVec3::z_axis(),
            Angle::new::<radian>(core::f64::consts::FRAC_PI_2),
        ),
        zoom,
        Point3::from_mm(1.0, 2.0, 3.0),
    ) else {
        panic!("finite pan target");
    };
    let surface = format!(
        "ortho_debug      = {ortho:?}\n\
         ortho_display    = {ortho}\n\
         persp_debug      = {persp:?}\n\
         persp_display    = {persp}\n\
         camera_debug     = {camera:?}\n\
         camera_display   = {camera}\n\
         zoom_display     = {zoom}\n\
         orbit_id_debug   = {orbit_id:?}\n\
         orbit_id_display = {orbit_id}\n\
         orbit_rot_debug  = {orbit_rot:?}\n\
         orbit_rot_disp   = {orbit_rot}\n\
         view_front       = {vf}\n\
         view_isometric   = {vi}\n\
         view_normal_to   = {vn}",
        zoom = ZoomFactor::IDENTITY,
        vf = StandardView::Front,
        vi = StandardView::Isometric,
        vn = StandardView::NormalTo,
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn step_surface() {
    let header = StepFileHeader {
        schema: StepSchema::Ap214,
        originating_system: StepOriginatingSystem::new("bone 0.0.0"),
        organization: StepOrganization::new("witchcraft.systems"),
        file_name: StepFileName::new("limpet.step"),
    };
    let Ok(entity) = StepEntityId::new(123) else {
        panic!("123 is a positive instance name");
    };
    let surface = format!(
        "entity_debug    = {entity:?}\n\
         entity_display  = {entity}\n\
         schema_214_disp = {s214}\n\
         schema_242_disp = {s242}\n\
         header_debug    = {header:?}\n\
         header_display  = {header}",
        s214 = StepSchema::Ap214,
        s242 = StepSchema::Ap242E2,
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn reference_surface() {
    let plane = Plane3::new_unchecked(
        Point3::from_mm(1.0, 2.0, 3.0),
        UnitVec3::x_axis(),
        UnitVec3::y_axis(),
    );
    let face_fp = FaceFingerprint {
        plane,
        centroid: Point3::from_mm(4.0, 5.0, 6.0),
    };
    let edge_fp = EdgeFingerprint {
        sample: Point3::from_mm(1.0, 0.0, 0.0),
        direction: UnitVec3::z_axis(),
    };
    let vertex_fp = VertexFingerprint {
        point: Point3::origin(),
    };
    let face_ref = EntityRef::Face(FaceRef::new(
        FaceLabel {
            feature: FeatureId::default(),
            role: FaceRole::EndCap,
        },
        face_fp,
    ));
    let Ok(score) = MatchScore::new(0.875) else {
        panic!("0.875 is in range");
    };
    let resolved: Resolution<FeatureId> = Resolution::Resolved(FeatureId::default());
    let repaired: Resolution<FeatureId> = Resolution::Repaired {
        id: FeatureId::default(),
        score,
    };
    let dangling: Resolution<FeatureId> = Resolution::Dangling {
        last_known: face_ref,
    };
    let surface = format!(
        "face_fp_display   = {face_fp}\n\
         edge_fp_display   = {edge_fp}\n\
         vertex_fp_display = {vertex_fp}\n\
         entity_fp_display = {efp}\n\
         face_ref_display  = {face_ref}\n\
         face_ref_debug    = {face_ref:?}\n\
         match_score_disp  = {score}\n\
         match_score_value = {sv}\n\
         resolved_debug    = {resolved:?}\n\
         repaired_debug    = {repaired:?}\n\
         dangling_debug    = {dangling:?}",
        efp = face_ref.fingerprint(),
        sv = score.value(),
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn history_surface() {
    let face_ref = EntityRef::Face(FaceRef::new(
        FaceLabel {
            feature: FeatureId::default(),
            role: FaceRole::EndCap,
        },
        FaceFingerprint {
            plane: Plane3::new_unchecked(Point3::origin(), UnitVec3::x_axis(), UnitVec3::y_axis()),
            centroid: Point3::from_mm(1.0, 2.0, 3.0),
        },
    ));
    let Ok(score) = MatchScore::new(0.5) else {
        panic!("0.5 is in range");
    };
    let mark = FeatureGeneration::new(7);
    let dangling = RebuildStatus::Error(RebuildError::DanglingReference(face_ref));
    let non_planar = RebuildStatus::Error(RebuildError::NonPlanarSketchTarget);
    let warned = RebuildStatus::Warning(RebuildWarning::RepairedReference {
        reference: face_ref,
        score,
    });
    let surface = format!(
        "feature_gen_disp    = {mark}\n\
         feature_gen_succ    = {succ}\n\
         rollback_end_disp   = {rb_end}\n\
         rollback_above_disp = {rb_above}\n\
         suppress_active     = {sa}\n\
         suppress_suppressed = {ss}\n\
         status_uptodate     = {uptodate}\n\
         status_needs        = {needs}\n\
         status_dangling     = {dangling}\n\
         status_non_planar   = {non_planar}\n\
         status_warned       = {warned}\n\
         status_error_flag   = {ef}\n\
         status_builds_geo   = {bg}",
        succ = mark.succ(),
        rb_end = RollbackMarker::AtEnd,
        rb_above = RollbackMarker::Above(FeatureId::default()),
        sa = SuppressionState::Active,
        ss = SuppressionState::Suppressed,
        uptodate = RebuildStatus::UpToDate,
        needs = RebuildStatus::NeedsRebuild,
        ef = dangling.is_error(),
        bg = warned.builds_geometry(),
    );
    insta::assert_snapshot!(surface);
}

#[test]
fn display_surface() {
    let surface = format!(
        "display_default_disp = {default}\n\
         display_default_dbg  = {default:?}\n\
         wireframe_disp       = {wf}\n\
         wireframe_dbg        = {wf:?}\n\
         hidden_removed_disp  = {hlr}\n\
         hidden_removed_dbg   = {hlr:?}\n\
         hidden_gray_disp     = {hlg}\n\
         hidden_gray_dbg      = {hlg:?}\n\
         shaded_edges_disp    = {swe}\n\
         shaded_edges_dbg     = {swe:?}\n\
         shaded_no_edges_disp = {sne}\n\
         shaded_no_edges_dbg  = {sne:?}\n\
         shading_flat_disp    = {flat}\n\
         shading_flat_dbg     = {flat:?}\n\
         shading_gouraud_disp = {gouraud}\n\
         shading_gouraud_dbg  = {gouraud:?}\n\
         shading_phong_disp   = {phong}\n\
         shading_phong_dbg    = {phong:?}",
        default = DisplayMode::DEFAULT,
        wf = DisplayMode::Wireframe,
        hlr = DisplayMode::HiddenLineRemoved,
        hlg = DisplayMode::HiddenLineGray,
        swe = DisplayMode::ShadedWithEdges,
        sne = DisplayMode::ShadedNoEdges,
        flat = ShadingModel::Flat,
        gouraud = ShadingModel::Gouraud,
        phong = ShadingModel::Phong,
    );
    insta::assert_snapshot!(surface);
}
