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

import argparse
import os
import subprocess
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 26)
    if end > y + h + 5:
        raise ValueError(f"{label}: text exceeds panel")
    return out + body


def tag(kind: str, x: int, y: int, scale: float = 1) -> str:
    """Illustrate fictional archive marks without implying actual material or use."""
    if kind == "main":
        out = box(x, y, 130 * scale, 265 * scale)
        out += circle(x + 65 * scale, y + 42 * scale, 15 * scale)
        for offset in (100, 155, 210):
            out += path(
                f"M{x + 28 * scale} {y + offset * scale} L{x + 102 * scale} {y + offset * scale}",
                10,
            )
    elif kind == "larch":
        out = box(x, y, 255 * scale, 145 * scale)
        for offset in (55, 123):
            out += circle(x + offset * scale, y + 74 * scale, 22 * scale)
        out += f'<polygon points="{x+175*scale},{y+50*scale} {x+222*scale},{y+50*scale} {x+222*scale},{y+97*scale} {x+175*scale},{y+97*scale}" fill="{INK}"/>'
    else:
        out = path(
            f"M{x} {y+30*scale} L{x+45*scale} {y} L{x+180*scale} {y} L{x+180*scale} {y+140*scale} L{x+45*scale} {y+140*scale} L{x} {y+110*scale} Z"
        )
        out += f'<polygon points="{x+52*scale},{y+72*scale} {x+81*scale},{y+26*scale} {x+110*scale},{y+72*scale}" fill="white" stroke="{INK}" stroke-width="3"/>'
        for offset in (59, 117):
            out += circle(x + offset * scale, y + 104 * scale, 11 * scale, INK)
    return out


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 · W27–28", 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 fictional material · SubjectNest by NeuroForgeIO Pty Ltd",
                12,
            ),
            text(
                42,
                1085,
                "© 2026 · CC BY 4.0 · exact text/tactile alternatives in pack",
                12,
            ),
            "</svg>",
            "",
        ]
    )


def archive() -> str:
    b = text(48, 202, "SOURCE A · DRAWING", 20, True) + box(48, 228, 698, 350, FILLS[0])
    b += tag("main", 332, 266)
    b += block(
        "SOURCE B · CATALOGUE",
        "“Paper Marker Box: narrow strip drawing, three dark bars, one round hole. Use not recorded.”",
        611,
        184,
        FILLS[1],
    )
    b += block(
        "ARCHIVE NOTE",
        "The collection name does not identify actual material. A drawing is not an object test or witnessed use.",
        824,
        165,
        "white",
        19,
    )
    return page(
        "Paper Marker Box · drawing and label",
        "Three drawn bars and one round hole. All sources are invented.",
        b,
    )


def note() -> str:
    b = block(
        "SOURCE C · MIRA'S MAKER NOTE",
        "“I drew the three bars so I would remember which tray to return my model to.”",
        192,
        218,
        FILLS[1],
        24,
    )
    b += box(48, 447, 698, 360, "white") + tag("main", 139, 488, 0.95)
    b += box(398, 569, 247, 76, FILLS[0]) + path(
        "M422 602 L620 602 M454 551 L454 576 M596 551 L596 576"
    )
    b += text(403, 687, "PRETEND TRAY", 18, True)
    b += block(
        "WHOSE ACCOUNT?",
        "Mira reports Mira's purpose. The tray icon illustrates her note; it is not a recorded return or other-user history.",
        845,
        145,
        FILLS[2],
        19,
    )
    return page(
        "Mira · one maker account",
        "A personal purpose remains attributed to its speaker.",
        b,
    )


def draft() -> str:
    b = block(
        "SOURCE D · LATER DRAFT CAPTION",
        "“Mira said this was a bookmark that everyone used.”",
        192,
        235,
        FILLS[2],
        26,
    )
    b += box(48, 465, 698, 330, "white")
    b += path("M158 563 L158 720 L266 689 L374 720 L374 563 Z") + circle(580, 628, 70)
    b += text(557, 648, "?", 55, True)
    b += text(103, 757, "DRAFT WORDS NEED A SOURCE CHECK", 20, True)
    b += block(
        "COMPARE BEFORE REPORTING",
        "What does this draft print? Which earlier source supports each clause? A printed draft alone is not a witnessed history.",
        834,
        156,
        FILLS[4],
        19,
    )
    return page(
        "A later draft · check its words",
        "This caption is a fictional draft to examine, not a new use record.",
        b,
    )


