# SPDX-License-Identifier: Apache-2.0
"""Reproduce nine original A4 SVG/PDF source aids and exact alternatives."""
from __future__ import annotations

import argparse
import hashlib
import os
import subprocess
import tempfile
import xml.etree.ElementTree as ET
from html import escape
from pathlib import Path

from PIL import ImageFont

HERE = Path(__file__).resolve().parent
INK = "#183743"
FILLS = ("#e7f2f6", "#edf4e8", "#fff0de", "#f0eafa", "#eef0f2")
FONT = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"


def text(x: float, y: float, value: str, size: int = 20, bold: bool = False) -> str:
    """Return visible selectable text with a shared dark ink colour."""
    return f'<text x="{x}" y="{y}" font-family="DejaVu Sans" font-size="{size}" font-weight="{700 if bold else 400}" fill="{INK}">{escape(value)}</text>'


def box(x: float, y: float, w: float, h: float, fill: str = "white") -> str:
    """Draw a clearly outlined panel; colour is supplementary."""
    return f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="12" fill="{fill}" stroke="{INK}" stroke-width="3"/>'


def path(d: str, width: int = 4) -> str:
    """Draw an original rounded line illustration."""
    return f'<path d="{d}" fill="none" stroke="{INK}" stroke-width="{width}" stroke-linecap="round" stroke-linejoin="round"/>'


def circle(x: float, y: float, r: float, fill: str = "white") -> str:
    """Draw an outlined or dark filled circular mark."""
    return f'<circle cx="{x}" cy="{y}" r="{r}" fill="{fill}" stroke="{INK}" stroke-width="3"/>'


def paragraph(
    value: str,
    x: int,
    y: int,
    width: int = 640,
    size: int = 20,
    leading: int = 30,
    bold: bool = False,
) -> tuple[str, int]:
    """Wrap measured text within a panel and return the next baseline."""
    font = ImageFont.truetype(BOLD if bold else FONT, size)
    rows: list[str] = []
    current = ""
    for word in value.split():
        candidate = f"{current} {word}".strip()
        if font.getlength(candidate) > width and current:
            rows.append(current)
            current = word
        else:
            current = candidate
    rows.append(current)
    return (
        "".join(
            text(x, y + i * leading, row, size, bold) for i, row in enumerate(rows)
        ),
        y + len(rows) * leading,
    )


def block(label: str, value: str, y: int, h: int, fill: str, size: int = 20) -> str:
    """Place an exact source quotation in an outlined labelled panel."""
    out = box(48, y, 698, h, fill) + text(68, y + 34, label, 18, True)
    body, end = paragraph(value, 68, y + 70, 655, size, 29 if size >= 19 else 23)
    if end > y + h + 5:
        raise ValueError(f"{label}: text exceeds panel")
    return out + body


def page(title: str, desc: str, body: str) -> str:
    """Wrap a semantic SVG with A4 geometry and original-material attribution."""
    return "\n".join(
        [
            '<svg xmlns="http://www.w3.org/2000/svg" width="210mm" height="297mm" viewBox="0 0 794 1123" role="img" aria-labelledby="title desc">',
            f'<title id="title">{escape(title)}</title>',
            f'<desc id="desc">{escape(desc)}</desc>',
            '<rect width="794" height="1123" fill="white"/>',
            text(42, 53, "SUBJECTNEST · FOUNDATION HASS · W37–38", 17, True),
            text(42, 95, title, 25, True),
            paragraph(desc, 42, 130, 705, 16, 23)[0],
            body,
            path("M42 1038 L752 1038", 2),
            text(
                42,
                1064,
                "Original teaching material · SubjectNest by NeuroForgeIO Pty Ltd",
                12,
            ),
            text(
                42,
                1085,
                "© 2026 · CC BY 4.0 · exact text/tactile alternatives in pack",
                12,
            ),
            "</svg>",
            "",
        ]
    )


