use bone_document::{SketchEntityKind, SketchRelation};
use bone_types::Point2;

use crate::sketch_mode::{ClickAnchor, Pending, SketchTool};
use crate::snap::{SnapHit, SnapKind};
use crate::tools::{add_point, place};

use super::{count_kind, fresh};

#[test]
fn corner_rectangle_emits_four_lines_and_axis_relations() {
    let p1 = Point2::from_mm(0.0, 0.0);
    let p2 = Point2::from_mm(10.0, 5.0);
    let (_, pending) = place(fresh(), SketchTool::CornerRectangle, p1, None, None);
    let (next, pending) = place(fresh(), SketchTool::CornerRectangle, p2, pending, None);
    let Some(next) = next else {
        panic!("corner rect commits")
    };
    assert_eq!(count_kind(&next, SketchEntityKind::Point), 4);
    assert_eq!(count_kind(&next, SketchEntityKind::Line), 4);
    assert_eq!(pending, None);
    let h = next
        .relation_order()
        .iter()
        .filter(|r| {
            matches!(
                next.relations().get(**r),
                Some(SketchRelation::Horizontal(_))
            )
        })
        .count();
    let v = next
        .relation_order()
        .iter()
        .filter(|r| matches!(next.relations().get(**r), Some(SketchRelation::Vertical(_))))
        .count();
    assert_eq!(h, 2);
    assert_eq!(v, 2);
}

#[test]
fn center_rectangle_emits_six_lines_diagonals_and_midpoint_relations() {
    let center = Point2::from_mm(0.0, 0.0);
    let corner = Point2::from_mm(4.0, 3.0);
    let (_, pending) = place(fresh(), SketchTool::CenterRectangle, center, None, None);
    let (next, _) = place(fresh(), SketchTool::CenterRectangle, corner, pending, None);
    let Some(next) = next else {
        panic!("center rect commits")
    };
    assert_eq!(count_kind(&next, SketchEntityKind::Point), 5);
    assert_eq!(count_kind(&next, SketchEntityKind::Line), 6);
    let mids = next
        .relation_order()
        .iter()
        .filter(|r| {
            matches!(
                next.relations().get(**r),
                Some(SketchRelation::Midpoint { .. })
            )
        })
        .count();
    assert_eq!(mids, 2);
}

#[test]
fn three_point_corner_rectangle_emits_parallel_and_perpendicular() {
    let p1 = Point2::from_mm(0.0, 0.0);
    let p2 = Point2::from_mm(4.0, 3.0);
    let click3 = Point2::from_mm(0.0, 5.0);
    let (_, pending) = place(
        fresh(),
        SketchTool::ThreePointCornerRectangle,
        p1,
        None,
        None,
    );
    let (_, pending) = place(
        fresh(),
        SketchTool::ThreePointCornerRectangle,
        p2,
        pending,
        None,
    );
    let (next, _) = place(
        fresh(),
        SketchTool::ThreePointCornerRectangle,
        click3,
        pending,
        None,
    );
    let Some(next) = next else {
        panic!("3-pt corner rect commits")
    };
    assert_eq!(count_kind(&next, SketchEntityKind::Line), 4);
    let par = next
        .relation_order()
        .iter()
        .filter(|r| {
            matches!(
                next.relations().get(**r),
                Some(SketchRelation::Parallel(_, _))
            )
        })
        .count();
    let perp = next
        .relation_order()
        .iter()
        .filter(|r| {
            matches!(
                next.relations().get(**r),
                Some(SketchRelation::Perpendicular(_, _))
            )
        })
        .count();
    assert_eq!(par, 2);
    assert_eq!(perp, 1);
}

#[test]
fn three_point_center_rectangle_emits_diagonals_and_orientation_pin() {
    let center = Point2::from_mm(0.0, 0.0);
    let mid = Point2::from_mm(5.0, 0.0);
    let click3 = Point2::from_mm(5.0, 3.0);
    let (_, pending) = place(
        fresh(),
        SketchTool::ThreePointCenterRectangle,
        center,
        None,
        None,
    );
    let (_, pending) = place(
        fresh(),
        SketchTool::ThreePointCenterRectangle,
        mid,
        pending,
        None,
    );
    let (next, _) = place(
        fresh(),
        SketchTool::ThreePointCenterRectangle,
        click3,
        pending,
        None,
    );
    let Some(next) = next else {
        panic!("3-pt center rect commits")
    };
    assert_eq!(count_kind(&next, SketchEntityKind::Line), 6);
    assert_eq!(count_kind(&next, SketchEntityKind::Point), 6);
    let mids = next
        .relation_order()
        .iter()
        .filter(|r| {
            matches!(
                next.relations().get(**r),
                Some(SketchRelation::Midpoint { .. })
            )
        })
        .count();
    assert_eq!(
        mids, 3,
        "two diagonal midpoints plus mid-edge midpoint pinning orientation",
    );
    let par = next
        .relation_order()
        .iter()
        .filter(|r| {
            matches!(
                next.relations().get(**r),
                Some(SketchRelation::Parallel(_, _))
            )
        })
        .count();
    assert_eq!(
        par, 0,
        "parallels are redundant given diagonal-midpoint constraints",
    );
}