def correction() -> str:
    b = block(
        "SOURCE E · MIRA'S CORRECTION",
        "“I did not say it was a bookmark. I said I drew the bars to remember my tray. I do not know how anyone else used it.”",
        192,
        286,
        FILLS[3],
        23,
    )
    b += box(48, 516, 698, 258, "white")
    b += circle(169, 636, 67) + path("M135 638 L159 663 L204 611", 7)
    b += circle(598, 636, 67) + text(578, 655, "?", 50, True)
    b += text(85, 747, "MIRA SAYS", 19, True) + text(486, 747, "OTHER USERS?", 19, True)
    b += block(
        "CORRECTION HAS A LIMIT",
        "Correct what may be attributed to Mira. Other people's uses stay open. The invented correction grants no real display permission.",
        814,
        176,
        FILLS[1],
        19,
    )
    return page(
        "Mira · correction and open use",
        "A corrected report does not settle every possible later use.",
        b,
    )


def scope() -> str:
    b = ""
    rows = [
        (
            "A / B · DRAWING AND CATALOGUE",
            "Three bars; one hole; use not recorded.",
            FILLS[0],
        ),
        ("C · MIRA SAYS", "Mira drew the bars as her own tray reminder.", FILLS[1]),
        (
            "D / E · DRAFT AND CORRECTION",
            "Check the draft against Mira’s actual note and correction.",
            FILLS[3],
        ),
        (
            "UNKNOWN · OTHER USERS",
            "A blank use record is not a claim that nobody used it.",
            FILLS[4],
        ),
    ]
    for i, (label, value, fill) in enumerate(rows):
        y = 191 + i * 198
        b += block(label, value, y, 172, fill, 20)
        b += circle(708, y + 33, 14) + path(f"M687 {y+135} L713 {y+135}", 3)
    return page(
        "Source scope · four separate trays",
        "Place a fact beside its source; leave a gap visible.",
        b,
    )


def revision() -> str:
    b = ""
    rows = [
        ("1 · MARKS AND SOURCE", "What is shown in the drawing?"),
        ("2 · SPEAKER AND SOURCE", "What does Mira say was her purpose?"),
        ("3 · EARLIER / LATER / AFTER", "Which words changed after the correction?"),
        ("4 · STILL UNKNOWN", "What respectful question might a future source answer?"),
    ]
    for i, (label, value) in enumerate(rows):
        y = 191 + i * 198
        b += block(label, value, y, 173, FILLS[i], 19)
        b += path(f"M70 {y+133} L714 {y+133}", 2)
        b += circle(708, y + 32, 13)
    return page(
        "Caption revision · make a faithful label",
        "Point, dictate, sign, use AAC or direct a scribe. Blank lines are optional.",
        b,
    )


def check_a() -> str:
    b = text(48, 195, "SOURCE 1 · DRAWING", 18, True) + tag("larch", 271, 218, 0.9)
    b += block(
        "SOURCE 1 · EXACT DESCRIPTION",
        "A broad rectangular card outline has two outlined circles and one dark square. It is a drawing, not an actual object or test.",
        376,
        143,
        FILLS[0],
        18,
    )
    b += block(
        "SOURCE 2 · CATALOGUE",
        "“Larch Box: broad card drawing, two outlined circles, one dark square. Use not recorded.”",
        534,
        145,
        FILLS[1],
        19,
    )
    b += block(
        "SOURCE 3 · TAVI'S NOTE",
        "“I drew the square to remember my shelf.”",
        695,
        120,
        FILLS[2],
        21,
    )
    b += block(
        "SOURCE 4 · LATER DRAFT CAPTION",
        "“Tavi said it was a ticket that everyone used.”",
        831,
        154,
        FILLS[3],
        21,
    )
    return page(
        "Fresh Check A · Larch Box",
        "Invented sources. Read the four prompts in the learner copy neutrally.",
        b,
    )


