use crate::KernelError;
use crate::extrude::{
    ContourSelection, ExtrudeOperation, ExtrudeSense, FeatureScope, ThinPlan, ThinWall,
};
use bone_types::dimensioned_serde;
use bone_types::{
    Angle, Axis3, EdgeLabel, FaceLabel, FeatureId, SketchEntityId, SketchId, VertexLabel, radian,
};
use core::f64::consts::TAU;
use serde::{Deserialize, Serialize};

const ANGLE_EPS_RAD: f64 = 1.0e-9;

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "f64", into = "f64")]
pub struct RevolveAngle(Angle);

impl RevolveAngle {
    pub fn new(angle: Angle) -> Result<Self, KernelError> {
        let radians = angle.get::<radian>();
        if radians.is_finite() && radians > ANGLE_EPS_RAD && radians <= TAU + ANGLE_EPS_RAD {
            Ok(Self(angle))
        } else {
            Err(KernelError::RevolveAngleOutOfRange(radians))
        }
    }

    #[must_use]
    pub fn full() -> Self {
        Self(Angle::new::<radian>(TAU))
    }

    #[must_use]
    pub fn get(self) -> Angle {
        self.0
    }

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

impl From<RevolveAngle> for f64 {
    fn from(value: RevolveAngle) -> Self {
        value.0.get::<radian>()
    }
}

impl TryFrom<f64> for RevolveAngle {
    type Error = KernelError;

    fn try_from(value: f64) -> Result<Self, Self::Error> {
        Self::new(Angle::new::<radian>(value))
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum RevolveAxis {
    SketchLine(SketchEntityId),
    Edge(EdgeLabel),
    Datum(FeatureId),
    Sketch3Line {
        sketch: FeatureId,
        entity: SketchEntityId,
    },
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum RevolveEndCondition {
    Blind {
        angle: RevolveAngle,
    },
    MidPlane {
        angle: RevolveAngle,
    },
    UpToVertex {
        vertex: VertexLabel,
    },
    UpToSurface {
        surface: FaceLabel,
    },
    OffsetFromSurface {
        surface: FaceLabel,
        #[serde(with = "dimensioned_serde::angle_si")]
        offset: Angle,
        #[serde(default)]
        reverse_offset: bool,
    },
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RevolveDirection2 {
    pub end_condition: RevolveEndCondition,
    #[serde(default)]
    pub reverse: bool,
}

impl RevolveDirection2 {
    #[must_use]
    pub fn blind(angle: RevolveAngle) -> Self {
        Self {
            end_condition: RevolveEndCondition::Blind { angle },
            reverse: false,
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RevolveFeature {
    pub sketch: SketchId,
    pub axis: RevolveAxis,
    #[serde(default = "forward_sense")]
    pub sense: ExtrudeSense,
    pub end_condition: RevolveEndCondition,
    #[serde(default)]
    pub direction2: Option<RevolveDirection2>,
    #[serde(default)]
    pub thin_wall: Option<ThinWall>,
    #[serde(default)]
    pub flip_side: bool,
    #[serde(default)]
    pub operation: ExtrudeOperation,
    #[serde(default)]
    pub scope: FeatureScope,
    #[serde(default)]
    pub contours: ContourSelection,
}

fn forward_sense() -> ExtrudeSense {
    ExtrudeSense::Forward
}

impl RevolveFeature {
    #[must_use]
    pub fn blind(sketch: SketchId, axis: RevolveAxis, angle: RevolveAngle) -> Self {
        Self {
            sketch,
            axis,
            sense: ExtrudeSense::Forward,
            end_condition: RevolveEndCondition::Blind { angle },
            direction2: None,
            thin_wall: None,
            flip_side: false,
            operation: ExtrudeOperation::default(),
            scope: FeatureScope::default(),
            contours: ContourSelection::All,
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ResolvedRevolve {
    pub axis: Axis3,
    pub forward_rad: f64,
    pub backward_rad: f64,
    pub thin: Option<ThinPlan>,
}

impl ResolvedRevolve {
    #[must_use]
    pub fn total_rad(self) -> f64 {
        self.forward_rad + self.backward_rad
    }
}