#[test]
fn three_point_center_rectangle_reuses_mid_endpoint_on_snap() {
    let (sketch, q) = add_point(fresh(), Point2::from_mm(5.0, 0.0));
    let snap_q = SnapHit {
        kind: SnapKind::Endpoint(q),
        world: Point2::from_mm(5.0, 0.0),
    };
    let pending = Some(Pending::First(ClickAnchor::Position(Point2::from_mm(
        0.0, 0.0,
    ))));
    let (_, pending) = place(
        sketch.clone(),
        SketchTool::ThreePointCenterRectangle,
        Point2::from_mm(5.0, 0.0),
        pending,
        Some(snap_q),
    );
    let (next, _) = place(
        sketch,
        SketchTool::ThreePointCenterRectangle,
        Point2::from_mm(5.0, 3.0),
        pending,
        None,
    );
    let Some(next) = next else { panic!("commit") };
    assert!(
        next.entity_order().contains(&q),
        "snapped mid endpoint reused",
    );
    assert!(
        next.relation_order().iter().any(|rid| matches!(
            next.relations().get(*rid),
            Some(SketchRelation::Midpoint { point, .. }) if *point == q,
        )),
        "midpoint relation pins reused mid to the right edge",
    );
}

#[test]
fn centerpoint_arc_drops_pending_when_start_and_end_share_endpoint() {
    let (sketch, q) = add_point(fresh(), Point2::from_mm(5.0, 0.0));
    let snap_q = SnapHit {
        kind: SnapKind::Endpoint(q),
        world: Point2::from_mm(5.0, 0.0),
    };
    let pending = Some(Pending::Second(
        ClickAnchor::Position(Point2::from_mm(10.0, 10.0)),
        ClickAnchor::Endpoint(q),
    ));
    let before = count_kind(&sketch, SketchEntityKind::Point);
    let (next, kept) = place(
        sketch,
        SketchTool::CenterpointArc,
        Point2::from_mm(5.0, 0.0),
        pending,
        Some(snap_q),
    );
    assert!(
        next.is_none(),
        "must not commit when end snap collides with start"
    );
    assert!(matches!(kept, Some(Pending::Second(_, _))));
    assert_eq!(before, 1, "no orphan center point added");
}

#[test]
fn corner_rectangle_rejects_zero_width_drag() {
    let p1 = Point2::from_mm(0.0, 0.0);
    let zero_width = Point2::from_mm(0.0, 5.0);
    let (_, pending) = place(fresh(), SketchTool::CornerRectangle, p1, None, None);
    let (next, kept) = place(
        fresh(),
        SketchTool::CornerRectangle,
        zero_width,
        pending,
        None,
    );
    assert!(next.is_none(), "zero-width drag must not commit");
    assert!(matches!(kept, Some(Pending::First(_))));
}

#[test]
fn corner_rectangle_rejects_zero_height_drag() {
    let p1 = Point2::from_mm(0.0, 0.0);
    let zero_height = Point2::from_mm(5.0, 0.0);
    let (_, pending) = place(fresh(), SketchTool::CornerRectangle, p1, None, None);
    let (next, kept) = place(
        fresh(),
        SketchTool::CornerRectangle,
        zero_height,
        pending,
        None,
    );
    assert!(next.is_none(), "zero-height drag must not commit");
    assert!(matches!(kept, Some(Pending::First(_))));
}

#[test]
fn center_rectangle_rejects_zero_width_drag() {
    let center = Point2::from_mm(0.0, 0.0);
    let zero_width = Point2::from_mm(0.0, 5.0);
    let (_, pending) = place(fresh(), SketchTool::CenterRectangle, center, None, None);
    let (next, kept) = place(
        fresh(),
        SketchTool::CenterRectangle,
        zero_width,
        pending,
        None,
    );
    assert!(next.is_none());
    assert!(matches!(kept, Some(Pending::First(_))));
}