SOURCES = {
    "A": "On Story Day Two, the paper group finished its shared story book. On Story Day Three, some visitors looked at a book-page display to celebrate finishing it. This is an invented event report.",
    "B": "Story Day Three · book-page display to celebrate the finished story book. The drawing shows an open book and a wide banner. It does not list everyone who was there.",
    "C": "I am Kemi. Finishing the shared book matters to me because we can read our made-up stories together. I looked at the display on Story Day Three. These are my own words.",
    "D": "I am Arlo. I looked at the display with my cousin Niv on Story Day Three. I have not spoken for other families or visitors.",
    "E": "Story Day Six · I plan a new paper label about the earlier display. A label made now is not a record that a new celebration happened.",
    "F": "You may make a plain source label for this paper archive. Ask before showing it. Return the copied pages to the broad envelope. This is a request, not a record of use.",
}
FRESH = {
    "frame": (
        "On Story Day Five, the paper group finished its shared picture book. On Story Day Seven, some people drew page borders to celebrate finishing it. This is an invented report.",
        "Story Day Seven · page-border drawing to celebrate the finished picture book. The drawing shows a square page inside a wide frame. It does not show every person.",
        "I am Bela. The finished book matters to me because I can share its made-up pictures. This is my own reason.",
        "I am Cato. I drew a border with my aunt Rumi on Story Day Seven. I have not reported every family’s view.",
        "I plan a new plain label on Story Day Nine. A label made then is not proof that another celebration happened.",
    ),
    "circle": (
        "On Story Day One, the paper group read its first shared story page. I plan a quiet look on Story Day Eight to remember that first reading. I have not reported that look taking place.",
        "This undated drawing shows a rectangle page and a round dot label. It does not say when or why either was made.",
        "I am Davi. That first shared reading matters to me because we read our made-up words together. This is my own reason.",
        "I am Esme. I read on Story Day One with other paper readers. I have not said whether I joined the later look or who else would.",
        "I ask for a plain archive marker about the plan. Ask before showing it. I have not reported any reader using it.",
    ),
}
TIMES = {
    "main": (
        ("Story Day Two", "EVENT", "REPORTED"),
        ("Story Day Three", "MARKING", "REPORTED"),
        ("Story Day Six", "LABEL", "PLAN"),
    ),
    "frame": (
        ("Story Day Five", "EVENT", "REPORTED"),
        ("Story Day Seven", "MARKING", "REPORTED"),
        ("Story Day Nine", "LABEL", "PLAN"),
    ),
    "circle": (
        ("Story Day One", "READING", "REPORTED"),
        ("Story Day Eight", "QUIET LOOK", "PLAN"),
        ("Drawing date", "UNDATED", "OPEN"),
    ),
}


def book(x: float, y: float, w: float, h: float) -> str:
    """Draw an original open book; no people/attendance inferred."""
    out = '<g data-object="open-book">'
    out += path(
        f"M{x} {y+h*.15} Q{x+w*.22} {y} {x+w*.5} {y+h*.15} Q{x+w*.78} {y} {x+w} {y+h*.15} L{x+w} {y+h} Q{x+w*.78} {y+h*.86} {x+w*.5} {y+h} Q{x+w*.22} {y+h*.86} {x} {y+h} Z",
        3,
    )
    out += path(f"M{x+w*.5} {y+h*.15} L{x+w*.5} {y+h}", 2)
    for k in (0.35, 0.55, 0.75):
        out += path(
            f"M{x+w*.10} {y+h*k} L{x+w*.40} {y+h*k} M{x+w*.60} {y+h*k} L{x+w*.90} {y+h*k}",
            2,
        )
    return out + "</g>"


def trace(kind: str, x: int, y: int, w: int, h: int) -> str:
    """Draw only source objects; labels/source scope carry their meaning."""
    out = box(x, y, w, h, FILLS[0]) + f'<g data-trace-kind="{kind}">'
    if kind == "main":
        out += book(x + w * 0.15, y + h * 0.33, w * 0.70, h * 0.52)
        out += f'<rect data-object="wide-banner" x="{x+w*.10}" y="{y+h*.10}" width="{w*.80}" height="{h*.13}" fill="white" stroke="{INK}" stroke-width="3"/>'
    elif kind == "frame":
        fx, fy, fw, fh = x + w * 0.17, y + h * 0.12, w * 0.66, h * 0.73
        side = min(w * 0.33, h * 0.48)
        out += f'<rect data-object="wide-frame" x="{fx}" y="{fy}" width="{fw}" height="{fh}" fill="white" stroke="{INK}" stroke-width="3"/>'
        out += f'<rect data-object="square-page" x="{x+w*.5-side/2}" y="{y+h*.49-side/2}" width="{side}" height="{side}" fill="white" stroke="{INK}" stroke-width="3"/>'
    else:
        out += f'<rect data-object="rectangle-page" x="{x+w*.10}" y="{y+h*.19}" width="{w*.43}" height="{h*.60}" fill="white" stroke="{INK}" stroke-width="3"/>'
        out += f'<circle data-object="round-label" cx="{x+w*.78}" cy="{y+h*.49}" r="{min(w,h)*.17}" fill="white" stroke="{INK}" stroke-width="3"/>'
    return out + "</g>"


