# SPDX-License-Identifier: Apache-2.0
"""Deterministically render eight original Foundation Visual Arts W7-8 aids."""

from __future__ import annotations

import subprocess
from io import BytesIO
from pathlib import Path
from xml.sax.saxutils import escape

from pypdf import PdfReader, PdfWriter

HERE = Path(__file__).resolve().parent
INK = "#26374c"
BLUE = "#eaf2fa"
SAND = "#fff1df"
MINT = "#eaf5e9"
GREY = "#f3f5f7"


def box(x: int, y: int, w: int, h: int, fill: str = "white", *, radius: int = 10, **data: str) -> str:
    attrs = " ".join(f'{key.replace("_", "-")}="{escape(value)}"' for key, value in data.items())
    return f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{radius}" fill="{fill}" stroke="{INK}" stroke-width="2" {attrs}/>'


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


def line(x1: int, y1: int, x2: int, y2: int, width: int = 2) -> str:
    return f'<path d="M{x1} {y1} L{x2} {y2}" fill="none" stroke="{INK}" stroke-width="{width}"/>'


def arch(cx: int, y: int, size: int = 60) -> str:
    return f'<path d="M{cx-size//2} {y+size} V{y+size//2} A{size//2} {size//2} 0 0 1 {cx+size//2} {y+size//2} V{y+size}" fill="none" stroke="{INK}" stroke-width="8"/>'


def bar(cx: int, y: int, width: int = 67) -> str:
    return f'<rect x="{cx-width//2}" y="{y+43}" width="{width}" height="14" rx="5" fill="{INK}"/>'


def mark_card(x: int, y: int, title: str, drawing: str, fill: str = "white") -> str:
    return box(x, y, 250, 223, fill, data_mark=title) + word(x + 18, y + 44, title, 19, bold=True) + drawing


def page(title: str, description: str, body: str) -> str:
    return (
        '<svg xmlns="http://www.w3.org/2000/svg" width="210mm" height="297mm" '
        'viewBox="0 0 794 1123" role="img">'
        f'<title>{escape(title)}</title><desc>{escape(description)}</desc>'
        '<rect width="794" height="1123" fill="white"/>'
        f'{word(42, 65, title, 27, bold=True)}'
        f'{word(42, 101, description, 13)}'
        f'{body}{line(42, 1044, 752, 1044)}'
        f'{word(42, 1076, "SubjectNest original Foundation Visual Arts W7-8 · CC BY 4.0", 10)}'
        '</svg>\n'
    )


def unknown_cue() -> str:
    body = word(42, 149, "FICTIONAL PAPER PLANET SHELF", 19, bold=True)
    body += box(42, 184, 710, 563, BLUE, data_source="LOOP-SQUARE UNKNOWN")
    body += box(238, 236, 318, 335, "white")
    body += f'<circle cx="397" cy="335" r="58" fill="none" stroke="{INK}" stroke-width="8"/>'
    body += box(349, 425, 96, 96, "white", radius=0)
    body += word(275, 617, "LOOP above SQUARE", 21, bold=True)
    body += word(61, 704, "Made-up card · no real sign or instruction.", 16)
    body += box(42, 788, 710, 210, SAND)
    body += word(60, 846, "MEANING UNKNOWN", 23, bold=True)
    body += word(60, 895, "What can you see? What would a maker need to tell us?", 16)
    body += word(60, 950, "Do not guess a school safety rule from this card.", 15)
    return page("A card with unknown meaning", "Original loop-over-square mark on a fictional story shelf.", body)


def two_panel_path() -> str:
    body = word(42, 151, "SAME TWO BLANK PANELS · TWO VIEWING PATHS", 17, bold=True)
    body += box(42, 190, 710, 338, BLUE, data_layout="ACROSS")
    body += word(60, 236, "ACROSS", 21, bold=True)
    body += box(62, 268, 308, 209, "white", data_panel="NOTICE")
    body += box(424, 268, 308, 209, "white", data_panel="ASK")
    body += word(80, 318, "NOTICE", 20, bold=True)
    body += word(442, 318, "ASK", 20, bold=True)
    body += word(380, 384, "→", 29, bold=True)
    body += box(42, 557, 710, 437, SAND, data_layout="DOWN")
    body += word(60, 602, "DOWN", 21, bold=True)
    body += box(207, 625, 380, 145, "white", data_panel="NOTICE")
    body += box(207, 811, 380, 145, "white", data_panel="ASK")
    body += word(230, 669, "NOTICE", 20, bold=True)
    body += word(230, 855, "ASK", 20, bold=True)
    body += word(390, 801, "↓", 27, bold=True)
    return page("Across or down?", "Two blank NOTICE and ASK panels shown across, then stacked down.", body)


