use bone_document::{Sketch, SketchEntity};
use bone_types::{Point2, SketchEntityId, Vec2};

use crate::snap::{SnapHit, SnapKind};

pub(super) fn distance(a: Point2, b: Point2) -> f64 {
    let (ax, ay) = a.coords_mm();
    let (bx, by) = b.coords_mm();
    (ax - bx).hypot(ay - by)
}

pub(super) fn project_to_circle(center: Point2, radius_mm: f64, point: Point2) -> Point2 {
    let (cx, cy) = center.coords_mm();
    let (px, py) = point.coords_mm();
    let dx = px - cx;
    let dy = py - cy;
    let len = (dx * dx + dy * dy).sqrt();
    if !(len.is_finite() && len > 0.0) {
        return Point2::from_mm(cx + radius_mm, cy);
    }
    let scale = radius_mm / len;
    Point2::from_mm(cx + dx * scale, cy + dy * scale)
}

pub(super) fn circumcircle(p1: Point2, p2: Point2, p3: Point2) -> Option<(Point2, f64)> {
    let (ax, ay) = p1.coords_mm();
    let (bx, by) = p2.coords_mm();
    let (cx, cy) = p3.coords_mm();
    let d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
    if !(d.is_finite()) || d.abs() < 1e-12 {
        return None;
    }
    let a_sq = ax * ax + ay * ay;
    let b_sq = bx * bx + by * by;
    let c_sq = cx * cx + cy * cy;
    let ux = (a_sq * (by - cy) + b_sq * (cy - ay) + c_sq * (ay - by)) / d;
    let uy = (a_sq * (cx - bx) + b_sq * (ax - cx) + c_sq * (bx - ax)) / d;
    if !(ux.is_finite() && uy.is_finite()) {
        return None;
    }
    let center = Point2::from_mm(ux, uy);
    let radius = distance(center, p1);
    (radius.is_finite() && radius > 0.0).then_some((center, radius))
}

pub(super) fn minor_arc_ccw(center: Point2, start: Point2, end: Point2) -> bool {
    let (cx, cy) = center.coords_mm();
    let (sx, sy) = start.coords_mm();
    let (ex, ey) = end.coords_mm();
    let cross = (sx - cx) * (ey - cy) - (sy - cy) * (ex - cx);
    cross >= 0.0
}

pub(super) fn on_circle_endpoint(
    snap: Option<SnapHit>,
    center: Point2,
    radius_mm: f64,
) -> Option<SketchEntityId> {
    let hit = snap?;
    let SnapKind::Endpoint(id) = hit.kind else {
        return None;
    };
    ((distance(center, hit.world) - radius_mm).abs() < 1e-9).then_some(id)
}

pub(super) fn ccw_through(center: Point2, start: Point2, end: Point2, via: Point2) -> bool {
    let (cx, cy) = center.coords_mm();
    let (sx, sy) = start.coords_mm();
    let (ex, ey) = end.coords_mm();
    let (vx, vy) = via.coords_mm();
    let start_angle = (sy - cy).atan2(sx - cx);
    let end_angle = (ey - cy).atan2(ex - cx);
    let via_angle = (vy - cy).atan2(vx - cx);
    let tau = std::f64::consts::TAU;
    let delta_via = (via_angle - start_angle).rem_euclid(tau);
    let delta_end = (end_angle - start_angle).rem_euclid(tau);
    delta_via < delta_end
}

pub(super) fn axes_distinct(a: (f64, f64), b: (f64, f64)) -> bool {
    let dx = b.0 - a.0;
    let dy = b.1 - a.1;
    dx.is_finite() && dy.is_finite() && dx.abs() > 1e-12 && dy.abs() > 1e-12
}

pub(super) fn tangent_at_point(
    sketch: &Sketch,
    point: SketchEntityId,
) -> Option<(SketchEntityId, Vec2)> {
    sketch.entity_order().iter().copied().find_map(|id| {
        let tangent = match sketch.entities().get(id)? {
            SketchEntity::Line(l) => {
                let other = if l.a() == point {
                    l.b()
                } else if l.b() == point {
                    l.a()
                } else {
                    return None;
                };
                let (hx, hy) = endpoint_position(sketch, point)?.coords_mm();
                let (tx, ty) = endpoint_position(sketch, other)?.coords_mm();
                unit_vec((tx - hx, ty - hy))?
            }
            SketchEntity::Arc(a) => {
                let center = endpoint_position(sketch, a.center())?;
                let here = endpoint_position(sketch, point)?;
                let (cx, cy) = center.coords_mm();
                let (px, py) = here.coords_mm();
                let ccw_perp = (-(py - cy), px - cx);
                let into_host = if a.start() == point {
                    ccw_perp
                } else if a.end() == point {
                    (-ccw_perp.0, -ccw_perp.1)
                } else {
                    return None;
                };
                unit_vec(into_host)?
            }
            _ => return None,
        };
        Some((id, tangent))
    })
}

