use bone_types::{
    Aabb3, BrepEdgeId, ChordHeightTolerance, CreaseAngle, EdgeLabel, EdgeRole, Parameter, Plane3,
    Point2, Point3, SideKind, Tolerance, UnitVec3, Vec3,
};
use serde::{Deserialize, Serialize};
use std::ops::Deref;

use crate::arc3::Arc3;
use crate::circle3::Circle3;
use crate::closest::ClosestPoint3;
use crate::curve2::Curve2Kind;
use crate::curve3::Curve3;
use crate::line3::Line3;

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum EdgeCurve3 {
    Line(Line3),
    Arc(Arc3),
    Circle(Circle3),
}

impl Curve3 for EdgeCurve3 {
    fn evaluate(&self, t: Parameter) -> Point3 {
        match self {
            Self::Line(c) => c.evaluate(t),
            Self::Arc(c) => c.evaluate(t),
            Self::Circle(c) => c.evaluate(t),
        }
    }

    fn derivative(&self, t: Parameter) -> Vec3 {
        match self {
            Self::Line(c) => c.derivative(t),
            Self::Arc(c) => c.derivative(t),
            Self::Circle(c) => c.derivative(t),
        }
    }

    fn bounding_box(&self) -> Aabb3 {
        match self {
            Self::Line(c) => c.bounding_box(),
            Self::Arc(c) => c.bounding_box(),
            Self::Circle(c) => c.bounding_box(),
        }
    }

    fn closest_point(&self, p: Point3, tolerance: Tolerance) -> ClosestPoint3 {
        match self {
            Self::Line(c) => c.closest_point(p, tolerance),
            Self::Arc(c) => c.closest_point(p, tolerance),
            Self::Circle(c) => c.closest_point(p, tolerance),
        }
    }

    fn tessellate(&self, tolerance: ChordHeightTolerance) -> Vec<Point3> {
        match self {
            Self::Line(c) => c.tessellate(tolerance),
            Self::Arc(c) => c.tessellate(tolerance),
            Self::Circle(c) => c.tessellate(tolerance),
        }
    }
}

impl core::fmt::Display for EdgeCurve3 {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::Line(c) => write!(f, "{c}"),
            Self::Arc(c) => write!(f, "{c}"),
            Self::Circle(c) => write!(f, "{c}"),
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct EdgePolyline {
    edge: BrepEdgeId,
    label: EdgeLabel,
    points: Vec<Point3>,
    curve: EdgeCurve3,
    crease: CreaseAngle,
}

impl EdgePolyline {
    #[must_use]
    pub(crate) fn new(
        edge: BrepEdgeId,
        label: EdgeLabel,
        points: Vec<Point3>,
        curve: EdgeCurve3,
        crease: CreaseAngle,
    ) -> Self {
        Self {
            edge,
            label,
            points,
            curve,
            crease,
        }
    }

    #[must_use]
    pub fn edge(&self) -> BrepEdgeId {
        self.edge
    }

    #[must_use]
    pub fn label(&self) -> EdgeLabel {
        self.label
    }

    #[must_use]
    pub fn points(&self) -> &[Point3] {
        &self.points
    }

    #[must_use]
    pub fn curve(&self) -> &EdgeCurve3 {
        &self.curve
    }

    #[must_use]
    pub fn crease(&self) -> CreaseAngle {
        self.crease
    }
}

#[derive(Clone, Debug, PartialEq, Default)]
pub struct EdgePolylines(Vec<EdgePolyline>);

impl EdgePolylines {
    #[must_use]
    pub(crate) fn new(polylines: Vec<EdgePolyline>) -> Self {
        Self(polylines)
    }

    #[must_use]
    pub fn as_slice(&self) -> &[EdgePolyline] {
        &self.0
    }

    #[must_use]
    pub fn len(&self) -> usize {
        self.0.len()
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub fn iter(&self) -> core::slice::Iter<'_, EdgePolyline> {
        self.0.iter()
    }
}

impl Deref for EdgePolylines {
    type Target = [EdgePolyline];

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl IntoIterator for EdgePolylines {
    type Item = EdgePolyline;
    type IntoIter = std::vec::IntoIter<EdgePolyline>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

impl<'a> IntoIterator for &'a EdgePolylines {
    type Item = &'a EdgePolyline;
    type IntoIter = core::slice::Iter<'a, EdgePolyline>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.iter()
    }
}

#[must_use]
pub(crate) fn is_render_visible(label: EdgeLabel) -> bool {
    !matches!(
        label.role,
        EdgeRole::SideEdge {
            side: SideKind::Seam,
            ..
        }
    )
}

#[must_use]
pub(crate) fn lift_curve2(
    curve: Curve2Kind,
    plane: Plane3,
    offset_mm: f64,
    tolerance: Tolerance,
) -> Option<EdgeCurve3> {
    match curve {
        Curve2Kind::Line(line) => {
            let start = lift_point(plane, offset_mm, line.start());
            let end = lift_point(plane, offset_mm, line.end());
            Line3::new(start, end, tolerance).ok().map(EdgeCurve3::Line)
        }
        Curve2Kind::Arc(arc) => {
            let cap_plane = lifted_origin_plane(plane, offset_mm);
            let (cx, cy) = arc.center().coords_mm();
            let arc_plane = recentered_plane(cap_plane, cx, cy);
            Arc3::new(
                arc_plane,
                arc.radius(),
                arc.start_angle(),
                arc.sweep_angle(),
                tolerance,
            )
            .ok()
            .map(EdgeCurve3::Arc)
        }
        Curve2Kind::Circle(circle) => {
            let cap_plane = lifted_origin_plane(plane, offset_mm);
            let (cx, cy) = circle.center().coords_mm();
            let circle_plane = recentered_plane(cap_plane, cx, cy);
            Circle3::new(circle_plane, circle.radius(), tolerance)
                .ok()
                .map(EdgeCurve3::Circle)
        }
    }
}

fn lifted_origin_plane(plane: Plane3, offset_mm: f64) -> Plane3 {
    Plane3::new_unchecked(
        plane.point_at_local(0.0, 0.0, offset_mm),
        plane.x_axis(),
        plane.y_axis(),
    )
}

fn recentered_plane(plane: Plane3, cx: f64, cy: f64) -> Plane3 {
    Plane3::new_unchecked(
        plane.point_at_local(cx, cy, 0.0),
        plane.x_axis(),
        plane.y_axis(),
    )
}

fn lift_point(plane: Plane3, offset_mm: f64, point: Point2) -> Point3 {
    let (u, v) = point.coords_mm();
    plane.point_at_local(u, v, offset_mm)
}

#[must_use]
pub(crate) fn line_between(start: Point3, end: Point3) -> EdgeCurve3 {
    EdgeCurve3::Line(Line3::new_unchecked(start, end))
}

#[must_use]
pub(crate) fn crease_from_normals(a: UnitVec3, b: UnitVec3) -> CreaseAngle {
    let (ax, ay, az) = a.components();
    let (bx, by, bz) = b.components();
    CreaseAngle::from_dot(ax * bx + ay * by + az * bz)
}
