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

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PointData {
    at: Point2,
}

impl PointData {
    #[must_use]
    pub const fn new(at: Point2) -> Self {
        Self { at }
    }

    #[must_use]
    pub const fn at(self) -> Point2 {
        self.at
    }

    #[must_use]
    pub const fn for_construction(self) -> bool {
        true
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LineData {
    a: SketchEntityId,
    b: SketchEntityId,
    for_construction: bool,
}

impl LineData {
    #[must_use]
    pub const fn new(a: SketchEntityId, b: SketchEntityId, for_construction: bool) -> Self {
        Self {
            a,
            b,
            for_construction,
        }
    }

    #[must_use]
    pub const fn a(self) -> SketchEntityId {
        self.a
    }

    #[must_use]
    pub const fn b(self) -> SketchEntityId {
        self.b
    }

    #[must_use]
    pub const fn for_construction(self) -> bool {
        self.for_construction
    }

    #[must_use]
    pub const fn with_construction(self, for_construction: bool) -> Self {
        Self {
            for_construction,
            ..self
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ArcData {
    center: SketchEntityId,
    start: SketchEntityId,
    end: SketchEntityId,
    for_construction: bool,
}

impl ArcData {
    #[must_use]
    pub const fn new(
        center: SketchEntityId,
        start: SketchEntityId,
        end: SketchEntityId,
        for_construction: bool,
    ) -> Self {
        Self {
            center,
            start,
            end,
            for_construction,
        }
    }

    #[must_use]
    pub const fn center(self) -> SketchEntityId {
        self.center
    }

    #[must_use]
    pub const fn start(self) -> SketchEntityId {
        self.start
    }

    #[must_use]
    pub const fn end(self) -> SketchEntityId {
        self.end
    }

    #[must_use]
    pub const fn for_construction(self) -> bool {
        self.for_construction
    }

    #[must_use]
    pub const fn with_construction(self, for_construction: bool) -> Self {
        Self {
            for_construction,
            ..self
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CircleData {
    center: SketchEntityId,
    #[serde(with = "dimensioned_serde::length_si")]
    radius: Length,
    for_construction: bool,
}

impl CircleData {
    #[must_use]
    pub const fn new(center: SketchEntityId, radius: Length, for_construction: bool) -> Self {
        Self {
            center,
            radius,
            for_construction,
        }
    }

    #[must_use]
    pub const fn center(self) -> SketchEntityId {
        self.center
    }

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

    #[must_use]
    pub const fn for_construction(self) -> bool {
        self.for_construction
    }

    #[must_use]
    pub const fn with_construction(self, for_construction: bool) -> Self {
        Self {
            for_construction,
            ..self
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SketchEntityKind {
    Point,
    Line,
    Arc,
    Circle,
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum SketchEntity {
    Point(PointData),
    Line(LineData),
    Arc(ArcData),
    Circle(CircleData),
}

impl SketchEntity {
    #[must_use]
    pub const fn point(at: Point2) -> Self {
        Self::Point(PointData::new(at))
    }

    #[must_use]
    pub const fn line(a: SketchEntityId, b: SketchEntityId, for_construction: bool) -> Self {
        Self::Line(LineData::new(a, b, for_construction))
    }

    #[must_use]
    pub const fn arc(
        center: SketchEntityId,
        start: SketchEntityId,
        end: SketchEntityId,
        for_construction: bool,
    ) -> Self {
        Self::Arc(ArcData::new(center, start, end, for_construction))
    }

    #[must_use]
    pub const fn circle(center: SketchEntityId, radius: Length, for_construction: bool) -> Self {
        Self::Circle(CircleData::new(center, radius, for_construction))
    }

    #[must_use]
    pub const fn for_construction(&self) -> bool {
        match *self {
            Self::Point(p) => p.for_construction(),
            Self::Line(l) => l.for_construction(),
            Self::Arc(a) => a.for_construction(),
            Self::Circle(c) => c.for_construction(),
        }
    }

    #[must_use]
    pub const fn is_point(&self) -> bool {
        matches!(self, Self::Point(_))
    }

    #[must_use]
    pub const fn kind(&self) -> SketchEntityKind {
        match *self {
            Self::Point(_) => SketchEntityKind::Point,
            Self::Line(_) => SketchEntityKind::Line,
            Self::Arc(_) => SketchEntityKind::Arc,
            Self::Circle(_) => SketchEntityKind::Circle,
        }
    }

    #[must_use]
    pub const fn references(&self) -> EntityRefs {
        match *self {
            Self::Point(_) => EntityRefs([None, None, None]),
            Self::Line(l) => EntityRefs([Some(l.a()), Some(l.b()), None]),
            Self::Arc(a) => EntityRefs([Some(a.center()), Some(a.start()), Some(a.end())]),
            Self::Circle(c) => EntityRefs([Some(c.center()), None, None]),
        }
    }
}

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

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

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