"""Render Mermaid diagram sources to PNG for PDF embedding (mermaid-cli + Chromium)."""

from __future__ import annotations

import hashlib
import subprocess
import tempfile
from pathlib import Path

from reportlab.lib.units import cm
from reportlab.lib.utils import ImageReader
from reportlab.platypus import Image, Paragraph, Spacer

BUILD_DIR = Path(__file__).resolve().parent
CONFIG = BUILD_DIR / "mermaid-config.json"
CACHE_DIR = BUILD_DIR / "mermaid_cache"
MERMAID_CLI = ("npx", "--yes", "@mermaid-js/mermaid-cli@11.4.0")

MAX_DIAGRAM_WIDTH_CM = 15.8
MAX_DIAGRAM_HEIGHT_CM = 17.0
CAPTION_STYLE_NAME = "DiagramCaption"


def _content_hash(content: str) -> str:
    return hashlib.sha256(content.strip().encode("utf-8")).hexdigest()[:16]


def estimate_mermaid_pixels(content: str) -> tuple[int, int, int]:
    """Return (width_px, height_px, scale) heuristics for flowchart layout."""
    lines = [ln for ln in content.splitlines() if ln.strip() and not ln.strip().startswith("%%")]
    n_edges = sum(1 for ln in lines if "-->" in ln or "---" in ln or "-.->" in ln)
    n_nodes = sum(
        1
        for ln in lines
        if "[" in ln or "(" in ln or "{" in ln or (ln.strip() and "-->" not in ln and "subgraph" not in ln.lower())
    )
    n_sub = sum(1 for ln in lines if "subgraph" in ln.lower())
    lr = any("flowchart LR" in ln or "graph LR" in ln for ln in lines)

    scale = 2
    if lr:
        width = 2200 + min(n_nodes, 12) * 120
        height = 650 + n_sub * 180 + min(n_edges, 10) * 30
    else:
        width = 1700 + min(n_sub, 4) * 200
        height = 650 + n_edges * 70 + n_sub * 220 + min(n_nodes, 14) * 25

    width = int(min(max(width, 1200), 3600))
    height = int(min(max(height, 550), 3400))
    return width, height, scale


def render_mermaid_to_png(content: str, dest: Path, *, width: int, height: int, scale: int) -> None:
    if not CONFIG.exists():
        raise FileNotFoundError(f"mermaid config missing: {CONFIG}")
    dest.parent.mkdir(parents=True, exist_ok=True)
    with tempfile.NamedTemporaryFile("w", suffix=".mmd", delete=False, encoding="utf-8") as tmp:
        tmp.write(content.strip() + "\n")
        mmd_path = Path(tmp.name)
    try:
        subprocess.run(
            [
                *MERMAID_CLI,
                "-i",
                str(mmd_path),
                "-o",
                str(dest),
                "-c",
                str(CONFIG),
                "-b",
                "white",
                "-w",
                str(width),
                "-H",
                str(height),
                "-s",
                str(scale),
            ],
            cwd=str(BUILD_DIR.parents[1]),
            check=True,
            timeout=180,
            capture_output=True,
            text=True,
        )
    except subprocess.CalledProcessError as exc:
        msg = (exc.stderr or exc.stdout or str(exc)).strip()
        raise RuntimeError(f"mermaid-cli failed: {msg}") from exc
    finally:
        mmd_path.unlink(missing_ok=True)
    if not dest.is_file() or dest.stat().st_size == 0:
        raise RuntimeError(f"mermaid-cli produced no PNG: {dest}")


def cached_mermaid_png(content: str) -> Path:
    """Render or reuse cached PNG for Mermaid source."""
    CACHE_DIR.mkdir(parents=True, exist_ok=True)
    key = _content_hash(content)
    png = CACHE_DIR / f"{key}.png"
    if png.is_file() and png.stat().st_size > 0:
        return png
    w, h, scale = estimate_mermaid_pixels(content)
    render_mermaid_to_png(content, png, width=w, height=h, scale=scale)
    return png


def diagram_flowables(
    png_path: Path,
    styles,
    *,
    caption: str = "",
    max_width_cm: float = MAX_DIAGRAM_WIDTH_CM,
    max_height_cm: float = MAX_DIAGRAM_HEIGHT_CM,
) -> list:
    """ReportLab flowables: image (+ optional caption) sized to fit A4 body."""
    reader = ImageReader(str(png_path))
    w_px, h_px = reader.getSize()
    max_w = max_width_cm * cm
    max_h = max_height_cm * cm
    scale = min(max_w / w_px, max_h / h_px)
    img = Image(str(png_path), width=w_px * scale, height=h_px * scale)
    img.hAlign = "CENTER"
    out: list = [Spacer(1, 0.12 * cm), img, Spacer(1, 0.06 * cm)]
    if caption:
        cap = styles[CAPTION_STYLE_NAME] if CAPTION_STYLE_NAME in styles else styles["Normal"]
        out.insert(0, Spacer(1, 0.06 * cm))
        out.append(Paragraph(f"<i>{caption}</i>", cap))
    out.append(Spacer(1, 0.1 * cm))
    return out


def ensure_diagram_caption_style(styles) -> None:
    from reportlab.lib.styles import ParagraphStyle
    from reportlab.lib import colors

    if CAPTION_STYLE_NAME not in styles:
        styles.add(
            ParagraphStyle(
                CAPTION_STYLE_NAME,
                parent=styles["Normal"],
                fontSize=7,
                textColor=colors.grey,
                alignment=1,
                spaceAfter=4,
            )
        )
