"""Generate original page-label examples in a NEW directory; never modify an input PDF."""
import argparse
import csv
import hashlib
import json
from pathlib import Path
import platform
import subprocess

import PIL
from PIL import Image, ImageChops, ImageDraw, ImageFont
import pypdf
from pypdf import PdfReader, PdfWriter
from pypdf.constants import PageLabelStyle
import reportlab
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

BASE = Path(__file__).resolve().parent
FONT = BASE / "fonts/NotoSans-Regular.ttf"
FONT_SHA = "b85c38ecea8a7cfb39c24e395a4007474fa5a4fc864f6ee33309eb4948d232d5"


def sha(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def page(title, number, lines):
    return {"title": title, "visibleNumber": number, "lines": lines}


common = [
    page("A small field notebook", None, ["An original page-label teaching fixture.", "Not a scan, facsimile, or real published edition.", "The cover has no printed page number."]),
    page("Preface", "i", ["This notebook begins with Roman-numbered front matter.", "The next two body pages restart at one."]),
    page("Reading notes", "ii", ["Keep the visible numbers separate from file position.", "Changing navigation labels should not repaint this page."]),
    page("The first observation", "1", ["An oak stands beside the northern path.", "Three leaves lie under a small stone."]),
    page("The second observation", "2", ["The stone remains beside the path.", "A drawing can be inserted without renumbering this text."]),
    page("Appendix: locations", "A-1", ["The appendix uses its own prefixed numbering sequence.", "Location A is the northern path."]),
    page("Appendix: objects", "A-2", ["Object one is the stone.", "Object two is the notebook."]),
]
plate = page("A leaf illustration", None, ["This inserted plate has no printed page number.", "Plate-A is an editorial navigation label, not a source numeral."])
excerpt = [page("An excerpt from the notebook", str(number), [
    "This is an original teaching page, not an extract from a real book.",
    f"Its visible page number is {number}.",
    "The file position starts at one; the label need not."
]) for number in (37, 38, 39)]
CASES = [
    {"id": "seven-page", "pages": common, "labels": ["Cover", "i", "ii", "1", "2", "A-1", "A-2"],
     "ranges": [(0, 0, None, "Cover", 1), (1, 2, PageLabelStyle.LOWERCASE_ROMAN, "", 1),
                (3, 4, PageLabelStyle.DECIMAL, "", 1), (5, 6, PageLabelStyle.DECIMAL, "A-", 1)]},
    {"id": "inserted-plate", "pages": common[:4] + [plate] + common[4:],
     "labels": ["Cover", "i", "ii", "1", "Plate-A", "2", "A-1", "A-2"],
     "ranges": [(0, 0, None, "Cover", 1), (1, 2, PageLabelStyle.LOWERCASE_ROMAN, "", 1),
                (3, 3, PageLabelStyle.DECIMAL, "", 1), (4, 4, None, "Plate-A", 1),
                (5, 5, PageLabelStyle.DECIMAL, "", 2), (6, 7, PageLabelStyle.DECIMAL, "A-", 1)]},
    {"id": "excerpt-37", "pages": excerpt, "labels": ["37", "38", "39"],
     "ranges": [(0, 2, PageLabelStyle.DECIMAL, "", 37)]},
]


def draw_pdf(path, pages):
    document = canvas.Canvas(str(path), pagesize=(540, 720), invariant=1)
    document.setTitle("BookTranslator page-label teaching example")
    document.setAuthor("BookTranslator Team")
    for content in pages:
        document.setFillColorRGB(0.98, 0.97, 0.94)
        document.rect(0, 0, 540, 720, fill=1, stroke=0)
        document.setFillColorRGB(0.35, 0.40, 0.32)
        document.setFont("FixtureSans", 10)
        document.drawString(48, 669, "PAGE-LABEL LAB / ORIGINAL TEACHING MATERIAL")
        document.setStrokeColorRGB(0.7, 0.71, 0.64)
        document.line(48, 650, 492, 650)
        document.setFillColorRGB(0.13, 0.16, 0.12)
        document.setFont("FixtureSans", 24)
        document.drawString(48, 592, content["title"])
        document.setFont("FixtureSans", 12)
        for line_index, line in enumerate(content["lines"]):
            assert pdfmetrics.stringWidth(line, "FixtureSans", 12) <= 444, line
            document.drawString(48, 538 - 25 * line_index, line)
        if content is plate:
            document.setStrokeColorRGB(0.35, 0.40, 0.32)
            document.setLineWidth(2)
            document.ellipse(210, 220, 330, 390, stroke=1, fill=0)
            document.line(245, 200, 300, 370)
            document.line(269, 274, 234, 299)
            document.line(280, 307, 314, 325)
        document.setStrokeColorRGB(0.7, 0.71, 0.64)
        document.line(48, 88, 492, 88)
        if content["visibleNumber"]:
            document.setFont("FixtureSans", 20)
            document.drawCentredString(270, 51, content["visibleNumber"])
        document.showPage()
    document.save()


def render(pdf, destination):
    destination.mkdir()
    subprocess.run(["pdftoppm", "-r", "144", "-png", str(pdf), str(destination / "page")],
                   check=True, capture_output=True, timeout=90)
    return sorted(destination.glob("page-*.png"), key=lambda p: int(p.stem.split("-")[-1]))


parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--render-dir", type=Path, required=True)
args = parser.parse_args()
assert sha(FONT) == FONT_SHA, "Unexpected font bytes"
if args.output_dir.exists() or args.render_dir.exists():
    raise SystemExit("Choose new output and render directories; existing paths are not overwritten.")
args.output_dir.mkdir(parents=True)
args.render_dir.mkdir(parents=True)
pdfmetrics.registerFont(TTFont("FixtureSans", str(FONT)))
version = subprocess.run(["pdftoppm", "-v"], capture_output=True, text=True, check=True).stderr.splitlines()[0]
results = {"date": "2026-09-12", "corpus": "Three original synthetic PDF pairs; no reader UI, Acrobat, OCR, or BookTranslator test.",
           "versions": {"python": platform.python_version(), "pypdf": pypdf.__version__, "reportlab": reportlab.Version,
                        "Pillow": PIL.__version__, "renderer": version},
           "renderDpi": 144, "fontSha256": FONT_SHA, "cases": []}
mapping = []
for case in CASES:
    before_path = args.output_dir / f'{case["id"]}-before.pdf'
    after_path = args.output_dir / f'{case["id"]}-after.pdf'
    draw_pdf(before_path, case["pages"])
    before = PdfReader(before_path)
    assert before.page_labels == [str(n) for n in range(1, len(case["pages"]) + 1)]
    writer = PdfWriter(clone_from=before)
    for first, last, style, prefix, start in case["ranges"]:
        writer.set_page_label(first, last, style=style, prefix=prefix, start=start)
    with after_path.open("wb") as handle:
        writer.write(handle)
    after = PdfReader(after_path)
    assert len(after.pages) == len(before.pages)
    assert after.page_labels == case["labels"]
    before_renders = render(before_path, args.render_dir / f'{case["id"]}-before')
    after_renders = render(after_path, args.render_dir / f'{case["id"]}-after')
    assert len(before_renders) == len(after_renders) == len(case["pages"])
    checks = []
    for index, (left_path, right_path) in enumerate(zip(before_renders, after_renders)):
        left = Image.open(left_path).convert("RGB")
        right = Image.open(right_path).convert("RGB")
        equal = left.size == right.size and ImageChops.difference(left, right).getbbox() is None
        content_equal = before.pages[index].get_contents().get_data() == after.pages[index].get_contents().get_data()
        assert equal and content_equal, (case["id"], index)
        entry = {"filePosition": index + 1, "zeroBasedIndex": index,
                 "visibleNumber": case["pages"][index]["visibleNumber"], "beforeLabel": before.page_labels[index],
                 "afterLabel": after.page_labels[index], "pixelsEqual": equal, "contentStreamsEqual": content_equal,
                 "dimensions": list(left.size), "beforePixelSha256": hashlib.sha256(left.tobytes()).hexdigest(),
                 "afterPixelSha256": hashlib.sha256(right.tobytes()).hexdigest()}
        checks.append(entry)
        mapping.append({"case": case["id"], **{key: entry[key] for key in ["filePosition", "zeroBasedIndex", "visibleNumber", "beforeLabel", "afterLabel"]}})
    results["cases"].append({"case": case["id"], "before": before_path.name, "after": after_path.name,
                             "beforeSha256": sha(before_path), "afterSha256": sha(after_path), "pages": checks})
    # A contact sheet of all after-pages for complete visual review, not a viewer screenshot.
    font = ImageFont.truetype(str(FONT), 18)
    sheet = Image.new("RGB", (1200, ((len(after_renders) + 3) // 4) * 450), "white")
    draw = ImageDraw.Draw(sheet)
    for index, path in enumerate(after_renders):
        im = Image.open(path).convert("RGB")
        im.thumbnail((285, 380))
        x, y = (index % 4) * 300, (index // 4) * 450
        sheet.paste(im, (x + 7, y + 36))
        draw.text((x + 7, y + 8), f'File {index+1} / label {case["labels"][index]}', fill="black", font=font)
    sheet.save(args.render_dir / f'{case["id"]}-contact.png')
    if case["id"] == "seven-page":
        sheet.save(args.output_dir / "seven-page-preview.png", optimize=True)
results["totalComparedPagePairs"] = sum(len(case["pages"]) for case in results["cases"])
assert results["totalComparedPagePairs"] == 18
(args.output_dir / "results.json").write_text(json.dumps(results, indent=2) + "\n")
with (args.output_dir / "page-map.csv").open("w", newline="") as handle:
    writer = csv.DictWriter(handle, fieldnames=list(mapping[0]), lineterminator="\n")
    writer.writeheader()
    writer.writerows(mapping)
print(json.dumps({"pdfs": 6, "comparedPagePairs": 18, "allPixelsEqual": True, "allContentStreamsEqual": True,
                  "labels": {case["id"]: case["labels"] for case in CASES}, "versions": results["versions"]}, indent=2))
