use bone_document::Sketch;
use bone_render::{PreviewArc, PreviewCircle, SketchPreview};
use bone_types::{Angle, Length, Point2};
use uom::si::angle::radian;
use uom::si::length::millimeter;

use crate::sketch_mode::{ClickAnchor, Pending, SketchTool};
use crate::snap::SnapHit;

use super::click_position;
use super::geometry::{
    ccw_through, circumcircle, degenerate_triangle, distance, minor_arc_ccw, project_to_circle,
    tangent_arc_ccw_natural, tangent_arc_center, tangent_at_point, three_point_center_corners,
    three_point_corner_extents,
};

pub(super) fn preview(
    sketch: &Sketch,
    tool: SketchTool,
    cursor: Point2,
    pending: Option<Pending>,
    snap: Option<SnapHit>,
) -> SketchPreview {
    let snap_dot = snap.map(|s| s.world);
    let effective = snap_dot.unwrap_or(cursor);
    match tool {
        SketchTool::Point => snap_only(snap_dot),
        SketchTool::Line => preview_line(sketch, effective, pending, snap_dot),
        SketchTool::Circle => preview_circle(sketch, effective, pending, snap_dot),
        SketchTool::PerimeterCircle => {
            preview_perimeter_circle(sketch, effective, pending, snap_dot)
        }
        SketchTool::CenterpointArc => preview_centerpoint_arc(sketch, effective, pending, snap_dot),
        SketchTool::TangentArc => preview_tangent_arc(sketch, effective, pending, snap_dot),
        SketchTool::ThreePointArc => preview_three_point_arc(sketch, effective, pending, snap_dot),
        SketchTool::CornerRectangle => {
            preview_corner_rectangle(sketch, effective, pending, snap_dot)
        }
        SketchTool::CenterRectangle => {
            preview_center_rectangle(sketch, effective, pending, snap_dot)
        }
        SketchTool::ThreePointCornerRectangle => {
            preview_three_point_corner_rectangle(sketch, effective, pending, snap_dot)
        }
        SketchTool::ThreePointCenterRectangle => {
            preview_three_point_center_rectangle(sketch, effective, pending, snap_dot)
        }
        SketchTool::Parallelogram => preview_parallelogram(sketch, effective, pending, snap_dot),
    }
}

fn snap_only(snap_dot: Option<Point2>) -> SketchPreview {
    SketchPreview {
        snap: snap_dot,
        ..SketchPreview::empty()
    }
}

fn preview_dispatch_two<F>(
    sketch: &Sketch,
    pending: Option<Pending>,
    effective: Point2,
    snap_dot: Option<Point2>,
    show_first: F,
) -> SketchPreview
where
    F: FnOnce(Point2, Point2) -> SketchPreview,
{
    match pending {
        None | Some(Pending::Second(_, _)) => snap_only(snap_dot),
        Some(Pending::First(a)) => click_position(sketch, a).map_or_else(
            || snap_only(snap_dot),
            |p| SketchPreview {
                anchors: vec![p],
                snap: snap_dot,
                ..show_first(p, effective)
            },
        ),
    }
}

fn preview_dispatch_three<F>(
    sketch: &Sketch,
    pending: Option<Pending>,
    effective: Point2,
    snap_dot: Option<Point2>,
    second_render: F,
) -> SketchPreview
where
    F: FnOnce(Point2, Point2, Point2) -> SketchPreview,
{
    match pending {
        None => snap_only(snap_dot),
        Some(Pending::First(_)) => {
            preview_dispatch_two(sketch, pending, effective, snap_dot, rubber_band)
        }
        Some(Pending::Second(a, b)) => {
            match (click_position(sketch, a), click_position(sketch, b)) {
                (Some(p1), Some(p2)) => SketchPreview {
                    anchors: vec![p1, p2],
                    snap: snap_dot,
                    ..second_render(p1, p2, effective)
                },
                _ => snap_only(snap_dot),
            }
        }
    }
}

