use bone_types::{
    EdgeLabel, EdgeRole, FaceLabel, Plane3, Point3, Tolerance, VertexLabel, VertexRole,
};

use super::{PlanarFace, PlanarVertex, as_vec};

pub(super) struct FaceLoop {
    pub verts: Vec<PlanarVertex>,
    pub edges: Vec<EdgeLabel>,
}

struct Edge2 {
    a: (f64, f64),
    b: (f64, f64),
    label: EdgeLabel,
}

struct Vertex2 {
    local: (f64, f64),
    label: VertexLabel,
}

fn to_local(plane: &Plane3, point: Point3) -> (f64, f64) {
    let offset = point - plane.origin();
    (
        offset.dot_mm2(as_vec(plane.x_axis())),
        offset.dot_mm2(as_vec(plane.y_axis())),
    )
}

fn signed_area(corners: &[(f64, f64)]) -> f64 {
    let count = corners.len();
    (0..count)
        .map(|index| {
            let (x0, y0) = corners[index];
            let (x1, y1) = corners[(index + 1) % count];
            x0 * y1 - x1 * y0
        })
        .sum::<f64>()
        / 2.0
}

pub(super) fn loop_is_convex(verts: &[PlanarVertex], plane: &Plane3, tolerance: Tolerance) -> bool {
    let locals: Vec<(f64, f64)> = verts.iter().map(|v| to_local(plane, v.position)).collect();
    let count = locals.len();
    if count < 3 {
        return false;
    }
    let eps = tolerance.value();
    let turns: Vec<f64> = (0..count)
        .filter_map(|i| {
            let a = locals[i];
            let b = locals[(i + 1) % count];
            let c = locals[(i + 2) % count];
            let cross = (b.0 - a.0) * (c.1 - b.1) - (b.1 - a.1) * (c.0 - b.0);
            (cross.abs() > eps * eps).then_some(cross)
        })
        .collect();
    turns
        .first()
        .is_none_or(|first| turns.iter().all(|turn| (turn > &0.0) == (first > &0.0)))
}

fn synthetic_edge(face: FaceLabel) -> EdgeLabel {
    EdgeLabel {
        feature: face.feature,
        role: EdgeRole::intersection(face, face),
    }
}

fn synthetic_vertex(face: FaceLabel) -> VertexLabel {
    VertexLabel {
        feature: face.feature,
        role: VertexRole::Intersection {
            faces: [face, face, face],
        },
    }
}

fn x_at(edge: &Edge2, y: f64) -> f64 {
    let (x0, y0) = edge.a;
    let (x1, y1) = edge.b;
    x0 + (x1 - x0) * (y - y0) / (y1 - y0)
}

fn straddles(edge: &Edge2, y: f64) -> bool {
    (edge.a.1 - y) * (edge.b.1 - y) < 0.0
}

fn unique_ys(verts: &[Vertex2], eps: f64) -> Vec<f64> {
    let mut ys: Vec<f64> = verts.iter().map(|v| v.local.1).collect();
    ys.sort_by(f64::total_cmp);
    ys.into_iter().fold(Vec::new(), |mut kept, y| {
        if kept.last().is_none_or(|last: &f64| (y - last).abs() > eps) {
            kept.push(y);
        }
        kept
    })
}

fn vertex_label_at(point: (f64, f64), verts: &[Vertex2], face: FaceLabel, eps: f64) -> VertexLabel {
    verts
        .iter()
        .find(|vertex| {
            (vertex.local.0 - point.0).abs() <= eps && (vertex.local.1 - point.1).abs() <= eps
        })
        .map_or_else(|| synthetic_vertex(face), |vertex| vertex.label)
}

fn segment_label(
    p0: (f64, f64),
    p1: (f64, f64),
    edges: &[Edge2],
    face: FaceLabel,
    eps: f64,
) -> EdgeLabel {
    edges
        .iter()
        .find(|edge| covers(edge, p0, eps) && covers(edge, p1, eps))
        .map_or_else(|| synthetic_edge(face), |edge| edge.label)
}

