use serde::{Deserialize, Serialize};

use crate::{Result, TypesError};

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(try_from = "u64", into = "u64")]
pub struct StepEntityId(u64);

impl StepEntityId {
    pub fn new(value: u64) -> Result<Self> {
        if value == 0 {
            return Err(TypesError::ZeroStepEntityId);
        }
        Ok(Self(value))
    }

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

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

impl From<StepEntityId> for u64 {
    fn from(id: StepEntityId) -> Self {
        id.0
    }
}

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

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum StepSchema {
    Ap214,
    Ap242E2,
}

impl StepSchema {
    #[must_use]
    pub const fn label(self) -> &'static str {
        match self {
            Self::Ap214 => "AP214",
            Self::Ap242E2 => "AP242E2",
        }
    }
}

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

macro_rules! string_newtype {
    ($($name:ident),+ $(,)?) => {
        $(
            #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
            #[serde(transparent)]
            pub struct $name(String);

            impl $name {
                #[must_use]
                pub fn new(value: impl Into<String>) -> Self {
                    Self(value.into())
                }

                #[must_use]
                pub fn as_str(&self) -> &str {
                    &self.0
                }
            }

            impl core::fmt::Display for $name {
                fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                    f.write_str(&self.0)
                }
            }
        )+
    };
}

string_newtype!(StepOriginatingSystem, StepOrganization, StepFileName);

#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StepFileHeader {
    pub schema: StepSchema,
    pub originating_system: StepOriginatingSystem,
    pub organization: StepOrganization,
    pub file_name: StepFileName,
}

impl core::fmt::Display for StepFileHeader {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "step_header{{ schema={}, originating_system={}, organization={}, file_name={} }}",
            self.schema, self.originating_system, self.organization, self.file_name
        )
    }
}
