"""Render a markdown file to PDF with TOC from ## / ### headings (internal links + outline)."""

from __future__ import annotations

import re
from pathlib import Path

from reportlab.lib import colors
from reportlab.lib.enums import TA_LEFT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.platypus import (
    Flowable,
    PageBreak,
    Paragraph,
    Preformatted,
    SimpleDocTemplate,
    Spacer,
    Table,
    TableStyle,
)

from md_common import TOC_END, TOC_START, collect_headings, github_slug, strip_md_link_text
from mermaid_render import (
    cached_mermaid_png,
    diagram_flowables,
    ensure_diagram_caption_style,
)


def _xml(text: str) -> str:
    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
    )


def _inline_md(text: str) -> str:
    text = _xml(text)
    text = re.sub(r"\*\*([^*]+)\*\*", r"<b>\1</b>", text)
    text = re.sub(r"`([^`]+)`", r'<font face="Courier" size="7">\1</font>', text)
    text = re.sub(
        r"\[([^\]]+)\]\(#([^)]+)\)",
        r'<a href="#\2" color="#1565C0">\1</a>',
        text,
    )
    text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
    return text


class AnchorParagraph(Paragraph):
    """Paragraph with PDF bookmark anchor."""

    def __init__(self, text, style, *, anchor: str, outline_level: int):
        super().__init__(f'<a name="{anchor}"/>{text}', style)
        self._anchor = anchor
        self._outline_level = outline_level


class OutlineDocTemplate(SimpleDocTemplate):
    def afterFlowable(self, flowable: Flowable) -> None:
        if isinstance(flowable, AnchorParagraph):
            self.canv.bookmarkPage(flowable._anchor)
            self.canv.addOutlineEntry(
                flowable.getPlainText(),
                flowable._anchor,
                level=max(0, flowable._outline_level - 2),
            )


def _cell(text: str, style: ParagraphStyle) -> Paragraph:
    return Paragraph(_inline_md(text), style)


def _parse_table_row(line: str) -> list[str]:
    line = line.strip()
    if line.startswith("|"):
        line = line[1:]
    if line.endswith("|"):
        line = line[:-1]
    return [c.strip() for c in line.split("|")]


def _is_table_sep(line: str) -> bool:
    return bool(re.match(r"^\|?[\s\-:|]+\|?$", line.strip()))


def build_slug_by_line(lines: list[str]) -> dict[int, str]:
    """Map source line index → anchor slug (same rules as collect_headings)."""
    out: dict[int, str] = {}
    used: dict[str, int] = {}
    for i, line in enumerate(lines):
        m = re.match(r"^(#{2,3})\s+(.+?)\s*$", line)
        if not m:
            continue
        title = m.group(2).strip()
        if title == "Table of contents":
            continue
        base = github_slug(title)
        if not base:
            continue
        n = used.get(base, 0)
        slug = base if n == 0 else f"{base}-{n}"
        used[base] = n + 1
        out[i] = slug
    return out


