"""
mermaid_graph.serializer
=====================
Convert the extended node_link_data dict back to Mermaid flowchart text.
"""

from __future__ import annotations

from typing import Any


# ── Shape → delimiter mapping ───────────────────────────────────────────

_SHAPE_MAP: dict[str, tuple[str, str]] = {
    "rect":              ("[",   "]"),
    "round_rect":        ("(",   ")"),
    "stadium":           ("([",  "])"),
    "diamond":           ("{",   "}"),
    "hexagon":           ("{{",  "}}"),
    "circle":            ("((",  "))"),
    "subroutine":        ("[[",  "]]"),
    "cylinder":          ("[(", ")]"),
    "asymmetric":        (">",   "]"),
    "parallelogram":     ("[/",  "/]"),
    "parallelogram_alt": ("[\\", "\\]"),
    "trapezoid":         ("[/",  "\\]"),
    "trapezoid_alt":     ("[\\", "/]"),
    "double_circle":     ("(((",  ")))"),
}

# ── Arrow mapping ───────────────────────────────────────────────────────

_ARROW_MAP: dict[tuple[str, str], str] = {
    ("solid",  "normal"):  "-->",
    ("solid",  "none"):    "---",
    ("solid",  "circle"):  "--o",
    ("solid",  "cross"):   "--x",
    ("dotted", "normal"):  "-.->",
    ("dotted", "none"):    "-.-",
    ("thick",  "normal"):  "==>",
    ("thick",  "none"):    "===",
}


# ════════════════════════════════════════════════════════════════════════
#  Public API
# ════════════════════════════════════════════════════════════════════════

def serialize(data: dict[str, Any]) -> str:
    """
    Convert an extended node_link_data dict to a Mermaid flowchart string.
    """
    meta = data.get("graph", {})
    diagram_type = meta.get("diagram_type", "flowchart")
    direction = meta.get("direction", "TD")
    lines: list[str] = [f"{diagram_type} {direction}"]

    # ── classDef ────────────────────────────────────────────────────
    for cls_name, props in meta.get("class_defs", {}).items():
        style_str = ",".join(f"{k}:{v}" for k, v in props.items())
        lines.append(f"    classDef {cls_name} {style_str}")

    if meta.get("class_defs"):
        lines.append("")

    # ── Collect which nodes belong to subgraphs ─────────────────────
    sg_node_ids: set[str] = set()
    for sg in meta.get("subgraphs", []):
        _collect_subgraph_nodes(sg, sg_node_ids)

    # ── Subgraph blocks ─────────────────────────────────────────────
    nodes_dict = {n["id"]: n for n in data.get("nodes", [])}
    links = data.get("links", [])

    for sg in meta.get("subgraphs", []):
        _render_subgraph(sg, nodes_dict, links, lines, indent=1)

    # ── Top-level nodes (not in any subgraph) ───────────────────────
    for node in data.get("nodes", []):
        if node["id"] not in sg_node_ids:
            lines.append(f"    {_render_node(node)}")

    # ── Edges ───────────────────────────────────────────────────────
    if links:
        lines.append("")
        for link in links:
            lines.append(f"    {_render_link(link)}")

    # ── Node inline styles ──────────────────────────────────────────
    for node in data.get("nodes", []):
        if node.get("style"):
            style_str = ",".join(f"{k}:{v}" for k, v in node["style"].items())
            lines.append(f"    style {node['id']} {style_str}")

    # ── linkStyle ───────────────────────────────────────────────────
    for idx_str, props in meta.get("link_styles", {}).items():
        style_str = ",".join(f"{k}:{v}" for k, v in props.items())
        lines.append(f"    linkStyle {idx_str} {style_str}")

    return "\n".join(lines)


# ════════════════════════════════════════════════════════════════════════
#  Internal helpers
# ════════════════════════════════════════════════════════════════════════

def _render_node(node: dict[str, Any]) -> str:
    """Render a single node declaration."""
    nid = node["id"]
    label = node.get("label")
    shape = node.get("shape", "rect")
    open_d, close_d = _SHAPE_MAP.get(shape, ("[", "]"))
    css = f":::{node['css_class']}" if node.get("css_class") else ""

    # If label is None or equals id, and shape is default, emit bare id
    if (label is None or label == nid) and shape == "rect" and not css:
        return nid

    # Use id as display label when label is None
    display_label = label if label is not None else nid

    return f"{nid}{open_d}{display_label}{close_d}{css}"


def _render_link(link: dict[str, Any]) -> str:
    """Render a single edge."""
    lt = link.get("line_type", "solid")
    at = link.get("arrow", "normal")
    arrow = _ARROW_MAP.get((lt, at), "-->")
    label = link.get("label")
    src = link["source"]
    tgt = link["target"]

    if label:
        return f"{src} {arrow}|{label}| {tgt}"
    return f"{src} {arrow} {tgt}"


def _render_subgraph(
    sg: dict[str, Any],
    nodes_dict: dict[str, dict],
    links: list[dict],
    lines: list[str],
    indent: int,
) -> None:
    """Recursively render a subgraph block."""
    pad = "    " * indent
    sg_id = sg["id"]
    sg_label = sg.get("label", sg_id)

    if sg_label != sg_id:
        lines.append(f'{pad}subgraph {sg_id}["{sg_label}"]')
    else:
        lines.append(f"{pad}subgraph {sg_id}")

    if sg.get("direction"):
        lines.append(f"{pad}    direction {sg['direction']}")

    # Nested subgraphs first
    for child_sg in sg.get("subgraphs", []):
        _render_subgraph(child_sg, nodes_dict, links, lines, indent + 1)

    # Nodes in this subgraph
    for nid in sg.get("nodes", []):
        node = nodes_dict.get(nid)
        if node:
            lines.append(f"{pad}    {_render_node(node)}")

    lines.append(f"{pad}end")


def _collect_subgraph_nodes(sg: dict[str, Any], result: set[str]) -> None:
    """Recursively collect all node ids within subgraphs."""
    for nid in sg.get("nodes", []):
        result.add(nid)
    for child in sg.get("subgraphs", []):
        _collect_subgraph_nodes(child, result)