# SPDX-License-Identifier: Apache-2.0
"""Reproduce nine 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 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 · W29–30", 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>",
            "",
        ]
    )


def room_map(
    kind: str,
    x: int,
    y: int,
    w: int = 620,
    h: int = 280,
    later: bool = False,
    name: str = "map",
) -> str:
    """Draw a source-keyed pictorial room, never a real travel route."""
    left, right, cy = x + w * 0.25, x + w * 0.75, y + h * 0.42
    movable_x = right if later else left
    if kind == "studio":
        out = circle(left, cy, h * 0.19)
        out += box(right - h * 0.18, cy - h * 0.18, h * 0.36, h * 0.36)
        sy = y + h * 0.80
        out += f'<polygon points="{movable_x},{sy-h*.10} {movable_x-h*.10},{sy+h*.08} {movable_x+h*.10},{sy+h*.08}" fill="white" stroke="{INK}" stroke-width="3"/>'
    elif kind == "moss":
        out = circle(left, cy, h * 0.17)
        out += box(right - h * 0.24, cy - h * 0.14, h * 0.48, h * 0.28)
        sy = y + h * 0.79
        out += f'<polygon points="{movable_x},{sy-h*.11} {movable_x-h*.11},{sy} {movable_x},{sy+h*.11} {movable_x+h*.11},{sy}" fill="white" stroke="{INK}" stroke-width="3"/>'
    else:
        # The fixed shelf is left, oval mat right. Earlier tub is by the mat.
        out = box(left - h * 0.23, cy - h * 0.18, h * 0.46, h * 0.36)
        out += f'<ellipse cx="{right}" cy="{cy}" rx="{h*.24}" ry="{h*.16}" fill="white" stroke="{INK}" stroke-width="3"/>'
        movable_x = left if later else right
        sy = y + h * 0.79
        out += f'<polygon points="{movable_x},{sy-h*.12} {movable_x+h*.115},{sy-h*.025} {movable_x+h*.07},{sy+h*.11} {movable_x-h*.07},{sy+h*.11} {movable_x-h*.115},{sy-h*.025}" fill="white" stroke="{INK}" stroke-width="3"/>'
    return (
        box(x, y, w, h, FILLS[0])
        + f'<g id="{name}" data-map-kind="{kind}" data-basket-position="{"later" if later else "earlier"}">'
        + out
        + "</g>"
    )


def earlier() -> str:
    b = text(48, 198, "SOURCE A · BEFORE THE MOVE", 20, True)
    b += room_map("studio", 68, 232, 658, 330, name="source-a")
    b += text(68, 610, "KEY: circle = rug; square = table;", 20)
    b += text(68, 642, "triangle = basket.", 20)
    b += block(
        "A · EXACT SOURCE WORDS",
        "“Paper Studio: circle rug on the left; square making table on the right; triangle basket beside the rug. This is the earlier picture map.”",
        685,
        236,
        FILLS[1],
        21,
    )
    b += text(68, 976, "A picture source is not today’s actual room.", 18)
    return page(
        "Paper Studio · earlier picture map",
        "An invented keyed room picture; no real route or material test.",
        b,
    )


def keeper() -> str:
    b = block(
        "SOURCE B · REMY'S LATER NOTE",
        "“Later I moved the basket beside the making table. I have not written down why.”",
        190,
        226,
        FILLS[1],
        24,
    )
    b += text(66, 466, "EARLIER A", 18, True) + text(
        419, 466, "LATER B REPORT", 18, True
    )
    b += room_map("studio", 55, 487, 329, 230, name="earlier-model")
    b += room_map("studio", 410, 487, 329, 230, True, "later-model")
    b += block(
        "KEEP THE SOURCE LIMIT",
        "Remy reports a move. The pictures here illustrate A and B. The reason, other users and actual room conditions are not recorded.",
        774,
        213,
        FILLS[2],
        20,
    )
    return page(
        "Remy · a later move report",
        "Keep the earlier picture; make a separate reported-later model.",
        b,
    )


def undated() -> str:
    b = text(48, 197, "SOURCE C · TIME NOT RECORDED", 20, True)
    b += room_map("studio", 68, 232, 658, 330, name="source-c")
    b += text(68, 611, "KEY: circle = rug; square = table;", 20)
    b += text(68, 643, "triangle = basket. Maker and time unknown.", 20)
    b += block(
        "C · EXACT SOURCE WORDS",
        "“Paper Studio: circle rug on the left; square making table on the right; triangle basket beside the rug. Time not recorded.”",
        685,
        220,
        FILLS[3],
        21,
    )
    b += text(68, 967, "Ask when it was made; do not invent a date.", 18)
    return page(
        "An undated picture copy",
        "Same depicted basket place as A; the source time is not supplied.",
        b,
    )


def visitor() -> str:
    b = block(
        "SOURCE D · TARA'S LATER NOTE",
        "“After the move, I found the basket beside the making table. I like it there because I can sort my picture cards.”",
        190,
        258,
        FILLS[2],
        23,
    )
    b += room_map("studio", 97, 483, 600, 258, True, "tara-reported-place")
    b += block(
        "ONE PERSON SAYS",
        "Tara gives Tara’s encounter and reason. Remy’s move reason and every other visitor’s views remain open.",
        789,
        194,
        FILLS[4],
        21,
    )
    return page(
        "Tara · a place matters to one person",
        "A named account is not everybody’s reason or the keeper’s cause.",
        b,
    )


def pockets() -> str:
    b = ""
    rows = [
        ("1 · EARLIER A", "Place the earlier picture here.", FILLS[0]),
        (
            "2 · LATER B / AFTER D",
            "Place the reported move and named later account here.",
            FILLS[1],
        ),
        (
            "3 · C TIME NOT RECORDED",
            "Keep this pocket beside the known source trail.",
            FILLS[3],
        ),
        (
            "4 · OPEN QUESTIONS",
            "When was C made? Why did Remy move it? Other users?",
            FILLS[4],
        ),
    ]
    for i, (label, value, fill) in enumerate(rows):
        y = 190 + i * 198
        b += block(label, value, y, 173, fill, 20)
        b += circle(709, y + 32, 12) + path(f"M70 {y+133} L715 {y+133}", 2)
    return page(
        "Time pockets · keep each source",
        "Broad trays or an adult-directed arrangement carry the same goal.",
        b,
    )


def update() -> str:
    b = block(
        "SOURCE E · HELPER'S PROPOSED MAP",
        "“I plan to show the basket beside the table on a new map, using Remy’s and Tara’s notes. I will ask when the undated copy was made.”",
        190,
        218,
        FILLS[3],
        20,
    )
    rows = [
        ("MY PLANNED MAP", "Place a keyed feature here; add its source tab."),
        ("WHAT I ACTUALLY MADE", "Record child map/model work, who helped and when."),
        ("STILL OPEN", "A map plan is not a physical room change. What source next?"),
    ]
    for i, (label, value) in enumerate(rows):
        y = 433 + i * 189
        b += block(label, value, y, 163, FILLS[i], 19)
        b += circle(709, y + 32, 12) + path(f"M70 {y+129} L715 {y+129}", 2)
    return page(
        "Map update · plan, make and ask",
        "Child-made paper maps stay distinct from actual room conditions.",
        b,
    )


def actual_record() -> str:
    b = ""
    rows = [
        (
            "1 · APPROVED PLACE / CONNECTION",
            "Place/source ID. Familiar connection or pass. Feature(s).",
        ),
        (
            "2 · ACTUAL OBSERVATION",
            "Date/time/view. Seen, described or approved image. Child words/help.",
        ),
        (
            "3 · ONE WILLING PERSON / MEANING",
            "Source/date/exact words/correction. Permission/withdrawal. Whose reason?",
        ),
        (
            "4 · LOCAL CARE / EVIDENCE GAPS",
            "Care procedure/source/date. Child explanation. Approval. Action/who if done. Open fields/next step.",
        ),
    ]
    for i, (label, value) in enumerate(rows):
        y = 190 + i * 198
        b += block(label, value, y, 173, FILLS[i], 18)
        b += path(f"M70 {y+137} L715 {y+137}", 2) + circle(709, y + 31, 12)
    return page(
        "Optional actual-place record",
        "Blank local template only. Educator preflight and actual sources are required.",
        b,
    )


def fresh_diagrams(kind: str) -> str:
    return (
        text(55, 189, "SOURCE 1 · EARLIER", 17, True)
        + text(417, 189, "SOURCE 3 · UNDATED", 17, True)
        + room_map(kind, 55, 206, 329, 132, name="check-earlier")
        + room_map(kind, 410, 206, 329, 132, name="check-undated")
    )


def check_a() -> str:
    b = fresh_diagrams("moss")
    b += text(55, 367, "KEY: circle = cushion; rectangle = table; diamond = box.", 17)
    b += block(
        "SOURCE 1 · EARLIER MAP",
        "“Moss Room: circle cushion on the left; rectangle table on the right; diamond box beside the cushion. This is the earlier picture map.”",
        391,
        138,
        FILLS[0],
        17,
    )
    b += block(
        "SOURCE 2 · KEEPER FENN",
        "“Later I moved the box beside the table. I did not record why.”",
        543,
        104,
        FILLS[1],
        18,
    )
    b += block(
        "SOURCE 3 · UNDATED COPY",
        "“Moss Room: circle cushion on the left; rectangle table on the right; diamond box beside the cushion. Time not recorded.”",
        661,
        137,
        FILLS[3],
        17,
    )
    b += block(
        "SOURCE 4 · ZIA'S NOTE",
        "“After the move, I found the box beside the table. I like laying out my paper cards there.”",
        812,
        174,
        FILLS[2],
        19,
    )
    return page(
        "Fresh Check A · Moss Room",
        "Invented sources. Read the four learner prompts neutrally.",
        b,
    )


def check_b() -> str:
    b = fresh_diagrams("harbour")
    b += text(55, 367, "KEY: rectangle = shelf; oval = mat; pentagon = tub.", 17)
    b += block(
        "SOURCE 1 · EARLIER MAP",
        "“Harbour Room: rectangle shelf on the left; oval mat on the right; pentagon tub beside the mat. This is the earlier picture map.”",
        380,
        126,
        FILLS[0],
        17,
    )
    b += block(
        "SOURCE 2 · KEEPER FAYE",
        "“Later I moved the tub beside the shelf. I have not said why.”",
        518,
        100,
        FILLS[1],
        18,
    )
    b += block(
        "SOURCE 3 · UNDATED COPY",
        "“Harbour Room: rectangle shelf on the left; oval mat on the right; pentagon tub beside the mat. Time not recorded.”",
        630,
        125,
        FILLS[3],
        17,
    )
    b += block(
        "SOURCE 4 · RAFI'S NOTE",
        "“After the move, I found the tub beside the shelf. I like putting my pretend leaf cards there.”",
        767,
        126,
        FILLS[2],
        18,
    )
    b += block(
        "SOURCE 5 · HELPER'S PROPOSAL",
        "“I plan a new map with the tub beside the shelf, using the keeper’s and Rafi’s notes.”",
        905,
        122,
        FILLS[4],
        18,
    )
    return page(
        "Fresh Check B · Harbour Room",
        "Invented sources. Read the four learner prompts neutrally.",
        b,
    )


PAGES = {
    "paper-studio-earlier-map": earlier,
    "remy-move-note": keeper,
    "undated-map-copy": undated,
    "tara-place-note": visitor,
    "time-pockets-mat": pockets,
    "map-update-mat": update,
    "real-place-record": actual_record,
    "check-a-moss-room": check_a,
    "check-b-harbour-room": check_b,
}
TACTILE = {
    "paper-studio-earlier-map": "A: room outline with circle rug left, square table right, triangle basket just below/beside the rug. Make three large raised features with an exact named key. BEFORE THE MOVE is a source label, not a real date or location. Read all words exactly; child may direct adult placement.",
    "remy-move-note": "Two separate small room diagrams: earlier A has circle rug left, square table right and triangle basket beside rug; later B illustration has basket beside table. Use two broad bases, fixed circle/square and one broad triangle each. Keep B as a reported source; no actual move is demonstrated.",
    "undated-map-copy": "C: room outline with circle rug left, square table right and triangle basket beside rug. Make the same raised diagram as A, but attach TIME NOT RECORDED and keep maker/date fields blank. No time is added to this source; adult-directed exact reading is equivalent.",
    "tara-place-note": "One diagram illustrates Tara’s reported later position: circle rug left, square table right, triangle basket beside table. Make fixed large outlines and attach TARA source card beside this model. Its position and personal meaning come from the exact note; actual room conditions are not supplied.",
    "time-pockets-mat": "Four broad numbered trays with the exact headings and question words. Raised circles and lines are decorative markers, not additional objects or data. Keep the undated pocket beside the known trail. Read all words; learner may direct adult placement without touching the tray.",
    "map-update-mat": "Three broad numbered fields below E’s exact proposal: planned map, actual child map/model making, and open questions. Raised circles/lines are decorative blank-field markers. Read the full wording and let the child direct a scribe or placement; actual physical-room action is recorded separately only if it occurred.",
    "real-place-record": "Four broad numbered fields with all printed local-record prompts. Raised circles/lines mark writable spaces and add no facts. Read the exact prompts one at a time; use a scribe/AAC/sign/large print as chosen. No actual place, person, date or answer is pre-filled. Follow the full approved pathway before use.",
    "check-a-moss-room": "Two separate room pictures, Source 1 earlier and Source 3 undated. Both have circle cushion left, rectangle table right, diamond box beside cushion. Make two broad raised room bases and exact keyed features, with separate numbered source cards. Read all source words/prompts neutrally; no worked answer or later-model hint is supplied.",
    "check-b-harbour-room": "Two separate room pictures, Source 1 earlier and Source 3 undated. Both have rectangle shelf left, oval mat right, pentagon tub beside mat. Make two broad raised room bases and exact keyed features, with five separate numbered source cards. Read exact source words/prompts neutrally; no worked answer or planned-model hint is supplied.",
}


def write() -> None:
    """Write only nine named print pairs and full exact alternatives."""
    readings = [
        "# Exact linear readings and tactile/no-print routes",
        "",
        "SVG semantic metadata and selectable PDF text support access. PDFs are not tagged; no PDF/UA or classroom accessibility certification is claimed. Each exact reading transcribes all visible page text in reading order, including attribution. Read the [source script](../MATERIALS.md), [fresh prompts](../STUDENT-CHECKS.md) or [actual-place route](../REAL-PLACE-PATHWAY.md) neutrally as appropriate. Colour is never the only cue. Tactile construction needs local individual review; it has not been physically learner-tested. No worked check 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 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 main() -> None:
    """Expose deliberate regeneration separately from read-only checks."""
    parser = argparse.ArgumentParser()
    parser.add_argument("--regenerate", action="store_true", required=True)
    parser.parse_args()
    write()
    print("Generated nine original A4 SVG/PDF pairs and exact alternatives")


if __name__ == "__main__":
    main()
