from dataclasses import dataclass
from typing import Optional, Union

from mermaid import Direction, text_to_snake_case
from mermaid.style import Style


@dataclass
class NodeShape:
    """NodeShape class.

    This class represents the shape of a node in a flowchart.

    Attributes:
        start (str): The start character of the node shape.
        end (str): The end character of the node shape.
    """

    start: str
    end: str


NODE_SHAPES: dict[str, NodeShape] = {
    "normal": NodeShape("[", "]"),
    "round-edge": NodeShape("(", ")"),
    "stadium-shape": NodeShape("([", "])"),
    "subroutine-shape": NodeShape("[[", "]]"),
    "cylindrical": NodeShape("[(", ")]"),
    "circle": NodeShape("((", "))"),
    "label-shape": NodeShape(">", "]"),
    "rhombus": NodeShape("{", "}"),
    "hexagon": NodeShape("{{", "}}"),
    "parallelogram": NodeShape("[/", "/]"),
    "parallelogram-alt": NodeShape("[\\", "\\]"),
    "trapezoid": NodeShape("[/", "\\]"),
    "trapezoid-alt": NodeShape("[\\", "/]"),
    "double-circle": NodeShape("(((", ")))"),
}

HREF_TYPES: dict[str, str] = {
    "blank": "_blank",
    "self": "_self",
    "parent": "_parent",
    "top": "_top",
}


class Node:
    """Node class.

    This class represents a node in a flowchart.

    Attributes:
        id_ (str): The ID of the node.
        content (str): The content of the node.
        shape (str): The shape of the node.
        sub_nodes (list[Node]): The sub-nodes of the node.
        href (str): The hyperlink reference of the node.
        href_type (str): The type of the hyperlink reference of the node.
        styles (list[Style]): The styles of the node.
    """

    def __init__(
        self,
        id_: str,
        content: str = "",
        shape: str = "normal",
        sub_nodes: Optional[list["Node"]] = None,
        href: Optional[str] = None,
        href_type: str = "blank",
        styles: Optional[list[Style]] = None,
        direction: Union[str, Direction] = "LR",
    ) -> None:
        """Initialize a new Node.

        Args:
            id_ (str): The ID of the node.
            content (str): The content of the node.
            shape (str): The shape of the node.
            sub_nodes (Optional[list[Node]]): The sub-nodes of the node.
            href (Optional[str]): The hyperlink reference of the node.
            href_type (str): The type of the hyperlink reference of the node.
            styles (Optional[list[Style]]): The styles of the node.
            direction (Optional[Union[str,Direction]]): The direction of the node in case of a subgraph.
        """
        self.id_: str = text_to_snake_case(id_)
        self.content: str = content if content else id_
        self.shape: NodeShape = NODE_SHAPES[shape]
        self.href: str = href if href is not None else "#"
        self.href_type: str = HREF_TYPES[href_type]
        self.sub_nodes: list["Node"] = sub_nodes if sub_nodes is not None else []
        self.styles: list[Style] = styles if styles is not None else []
        self.direction: str = (
            direction if isinstance(direction, str) else direction.value
        )

    def __str__(self) -> str:
        """Return a string representation of the node.

        If the node has sub-nodes, it returns a string representation of a subgraph.
        Otherwise, it returns a string representation of a single node.

        Returns:
            str: A string representation of the node.
        """
        string: str = ""
        if len(self.sub_nodes):
            string = "\n".join(
                [
                    f'subgraph {self.id_} ["{self.content}"]',
                    f"\tdirection {self.direction}",
                    "\n".join([str(f"\t{node}") for node in self.sub_nodes]),
                    "end",
                ]
            )
        else:
            string = "".join(
                [self.id_, self.shape.start, f'"{self.content}"', self.shape.end]
            )
            if self.href != "#":
                string = "".join(
                    [string, "\n", f'click {self.id_} "{self.href}" {self.href_type}']
                )

        for style in self.styles:
            string += f"\n{self.id_}:::{style.name}"
        return string
