use serde::{Deserialize, Serialize};

use crate::{EntityRef, FeatureId, MatchScore};

#[derive(
    Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct FeatureGeneration(u64);

impl FeatureGeneration {
    pub const START: Self = Self(0);

    #[must_use]
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

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

    #[must_use]
    pub const fn succ(self) -> Self {
        Self(self.0.saturating_add(1))
    }

    #[must_use]
    pub const fn raised_to(self, other: Self) -> Self {
        if other.0 > self.0 { other } else { self }
    }
}

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

#[derive(
    Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
pub enum RollbackMarker {
    #[default]
    AtEnd,
    Above(FeatureId),
}

impl RollbackMarker {
    #[must_use]
    pub const fn feature(self) -> Option<FeatureId> {
        match self {
            Self::AtEnd => None,
            Self::Above(id) => Some(id),
        }
    }
}

impl core::fmt::Display for RollbackMarker {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::AtEnd => f.write_str("rollback=end"),
            Self::Above(id) => write!(f, "rollback=above({id:?})"),
        }
    }
}

#[derive(
    Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
pub enum SuppressionState {
    #[default]
    Active,
    Suppressed,
}

impl SuppressionState {
    #[must_use]
    pub const fn is_suppressed(self) -> bool {
        matches!(self, Self::Suppressed)
    }

    #[must_use]
    pub const fn toggled(self) -> Self {
        match self {
            Self::Active => Self::Suppressed,
            Self::Suppressed => Self::Active,
        }
    }
}

impl core::fmt::Display for SuppressionState {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::Active => f.write_str("active"),
            Self::Suppressed => f.write_str("suppressed"),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum BuildFailure {
    UnsolvedSketch,
    Kernel,
    CutWithoutBody,
}

impl core::fmt::Display for BuildFailure {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::UnsolvedSketch => f.write_str("sketch did not solve"),
            Self::Kernel => f.write_str("solid build failed"),
            Self::CutWithoutBody => f.write_str("cut has no body to remove material from"),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum RebuildError {
    DanglingReference(EntityRef),
    NonPlanarSketchTarget,
    UpstreamUnresolved,
    Build(BuildFailure),
}

impl core::fmt::Display for RebuildError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::DanglingReference(reference) => write!(f, "dangling reference {reference}"),
            Self::NonPlanarSketchTarget => f.write_str("sketch plane target is not planar"),
            Self::UpstreamUnresolved => f.write_str("depends on a feature that failed to build"),
            Self::Build(failure) => failure.fmt(f),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum RebuildWarning {
    RepairedReference {
        reference: EntityRef,
        score: MatchScore,
    },
}

impl core::fmt::Display for RebuildWarning {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::RepairedReference { reference, score } => {
                write!(f, "reference {reference} repaired at {score}")
            }
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum RebuildStatus {
    UpToDate,
    NeedsRebuild,
    Warning(RebuildWarning),
    Error(RebuildError),
}

impl RebuildStatus {
    #[must_use]
    pub const fn is_error(self) -> bool {
        matches!(self, Self::Error(_))
    }

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

    #[must_use]
    pub const fn worse_of(self, other: Self) -> Self {
        match (self, other) {
            (own @ Self::Error(_), _) => own,
            (_, down @ Self::Error(_)) => down,
            (own @ Self::Warning(_), _) => own,
            (_, down @ Self::Warning(_)) => down,
            (own @ Self::NeedsRebuild, _) => own,
            (_, down @ Self::NeedsRebuild) => down,
            (own, _) => own,
        }
    }
}

impl core::fmt::Display for RebuildStatus {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::UpToDate => f.write_str("up to date"),
            Self::NeedsRebuild => f.write_str("needs rebuild"),
            Self::Warning(warning) => warning.fmt(f),
            Self::Error(error) => error.fmt(f),
        }
    }
}