def timeline(kind: str, x: int, y: int, w: int, h: int) -> str:
    """Show exact source date/order/status without inventing elapsed duration."""
    out = f'<g data-timeline-kind="{kind}">'
    for i, (date, event, status) in enumerate(TIMES[kind]):
        xx = x + i * w / 3
        out += f'<g data-position="{i}" data-date="{escape(date)}" data-event="{event}" data-status="{status}">'
        out += box(xx, y, w / 3 - 10, h, FILLS[i]) + text(
            xx + 10, y + 25, date, 12 if w < 550 else 17, True
        )
        out += text(xx + 10, y + 56, event, 12 if w < 550 else 17, True)
        out += text(xx + 10, y + h - 17, status, 12 if w < 550 else 17)
        if event in ("EVENT", "READING"):
            out += book(xx + 18, y + 70, w / 3 - 46, max(20, h - 121))
        elif event == "MARKING":
            out += path(
                f"M{xx+20} {y+77} L{xx+w/3-30} {y+77} L{xx+w/3-30} {y+h-45} L{xx+20} {y+h-45} Z",
                2,
            )
        else:
            out += circle(xx + w / 6 - 5, y + (h + 65) / 2, max(5, (h - 125) / 3))
        out += "</g>"
    return out + "</g>"


def q(key: str) -> str:
    return "“" + SOURCES[key] + "”"


def report_a() -> str:
    body = trace("main", 48, 194, 698, 235)
    body += block("A · COMPLETED EVENT REPORT", q("A"), 454, 247, FILLS[0], 19)
    body += block(
        "CHOOSE A SOURCE POCKET",
        "What happened? Which source tells us? Keep event, marking and today's paper making apart.",
        731,
        172,
        FILLS[1],
        19,
    )
    body += text(
        74, 967, "FICTION · no actual event or family evidence supplied", 17, True
    )
    return page(
        "Paper Book Gathering · A",
        "Invented report of event and marking. Original book/banner cues do not identify people or their reasons.",
        body,
    )


def trace_b() -> str:
    body = trace("main", 48, 194, 698, 255)
    body += block("B · DATED MARKING TRACE", q("B"), 478, 234, FILLS[2], 19)
    body += block(
        "ATTACH THE SOURCE",
        "What did people do to mark it? What source links that marking to the event? The picture does not list everyone.",
        743,
        192,
        FILLS[1],
        19,
    )
    body += path("M80 975 L175 975", 2) + circle(704, 975, 17)
    return page(
        "Book-page display trace · B",
        "Reported Story Day Three marking. A pictured object alone is not attendance, family or universal meaning evidence.",
        body,
    )


def accounts() -> str:
    body = block("C · KEMI'S OWN WORDS", q("C"), 193, 230, FILLS[1], 19)
    body += block("D · ARLO'S SHARING ACCOUNT", q("D"), 451, 204, FILLS[3], 19)
    body += box(48, 684, 698, 165, FILLS[0])
    body += path("M94 742 Q125 701 215 730 L241 706 L232 744 Q216 786 110 782 Z", 3)
    body += path("M448 742 Q479 701 569 730 L595 706 L586 744 Q570 786 464 782 Z", 3)
    body += text(84, 827, "PERSON'S OWN MEANING", 15, True) + text(
        443, 827, "PARTICULAR SHARING", 15, True
    )
    body += block(
        "KEEP SOURCE FLAPS SEPARATE",
        "One person's words are not everyone's. Other people and actual family breadth stay open.",
        877,
        134,
        FILLS[4],
        18,
    )
    return page(
        "Whose account? · C / D",
        "Fictional particular-person meaning and sharing. No actual family, identity, attendance or consensus supplied.",
        body,
    )