def check_b() -> str:
    b = text(48, 193, "SOURCE 1 · DRAWING", 18, True) + tag("cove", 310, 207, 0.82)
    b += block(
        "SOURCE 1 · EXACT DESCRIPTION",
        "A narrow tag outline has one triangle and two dark dots. It is a drawing, not an actual object or test.",
        342,
        122,
        FILLS[0],
        18,
    )
    b += block(
        "SOURCE 2 · CATALOGUE",
        "“Cove Shelf: tag drawing, one triangle, two dark dots. Use not recorded.”",
        478,
        124,
        FILLS[1],
        19,
    )
    b += block(
        "SOURCE 3 · ONA'S NOTE",
        "“I drew the triangle to remember my box.”",
        616,
        108,
        FILLS[2],
        20,
    )
    b += block(
        "SOURCE 4 · LATER DRAFT CAPTION",
        "“Ona said it was a badge that everyone wore.”",
        738,
        110,
        FILLS[3],
        20,
    )
    b += block(
        "SOURCE 5 · ONA'S CORRECTION",
        "“I did not say it was a badge. I said I drew the triangle to remember my box. I do not know how anyone else used it.”",
        862,
        150,
        FILLS[4],
        18,
    )
    return page(
        "Fresh Check B · Cove Shelf",
        "Invented sources. Read the four prompts in the learner copy neutrally.",
        b,
    )


PAGES = {
    "paper-marker-archive-card": archive,
    "mira-maker-note": note,
    "draft-caption": draft,
    "mira-correction": correction,
    "source-scope-mat": scope,
    "caption-revision-mat": revision,
    "check-a-larch-box": check_a,
    "check-b-cove-shelf": check_b,
}
TACTILE = {
    "paper-marker-archive-card": "Place three separate raised short bars below one round ring on a narrow raised strip outline. Read A/B exactly; do not identify actual material or use. Make broad, fixed pieces; touch is optional.",
    "mira-maker-note": "Use a narrow strip, three raised bars, one ring and a broad tray outline. Keep a separate MIRA name card beside C. The tray represents the note only, not an observed return.",
    "draft-caption": "Place a broad D card beside a bookmark-outline illustration and a question-mark tile. Read D exactly. Compare source wording using separate A/B/C cards.",
    "mira-correction": "Use E beside a MIRA card and a separate blank OTHER USERS tray. A tick and question mark depict correction and a remaining question; they do not add evidence.",
    "source-scope-mat": "Make four broad numbered trays matching the four printed headings. Keep drawing/label, Mira account, draft/correction and unknown cards separate. Do not rely on colour.",
    "caption-revision-mat": "Use four broad numbered strips with the exact questions and optional blank answer places. A learner may direct an adult, dictate, sign or use established AAC instead of moving or writing.",
    "check-a-larch-box": "Make a broad rectangular raised outline with exactly two outlined rings and one filled raised square. Keep four numbered source cards separate. Read the exact source wording and learner prompts neutrally; give no answer labels or prior worked example.",
    "check-b-cove-shelf": "Make a narrow tag outline with exactly one raised triangle and two filled raised dot disks. Keep five numbered source cards separate. Read exact wording and learner prompts neutrally; give no answer labels or prior worked example.",
}


def write() -> None:
    """Write only this pack's named aids and alternatives deterministically."""
    readings = [
        "# Exact linear readings and tactile/no-print routes",
        "",
        "SVG title/description and selectable PDF text support access; PDFs are not tagged and no PDF/UA or classroom accessibility certification is claimed. Each exact linear reading below transcribes **all visible page text in reading order**, including attribution. Read the [source script](../MATERIALS.md) or [fresh learner prompts](../STUDENT-CHECKS.md) neutrally. Colour is never the only cue. Tactile construction is an offered route requiring individual local review, not tested access evidence. No worked assessment answers are added.",
        "",
    ]
    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 out:
            subprocess.run(
                ["rsvg-convert", "--format", "pdf", str(svg)],
                check=True,
                stdout=out,
                env=dict(os.environ, SOURCE_DATE_EPOCH="1780000000"),
            )
        top = ET.fromstring(content)
        exact = " ".join(
            "".join(node.itertext()) for node 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 main() -> None:
    """Expose deliberate regeneration separately from the read-only verifier."""
    parser = argparse.ArgumentParser()
    parser.add_argument("--regenerate", action="store_true", required=True)
    parser.parse_args()
    write()
    print("Generated eight original A4 SVG/PDF pairs and exact alternatives")


if __name__ == "__main__":
    main()