fn rubber_band(p: Point2, effective: Point2) -> SketchPreview {
    SketchPreview {
        segments: vec![(p, effective)],
        ..SketchPreview::empty()
    }
}

fn preview_line(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_two(sketch, pending, effective, snap_dot, rubber_band)
}

fn preview_circle(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_two(sketch, pending, effective, snap_dot, |center_pos, eff| {
        let radius_mm = distance(center_pos, eff);
        if !(radius_mm.is_finite() && radius_mm > 0.0) {
            return SketchPreview::empty();
        }
        SketchPreview {
            circles: vec![PreviewCircle {
                center: center_pos,
                radius: Length::new::<millimeter>(radius_mm),
            }],
            ..SketchPreview::empty()
        }
    })
}

fn preview_perimeter_circle(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_three(
        sketch,
        pending,
        effective,
        snap_dot,
        |p1, p2, eff| match circumcircle(p1, p2, eff) {
            Some((center, radius_mm)) => SketchPreview {
                circles: vec![PreviewCircle {
                    center,
                    radius: Length::new::<millimeter>(radius_mm),
                }],
                ..SketchPreview::empty()
            },
            None => SketchPreview {
                segments: vec![(p1, p2)],
                ..SketchPreview::empty()
            },
        },
    )
}

fn preview_centerpoint_arc(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_three(
        sketch,
        pending,
        effective,
        snap_dot,
        |center_pos, start_pos, eff| {
            let radius_mm = distance(center_pos, start_pos);
            if !(radius_mm.is_finite() && radius_mm > 0.0) {
                return SketchPreview::empty();
            }
            let end_on_circle = project_to_circle(center_pos, radius_mm, eff);
            let (arc_start, arc_end) = if minor_arc_ccw(center_pos, start_pos, end_on_circle) {
                (start_pos, end_on_circle)
            } else {
                (end_on_circle, start_pos)
            };
            match ghost_arc_ccw(center_pos, radius_mm, arc_start, arc_end) {
                Some(arc) => SketchPreview {
                    arcs: vec![arc],
                    ..SketchPreview::empty()
                },
                None => SketchPreview::empty(),
            }
        },
    )
}

fn preview_tangent_arc(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    let Some(Pending::First(start)) = pending else {
        return snap_only(snap_dot);
    };
    let ClickAnchor::Endpoint(start_id) = start else {
        return snap_only(snap_dot);
    };
    let Some(start_pos) = click_position(sketch, start) else {
        return snap_only(snap_dot);
    };
    let arc = tangent_at_point(sketch, start_id).and_then(|(_host, tangent)| {
        let center_pos = tangent_arc_center(start_pos, effective, tangent)?;
        let radius_mm = distance(center_pos, start_pos);
        let (arc_start, arc_end) = if tangent_arc_ccw_natural(start_pos, center_pos, tangent) {
            (start_pos, effective)
        } else {
            (effective, start_pos)
        };
        ghost_arc_ccw(center_pos, radius_mm, arc_start, arc_end)
    });
    SketchPreview {
        anchors: vec![start_pos],
        arcs: arc.into_iter().collect(),
        snap: snap_dot,
        ..SketchPreview::empty()
    }
}

fn preview_three_point_arc(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_three(
        sketch,
        pending,
        effective,
        snap_dot,
        |start_pos, end_pos, eff| match circumcircle(start_pos, end_pos, eff) {
            Some((center, radius_mm)) => {
                let (s, e) = if ccw_through(center, start_pos, end_pos, eff) {
                    (start_pos, end_pos)
                } else {
                    (end_pos, start_pos)
                };
                match ghost_arc_ccw(center, radius_mm, s, e) {
                    Some(arc) => SketchPreview {
                        arcs: vec![arc],
                        ..SketchPreview::empty()
                    },
                    None => SketchPreview::empty(),
                }
            }
            None => SketchPreview {
                segments: vec![(start_pos, end_pos)],
                ..SketchPreview::empty()
            },
        },
    )
}

