from __future__ import annotations

from pathlib import Path
import re

from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Cm, Pt, RGBColor

ROOT = Path(__file__).resolve().parents[1]
SOURCE_TXT = ROOT / "tmp_bab2_rujukan.txt"
PROJECT_TEMPLATE = ROOT / "file-docs" / "PROJECT.docx"
OUTPUT_DIR = ROOT / "final"
OUTPUT_FILE = OUTPUT_DIR / "BAB_2_Rujukan_Lengkap_Project_Template.docx"

TITLE = "BAB 2 GAMBARAN UMUM DAN ANALISIS SITUASI EKSTERNAL"

TABLE_BODY_START = re.compile(
    r"^(No\b|NO\b|Tahun\b|Jenis\b|Kabupaten\b|Kota\b|Umur\b|Indikator\b|Kategori\b|Uraian\b|"
    r"Anggaran\b|Pertanyaan\b|Parameter\b|Faktor\b|RS\s|TANGIBLE\b|INTANGIBLE\b|"
    r"\d+\s+[A-ZA-ÿ]|Kabupaten\s*$|Kota\s*$)"
)
HEADING_RE = re.compile(r"^2\.\d+(?:\.\d+)?\s+")
NUMBERED_RE = re.compile(r"^\d+\.\s+")
LETTER_RE = re.compile(r"^[a-z]\.\s+")
BULLET_RE = re.compile(r"^(-|)\s*")
TABLE_RE = re.compile(r"^Tabel\s+2\.\d+\s*$")
FIGURE_RE = re.compile(r"^Gambar\s+2\.\d+\s*$")


def normalize_spaces(text: str) -> str:
    return re.sub(r"\s+", " ", text).strip()


def is_noise(line: str) -> bool:
    s = line.strip()
    return (
        not s
        or s == "===PAGE==="
        or s == "b"
        or s in {"BAB 1", "PENDAHULUAN"}
        or s.startswith("Bab 2 - ")
        or s.startswith("Studi Kelayakan Pembangunan Infrastruktur Rumah Sakit Umum Sofifi")
    )


def clear_document_body(document: Document):
    body = document._body._element
    for child in list(body):
        if child.tag.endswith("}sectPr"):
            continue
        body.remove(child)


def configure_document(document: Document):
    section = document.sections[0]
    section.page_width = Cm(21)
    section.page_height = Cm(29.7)
    section.top_margin = Cm(2.2)
    section.bottom_margin = Cm(2.2)
    section.left_margin = Cm(2.0)
    section.right_margin = Cm(2.0)

    normal = document.styles["Normal"]
    normal.font.name = "Arial"
    normal.font.size = Pt(11)
    normal.paragraph_format.line_spacing = 1.15
    normal.paragraph_format.space_after = Pt(3)


def style_runs(paragraph, size: float = 11, bold: bool | None = None, italic: bool | None = None):
    for run in paragraph.runs:
        run.font.name = "Arial"
        run.font.size = Pt(size)
        run.font.color.rgb = RGBColor(31, 41, 51)
        if bold is not None:
            run.bold = bold
        if italic is not None:
            run.italic = italic


def add_paragraph(document: Document, text: str, kind: str):
    if kind == "title":
        p = document.add_paragraph(text, style="Heading 1")
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        style_runs(p, 14, True)
    elif kind == "h2":
        p = document.add_paragraph(text, style="Heading 2")
        style_runs(p, 12, True)
    elif kind == "h3":
        p = document.add_paragraph(text, style="Heading 3")
        style_runs(p, 11, True)
    elif kind == "number":
        p = document.add_paragraph(text, style="Normal")
        p.paragraph_format.left_indent = Cm(0.75)
        style_runs(p, 11, True)
    elif kind == "letter":
        p = document.add_paragraph(text, style="Normal")
        p.paragraph_format.left_indent = Cm(1.25)
        style_runs(p, 11, False)
    elif kind == "bullet":
        p = document.add_paragraph(text, style="Normal")
        p.paragraph_format.left_indent = Cm(1.0)
        style_runs(p, 11, False)
    elif kind == "caption":
        p = document.add_paragraph(text, style="Normal")
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        style_runs(p, 10.5, True, False)
    else:
        p = document.add_paragraph(text, style="Normal")
        p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
        style_runs(p, 11, False)
    p.paragraph_format.space_after = Pt(4)
    return p


