#!/usr/bin/env python3
"""Build and inspect a synthetic PDF accessibility example, not a PDF/UA template.

Reproduce: python -m pip install reportlab==4.4.9 pypdf==6.10.0
           python build-fixture.py
No network requests, customer files, or translation API calls are made.
"""

from io import BytesIO
import json
from pathlib import Path
import re

import pypdf
import reportlab
from pypdf import PdfReader, PdfWriter
from pypdf.generic import (
    ArrayObject, BooleanObject, ContentStream, DictionaryObject,
    NameObject, NumberObject, TextStringObject,
)
from reportlab.lib.colors import HexColor
from reportlab.pdfgen import canvas


OUTPUT = Path(__file__).resolve().parent
BLOCKS = [
    (0, "H1", 48, 744, 21, "Preparar un archivo para traducir"),
    (1, "P", 48, 713, 10, "Ejemplo sintético: contenido inventado para inspección técnica."),
    (2, "H2", 48, 655, 15, "1. Revisar el archivo"),
    (3, "P", 48, 619, 12, "A1. Confirme que el archivo\ncontiene todas las páginas."),
    (4, "P", 48, 559, 12, "A2. Guarde una copia del\noriginal antes de editarlo."),
    (5, "H2", 326, 655, 15, "2. Revisar la entrega"),
    (6, "P", 326, 619, 12, "B1. Compare los títulos\ncon el archivo original."),
    (7, "P", 326, 559, 12, "B2. Revise las notas antes\nde compartir el resultado."),
    (8, "P", 48, 476, 12, "Etiqueta conservada de la interfaz:"),
    (9, "Span", 48, 453, 12, "Read only"),
    (10, "Figure", 48, 380, 12, ""),
    (11, "P", 48, 255, 10, "Los dos archivos tienen el mismo aspecto visual."),
    (12, "P", 48, 235, 10, "Este ejemplo no demuestra conformidad con PDF/UA."),
]
FIXED_ORDER = list(range(len(BLOCKS)))
BROKEN_ORDER = [0, 1, 2, 5, 3, 6, 4, 7, 8, 9, 10, 11, 12]
ALT = {
    "before": "A source folder points to a translated document.",
    "after": "Una carpeta de origen apunta a un documento traducido.",
}


def visible_page():
    stream = BytesIO()
    pdf = canvas.Canvas(stream, pagesize=(612, 792), invariant=1, pageCompression=0)
    pdf.setTitle("Ejemplo de accesibilidad de PDF traducido")
    pdf.setAuthor("BookTranslator Team")
    pdf.addLiteral("/Artifact BMC")
    pdf.setFillColor(HexColor("#F6F3EC"))
    pdf.rect(0, 0, 612, 792, fill=1, stroke=0)
    pdf.setStrokeColor(HexColor("#B8B0A0"))
    pdf.line(302, 535, 302, 675)
    pdf.line(48, 692, 564, 692)
    pdf.addLiteral("EMC")
    for mcid, role, x, y, size, value in BLOCKS:
        pdf.addLiteral(f"/{role} <</MCID {mcid}>> BDC")
        pdf.setFillColor(HexColor("#232C31"))
        if role == "Figure":
            pdf.setStrokeColor(HexColor("#23665D"))
            pdf.setLineWidth(2)
            pdf.roundRect(55, 316, 99, 56, 4, fill=0, stroke=1)
            pdf.line(55, 372, 75, 390)
            pdf.line(75, 390, 112, 390)
            pdf.line(112, 390, 129, 372)
            pdf.line(174, 344, 250, 344)
            pdf.line(239, 352, 250, 344)
            pdf.line(239, 336, 250, 344)
            pdf.rect(278, 306, 53, 80, fill=0, stroke=1)
            for line_y in [369, 355, 341, 327]:
                pdf.line(288, line_y, 321, line_y)
        else:
            text = pdf.beginText(x, y)
            text.setFont("Helvetica-Bold" if role.startswith("H") else "Helvetica", size)
            text.setLeading(size * 1.45)
            for line in value.splitlines():
                text.textLine(line)
            pdf.drawText(text)
        pdf.addLiteral("EMC")
    pdf.showPage()
    pdf.save()
    return stream.getvalue()