def plan_request() -> str:
    body = block("E · LATER LABEL PLAN", q("E"), 194, 214, FILLS[0], 19)
    body += block("F · PAPER ARCHIVE REQUEST", q("F"), 435, 240, FILLS[2], 19)
    body += box(48, 704, 698, 185, FILLS[1])
    body += path("M95 750 L188 750 L188 844 L95 844 Z", 3) + text(
        104, 812, "PLAN", 15, True
    )
    body += path("M261 796 L325 796 M305 780 L325 796 L305 812", 3)
    body += path("M404 752 L470 752 L490 777 L671 777 L671 844 L404 844 Z", 3)
    body += text(434, 817, "COPIED PAGES", 17, True)
    body += text(
        80, 967, "ASK FIRST · return copies · no use result supplied", 17, True
    )
    return page(
        "Label plan and request · E / F",
        "A plan or request isn't a completed celebration, current local permission or viewer result.",
        body,
    )


def fold() -> str:
    body = timeline("main", 48, 194, 708, 250)
    body += block(
        "UNFOLD THE SOURCE TRAIL",
        "Use A/B for event and marking. E is a later label plan. Do not treat today's fold as the earlier event.",
        475,
        180,
        FILLS[1],
        19,
    )
    body += blank_panel(
        1,
        "PERSON SAYS / SHARING SOURCE",
        "Attach C/D or approved actual accounts. Keep each speaker and source; others and family breadth OPEN when missing.",
        687,
        164,
    )
    body += blank_panel(
        2,
        "WHAT WE MADE NOW",
        "First words / exact help / source question. Present making is separate from earlier reported history.",
        859,
        157,
    )
    return page(
        "Event · marking · plan fold",
        "Three broad flat panels or adult-folded trace. Story Days are invented; no duration or real calendar fact implied.",
        body,
    )


def blank_panel(n: int, label: str, value: str, y: int, h: int) -> str:
    out = (
        box(48, y, 698, h, FILLS[(n - 1) % 5])
        + circle(80, y + 34, 18)
        + text(74, y + 40, str(n), 17, True)
        + text(112, y + 41, label, 17, True)
    )
    body, end = paragraph(value, 72, y + 80, 647, 17, 25)
    if end > y + h - 18:
        raise ValueError(label + ": exceeds blank panel")
    return out + body + path(f"M74 {y+h-19} L716 {y+h-19}", 2)


def marker() -> str:
    body = box(48, 194, 698, 242, FILLS[0])
    body += f'<g data-marker="blank"><path d="M96 225 L270 225 L270 397 L183 365 L96 397 Z" fill="white" stroke="{INK}" stroke-width="3"/></g>'
    body += (
        text(110, 266, "PLAIN", 19, True)
        + text(110, 298, "SOURCE", 19, True)
        + text(110, 330, "MARKER", 19, True)
    )
    body += path("M360 240 L693 240 L693 396 L360 396 Z M360 305 L526 345 L693 305", 3)
    body += text(399, 285, "SOURCE POCKET", 18, True)
    body += blank_panel(
        1,
        "CHILD CHOICE / SOURCE WORDS",
        "Tab / bookmark / oral no-marker guide / direct adult. Event/source; marking/source; person says; sharing/gap.",
        464,
        185,
    )
    body += blank_panel(
        2,
        "FIRST / REVISED",
        "Preserve first words. Repair against source or keep fitting phrase with reason. Ask before sharing; no viewer reply invented.",
        671,
        180,
    )
    body += blank_panel(
        3,
        "NOW / RETURN",
        "Observed paper action/help or not used. Return copied pages under scope; no new celebration inferred.",
        855,
        160,
    )
    return page(
        "Choose a plain source marker",
        "Useful source finder or oral guide. No cultural symbols, actual permission, history or user outcome is drawn.",
        body,
    )


def actual() -> str:
    body = blank_panel(
        1,
        "ACTUAL EVENT / MARKING",
        "Source ID/context/date precision; significant event; completed celebration/commemoration/link versus plan; first words/help.",
        194,
        188,
    )
    body += blank_panel(
        2,
        "PARTICULAR ACCOUNT / SHARING",
        "Willing exact account/date/scope; others source; optional private family supported/open; no presumed relationships.",
        399,
        188,
    )
    body += blank_panel(
        3,
        "MARKER / CURRENT PERMISSION",
        "Plain/no-marker child choice; authorised source copies/audience/action; first/revised words; review not use proof.",
        604,
        188,
    )
    body += blank_panel(
        4,
        "ACTUAL ACTION / K02 ELEMENTS",
        "Actor/time/help/use/pass/not done; exact response; return scope; actual event/marking/sharing evidence and gaps; retention.",
        809,
        188,
    )
    return page(
        "Actual event record · blank",
        "Use complete educator preflight/expanded local fields. Keep genuine data locally; no event, family, consent or outcome supplied.",
        body,
    )


