#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Rebuild six original, searchable A4 SVG/PDF classroom aids."""

from __future__ import annotations

import argparse
import os
import subprocess
from pathlib import Path
from xml.sax.saxutils import escape

HERE = Path(__file__).resolve().parent
INK = "#192632"
BLUE = "#155e75"
MIST = "#eaf3f6"
GREY = "#4b5d68"


def txt(x: int, y: int, value: str, size: int = 18, bold: bool = False,
        colour: str = INK) -> str:
    weight = 700 if bold else 400
    return (f'<text x="{x}" y="{y}" fill="{colour}" font-family="DejaVu Sans, sans-serif" '
            f'font-size="{size}" font-weight="{weight}">{escape(value)}</text>')


def rect(x: int, y: int, width: int, height: int, fill: str = "white",
         stroke: str = BLUE) -> str:
    return (f'<rect x="{x}" y="{y}" width="{width}" height="{height}" '
            f'rx="11" fill="{fill}" stroke="{stroke}" stroke-width="2"/>')


def line(x1: int, y1: int, x2: int, y2: int, colour: str = GREY) -> str:
    return f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="{colour}" stroke-width="2"/>'


def page(title: str, desc: str, content: list[str], footer: str) -> str:
    head = [('<svg xmlns="http://www.w3.org/2000/svg" width="210mm" height="297mm" '
             'viewBox="0 0 794 1123" role="img">'),
            f'<title>{escape(title)}</title>', f'<desc>{escape(desc)}</desc>',
            '<rect width="794" height="1123" fill="white"/>',
            rect(34, 26, 726, 99, MIST),
            txt(53, 64, "SUBJECTNEST / YEAR 9 / OPTIONAL", 16, True, BLUE),
            txt(53, 102, title, 27, True)]
    foot = [line(36, 1072, 758, 1072), txt(40, 1099, footer, 13, False, GREY), '</svg>']
    return "\n".join(head + content + foot) + "\n"


def card(x: int, y: int, width: int, height: int, number: str, heading: str,
         lines: tuple[str, ...]) -> list[str]:
    out = [rect(x, y, width, height), rect(x + 12, y + 12, width - 24, 48, MIST),
           txt(x + 27, y + 45, f"{number}  {heading}", 18, True, BLUE)]
    for i, phrase in enumerate(lines):
        out.append(txt(x + 27, y + 93 + 36 * i, phrase, 17))
    return out


def four_cards(title: str, desc: str, entries: tuple[tuple[str, tuple[str, ...]], ...],
               footer: str) -> str:
    out = [txt(40, 164, "Use the labels in order; each box can be read alone.", 19, True),
           txt(40, 197, "Point, write, direct a partner or speak/AAC your choice.", 16)]
    for idx, (heading, phrases) in enumerate(entries, 1):
        out.extend(card(45, 222 + (idx - 1) * 204, 704, 184, str(idx), heading, phrases))
    return page(title, desc, out, footer)


def science() -> str:
    entries = (
        ("MODEL / SOURCE", ("What was drawn, stated or observed?", "Label invented values and the system boundary.")),
        ("CLAIM", ("Write the exact claim before deciding.", "Does it report, predict or generalise?")),
        ("TEST / CONTRAST", ("What variable, control or comparison matters?", "Keep an anomaly visible until checked.")),
        ("LIMIT / NEXT STEP", ("Name what the source cannot establish.", "Choose one repeat or measurement to seek.")),
    )
    return four_cards("Science inquiry mat", "Four numbered boxes in a vertical list: model or source, claim, test or contrast, limit or next step. Every box is fully transcribed in the text alternative.", entries, "Original paper inquiry scaffold · text/tactile version available")


def hass() -> str:
    entries = (
        ("ORIGIN + STATUS", ("Who made this, when and why?", "Simulation is not an archive or real tally.")),
        ("EXACT DETAIL", ("Copy a number or a short source phrase.", "Check units and who is represented.")),
        ("SECOND CRITERION", ("Compare access, evidence, cost or viewpoint.", "A quick answer may omit a group or condition.")),
        ("BOUNDED DECISION", ("Say what follows from the supplied detail.", "Name one source you still need.")),
    )
    return four_cards("HASS source and choice grid", "Four numbered source-decision cards: origin and status, exact detail, second criterion, bounded decision. A tactile route uses four labelled cards in order.", entries, "Original source-audit aid · no real civic result depicted")