#[test]
fn center_rectangle_rejects_zero_height_drag() {
    let center = Point2::from_mm(0.0, 0.0);
    let zero_height = Point2::from_mm(5.0, 0.0);
    let (_, pending) = place(fresh(), SketchTool::CenterRectangle, center, None, None);
    let (next, kept) = place(
        fresh(),
        SketchTool::CenterRectangle,
        zero_height,
        pending,
        None,
    );
    assert!(next.is_none());
    assert!(matches!(kept, Some(Pending::First(_))));
}

#[test]
fn corner_rectangle_reuses_endpoint_id_on_diagonal_corner() {
    let (sketch, q) = add_point(fresh(), Point2::from_mm(5.0, 3.0));
    let snap_q = SnapHit {
        kind: SnapKind::Endpoint(q),
        world: Point2::from_mm(5.0, 3.0),
    };
    let pending = Some(Pending::First(ClickAnchor::Position(Point2::from_mm(
        0.0, 0.0,
    ))));
    let (next, _) = place(
        sketch,
        SketchTool::CornerRectangle,
        Point2::from_mm(5.0, 3.0),
        pending,
        Some(snap_q),
    );
    let Some(next) = next else { panic!("commit") };
    assert_eq!(count_kind(&next, SketchEntityKind::Point), 4);
    assert!(next.entity_order().contains(&q), "diagonal endpoint reused");
}

#[test]
fn center_rectangle_reuses_endpoint_id_on_corner() {
    let (sketch, q) = add_point(fresh(), Point2::from_mm(4.0, 3.0));
    let snap_q = SnapHit {
        kind: SnapKind::Endpoint(q),
        world: Point2::from_mm(4.0, 3.0),
    };
    let pending = Some(Pending::First(ClickAnchor::Position(Point2::from_mm(
        0.0, 0.0,
    ))));
    let (next, _) = place(
        sketch,
        SketchTool::CenterRectangle,
        Point2::from_mm(4.0, 3.0),
        pending,
        Some(snap_q),
    );
    let Some(next) = next else { panic!("commit") };
    assert!(next.entity_order().contains(&q), "corner endpoint reused");
    assert_eq!(count_kind(&next, SketchEntityKind::Point), 5);
}

#[test]
fn parallelogram_reuses_endpoint_on_third_corner() {
    let (sketch, q) = add_point(fresh(), Point2::from_mm(5.0, 3.0));
    let snap_q = SnapHit {
        kind: SnapKind::Endpoint(q),
        world: Point2::from_mm(5.0, 3.0),
    };
    let p1 = Point2::from_mm(0.0, 0.0);
    let p2 = Point2::from_mm(4.0, 0.0);
    let (_, pending) = place(sketch.clone(), SketchTool::Parallelogram, p1, None, None);
    let (_, pending) = place(sketch.clone(), SketchTool::Parallelogram, p2, pending, None);
    let (next, _) = place(
        sketch,
        SketchTool::Parallelogram,
        Point2::from_mm(5.0, 3.0),
        pending,
        Some(snap_q),
    );
    let Some(next) = next else { panic!("commit") };
    assert!(
        next.entity_order().contains(&q),
        "third corner endpoint reused"
    );
    assert_eq!(count_kind(&next, SketchEntityKind::Point), 4);
}

#[test]
fn parallelogram_fourth_corner_is_implied_by_first_three() {
    let p1 = Point2::from_mm(0.0, 0.0);
    let p2 = Point2::from_mm(4.0, 0.0);
    let p3 = Point2::from_mm(5.0, 3.0);
    let (_, pending) = place(fresh(), SketchTool::Parallelogram, p1, None, None);
    let (_, pending) = place(fresh(), SketchTool::Parallelogram, p2, pending, None);
    let (next, _) = place(fresh(), SketchTool::Parallelogram, p3, pending, None);
    let Some(next) = next else {
        panic!("parallelogram commits")
    };
    assert_eq!(count_kind(&next, SketchEntityKind::Point), 4);
    assert_eq!(count_kind(&next, SketchEntityKind::Line), 4);
    let par = next
        .relation_order()
        .iter()
        .filter(|r| {
            matches!(
                next.relations().get(**r),
                Some(SketchRelation::Parallel(_, _))
            )
        })
        .count();
    assert_eq!(par, 2);
}
