use bone_types::{Angle, Length, SketchEntityId, dimensioned_serde};
use serde::{Deserialize, Serialize};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DimensionKind {
    Driving,
    Driven,
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum DimensionValue {
    #[serde(with = "dimensioned_serde::length_si")]
    Length(Length),
    #[serde(with = "dimensioned_serde::angle_si")]
    Angle(Angle),
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum SketchDimension {
    Linear {
        a: SketchEntityId,
        b: SketchEntityId,
        #[serde(with = "dimensioned_serde::length_si")]
        value: Length,
        kind: DimensionKind,
    },
    Radius {
        target: SketchEntityId,
        #[serde(with = "dimensioned_serde::length_si")]
        value: Length,
        kind: DimensionKind,
    },
    Diameter {
        target: SketchEntityId,
        #[serde(with = "dimensioned_serde::length_si")]
        value: Length,
        kind: DimensionKind,
    },
    Angular {
        a: SketchEntityId,
        b: SketchEntityId,
        #[serde(with = "dimensioned_serde::angle_si")]
        value: Angle,
        kind: DimensionKind,
    },
}

impl SketchDimension {
    #[must_use]
    pub const fn kind(&self) -> DimensionKind {
        match *self {
            Self::Linear { kind, .. }
            | Self::Radius { kind, .. }
            | Self::Diameter { kind, .. }
            | Self::Angular { kind, .. } => kind,
        }
    }

    #[must_use]
    pub const fn value(&self) -> DimensionValue {
        match *self {
            Self::Linear { value, .. }
            | Self::Radius { value, .. }
            | Self::Diameter { value, .. } => DimensionValue::Length(value),
            Self::Angular { value, .. } => DimensionValue::Angle(value),
        }
    }

    #[must_use]
    pub const fn references(&self) -> DimensionRefs {
        match *self {
            Self::Linear { a, b, .. } | Self::Angular { a, b, .. } => {
                DimensionRefs([Some(a), Some(b)])
            }
            Self::Radius { target, .. } | Self::Diameter { target, .. } => {
                DimensionRefs([Some(target), None])
            }
        }
    }

    #[must_use]
    pub const fn with_kind(self, kind: DimensionKind) -> Self {
        match self {
            Self::Linear { a, b, value, .. } => Self::Linear { a, b, value, kind },
            Self::Radius { target, value, .. } => Self::Radius {
                target,
                value,
                kind,
            },
            Self::Diameter { target, value, .. } => Self::Diameter {
                target,
                value,
                kind,
            },
            Self::Angular { a, b, value, .. } => Self::Angular { a, b, value, kind },
        }
    }

    pub fn with_value(self, value: DimensionValue) -> Result<Self, DimensionValueMismatch> {
        match (self, value) {
            (Self::Linear { a, b, kind, .. }, DimensionValue::Length(value)) => {
                Ok(Self::Linear { a, b, value, kind })
            }
            (Self::Radius { target, kind, .. }, DimensionValue::Length(value)) => {
                Ok(Self::Radius {
                    target,
                    value,
                    kind,
                })
            }
            (Self::Diameter { target, kind, .. }, DimensionValue::Length(value)) => {
                Ok(Self::Diameter {
                    target,
                    value,
                    kind,
                })
            }
            (Self::Angular { a, b, kind, .. }, DimensionValue::Angle(value)) => {
                Ok(Self::Angular { a, b, value, kind })
            }
            _ => Err(DimensionValueMismatch),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DimensionRefs([Option<SketchEntityId>; 2]);

impl IntoIterator for DimensionRefs {
    type Item = SketchEntityId;
    type IntoIter = core::iter::Flatten<core::array::IntoIter<Option<SketchEntityId>, 2>>;

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

#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("dimension value kind does not match variant")]
pub struct DimensionValueMismatch;
