use crate::{EdgeId, NodeId, color::Color};

/// The connection information of the start or end of an [`Edge`].
pub type Terminus = (NodeId, Option<Side>, Option<End>);

/// A connection between two [`Node`]s.
///
/// [`Node`]: crate::Node
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Edge {
    /// The unique ID of this [`Edge`].
    pub id: EdgeId,
    /// The [`Node`] this [`Edge`] starts at.
    ///
    /// [`Node`]: crate::Node
    pub from_node: NodeId,
    #[serde(skip_serializing_if = "Option::is_none")]
    from_side: Option<Side>,
    #[serde(skip_serializing_if = "Option::is_none")]
    from_end: Option<End>,
    /// The [`Node`] this [`Edge`] ends at.
    ///
    /// [`Node`]: crate::Node
    pub to_node: NodeId,
    #[serde(skip_serializing_if = "Option::is_none")]
    to_side: Option<Side>,
    #[serde(skip_serializing_if = "Option::is_none")]
    to_end: Option<End>,
    #[serde(skip_serializing_if = "Option::is_none")]
    color: Option<Color>,
    #[serde(skip_serializing_if = "Option::is_none")]
    label: Option<String>,
}

impl Edge {
    /// Creates a new [`Edge`].
    #[allow(clippy::too_many_arguments)]
    #[must_use]
    pub const fn new(
        id: EdgeId,
        from_node: NodeId,
        from_side: Option<Side>,
        from_end: Option<End>,
        to_node: NodeId,
        to_side: Option<Side>,
        to_end: Option<End>,
        color: Option<Color>,
        label: Option<String>,
    ) -> Self {
        Self {
            id,
            from_node,
            from_side,
            from_end,
            to_node,
            to_side,
            to_end,
            color,
            label,
        }
    }

    /// Returns a reference to the id of this [`Edge`].
    #[must_use]
    pub const fn id(&self) -> &EdgeId {
        &self.id
    }

    /// Returns the sources's [`NodeId`] of this [`Edge`].
    #[must_use]
    pub const fn from_node(&self) -> &NodeId {
        &self.from_node
    }

    /// Returns the sources's [`Side`] of this [`Edge`].
    #[must_use]
    pub const fn from_side(&self) -> Option<&Side> {
        self.from_side.as_ref()
    }

    /// Returns the sources's [`End`] of this [`Edge`].
    #[must_use]
    pub const fn from_end(&self) -> Option<&End> {
        self.from_end.as_ref()
    }

    /// Returns the target's [`NodeId`] of this [`Edge`].
    #[must_use]
    pub const fn to_node(&self) -> &NodeId {
        &self.to_node
    }

    /// Returns the target's [`Side`] of this [`Edge`].
    #[must_use]
    pub const fn to_side(&self) -> Option<&Side> {
        self.to_side.as_ref()
    }

    /// Returns the target's [`End`] of this [`Edge`].
    #[must_use]
    pub const fn to_end(&self) -> Option<&End> {
        self.to_end.as_ref()
    }

    /// Returns the color of this [`Edge`].
    #[must_use]
    pub const fn color(&self) -> Option<&Color> {
        self.color.as_ref()
    }

    /// Returns the label of this [`Edge`].
    #[must_use]
    pub const fn label(&self) -> Option<&String> {
        self.label.as_ref()
    }

    /// Sets the color of this [`Edge`].
    pub const fn set_color(&mut self, color: Color) -> &mut Self {
        self.color = Some(color);
        self
    }

    /// Remove color of this [`Edge`].
    pub fn remove_color(&mut self) -> Option<Color> {
        std::mem::take(&mut self.color)
    }

    /// Sets the label of this [`Edge`].
    pub fn set_label(&mut self, label: String) -> &mut Self {
        self.label = Some(label);
        self
    }

    /// Removes the label from this [`Edge`].
    pub fn remove_label(&mut self) -> Option<String> {
        std::mem::take(&mut self.label)
    }

    /// Set the start connection of this [`Edge`], returning the old connection information.
    pub const fn set_from(
        &mut self,
        node: NodeId,
        side: Option<Side>,
        end: Option<End>,
    ) -> Terminus {
        (
            std::mem::replace(&mut self.from_node, node),
            std::mem::replace(&mut self.from_side, side),
            std::mem::replace(&mut self.from_end, end),
        )
    }

    /// Set the end connection of this [`Edge`], returning the old connection information.
    pub const fn set_to(&mut self, node: NodeId, side: Option<Side>, end: Option<End>) -> Terminus {
        (
            std::mem::replace(&mut self.to_node, node),
            std::mem::replace(&mut self.to_side, side),
            std::mem::replace(&mut self.to_end, end),
        )
    }
}

/// The side where the [`Edge`] conntects to the [`Node`].
///
/// [`Node`]: crate::Node
#[allow(missing_docs)]
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "camelCase")]
pub enum Side {
    Top,
    Left,
    Right,
    Bottom,
}

/// The shape of the [`Edge`] connection from [`Edge`] to [`Node`].
///
/// [`Node`]: crate::Node
#[allow(missing_docs)]
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "camelCase")]
pub enum End {
    None,
    Arrow,
}