def fresh(kind: str) -> str:
    label = "A" if kind == "frame" else "B"
    body = trace(kind, 48, 194, 240, 131) + timeline(kind, 310, 194, 438, 131)
    names = (
        ("COMPLETED REPORT", "DATED TRACE", "BELA SAYS", "CATO SAYS", "LABEL PLAN")
        if kind == "frame"
        else (
            "EVENT / FUTURE PLAN",
            "UNDATED DRAWING",
            "DAVI SAYS",
            "ESME SAYS",
            "MARKER REQUEST",
        )
    )
    y = 346
    for i, (name, value) in enumerate(zip(names, FRESH[kind]), 1):
        h = 140 if i in (1, 2) else 119
        body += block(
            f"{label}{i} · {name}", "“" + value + "”", y, h, FILLS[(i - 1) % 5], 17
        )
        y += h + 7
    return page(
        "Fresh "
        + label
        + " · Paper "
        + ("Frame Book" if kind == "frame" else "Circle Note"),
        "Hold until Day "
        + ("185" if kind == "frame" else "190")
        + ". Read neutrally; separate learner prompts after release.",
        body,
    )


def fresh_a() -> str:
    return fresh("frame")


def fresh_b() -> str:
    return fresh("circle")


PAGES = {
    "book-gathering-report": report_a,
    "book-display-trace": trace_b,
    "event-source-accounts": accounts,
    "label-plan-request": plan_request,
    "event-trace-fold": fold,
    "marker-pocket-mat": marker,
    "actual-event-record": actual,
    "check-a-frame-book": fresh_a,
    "check-b-circle-note": fresh_b,
}
TACTILE = {
    "book-gathering-report": "Original neutral open-book outline with two pages below a wide outlined banner. It illustrates supplied fictional drawing cues, not people, attendance, dates or significance beyond source words. Use broad raised book/banner cues or exact no-touch reading and adult-directed source tab/pocket. Report distinguishes event and marking; actual event/family fields remain empty.",
    "book-display-trace": "Original open book with two page outlines and decorative writing lines below a wide blank rectangle banner. Source B captions Story Day Three book-page marking linked to finished story book; objects don't list people or reasons. Broad raised/flat cues, exact caption reading and directed placement preserve all facts without touch/colour; no ritual/cultural symbol depicted.",
    "event-source-accounts": "Two separate exact source panels labelled Kemi's own words and Arlo's sharing account. Two empty original speech outlines sit beneath PERSON'S OWN MEANING and PARTICULAR SHARING labels. No face/identity/family tree pictured. Use broad named/source-ID flaps or exact read/dictation; keep own reason, particular reported sharing and others OPEN separate.",
    "label-plan-request": "Exact E/F panels above broad PLAN page left, line arrow centre and outlined copied-page envelope right. These illustrate planned label/requested copy return only; no actual actor, event, consent, use or viewer response drawn. Broad raised plain page/envelope cues or exact no-touch reading/adult-directed placement allow same source distinctions; copies only, not real event objects.",
    "event-trace-fold": "Three source-labelled timeline panels in order Story Day Two EVENT/REPORTED, Story Day Three MARKING/REPORTED, Story Day Six LABEL/PLAN, with neutral book/frame/circle cue. Two blank fields prompt person/account/sharing and what was made now. Use broad flat/raised panels or adult-folded source fold, read all words and allow no-touch direction. Order comes from fiction, no elapsed-duration or actual calendar claim.",
    "marker-pocket-mat": "Original blank broad bookmark outline left, rectangular source-envelope/pocket outline right. Three unfilled panels prompt child form/source words, first/revised review, now/return. Tab/bookmark/oral/no-marker/adult-directed routes equivalent; shapes/lines are field markers only. Read every word, use broad raised flat outlines or exact no-touch dictation. No sacred symbol, event object, real permission or outcome pictured.",
    "actual-event-record": "Four broad empty local panels: actual event/marking; particular account/sharing; marker/current permission; actual action/K02 elements. Number circles/writing lines carry no data. Exact reading, broad flat/raised panels, scribe/sign/AAC/adult-directed placement preserve fields. Actual event/date/source/family/consent/action/knowledge are blank; expanded local preflight/record needed, no uploads.",
    "check-a-frame-book": "Neutral drawing of square page fully inside wider rectangle frame. Three source panels show Story Day Five EVENT/REPORTED, Story Day Seven MARKING/REPORTED, Story Day Nine LABEL/PLAN. Five complete sources read neutrally with broad source cues or exact no-touch relations; no added attendance, all-family meaning, later label completion, new celebration or actual permission/viewer reply. No geometric skill assessed.",
    "check-b-circle-note": "Neutral undated rectangle page left of round dot label, no people. Timeline fields show Story Day One READING/REPORTED, Story Day Eight QUIET LOOK/PLAN, drawing date UNDATED/OPEN. Five exact sources and learner prompts neutral after release; raised/flat broad cues or no-touch reading/directing adult equivalent. No completed quiet look, family relationship, marker use or motive inferred from objects.",
}