def build_pdf_from_markdown(
    source_md: Path,
    out_pdf: Path,
    *,
    doc_title: str | None = None,
    doc_subtitle: str = "",
) -> None:
    lines = source_md.read_text(encoding="utf-8").splitlines()
    headings = collect_headings(lines)
    slug_by_line = build_slug_by_line(lines)

    styles = getSampleStyleSheet()
    title_style = ParagraphStyle(
        "DocTitle",
        parent=styles["Title"],
        fontSize=15,
        spaceAfter=5,
        textColor=colors.HexColor("#1565C0"),
    )
    toc_title = ParagraphStyle(
        "TocTitle",
        parent=styles["Heading1"],
        fontSize=12,
        spaceBefore=4,
        spaceAfter=6,
        textColor=colors.HexColor("#1565C0"),
    )
    toc_item = ParagraphStyle(
        "TocItem",
        parent=styles["Normal"],
        fontSize=8,
        leading=11,
        leftIndent=0,
    )
    toc_sub = ParagraphStyle(
        "TocSub",
        parent=toc_item,
        leftIndent=14,
        fontSize=7.5,
        leading=10,
    )
    h1 = ParagraphStyle("H1", parent=styles["Heading1"], fontSize=11, spaceBefore=10, spaceAfter=4)
    h2 = ParagraphStyle("H2", parent=styles["Heading2"], fontSize=9.5, spaceBefore=6, spaceAfter=3)
    body = ParagraphStyle("Body", parent=styles["Normal"], fontSize=8, leading=10.5, alignment=TA_LEFT)
    bullet = ParagraphStyle("Bullet", parent=body, leftIndent=12, bulletIndent=4, fontSize=8, leading=10.5)
    small = ParagraphStyle("Small", parent=body, fontSize=7, textColor=colors.grey)
    code_style = ParagraphStyle("Code", parent=body, fontName="Courier", fontSize=6.5, leading=8)
    tbl_head = ParagraphStyle("TblHead", parent=body, fontName="Helvetica-Bold", fontSize=7, leading=9)
    tbl_body = ParagraphStyle("TblBody", parent=body, fontSize=7, leading=9)

    story: list = []

    # Title from first # line if not overridden
    if doc_title is None:
        for line in lines:
            if line.startswith("# "):
                doc_title = line[2:].strip()
                break
        doc_title = doc_title or source_md.stem

    story.append(Paragraph(_inline_md(doc_title), title_style))
    if doc_subtitle:
        story.append(Paragraph(_inline_md(doc_subtitle), small))
    story.append(Spacer(1, 0.15 * cm))

    # Table of contents (clickable internal links)
    story.append(Paragraph("Table of contents", toc_title))
    for level, title, slug in headings:
        text = strip_md_link_text(title)
        link = f'<a href="#{slug}" color="#1565C0">{_xml(text)}</a>'
        style = toc_sub if level == 3 else toc_item
        story.append(Paragraph(link, style))
    story.append(Spacer(1, 0.2 * cm))
    story.append(
        Paragraph(
            f"<i>Source: {source_md.name} — section links work in PDF viewers that support internal anchors.</i>",
            small,
        )
    )
    story.append(PageBreak())

    # Body — skip title, TOC block, index link
    skip_toc = False
    in_code = False
    code_lang = ""
    code_lines: list[str] = []
    ensure_diagram_caption_style(styles)
    i = 0
    while i < len(lines):
        line = lines[i]

        if line.strip() == TOC_START:
            skip_toc = True
            i += 1
            continue
        if line.strip() == TOC_END:
            skip_toc = False
            i += 1
            continue
        if skip_toc:
            i += 1
            continue
        if line.startswith("# ") and not line.startswith("## "):
            i += 1
            continue
        if line.strip() == "## Table of contents":
            i += 1
            continue
        if line.strip().startswith("**Documentation index:**"):
            i += 1
            continue

        if line.strip().startswith("```"):
            if in_code:
                block = "\n".join(code_lines)
                if code_lang == "mermaid" and block.strip():
                    try:
                        png = cached_mermaid_png(block)
                        story.extend(diagram_flowables(png, styles))
                    except Exception as exc:
                        story.append(
                            Paragraph(
                                f"<i>Diagram render failed ({_xml(str(exc))}) — source:</i>",
                                small,
                            )
                        )
                        story.append(Preformatted(block, code_style))
                        story.append(Spacer(1, 0.08 * cm))
                else:
                    story.append(Preformatted(block, code_style))
                    story.append(Spacer(1, 0.08 * cm))
                code_lines = []
                code_lang = ""
                in_code = False
            else:
                in_code = True
                code_lang = line.strip()[3:].strip().lower()
            i += 1
            continue
        if in_code:
            code_lines.append(line)
            i += 1
            continue

        if line.startswith("### "):
            title = line[4:].strip()
            slug = slug_by_line.get(i, github_slug(title))
            story.append(
                AnchorParagraph(_inline_md(title), h2, anchor=slug, outline_level=3)
            )
            i += 1
            continue

        if line.startswith("## "):
            title = line[3:].strip()
            slug = slug_by_line.get(i, github_slug(title))
            story.append(
                AnchorParagraph(_inline_md(title), h1, anchor=slug, outline_level=2)
            )
            i += 1
            continue

        if line.strip() == "---":
            story.append(Spacer(1, 0.1 * cm))
            i += 1
            continue

        if line.strip().startswith("|") and i + 1 < len(lines) and _is_table_sep(lines[i + 1]):
            headers = _parse_table_row(line)
            i += 2
            rows: list[list[str]] = []
            while i < len(lines) and lines[i].strip().startswith("|"):
                rows.append(_parse_table_row(lines[i]))
                i += 1
            ncol = len(headers)
            if ncol:
                width = (16.2 * cm) / ncol
                data = [[_cell(h, tbl_head) for h in headers]]
                for row in rows:
                    padded = row + [""] * (ncol - len(row))
                    data.append([_cell(c, tbl_body) for c in padded[:ncol]])
                t = Table(data, colWidths=[width] * ncol)
                t.setStyle(
                    TableStyle(
                        [
                            ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1565C0")),
                            ("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
                            ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
                            ("FONTSIZE", (0, 0), (-1, -1), 7),
                            ("VALIGN", (0, 0), (-1, -1), "TOP"),
                            ("GRID", (0, 0), (-1, -1), 0.5, colors.lightgrey),
                            ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F5F5F5")]),
                            ("LEFTPADDING", (0, 0), (-1, -1), 3),
                            ("RIGHTPADDING", (0, 0), (-1, -1), 3),
                            ("TOPPADDING", (0, 0), (-1, -1), 2),
                            ("BOTTOMPADDING", (0, 0), (-1, -1), 2),
                        ]
                    )
                )
                story.append(t)
                story.append(Spacer(1, 0.08 * cm))
            continue

        if line.strip().startswith("- "):
            story.append(Paragraph(f"• {_inline_md(line.strip()[2:])}", bullet))
            i += 1
            continue

        if line.strip().startswith("_") and line.strip().endswith("_") and len(line.strip()) > 2:
            story.append(Paragraph(f"<i>{_inline_md(line.strip()[1:-1])}</i>", small))
            i += 1
            continue

        if not line.strip():
            i += 1
            continue

        story.append(Paragraph(_inline_md(line.strip()), body))
        i += 1

    doc = OutlineDocTemplate(
        str(out_pdf),
        pagesize=A4,
        leftMargin=1.4 * cm,
        rightMargin=1.4 * cm,
        topMargin=1.3 * cm,
        bottomMargin=1.3 * cm,
        title=doc_title,
        author="Android Cast project",
    )
    doc.build(story)
    print(f"Wrote {out_pdf} ({len(headings)} TOC entries from markdown headings)")
