mod file;
mod group;
mod link;
mod text;

use crate::{NodeId, PixelCoordinate, PixelDimension, color::Color};

pub use self::{
    file::FileNode,
    group::{Background, BackgroundStyle, GroupNode},
    link::LinkNode,
    text::TextNode,
};

/// The shared attributes all nodes have.
#[derive(
    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct GenericNode {
    /// The unique ID of the node.
    pub id: NodeId,
    x: PixelCoordinate,
    y: PixelCoordinate,
    width: PixelDimension,
    height: PixelDimension,
    #[serde(skip_serializing_if = "Option::is_none")]
    color: Option<Color>,
}

impl GenericNode {
    /// Creates a new [`GenericNode`].
    #[must_use]
    pub const fn new(
        id: NodeId,
        x: PixelCoordinate,
        y: PixelCoordinate,
        width: PixelDimension,
        height: PixelDimension,
        color: Option<Color>,
    ) -> Self {
        Self {
            id,
            x,
            y,
            width,
            height,
            color,
        }
    }
}

/// Trait to access the shared attributes of all nodes.
pub trait GenericNodeInfo {
    /// Get the unique ID of the node.
    fn id(&self) -> &NodeId;
    /// Get the `x` position of the node in pixels.
    fn x(&self) -> PixelCoordinate;
    /// Get the `y` position of the node in pixels.
    fn y(&self) -> PixelCoordinate;
    /// Get the `width` position of the node in pixels.
    fn width(&self) -> PixelDimension;
    /// Get the `height` position of the node in pixels.
    fn height(&self) -> PixelDimension;
    /// Get color of the node.
    fn color(&self) -> &Option<Color>;
}

impl GenericNodeInfo for GenericNode {
    fn id(&self) -> &NodeId {
        &self.id
    }

    fn x(&self) -> PixelCoordinate {
        self.x
    }

    fn y(&self) -> PixelCoordinate {
        self.y
    }

    fn width(&self) -> PixelDimension {
        self.width
    }

    fn height(&self) -> PixelDimension {
        self.height
    }

    fn color(&self) -> &Option<Color> {
        &self.color
    }
}

/// Wrapper around all the types of nodes of a canvas.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Node {
    /// A text node.
    ///
    /// See [`TextNode`].
    Text(TextNode),
    /// A file node.
    ///
    /// See [`FileNode`].
    File(FileNode),
    /// A link node.
    ///
    /// See [`LinkNode`].
    Link(LinkNode),
    /// A group node.
    ///
    /// See [`GroupNode`].
    Group(GroupNode),
}

impl From<GroupNode> for Node {
    fn from(node: GroupNode) -> Self {
        Self::Group(node)
    }
}

impl From<TextNode> for Node {
    fn from(node: TextNode) -> Self {
        Self::Text(node)
    }
}

impl From<FileNode> for Node {
    fn from(node: FileNode) -> Self {
        Self::File(node)
    }
}

impl From<LinkNode> for Node {
    fn from(node: LinkNode) -> Self {
        Self::Link(node)
    }
}

impl GenericNodeInfo for Node {
    fn id(&self) -> &NodeId {
        match &self {
            Self::Text(node) => node.id(),
            Self::File(node) => node.id(),
            Self::Link(node) => node.id(),
            Self::Group(node) => node.id(),
        }
    }

    fn x(&self) -> PixelCoordinate {
        match &self {
            Self::Text(node) => node.x(),
            Self::File(node) => node.x(),
            Self::Link(node) => node.x(),
            Self::Group(node) => node.x(),
        }
    }

    fn y(&self) -> PixelCoordinate {
        match &self {
            Self::Text(node) => node.y(),
            Self::File(node) => node.y(),
            Self::Link(node) => node.y(),
            Self::Group(node) => node.y(),
        }
    }

    fn width(&self) -> PixelDimension {
        match &self {
            Self::Text(node) => node.width(),
            Self::File(node) => node.width(),
            Self::Link(node) => node.width(),
            Self::Group(node) => node.width(),
        }
    }

    fn height(&self) -> PixelDimension {
        match &self {
            Self::Text(node) => node.height(),
            Self::File(node) => node.height(),
            Self::Link(node) => node.height(),
            Self::Group(node) => node.height(),
        }
    }

    fn color(&self) -> &Option<Color> {
        match &self {
            Self::Text(node) => node.color(),
            Self::File(node) => node.color(),
            Self::Link(node) => node.color(),
            Self::Group(node) => node.color(),
        }
    }
}