def sequence_repeat() -> str:
    body = word(42, 148, "A CHANGING STORY AND A RETURNING GROUP", 17, bold=True)
    body += box(42, 188, 710, 348, BLUE, data_structure="STORY CHANGE")
    body += word(60, 231, "STORY CHANGE", 19, bold=True)
    body += box(73, 286, 286, 196, "white")
    body += box(435, 286, 286, 196, "white")
    body += f'<path d="M152 382 H280 V446 H152 Z M142 356 L274 320" fill="none" stroke="{INK}" stroke-width="6"/>'
    body += f'<path d="M514 382 H642 V446 H514 Z M510 374 H646" fill="none" stroke="{INK}" stroke-width="6"/>'
    body += word(96, 328, "OPEN BOX", 17, bold=True)
    body += word(458, 328, "CLOSED BOX", 17, bold=True)
    body += word(376, 396, "→", 27, bold=True)
    body += box(42, 575, 710, 415, MINT, data_structure="REPEAT")
    body += word(60, 620, "REPEAT: ARCH, BAR | ARCH, BAR", 18, bold=True)
    for index, x in enumerate((112, 285, 458, 631)):
        body += arch(x, 705) if index % 2 == 0 else bar(x, 705)
        body += word(x - 31, 831, "ARCH" if index % 2 == 0 else "BAR", 16, bold=True)
    body += line(382, 690, 382, 850, 2)
    body += word(60, 918, "One box changes once; one ordered mark group returns.", 16)
    return page("Change or return?", "Open-to-closed story versus a repeated arch-bar visual group.", body)


def visual_unit() -> str:
    body = word(42, 148, "ONE UNIT: ARCH THEN BAR", 21, bold=True)
    for i, x in enumerate((42, 282, 522)):
        body += box(x, 232, 230, 427, (BLUE, SAND, MINT)[i], data_unit=str(i + 1))
        body += word(x + 18, 282, f"UNIT {i + 1}", 19, bold=True)
        body += arch(x + 66, 363, 65)
        body += bar(x + 165, 363, 65)
        body += word(x + 24, 552, "ARCH", 15, bold=True)
        body += word(x + 145, 552, "BAR", 15, bold=True)
    body += box(42, 710, 710, 288, GREY)
    body += word(60, 771, "The two-mark order returns three times.", 20, bold=True)
    body += word(60, 825, "The spaces BETWEEN whole units are an art choice.", 16)
    body += word(60, 885, "Point to one complete unit, not only the last mark.", 16)
    body += word(60, 945, "Make your own marks on blank paper or by direction.", 15)
    return page("A grouped visual beat", "Three separate arch-then-bar units, each enclosed in a broad box.", body)


def quiet_transfer() -> str:
    body = word(42, 149, "ONE INVENTED OPTION · NO SOUND NEEDED", 17, bold=True)
    steps = (
        ("1 VISUAL MARKS", "ARCH then BAR", BLUE),
        ("2 QUIET CUES", "HAND UP then STILL", SAND),
        ("3 RETURN TO MARKS", "ARCH then BAR", MINT),
    )
    for i, (heading, detail, fill) in enumerate(steps):
        y = 190 + i * 268
        body += box(42, y, 710, 229, fill, data_stage=heading)
        body += word(60, y + 49, heading, 20, bold=True)
        body += word(60, y + 101, detail, 21)
        body += word(60, y + 164, "Choose another safe/no-move route if needed." if i == 1 else "The ordered two-part idea stays visible.", 15)
        if i == 1:
            body += word(555, y + 156, "↑", 37, bold=True)
            body += line(650, y + 144, 698, y + 144, 8)
        else:
            body += arch(575, y + 81, 53)
            body += bar(675, y + 81, 57)
    return page("Visual to quiet and back", "Arch-bar marks map to optional hand-up/still cues, then return.", body)