def write() -> None:
    """Write only nine named A4 pairs and full exact alternatives."""
    readings = [
        "# Exact linear readings and tactile/no-print routes",
        "",
        "Semantic SVG metadata and selectable PDF text support access. PDFs are not tagged; no PDF/UA or classroom accessibility certification is claimed. Each exact reading includes every visible text item in SVG/PDF order, including attribution. Read [source script](../MATERIALS.md), [held fresh prompts](../STUDENT-CHECKS.md) or [actual pathway](../ACTUAL-SOURCE-PATHWAY.md) neutrally. Shape/word labels carry meaning; colour supplementary. Raised construction needs local individual review and has not been physically learner-tested. No worked check answers appended.",
        "",
    ]
    ns = {"s": "http://www.w3.org/2000/svg"}
    for stem, fn in PAGES.items():
        content = fn()
        svg, pdf = HERE / f"{stem}.svg", HERE / f"{stem}.pdf"
        svg.write_text(content, encoding="utf-8")
        with pdf.open("wb") as output:
            subprocess.run(
                ["rsvg-convert", "--format", "pdf", str(svg)],
                check=True,
                stdout=output,
                env=dict(os.environ, SOURCE_DATE_EPOCH="1780000000"),
            )
        top = ET.fromstring(content)
        exact = " ".join("".join(n.itertext()) for n in top.findall(".//s:text", ns))
        readings += [
            f"## {stem}.svg / {stem}.pdf",
            "",
            "**Exact visible text:** " + exact,
            "",
            "**Illustration and tactile/no-print:** " + TACTILE[stem],
            "",
        ]
    (HERE / "TEXT-ALTERNATIVES.md").write_text("\n".join(readings), encoding="utf-8")


def check_reproduction() -> None:
    """Generate only in a temporary directory; preserve every pack file."""
    global HERE
    original = HERE
    pack = HERE.parent

    def state():
        return {
            p.relative_to(pack).as_posix(): (
                hashlib.sha256(p.read_bytes()).hexdigest(),
                p.stat().st_size,
                p.stat().st_mtime_ns,
            )
            for p in pack.rglob("*")
            if p.is_file()
        }

    before = state()
    names = [f"{stem}.{ext}" for stem in PAGES for ext in ("svg", "pdf")] + [
        "TEXT-ALTERNATIVES.md"
    ]
    try:
        with tempfile.TemporaryDirectory(
            prefix="subjectnest-hass37-repro-"
        ) as directory:
            HERE = Path(directory)
            write()
            for name in names:
                if (HERE / name).read_bytes() != (original / name).read_bytes():
                    raise AssertionError("reproduction drift: " + name)
    finally:
        HERE = original
    if before != state():
        raise AssertionError("reproduction changed pack bytes/size/mtime")
    print(
        "PASS: 19/19 print artifacts reproduced in temporary storage; all pack bytes, sizes and modification times preserved"
    )


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument(
        "--regenerate",
        action="store_true",
        help="Deliberately rewrite only named print assets/alternatives",
    )
    group.add_argument(
        "--check-reproduction",
        action="store_true",
        help="Generate in temporary storage and compare; pack stays byte/mtime unchanged",
    )
    args = parser.parse_args()
    if args.check_reproduction:
        check_reproduction()
    else:
        write()
        print("Generated nine original A4 SVG/PDF pairs and exact alternatives")