pub(super) fn endpoint_position(sketch: &Sketch, id: SketchEntityId) -> Option<Point2> {
    match sketch.entities().get(id)? {
        SketchEntity::Point(p) => Some(p.at()),
        _ => None,
    }
}

fn unit_vec(v: (f64, f64)) -> Option<Vec2> {
    let len = (v.0 * v.0 + v.1 * v.1).sqrt();
    (len.is_finite() && len > 0.0).then(|| Vec2::from_mm(v.0 / len, v.1 / len))
}

pub(super) fn tangent_arc_ccw_natural(start: Point2, center: Point2, tangent: Vec2) -> bool {
    let (sx, sy) = start.coords_mm();
    let (cx, cy) = center.coords_mm();
    let radial_x = sx - cx;
    let radial_y = sy - cy;
    let ccw_init_x = -radial_y;
    let ccw_init_y = radial_x;
    let (tx, ty) = tangent.coords_mm();
    ccw_init_x * (-tx) + ccw_init_y * (-ty) > 0.0
}

pub(super) fn tangent_arc_center(start: Point2, end: Point2, tangent: Vec2) -> Option<Point2> {
    let (sx, sy) = start.coords_mm();
    let (ex, ey) = end.coords_mm();
    let (tx, ty) = tangent.coords_mm();
    let (nx, ny) = (-ty, tx);
    let dx = sx - ex;
    let dy = sy - ey;
    let denom = nx * dx + ny * dy;
    if !denom.is_finite() || denom.abs() < 1e-9 {
        return None;
    }
    let alpha = -(dx * dx + dy * dy) / (2.0 * denom);
    if !alpha.is_finite() {
        return None;
    }
    Some(Point2::from_mm(sx + alpha * nx, sy + alpha * ny))
}

pub(super) fn three_point_corner_extents(
    p1: Point2,
    p2: Point2,
    click3: Point2,
) -> Option<(Point2, Point2)> {
    let (p1x, p1y) = p1.coords_mm();
    let (p2x, p2y) = p2.coords_mm();
    let (c3x, c3y) = click3.coords_mm();
    let edge = (p2x - p1x, p2y - p1y);
    let len = (edge.0 * edge.0 + edge.1 * edge.1).sqrt();
    if !(len.is_finite() && len > 0.0) {
        return None;
    }
    let u = (edge.0 / len, edge.1 / len);
    let n = (-u.1, u.0);
    let from_p2 = (c3x - p2x, c3y - p2y);
    let extent = from_p2.0 * n.0 + from_p2.1 * n.1;
    if !extent.is_finite() || extent.abs() < 1e-9 {
        return None;
    }
    Some((
        Point2::from_mm(p2x + n.0 * extent, p2y + n.1 * extent),
        Point2::from_mm(p1x + n.0 * extent, p1y + n.1 * extent),
    ))
}

pub(super) fn three_point_center_corners(
    center: Point2,
    mid: Point2,
    click3: Point2,
) -> Option<(Point2, Point2, Point2, Point2)> {
    let (cx, cy) = center.coords_mm();
    let (mx, my) = mid.coords_mm();
    let (kx, ky) = click3.coords_mm();
    let mid_v = (mx - cx, my - cy);
    let mid_len = (mid_v.0 * mid_v.0 + mid_v.1 * mid_v.1).sqrt();
    if !(mid_len.is_finite() && mid_len > 0.0) {
        return None;
    }
    let u = (mid_v.0 / mid_len, mid_v.1 / mid_len);
    let n = (-u.1, u.0);
    let from_center = (kx - cx, ky - cy);
    let perp_extent = from_center.0 * n.0 + from_center.1 * n.1;
    if !perp_extent.is_finite() || perp_extent.abs() < 1e-9 {
        return None;
    }
    let along = mid_len;
    let p1 = Point2::from_mm(
        cx + along * u.0 + perp_extent * n.0,
        cy + along * u.1 + perp_extent * n.1,
    );
    let p2 = Point2::from_mm(
        cx - along * u.0 + perp_extent * n.0,
        cy - along * u.1 + perp_extent * n.1,
    );
    let p3 = Point2::from_mm(
        cx - along * u.0 - perp_extent * n.0,
        cy - along * u.1 - perp_extent * n.1,
    );
    let p4 = Point2::from_mm(
        cx + along * u.0 - perp_extent * n.0,
        cy + along * u.1 - perp_extent * n.1,
    );
    Some((p1, p2, p3, p4))
}

pub(super) fn degenerate_triangle(p1: Point2, p2: Point2, p3: Point2) -> bool {
    let (ax, ay) = p1.coords_mm();
    let (bx, by) = p2.coords_mm();
    let (cx, cy) = p3.coords_mm();
    let cross = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
    !cross.is_finite() || cross.abs() < 1e-9
}
