use bone_types::{
    Aabb3, Angle, AngleTolerance, ChordHeightTolerance, Length, Parameter, Plane3, Point3,
    Tolerance, UnitVec3, Vec3,
};
use core::f64::consts::{FRAC_PI_2, TAU};
use uom::si::angle::radian;
use uom::si::length::millimeter;

use crate::KernelError;
use crate::angles;
use crate::angular_span::AngularSpan;
use crate::circular3;
use crate::mesh::{MeshVertex, TriMesh};
use crate::surface3::Surface3;

const MIN_SEGMENTS: u32 = 3;

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ConeSurface {
    plane: Plane3,
    base_radius: Length,
    half_angle: Angle,
    start_angle: Angle,
    sweep_angle: Angle,
    height: Length,
}

impl ConeSurface {
    pub fn new(
        plane: Plane3,
        base_radius: Length,
        half_angle: Angle,
        revolution: AngularSpan,
        height: Length,
        tolerance: Tolerance,
    ) -> Result<Self, KernelError> {
        let start_angle = revolution.start();
        let sweep_angle = revolution.sweep();
        let base = base_radius.get::<millimeter>();
        let h = height.get::<millimeter>();
        let a = half_angle.get::<radian>();
        if !base.is_finite() || !h.is_finite() || !a.is_finite() {
            return Err(KernelError::DegenerateCone);
        }
        if base < 0.0 || h < tolerance.value() || a.abs() >= FRAC_PI_2 {
            return Err(KernelError::DegenerateCone);
        }
        let top = base + h * a.tan();
        if top < 0.0 || base.max(top) < tolerance.value() {
            return Err(KernelError::DegenerateCone);
        }
        let sweep = sweep_angle.get::<radian>().abs();
        let angle_eps =
            AngleTolerance::from_arc_length(tolerance, Length::new::<millimeter>(base.max(top)))
                .radians();
        if !sweep.is_finite() || sweep < angle_eps || sweep > TAU + angle_eps {
            return Err(KernelError::DegenerateCone);
        }
        Ok(Self {
            plane,
            base_radius,
            half_angle,
            start_angle,
            sweep_angle,
            height,
        })
    }

    #[must_use]
    pub const fn plane(self) -> Plane3 {
        self.plane
    }

    #[must_use]
    pub fn center(self) -> Point3 {
        self.plane.origin()
    }

    #[must_use]
    pub fn axis(self) -> UnitVec3 {
        self.plane.normal()
    }

    #[must_use]
    pub const fn base_radius(self) -> Length {
        self.base_radius
    }

    #[must_use]
    pub const fn half_angle(self) -> Angle {
        self.half_angle
    }

    #[must_use]
    pub const fn start_angle(self) -> Angle {
        self.start_angle
    }

    #[must_use]
    pub const fn sweep_angle(self) -> Angle {
        self.sweep_angle
    }

    #[must_use]
    pub const fn height(self) -> Length {
        self.height
    }

    #[must_use]
    pub fn base_radius_mm(self) -> f64 {
        self.base_radius.get::<millimeter>()
    }

    #[must_use]
    pub fn height_mm(self) -> f64 {
        self.height.get::<millimeter>()
    }

    #[must_use]
    pub fn half_angle_rad(self) -> f64 {
        self.half_angle.get::<radian>()
    }

    #[must_use]
    pub fn start_rad(self) -> f64 {
        self.start_angle.get::<radian>()
    }

    #[must_use]
    pub fn sweep_rad(self) -> f64 {
        self.sweep_angle.get::<radian>()
    }

    #[must_use]
    pub fn top_radius_mm(self) -> f64 {
        self.base_radius_mm() + self.height_mm() * self.half_angle_rad().tan()
    }

    #[must_use]
    fn radius_at(self, v: f64) -> f64 {
        self.base_radius_mm() + v * self.height_mm() * self.half_angle_rad().tan()
    }

    #[must_use]
    fn point_at(self, u: f64, v: f64) -> Point3 {
        let theta = Angle::new::<radian>(self.start_rad() + self.sweep_rad() * u);
        let ring = circular3::point_at(
            self.plane,
            Length::new::<millimeter>(self.radius_at(v)),
            theta,
        );
        ring + self.plane.normal().into_vec(self.height * v)
    }

    #[must_use]
    fn radial_components(self, u: f64) -> (f64, f64, f64) {
        let theta = self.start_rad() + self.sweep_rad() * u;
        let (xx, xy, xz) = self.plane.x_axis().components();
        let (yx, yy, yz) = self.plane.y_axis().components();
        let (c, s) = (theta.cos(), theta.sin());
        (c * xx + s * yx, c * xy + s * yy, c * xz + s * yz)
    }