fn preview_corner_rectangle(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_two(sketch, pending, effective, snap_dot, |corner_pos, eff| {
        let (cx, cy) = corner_pos.coords_mm();
        let (ox, oy) = eff.coords_mm();
        SketchPreview {
            segments: rect_edges(
                corner_pos,
                Point2::from_mm(ox, cy),
                Point2::from_mm(ox, oy),
                Point2::from_mm(cx, oy),
            ),
            ..SketchPreview::empty()
        }
    })
}

fn preview_center_rectangle(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_two(sketch, pending, effective, snap_dot, |center_pos, eff| {
        let (cx, cy) = center_pos.coords_mm();
        let (ox, oy) = eff.coords_mm();
        let dx = ox - cx;
        let dy = oy - cy;
        SketchPreview {
            segments: rect_edges(
                Point2::from_mm(cx + dx, cy + dy),
                Point2::from_mm(cx - dx, cy + dy),
                Point2::from_mm(cx - dx, cy - dy),
                Point2::from_mm(cx + dx, cy - dy),
            ),
            ..SketchPreview::empty()
        }
    })
}

fn preview_three_point_corner_rectangle(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_three(sketch, pending, effective, snap_dot, |p1, p2, eff| {
        match three_point_corner_extents(p1, p2, eff) {
            Some((p3, p4)) => SketchPreview {
                segments: rect_edges(p1, p2, p3, p4),
                ..SketchPreview::empty()
            },
            None => SketchPreview {
                segments: vec![(p1, p2)],
                ..SketchPreview::empty()
            },
        }
    })
}

fn preview_three_point_center_rectangle(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_three(
        sketch,
        pending,
        effective,
        snap_dot,
        |center_pos, mid_pos, eff| match three_point_center_corners(center_pos, mid_pos, eff) {
            Some((p1, p2, p3, p4)) => SketchPreview {
                segments: rect_edges(p1, p2, p3, p4),
                ..SketchPreview::empty()
            },
            None => SketchPreview {
                segments: vec![(center_pos, mid_pos)],
                ..SketchPreview::empty()
            },
        },
    )
}

fn preview_parallelogram(
    sketch: &Sketch,
    effective: Point2,
    pending: Option<Pending>,
    snap_dot: Option<Point2>,
) -> SketchPreview {
    preview_dispatch_three(sketch, pending, effective, snap_dot, |p1, p2, eff| {
        if degenerate_triangle(p1, p2, eff) {
            return SketchPreview {
                segments: vec![(p1, p2)],
                ..SketchPreview::empty()
            };
        }
        let (p1x, p1y) = p1.coords_mm();
        let (p2x, p2y) = p2.coords_mm();
        let (p3x, p3y) = eff.coords_mm();
        let p4 = Point2::from_mm(p1x + (p3x - p2x), p1y + (p3y - p2y));
        SketchPreview {
            segments: rect_edges(p1, p2, eff, p4),
            ..SketchPreview::empty()
        }
    })
}

fn rect_edges(p1: Point2, p2: Point2, p3: Point2, p4: Point2) -> Vec<(Point2, Point2)> {
    vec![(p1, p2), (p2, p3), (p3, p4), (p4, p1)]
}

fn ghost_arc_ccw(center: Point2, radius_mm: f64, start: Point2, end: Point2) -> Option<PreviewArc> {
    if !(radius_mm.is_finite() && radius_mm > 0.0) {
        return None;
    }
    let (cx, cy) = center.coords_mm();
    let (sx, sy) = start.coords_mm();
    let (ex, ey) = end.coords_mm();
    let start_angle = (sy - cy).atan2(sx - cx);
    let end_angle = (ey - cy).atan2(ex - cx);
    if !(start_angle.is_finite() && end_angle.is_finite()) {
        return None;
    }
    let mut sweep = (end_angle - start_angle).rem_euclid(std::f64::consts::TAU);
    if sweep == 0.0 {
        sweep = std::f64::consts::TAU;
    }
    Some(PreviewArc {
        center,
        radius: Length::new::<millimeter>(radius_mm),
        start_angle: Angle::new::<radian>(start_angle),
        sweep_angle: Angle::new::<radian>(sweep),
    })
}
