/// An empty ID of a [`Node`] or [`Edge`].
///
/// [`Edge`]: crate::Edge
/// [`Node`]: crate::Node
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
#[error("ID is empty")]
pub struct EmptyId;

/// The unique ID of an [`Node`].
///
/// [`Node`]: crate::Node
#[derive(
    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[repr(transparent)]
#[serde(transparent)]
pub struct NodeId(pub(self) String);

impl NodeId {
    /// Returns the inner [`String`] representation.
    #[must_use]
    pub fn into_inner(self) -> String {
        self.0
    }

    /// Returns a reference to the inner [`String`] representation.
    #[must_use]
    pub const fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

impl std::fmt::Display for NodeId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl std::str::FromStr for NodeId {
    type Err = EmptyId;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        if value.is_empty() {
            return Err(EmptyId);
        }
        Ok(Self(value.to_string()))
    }
}

impl TryFrom<String> for NodeId {
    type Error = EmptyId;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        if value.is_empty() {
            return Err(EmptyId);
        }
        Ok(Self(value))
    }
}

/// The unique ID of an [`Edge`].
///
/// [`Edge`]: crate::Edge
#[derive(
    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[repr(transparent)]
#[serde(transparent)]
pub struct EdgeId(pub(self) String);

impl EdgeId {
    /// Returns the inner [`String`] representation.
    #[must_use]
    pub fn into_inner(self) -> String {
        self.0
    }

    /// Returns a reference to the inner [`String`] representation.
    #[must_use]
    pub const fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

impl std::fmt::Display for EdgeId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl std::str::FromStr for EdgeId {
    type Err = EmptyId;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        if value.is_empty() {
            return Err(EmptyId);
        }
        Ok(Self(value.to_string()))
    }
}

impl TryFrom<String> for EdgeId {
    type Error = EmptyId;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        if value.is_empty() {
            return Err(EmptyId);
        }
        Ok(Self(value))
    }
}
