"""TürkPatent DOCX builder — in-memory, returns bytes.

Format kuralları reference-scripts/build_turkpatent_separate_docs.py ile aynı:
- 20/25/20/20 mm kenar marjları
- Times New Roman 12pt, 1.5 satır aralığı
- Her 5 satırda sol kenar satır numarası (restart=newPage)
- PAGE field alt ortalanmış sayfa numarası
"""

from __future__ import annotations

import io
import re
from typing import TypedDict

from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Mm, Pt


class ClaimDict(TypedDict):
    number: int
    type: str  # "independent" | "dependent"
    parent: int | None
    text: str


# --------------------------- Format helpers ---------------------------


def _add_line_numbering(section) -> None:
    """Her 5 satırda sol kenar satır numarası, her sayfada restart."""
    sect_pr = section._sectPr
    ln_num_type = OxmlElement("w:lnNumType")
    ln_num_type.set(qn("w:countBy"), "5")
    ln_num_type.set(qn("w:start"), "1")
    ln_num_type.set(qn("w:restart"), "newPage")
    ln_num_type.set(qn("w:distance"), "360")
    sect_pr.append(ln_num_type)


def _add_page_number(footer) -> None:
    """Alt ortalanmış sayfa numarası (Word PAGE field)."""
    p = footer.paragraphs[0]
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run = p.add_run()
    begin = OxmlElement("w:fldChar")
    begin.set(qn("w:fldCharType"), "begin")
    instr = OxmlElement("w:instrText")
    instr.set(qn("xml:space"), "preserve")
    instr.text = "PAGE"
    end = OxmlElement("w:fldChar")
    end.set(qn("w:fldCharType"), "end")
    run._r.append(begin)
    run._r.append(instr)
    run._r.append(end)
    run.font.name = "Times New Roman"
    run.font.size = Pt(11)


def _set_base_style(doc) -> None:
    style = doc.styles["Normal"]
    style.font.name = "Times New Roman"
    style.font.size = Pt(12)
    pf = style.paragraph_format
    pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
    pf.space_before = Pt(0)
    pf.space_after = Pt(0)


def _set_margins(section) -> None:
    # TürkPatent tarifname/istem/özet: 20/25/20/20 mm
    section.page_height = Mm(297)
    section.page_width = Mm(210)
    section.top_margin = Mm(20)
    section.left_margin = Mm(25)
    section.right_margin = Mm(20)
    section.bottom_margin = Mm(20)
    section.header_distance = Mm(10)
    section.footer_distance = Mm(10)


def _new_doc():
    doc = Document()
    _set_base_style(doc)
    section = doc.sections[0]
    _set_margins(section)
    _add_line_numbering(section)
    _add_page_number(section.footer)
    return doc


def _add_heading(doc, text: str, level: int = 1):
    p = doc.add_paragraph()
    p.paragraph_format.space_before = Pt(12)
    p.paragraph_format.space_after = Pt(6)
    run = p.add_run(text)
    run.bold = True
    run.font.name = "Times New Roman"
    if level == 1:
        run.font.size = Pt(14)
    elif level == 2:
        run.font.size = Pt(13)
    else:
        run.font.size = Pt(12)
    return p


def _add_para(
    doc,
    text: str,
    justify: bool = True,
    center: bool = False,
    bold: bool = False,
    size_pt: int = 12,
):
    p = doc.add_paragraph()
    if center:
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    elif justify:
        p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
    pf = p.paragraph_format
    pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
    pf.space_before = Pt(0)
    pf.space_after = Pt(6)
    run = p.add_run(text)
    run.font.name = "Times New Roman"
    run.font.size = Pt(size_pt)
    run.bold = bold
    return p


def _clean_inline_markdown(text: str) -> str:
    """Bold/italic/kod/link markdown'ı düz metne çevirir."""
    text = re.sub(r"\*\*([^*]+)\*\*", r"\1", text)
    text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"\1", text)
    text = re.sub(r"`([^`]+)`", r"\1", text)
    text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
    return text


def _parse_markdown(md: str) -> list[tuple[str, str]]:
    """Markdown'ı (kind, text) listesine çevir. kind: h1-h6 | p | quote."""
    lines = md.splitlines()
    out: list[tuple[str, str]] = []
    in_code = False
    buffer_para: list[str] = []

    def flush_para() -> None:
        nonlocal buffer_para
        if buffer_para:
            t = " ".join(buffer_para).strip()
            if t:
                out.append(("p", t))
            buffer_para = []

    for raw in lines:
        stripped = raw.strip()
        if stripped.startswith("```"):
            flush_para()
            in_code = not in_code
            continue
        if in_code:
            continue

        m = re.match(r"^(#{1,6})\s+(.*)", raw)
        if m:
            flush_para()
            level = len(m.group(1))
            text = m.group(2).strip()
            out.append((f"h{min(level, 6)}", text))
            continue

        if not stripped:
            flush_para()
            continue

        if stripped.startswith(("- ", "* ", "• ")):
            flush_para()
            bullet = stripped[2:].strip()
            out.append(("p", "• " + bullet))
            continue

        if stripped.startswith(">"):
            content = stripped.lstrip("> ").rstrip()
            if content:
                out.append(("quote", content))
            continue

        if stripped in ("---", "***"):
            flush_para()
            continue

        buffer_para.append(stripped)

    flush_para()
    return out


# --------------------------- Public builders ---------------------------


def build_description_docx(title: str, markdown: str) -> bytes:
    """Tarifname.docx — markdown içinden üretir, in-memory bytes döner."""
    doc = _new_doc()
    _add_heading(doc, "TARİFNAME", level=1)
    _add_para(doc, title, center=True, bold=True, size_pt=13, justify=False)

    for kind, text in _parse_markdown(markdown):
        clean = _clean_inline_markdown(text).strip()
        if not clean:
            continue
        if kind.startswith("h"):
            level = int(kind[1])
            _add_heading(doc, clean, level=max(2, min(level, 4)))
        elif kind in ("p", "quote"):
            _add_para(doc, clean)

    buffer = io.BytesIO()
    doc.save(buffer)
    return buffer.getvalue()


def build_claims_docx(title: str, claims: list[ClaimDict]) -> bytes:
    """Istemler.docx — yapılandırılmış claims listesinden üretir."""
    doc = _new_doc()
    _add_heading(doc, "İSTEMLER", level=1)
    _add_para(doc, title, center=True, bold=True, size_pt=13, justify=False)

    # Bağımsız istemleri (independent) grupla — her grup öncesi bir alt başlık verebiliriz
    current_independent: int | None = None
    for claim in claims:
        if claim["type"] == "independent":
            current_independent = claim["number"]
        # İstem metni: "1. <text>"
        _add_para(doc, f"{claim['number']}. {claim['text']}")

    _ = current_independent  # (gelecekte alt başlık için)

    buffer = io.BytesIO()
    doc.save(buffer)
    return buffer.getvalue()


def build_abstract_docx(title: str, abstract: str) -> bytes:
    """Ozet.docx — tek paragraf, 150 kelime altı olmalı (aramada uyarı değil)."""
    doc = _new_doc()
    _add_heading(doc, "ÖZET", level=1)
    _add_para(doc, title, center=True, bold=True, size_pt=13, justify=False)
    _add_para(doc, abstract.strip())

    buffer = io.BytesIO()
    doc.save(buffer)
    return buffer.getvalue()