def art_viewer_record() -> str:
    fields = (
        ("MY IMAGE IDEA / FIRST VIEW", "What should someone notice first?"),
        ("TWO OPTIONS / ONE CHANGE", "What stayed, changed or returned?"),
        ("ACTUAL MAKER / SUPPORT", "Who really marked/moved? Or PLAN ONLY?"),
        ("ACTUAL WILLING VIEWER", "Who truly saw it? Response or NOT SHARED?"),
    )
    body = word(42, 149, "Keep intention, process and audience separate.", 17, bold=True)
    for i, (heading, prompt) in enumerate(fields):
        y = 193 + i * 201
        body += box(42, y, 710, 174, (BLUE, SAND, MINT, GREY)[i], data_field=heading)
        body += word(60, y + 43, heading, 18, bold=True)
        body += word(60, y + 86, prompt, 15)
        body += line(60, y + 143, 734, y + 143, 1)
    return page("My art and viewer record", "Four blank fields for art intention, change, actual maker and viewer.", body)


def check_m() -> str:
    body = word(42, 147, "FRESH CHECK M · FICTIONAL CARD", 17, bold=True)
    body += box(42, 181, 710, 283, BLUE, data_source="TEARDROP-BRACKET UNKNOWN")
    body += f'<path d="M282 256 Q247 313 282 344 Q317 313 282 256 Z" fill="white" stroke="{INK}" stroke-width="5"/>'
    body += f'<path d="M424 278 H488 M424 278 V344 M424 344 H488" fill="none" stroke="{INK}" stroke-width="7"/>'
    body += word(71, 412, "TEARDROP left of OPEN BRACKET · MEANING UNKNOWN", 17, bold=True)
    body += word(42, 497, "Two different two-panel image plans; choose one.", 16)
    for y, title in ((519, "IDEA 1"), (752, "IDEA 2")):
        body += box(42, y, 710, 208, "white", data_canvas=title)
        body += word(60, y + 41, title, 18, bold=True)
        body += box(61, y + 62, 314, 127, GREY)
        body += box(419, y + 62, 314, 127, GREY)
    body += word(42, 1000, "Where does a viewer begin? What stays or changes?", 15)
    return page("Check M: Fictional shelf card", "Fresh unknown teardrop-bracket card and two blank two-panel plans.", body)


def check_n() -> str:
    body = word(42, 147, "FRESH CHECK N · TWO NEW MARKS", 17, bold=True)
    body += box(42, 181, 710, 287, SAND, data_source="BENT LINE AND SMALL BLOCK")
    body += f'<path d="M169 366 L219 275 L283 344" fill="none" stroke="{INK}" stroke-width="8"/>'
    body += f'<rect x="537" y="312" width="53" height="53" rx="3" fill="{INK}"/>'
    body += word(115, 422, "BENT LINE", 19, bold=True)
    body += word(467, 422, "SMALL BLOCK", 19, bold=True)
    body += box(42, 493, 710, 107, MINT, data_canvas="MY UNIT")
    body += word(60, 537, "MY TWO-MARK UNIT:", 18, bold=True)
    body += line(268, 554, 729, 554, 1)
    for y, title in ((626, "IDEA 1"), (801, "IDEA 2")):
        body += box(42, y, 710, 151, "white", data_canvas=title)
        body += word(60, y + 42, title, 18, bold=True)
    body += word(42, 997, "Two full returns in each; choose an art grouping.", 15)
    return page("Check N: Path strip", "Fresh bent-line and small-block marks, with blank unit and strips.", body)


PAGES = {
    "unknown-cue": unknown_cue,
    "two-panel-path": two_panel_path,
    "sequence-repeat": sequence_repeat,
    "visual-unit": visual_unit,
    "quiet-transfer": quiet_transfer,
    "art-viewer-record": art_viewer_record,
    "check-m-shelf-card": check_m,
    "check-n-path-strip": check_n,
}


def main() -> None:
    for stem, make in PAGES.items():
        svg = HERE / f"{stem}.svg"
        pdf = HERE / f"{stem}.pdf"
        svg.write_text(make(), encoding="utf-8")
        rendered = subprocess.run(["rsvg-convert", "-f", "pdf", str(svg)], check=True, capture_output=True)
        writer = PdfWriter()
        writer.append_pages_from_reader(PdfReader(BytesIO(rendered.stdout)))
        writer.add_metadata({
            "/Producer": "SubjectNest / Cairo / pypdf",
            "/CreationDate": "D:20260930000000+10'00'",
            "/ModDate": "D:20260930000000+10'00'",
        })
        writer.write(str(pdf))
        print(f"WROTE {stem}.svg + .pdf")


if __name__ == "__main__":
    main()
