use serde::{Deserialize, Serialize};

use crate::{EdgeLabel, FaceLabel, Plane3, Point3, Result, TypesError, UnitVec3, VertexLabel};

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FaceFingerprint {
    pub plane: Plane3,
    pub centroid: Point3,
}

impl core::fmt::Display for FaceFingerprint {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "face@{}", self.centroid)
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EdgeFingerprint {
    pub sample: Point3,
    pub direction: UnitVec3,
}

impl core::fmt::Display for EdgeFingerprint {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "edge@{}", self.sample)
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VertexFingerprint {
    pub point: Point3,
}

impl core::fmt::Display for VertexFingerprint {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "vertex@{}", self.point)
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum EntityFingerprint {
    Face(FaceFingerprint),
    Edge(EdgeFingerprint),
    Vertex(VertexFingerprint),
}

impl core::fmt::Display for EntityFingerprint {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::Face(g) => g.fmt(f),
            Self::Edge(g) => g.fmt(f),
            Self::Vertex(g) => g.fmt(f),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum EntityRef {
    Face(FaceLabel, FaceFingerprint),
    Edge(EdgeLabel, EdgeFingerprint),
    Vertex(VertexLabel, VertexFingerprint),
}

impl EntityRef {
    #[must_use]
    pub fn fingerprint(self) -> EntityFingerprint {
        match self {
            Self::Face(_, g) => EntityFingerprint::Face(g),
            Self::Edge(_, g) => EntityFingerprint::Edge(g),
            Self::Vertex(_, g) => EntityFingerprint::Vertex(g),
        }
    }
}

impl core::fmt::Display for EntityRef {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::Face(label, _) => label.fmt(f),
            Self::Edge(label, _) => label.fmt(f),
            Self::Vertex(label, _) => label.fmt(f),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FaceRef {
    pub label: FaceLabel,
    pub fingerprint: FaceFingerprint,
}

impl FaceRef {
    #[must_use]
    pub const fn new(label: FaceLabel, fingerprint: FaceFingerprint) -> Self {
        Self { label, fingerprint }
    }

    #[must_use]
    pub const fn entity_ref(self) -> EntityRef {
        EntityRef::Face(self.label, self.fingerprint)
    }
}

impl core::fmt::Display for FaceRef {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.label.fmt(f)
    }
}

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

impl MatchScore {
    pub fn new(value: f64) -> Result<Self> {
        if value.is_finite() && (0.0..=1.0).contains(&value) {
            Ok(Self(value))
        } else {
            Err(TypesError::MatchScoreOutOfRange(value))
        }
    }

    #[must_use]
    pub const fn value(self) -> f64 {
        self.0
    }
}

impl TryFrom<f64> for MatchScore {
    type Error = TypesError;
    fn try_from(value: f64) -> Result<Self> {
        Self::new(value)
    }
}

impl From<MatchScore> for f64 {
    fn from(score: MatchScore) -> Self {
        score.0
    }
}

impl core::fmt::Display for MatchScore {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "score={}", self.0)
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum Resolution<Id> {
    Resolved(Id),
    Repaired { id: Id, score: MatchScore },
    Dangling { last_known: EntityRef },
}

impl<Id> Resolution<Id> {
    #[must_use]
    pub fn id(self) -> Option<Id> {
        match self {
            Self::Resolved(id) | Self::Repaired { id, .. } => Some(id),
            Self::Dangling { .. } => None,
        }
    }

    #[must_use]
    pub fn map<U>(self, f: impl FnOnce(Id) -> U) -> Resolution<U> {
        match self {
            Self::Resolved(id) => Resolution::Resolved(f(id)),
            Self::Repaired { id, score } => Resolution::Repaired { id: f(id), score },
            Self::Dangling { last_known } => Resolution::Dangling { last_known },
        }
    }

    #[must_use]
    pub const fn is_dangling(&self) -> bool {
        matches!(self, Self::Dangling { .. })
    }
}