fn covers(edge: &Edge2, point: (f64, f64), eps: f64) -> bool {
    let (ax, ay) = edge.a;
    let (bx, by) = edge.b;
    let (dx, dy) = (bx - ax, by - ay);
    let length_sq = dx * dx + dy * dy;
    if length_sq <= eps * eps {
        return false;
    }
    let t = ((point.0 - ax) * dx + (point.1 - ay) * dy) / length_sq;
    if t < -eps || t > 1.0 + eps {
        return false;
    }
    let foot = (ax + t * dx, ay + t * dy);
    (point.0 - foot.0).hypot(point.1 - foot.1) <= eps
}

fn dedup_corners(corners: &[(f64, f64)], eps: f64) -> Vec<(f64, f64)> {
    corners.iter().fold(Vec::new(), |mut kept, &corner| {
        let coincident = kept.last().is_some_and(|last: &(f64, f64)| {
            (last.0 - corner.0).abs() <= eps && (last.1 - corner.1).abs() <= eps
        });
        if !coincident {
            kept.push(corner);
        }
        kept
    })
}

fn trapezoid(
    locals: &[(f64, f64)],
    plane: &Plane3,
    edges: &[Edge2],
    verts: &[Vertex2],
    face: FaceLabel,
    eps: f64,
) -> Option<PlanarFace> {
    let mut corners = dedup_corners(locals, eps);
    if corners.len() >= 2 {
        let first = corners[0];
        let last = corners[corners.len() - 1];
        if (first.0 - last.0).abs() <= eps && (first.1 - last.1).abs() <= eps {
            corners.pop();
        }
    }
    if corners.len() < 3 {
        return None;
    }
    if signed_area(&corners) < 0.0 {
        corners.reverse();
    }
    if signed_area(&corners).abs() <= eps * eps {
        return None;
    }
    let count = corners.len();
    let polygon: Vec<PlanarVertex> = corners
        .iter()
        .map(|&(x, y)| PlanarVertex {
            position: plane.point_at_local(x, y, 0.0),
            label: vertex_label_at((x, y), verts, face, eps),
        })
        .collect();
    let edge_labels: Vec<EdgeLabel> = (0..count)
        .map(|index| {
            segment_label(
                corners[index],
                corners[(index + 1) % count],
                edges,
                face,
                eps,
            )
        })
        .collect();
    Some(PlanarFace::straight(face, *plane, polygon, edge_labels))
}

pub(super) fn decompose(
    face: FaceLabel,
    plane: &Plane3,
    loops: &[FaceLoop],
    tolerance: Tolerance,
) -> Vec<PlanarFace> {
    let eps = tolerance.value();
    let verts: Vec<Vertex2> = loops
        .iter()
        .flat_map(|face_loop| {
            face_loop.verts.iter().map(|vertex| Vertex2 {
                local: to_local(plane, vertex.position),
                label: vertex.label,
            })
        })
        .collect();
    let edges: Vec<Edge2> = loops
        .iter()
        .flat_map(|face_loop| {
            let count = face_loop.verts.len();
            (0..count).map(move |index| Edge2 {
                a: to_local(plane, face_loop.verts[index].position),
                b: to_local(plane, face_loop.verts[(index + 1) % count].position),
                label: face_loop.edges[index],
            })
        })
        .collect();
    let ys = unique_ys(&verts, eps);
    ys.windows(2)
        .filter(|slab| slab[1] - slab[0] > eps)
        .flat_map(|slab| {
            let (ya, yb) = (slab[0], slab[1]);
            let ymid = f64::midpoint(ya, yb);
            let mut crossings: Vec<&Edge2> =
                edges.iter().filter(|edge| straddles(edge, ymid)).collect();
            crossings.sort_by(|left, right| x_at(left, ymid).total_cmp(&x_at(right, ymid)));
            crossings
                .chunks_exact(2)
                .filter_map(|pair| {
                    let (left, right) = (pair[0], pair[1]);
                    trapezoid(
                        &[
                            (x_at(left, ya), ya),
                            (x_at(right, ya), ya),
                            (x_at(right, yb), yb),
                            (x_at(left, yb), yb),
                        ],
                        plane,
                        &edges,
                        &verts,
                        face,
                        eps,
                    )
                })
                .collect::<Vec<_>>()
        })
        .collect()
}