    #[must_use]
    pub(crate) fn orientation(self) -> f64 {
        if self.sweep_rad() < 0.0 { -1.0 } else { 1.0 }
    }

    #[must_use]
    pub(crate) fn min_radius_of_curvature(self) -> Length {
        let smallest = self.base_radius_mm().min(self.top_radius_mm());
        Length::new::<millimeter>(smallest / self.half_angle_rad().cos())
    }

    #[must_use]
    fn normal_at(self, u: f64) -> UnitVec3 {
        let (rx, ry, rz) = self.radial_components(u);
        let (ax, ay, az) = self.plane.normal().components();
        let (ca, sa) = (self.half_angle_rad().cos(), self.half_angle_rad().sin());
        let normal =
            UnitVec3::new_unchecked(ca * rx - sa * ax, ca * ry - sa * ay, ca * rz - sa * az);
        if self.orientation() < 0.0 {
            normal.reversed()
        } else {
            normal
        }
    }
}

impl Surface3 for ConeSurface {
    fn evaluate(&self, u: Parameter, v: Parameter) -> Point3 {
        self.point_at(u.value(), v.value())
    }

    fn partials(&self, u: Parameter, v: Parameter) -> (Vec3, Vec3) {
        let theta = Angle::new::<radian>(self.start_rad() + self.sweep_rad() * u.value());
        let tangent = circular3::tangent_vec(
            self.plane,
            Length::new::<millimeter>(self.radius_at(v.value())),
            theta,
        ) * self.sweep_rad();
        let (rx, ry, rz) = self.radial_components(u.value());
        let slope = self.height_mm() * self.half_angle_rad().tan();
        let axial = self.plane.normal().into_vec(self.height) + Vec3::from_mm(rx, ry, rz) * slope;
        (tangent, axial)
    }

    fn normal(&self, u: Parameter, _v: Parameter) -> UnitVec3 {
        self.normal_at(u.value())
    }

    fn bounding_box(&self) -> Aabb3 {
        let base = circular3::bounding_box(
            self.plane,
            self.base_radius,
            self.start_angle,
            self.sweep_angle,
        );
        let top_origin = self.plane.origin() + self.plane.normal().into_vec(self.height);
        let top_plane = Plane3::new_unchecked(top_origin, self.plane.x_axis(), self.plane.y_axis());
        let top = circular3::bounding_box(
            top_plane,
            Length::new::<millimeter>(self.top_radius_mm()),
            self.start_angle,
            self.sweep_angle,
        );
        base.union(top)
    }

    fn contains_point(&self, p: Point3, tolerance: Tolerance) -> bool {
        let (px, py, pn) = circular3::local_coords(self.plane, p);
        if pn < -tolerance.value() || pn > self.height_mm() + tolerance.value() {
            return false;
        }
        let radial = (px * px + py * py).sqrt();
        let expected = self.base_radius_mm() + pn * self.half_angle_rad().tan();
        let distance = (radial - expected) * self.half_angle_rad().cos();
        if distance.abs() > tolerance.value() {
            return false;
        }
        if expected < tolerance.value() {
            return true;
        }
        let angle_eps =
            AngleTolerance::from_arc_length(tolerance, Length::new::<millimeter>(expected))
                .radians();
        angles::contains(py.atan2(px), self.start_rad(), self.sweep_rad(), angle_eps)
    }

    fn tessellate(&self, chord: ChordHeightTolerance, angle: AngleTolerance) -> TriMesh {
        let radius = Length::new::<millimeter>(self.base_radius_mm().max(self.top_radius_mm()));
        let n = circular3::sample_count(radius, self.sweep_angle, chord, MIN_SEGMENTS).max(
            circular3::angular_sample_count(self.sweep_angle, angle, MIN_SEGMENTS),
        );
        let inv_n = 1.0 / f64::from(n);
        TriMesh::from_grid(n + 1, 2, |i, j| {
            let u = f64::from(i) * inv_n;
            MeshVertex::new(self.point_at(u, f64::from(j)), self.normal_at(u))
        })
    }
}

impl core::fmt::Display for ConeSurface {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "cone_surface{{ c={}, base_r={} mm, half_angle={} rad, start={} rad, sweep={} rad, h={} mm, axis={} }}",
            self.center(),
            self.base_radius_mm(),
            self.half_angle_rad(),
            self.start_rad(),
            self.sweep_rad(),
            self.height_mm(),
            self.axis(),
        )
    }
}