def hpe() -> str:
    entries = (
        ("OFFER CHOICE", ("Ask which role or route works for the person.", "Do not assign a fixed ability label.")),
        ("ASK + HEAR NO", ("Use a specific request before touch or sharing.", "Accept refusal; choose a waiting/other route.")),
        ("TRY OR DIRECT", ("Use safe movement only with local approval.", "Record who actually moved or performed.")),
        ("CHECK FAIRNESS", ("What did each willing person get to do?", "A paper plan is not observed participation.")),
    )
    return four_cards("HPE choice and participation", "Four vertical numbered cards: offer choice, ask and hear no, try or direct, check fairness. Words carry the full order without relying on colour.", entries, "Original access/fairness aid · no personal disclosure needed")


def technology() -> str:
    entries = (
        ("USER + PURPOSE", ("What does the imaginary user need?", "Which data are actually required?")),
        ("RULE / CRITERIA", ("Write a condition that can be tested.", "Include a counterexample or edge case.")),
        ("TEST + RECORD", ("What did the paper/code model output?", "Separate observed from predicted output.")),
        ("REVISE + LIMIT", ("Change one design factor and test again.", "Paper success is not real-world approval.")),
    )
    return four_cards("Technology decision mat", "Four numbered cards: user and purpose, rule or criteria, test and record, revise and limit. Includes data-minimisation and testing reminders.", entries, "Original offline design scaffold · no personal data")


def arts() -> str:
    entries = (
        ("INTENTION", ("What might an audience notice or infer?", "Name the fictional source or original idea.")),
        ("ELEMENT CHOICE", ("Choose pause, line, crop, sound or focus.", "Describe its position in words.")),
        ("MAKE / REHEARSE", ("Record the actual work, not only a plan.", "Performance is marked only if observed.")),
        ("FEEDBACK / EDIT", ("Quote one specific observation or prompt.", "Show the changed line, count or frame.")),
    )
    return four_cards("Arts process page", "Four numbered vertical cards: intention, element choice, make or rehearse, feedback or edit. All words and reading order are in the text alternative.", entries, "Original five-form process aid · private audience is valid")


def music() -> str:
    out = [txt(40, 161, "Eight equal boxes. A rest is a deliberate silence.", 19, True),
           txt(40, 195, "One box = one count; read left to right.", 16)]
    width, start = 82, 51
    for i, label in enumerate(("TAP", "REST", "TAP", "TAP", "REST", "TAP", "REST", "TAP"), 1):
        x = start + (i - 1) * 87
        out += [rect(x, 263, width, 93, MIST if label == "REST" else "white"),
                txt(x + 15, 300, str(i), 19, True, BLUE), txt(x + 10, 337, label, 14, True)]
    out += [txt(40, 425, "Original score: TAP / REST / TAP / TAP / REST / TAP / REST / TAP", 16),
            txt(40, 476, "Your first layer: mark exactly eight boxes.", 18, True, BLUE)]
    for row, y in enumerate((526, 659), 1):
        out.append(txt(41, y - 15, f"Layer {row}", 16, True))
        for i in range(8):
            out.append(rect(start + i * 87, y, width, 93))
    out += [rect(42, 802, 708, 201), txt(61, 843, "Score check", 18, True, BLUE),
            txt(61, 884, "Which boxes rest? __________________________", 17),
            txt(61, 925, "Where does layer 2 enter? __________________", 17),
            txt(61, 966, "Was sound actually made or only planned? ____", 17)]
    return page("Eight-count music score", "Eight labelled TAP or REST boxes, followed by two blank eight-box layer rows and three questions. The complete linear score and tactile placement route are in the text alternative.", out, "Original quiet/silent score · no sound required")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--write", action="store_true")
    args = parser.parse_args()
    if not args.write:
        parser.error("pass --write to regenerate six SVG/PDF pairs")
    env = {**os.environ, "SOURCE_DATE_EPOCH": "1790640000"}
    for stem, build in (("science-inquiry", science), ("hass-source-grid", hass),
                        ("hpe-participation", hpe), ("technology-decision", technology),
                        ("arts-process", arts), ("eight-count-score", music)):
        svg = HERE / f"{stem}.svg"
        pdf = HERE / f"{stem}.pdf"
        svg.write_text(build(), encoding="utf-8")
        subprocess.run(["rsvg-convert", "--format", "pdf", "--output", str(pdf), str(svg)],
                       check=True, env=env)
        print(f"generated {svg.name} + {pdf.name}")


if __name__ == "__main__":
    main()