def tagged_version(raw, variant):
    writer = PdfWriter()
    writer.clone_document_from_reader(PdfReader(BytesIO(raw)))
    root = writer.root_object
    page = writer.pages[0]
    root[NameObject("/Lang")] = TextStringObject("es-ES" if variant == "after" else "en-US")
    root[NameObject("/MarkInfo")] = DictionaryObject({NameObject("/Marked"): BooleanObject(True)})
    root[NameObject("/ViewerPreferences")] = DictionaryObject({NameObject("/DisplayDocTitle"): BooleanObject(True)})
    page[NameObject("/StructParents")] = NumberObject(0)
    tree = DictionaryObject({NameObject("/Type"): NameObject("/StructTreeRoot")})
    tree_ref = writer._add_object(tree)
    document = DictionaryObject({
        NameObject("/Type"): NameObject("/StructElem"),
        NameObject("/S"): NameObject("/Document"),
        NameObject("/P"): tree_ref,
    })
    document_ref = writer._add_object(document)
    elements = []
    for mcid, role, *_ in BLOCKS:
        element = DictionaryObject({
            NameObject("/Type"): NameObject("/StructElem"),
            NameObject("/S"): NameObject("/" + role),
            NameObject("/P"): document_ref,
            NameObject("/Pg"): page.indirect_reference,
            NameObject("/K"): NumberObject(mcid),
        })
        if role == "Figure":
            element[NameObject("/Alt")] = TextStringObject(ALT[variant])
        if mcid == 9 and variant == "after":
            element[NameObject("/Lang")] = TextStringObject("en-US")
        elements.append(writer._add_object(element))
    order = FIXED_ORDER if variant == "after" else BROKEN_ORDER
    document[NameObject("/K")] = ArrayObject([elements[index] for index in order])
    tree[NameObject("/K")] = document_ref
    tree[NameObject("/ParentTree")] = writer._add_object(DictionaryObject({
        NameObject("/Nums"): ArrayObject([NumberObject(0), ArrayObject(elements)])
    }))
    tree[NameObject("/ParentTreeNextKey")] = NumberObject(1)
    root[NameObject("/StructTreeRoot")] = tree_ref
    destination = OUTPUT / f"translated-pdf-{variant}.pdf"
    with destination.open("wb") as handle:
        writer.write(handle)
    return destination


def inspect(path):
    reader = PdfReader(path)
    page = reader.pages[0]
    root = reader.trailer["/Root"]
    text_by_id = {}
    stack = []
    for operands, operator in ContentStream(page["/Contents"], reader).operations:
        if operator in (b"BMC", b"BDC"):
            properties = operands[1] if operator == b"BDC" else {}
            stack.append(properties.get("/MCID"))
        elif operator == b"EMC":
            stack.pop()
        elif operator == b"Tj" and stack and stack[-1] is not None:
            text_by_id.setdefault(int(stack[-1]), []).append(str(operands[0]))
    assert not stack, "Unbalanced marked content"
    tree = root["/StructTreeRoot"]
    elements = tree["/K"]["/K"]
    parent_map = tree["/ParentTree"]["/Nums"][1]
    rows = []
    for reference in elements:
        element = reference.get_object()
        mcid = int(element["/K"])
        assert parent_map[mcid] == reference, "MCID parent-tree mismatch"
        rows.append({
            "mcid": mcid,
            "role": str(element["/S"]),
            "effective_language": str(element.get("/Lang", root["/Lang"])),
            "text": re.sub(r"\s+", " ", " ".join(text_by_id.get(mcid, []))),
            "alt": str(element.get("/Alt", "")),
        })
    assert sorted(row["mcid"] for row in rows) == FIXED_ORDER
    return {"file": path.name, "document_language": str(root["/Lang"]), "tag_order": rows}


def main():
    raw = visible_page()
    reports = [inspect(tagged_version(raw, version)) for version in ("before", "after")]
    before, after = reports
    assert [row["mcid"] for row in before["tag_order"]] == BROKEN_ORDER
    assert [row["mcid"] for row in after["tag_order"]] == FIXED_ORDER
    assert before["document_language"] == "en-US"
    assert after["document_language"] == "es-ES"
    assert after["tag_order"][9]["effective_language"] == "en-US"
    assert before["tag_order"][10]["alt"] == ALT["before"]
    assert after["tag_order"][10]["alt"] == ALT["after"]
    before_reader = PdfReader(OUTPUT / before["file"])
    after_reader = PdfReader(OUTPUT / after["file"])
    assert before_reader.pages[0]["/Contents"].get_data() == after_reader.pages[0]["/Contents"].get_data()
    result = {
        "created": "2026-09-07",
        "provenance": "Synthetic Spanish example authored for this article; not translation service output.",
        "method": "Read PDF catalog, structure tree, MCID parent tree and marked-content text with pypdf.",
        "versions": {"reportlab": reportlab.Version, "pypdf": pypdf.__version__},
        "limitations": ["Not a screen-reader execution or audio transcript.", "Not a PDF/UA or WCAG conformance audit.", "No interactive controls or tables in this fixture."],
        "checks": {"page_content_streams_identical": True, "mcid_parent_tree_mapping": True, "expected_tag_order": True, "expected_language_and_alt": True},
        "files": reports,
    }
    (OUTPUT / "inspection.json").write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps({"outputs": [report["file"] for report in reports], "checks": result["checks"]}, indent=2))


if __name__ == "__main__":
    main()