def classify(text: str) -> str:
    if text == TITLE:
        return "title"
    if re.match(r"^2\.\d+\s+", text):
        return "h2"
    if re.match(r"^2\.\d+\.\d+\s+", text):
        return "h3"
    if text.startswith("Tabel 2.") or text.startswith("Gambar 2."):
        return "caption"
    if NUMBERED_RE.match(text):
        return "number"
    if LETTER_RE.match(text):
        return "letter"
    if BULLET_RE.match(text):
        return "bullet"
    return "normal"


def clean_line(raw: str) -> str:
    s = raw.strip()
    s = s.replace("", "")
    return normalize_spaces(s)


def extract_items() -> list[str]:
    raw_lines = SOURCE_TXT.read_text(encoding="utf-8", errors="ignore").splitlines()
    lines = [clean_line(line) for line in raw_lines]

    try:
        start = next(i for i, line in enumerate(lines) if line.startswith("Rumah Sakit Umum (RSU) Sofifi"))
    except StopIteration:
        start = 0

    items: list[str] = [TITLE]
    buffer: list[str] = []

    def flush():
        nonlocal buffer
        if buffer:
            text = normalize_spaces(" ".join(buffer))
            if text:
                items.append(text)
            buffer = []

    i = start
    while i < len(lines):
        line = lines[i]
        if is_noise(line):
            flush()
            i += 1
            continue

        if TABLE_RE.match(line) or FIGURE_RE.match(line):
            flush()
            caption_parts = [line]
            j = i + 1
            while j < len(lines):
                nxt = lines[j]
                if is_noise(nxt):
                    j += 1
                    continue
                if TABLE_BODY_START.match(nxt) or nxt.startswith("Sumber") or HEADING_RE.match(nxt):
                    break
                # Captions in the reference are short; stop before obvious table values.
                caption_parts.append(nxt)
                if len(caption_parts) >= 4:
                    break
                j += 1
            items.append(normalize_spaces(" ".join(caption_parts)))

            # Skip table/figure body through its source line, or until the next heading/caption if no source exists.
            k = j
            while k < len(lines):
                nxt = lines[k]
                if nxt.startswith("Sumber"):
                    k += 1
                    break
                if k > j and (HEADING_RE.match(nxt) or TABLE_RE.match(nxt) or FIGURE_RE.match(nxt)):
                    break
                k += 1
            i = k
            continue

        if BULLET_RE.match(line):
            flush()
            parts = [line]
            j = i + 1
            while j < len(lines):
                nxt = lines[j]
                if is_noise(nxt):
                    j += 1
                    continue
                if (
                    HEADING_RE.match(nxt)
                    or NUMBERED_RE.match(nxt)
                    or LETTER_RE.match(nxt)
                    or BULLET_RE.match(nxt)
                    or TABLE_RE.match(nxt)
                    or FIGURE_RE.match(nxt)
                    or nxt.startswith("Sumber")
                ):
                    break
                parts.append(nxt)
                j += 1
            items.append(normalize_spaces(" ".join(parts)))
            i = j
            continue

        if HEADING_RE.match(line) or NUMBERED_RE.match(line) or LETTER_RE.match(line):
            flush()
            items.append(line)
            i += 1
            continue

        if line.startswith("Sumber"):
            flush()
            i += 1
            continue

        buffer.append(line)
        i += 1

    flush()
    return items


def build_docx():
    document = Document(PROJECT_TEMPLATE)
    clear_document_body(document)
    configure_document(document)

    for item in extract_items():
        add_paragraph(document, item, classify(item))

    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    document.save(OUTPUT_FILE)
    print(OUTPUT_FILE)


if __name__ == "__main__":
    build_docx()
