# SPDX-License-Identifier: Apache-2.0
"""Build eight original Foundation Visual Arts W5-6 A4 SVG/PDF 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"

# One original body, three rounded arms, two open notches. No natural specimen.
FORK = (
    "M70 20 Q55 20 55 40 L55 320 Q55 340 75 340 L115 340 "
    "Q135 340 135 320 L135 285 L215 285 Q235 285 235 265 "
    "L235 250 Q235 230 215 230 L135 230 L135 195 L215 195 "
    "Q235 195 235 175 L235 160 Q235 140 215 140 L135 140 "
    "L135 105 L215 105 Q235 105 235 85 L235 70 Q235 50 215 50 "
    "L135 50 L135 40 Q135 20 115 20 Z"
)


def box(x: int, y: int, w: int, h: int, fill: str = "white", **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="10" 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 fork(x: int, y: int, scale: float, *, filled: bool) -> str:
    fill = INK if filled else "white"
    return f'<path d="{FORK}" transform="translate({x} {y}) scale({scale})" fill="{fill}" stroke="{INK}" stroke-width="{4 / scale}"/>'


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 W5-6 · CC BY 4.0", 10)}'
        '</svg>\n'
    )


def source_choice() -> str:
    body = word(42, 150, "INVENTED FORK FORM · NOT A REAL PLANT", 18, bold=True)
    body += box(42, 185, 710, 573, BLUE, data_source="INVENTED FORK FORM")
    body += fork(239, 228, 1.25, filled=False)
    body += word(60, 730, "One body · three rounded arms · two open notches", 16)
    body += box(42, 800, 710, 165, "white", data_source="REAL SOURCE EMPTY")
    body += word(60, 850, "REAL SOURCE: EMPTY", 21, bold=True)
    body += word(60, 894, "Only fill from a separately approved actual source.", 16)
    body += word(60, 934, "Invented drawing is not a species observation.", 15)
    return page("Invented form or real source?", "An original three-arm form; the real-source slot stays empty.", body)


def contour_silhouette() -> str:
    body = word(42, 150, "SAME SHAPE · TWO ART TREATMENTS", 18, bold=True)
    body += box(42, 194, 338, 691, BLUE, data_treatment="CONTOUR")
    body += box(414, 194, 338, 691, SAND, data_treatment="FILLED SILHOUETTE")
    body += word(60, 244, "CONTOUR", 22, bold=True)
    body += word(432, 244, "FILLED SILHOUETTE", 19, bold=True)
    body += fork(110, 317, 1.02, filled=False)
    body += fork(481, 317, 1.02, filled=True)
    body += word(60, 770, "Outside line; inside stays white.", 15)
    body += word(432, 770, "Body filled; same outer edge.", 15)
    body += box(42, 920, 710, 81, MINT)
    body += word(59, 969, "Which would serve your own image idea?", 18)
    return page("Contour or filled silhouette", "The identical invented fork form, outlined or solid-filled.", body)


def gap_between() -> str:
    body = word(42, 150, "TWO UNCHANGED FORMS · MOVE ONE FARTHER", 17, bold=True)
    for x, title, second_x, fill in ((42, "NARROW GAP", 255, BLUE), (414, "WIDE GAP", 665, SAND)):
        body += box(x, 194, 338, 697, fill, data_gap=title)
        body += word(x + 19, 244, title, 21, bold=True)
        body += f'<rect x="{x + 39}" y="395" width="95" height="210" rx="22" fill="{MINT}" stroke="{INK}" stroke-width="4"/>'
        body += f'<ellipse cx="{second_x}" cy="500" rx="42" ry="105" fill="{SAND if fill == BLUE else BLUE}" stroke="{INK}" stroke-width="4"/>'
    body += word(61, 756, "A small opening between edges.", 15)
    body += word(433, 756, "A larger opening between edges.", 15)
    body += box(42, 921, 710, 80, MINT)
    body += word(59, 970, "The forms did not grow or change shape.", 17)
    return page("The gap is a shape", "Same large forms, placed close together and farther apart.", body)


def layer_order() -> str:
    body = word(42, 150, "SAME TWO BROAD PIECES · CHANGE FRONT ORDER", 16, bold=True)
    body += box(42, 194, 338, 697, BLUE, data_front="CURVED PIECE")
    body += box(414, 194, 338, 697, SAND, data_front="ANGLED PIECE")
    body += word(60, 244, "CURVED IN FRONT", 19, bold=True)
    body += word(432, 244, "ANGLED IN FRONT", 19, bold=True)
    body += f'<path d="M170 370 L315 420 L284 653 L151 601 Z" fill="{SAND}" stroke="{INK}" stroke-width="4"/>'
    body += f'<ellipse cx="181" cy="508" rx="98" ry="120" fill="{MINT}" stroke="{INK}" stroke-width="4"/>'
    body += f'<ellipse cx="553" cy="508" rx="98" ry="120" fill="{MINT}" stroke="{INK}" stroke-width="4"/>'
    body += f'<path d="M542 370 L687 420 L656 653 L523 601 Z" fill="{SAND}" stroke="{INK}" stroke-width="4"/>'
    body += word(60, 759, "Part of the angled edge is hidden.", 15)
    body += word(432, 759, "Part of the curved edge is hidden.", 15)
    body += box(42, 920, 710, 81, GREY)
    body += word(59, 970, "Drawn overlap is a plan until pieces are actually placed.", 15)
    return page("Who is in front?", "A curved oval and an angled quadrilateral swap layer order.", body)


def material_choice() -> str:
    body = word(42, 150, "TWO CANDIDATE NAMES · NO MATERIAL RESULT", 17, bold=True)
    body += box(42, 194, 338, 608, BLUE, data_candidate="BROAD PAPER")
    body += box(414, 194, 338, 608, SAND, data_candidate="BROAD CARD")
    body += word(60, 250, "BROAD PAPER", 22, bold=True)
    body += word(432, 250, "BROAD CARD", 22, bold=True)
    body += box(92, 343, 238, 251)
    body += box(464, 343, 238, 251)
    body += word(60, 703, "Possible large art layer", 16)
    body += word(432, 703, "Possible large art layer", 16)
    body += box(42, 841, 710, 161, MINT)
    body += word(59, 897, "ACTUAL PROPERTIES: UNKNOWN", 21, bold=True)
    body += word(59, 941, "until an adult-approved real inspection, if any.", 16)
    body += word(59, 980, "A picture cannot tell you how either feels or holds.", 14)
    return page("Paper or card candidate?", "Equal-size diagrams label possibilities; no texture or strength evidence.", body)


def artwork_record() -> str:
    fields = (
        ("MY IDEA / INTENDED VIEWER", "What would you like someone to notice?"),
        ("TWO VISUAL OPTIONS / CHOICE", "Which edge, filled area, gap or front piece changed?"),
        ("ACTUAL MAKER / MATERIAL ROUTE", "Who really drew or placed? Or PLAN ONLY?"),
        ("WILLING VIEWER / NEXT QUESTION", "Who actually saw it? Or NOT SHARED?"),
    )
    body = word(42, 150, "Blank child choices; actual actions stay separate.", 17, bold=True)
    for i, (heading, prompt) in enumerate(fields):
        y = 194 + i * 200
        body += box(42, y, 710, 172, (BLUE, SAND, MINT, GREY)[i], data_field=heading)
        body += word(60, y + 43, heading, 18, bold=True)
        body += word(60, y + 80, prompt, 15)
        body += line(60, y + 143, 734, y + 143, 1)
    return page("My art-choice record", "Four blank areas for intention, choice, real process and actual viewer.", body)


def check_k() -> str:
    body = word(42, 150, "FRESH CHECK K · INVENTED PAPER FAN FORM", 17, bold=True)
    body += box(42, 184, 710, 380, BLUE, data_source="PAPER FAN FORM")
    # Broad rays fan upward. The middle pair leaves one clear open wedge.
    rays = (
        "M242 456 L161 253 L247 238 L285 469 Z",
        "M280 468 L257 225 L292 220 L319 472 Z",
        "M331 472 L365 220 L400 226 L369 466 Z",
        "M374 468 L410 239 L496 254 L415 455 Z",
    )
    for ray in rays:
        body += f'<path d="{ray}" fill="white" stroke="{INK}" stroke-width="4"/>'
    body += f'<path d="M205 438 Q328 505 457 438 L457 478 Q330 540 205 478 Z" fill="white" stroke="{INK}" stroke-width="4"/>'
    body += word(540, 309, "OPEN", 16, bold=True)
    body += word(540, 339, "WEDGE", 16, bold=True)
    body += word(58, 554, "Low curved base · four rays · middle open wedge", 15)
    body += word(42, 601, "Two different treatments; choose one for your idea.", 16)
    body += box(42, 620, 710, 171, "white", data_canvas="IDEA 1")
    body += word(60, 662, "IDEA 1", 18, bold=True)
    body += box(42, 807, 710, 171, "white", data_canvas="IDEA 2")
    body += word(60, 849, "IDEA 2", 18, bold=True)
    body += word(42, 999, "Explain chosen opening and contour/fill; no real source.", 15)
    return page("Check K: Paper Fan Form", "Fresh invented form and two blank response spaces.", body)


def check_l() -> str:
    body = word(42, 150, "FRESH CHECK L · TWO SEPARATE PIECES", 17, bold=True)
    body += box(42, 184, 710, 346, SAND, data_source="ROBOT POSTCARD")
    body += f'<circle cx="252" cy="355" r="103" fill="white" stroke="{INK}" stroke-width="4"/>'
    body += f'<path d="M492 255 H631 V304 H583 V350 H652 V450 H492 Z" fill="white" stroke="{INK}" stroke-width="4"/>'
    body += word(171, 490, "ROUND HEAD", 17, bold=True)
    body += word(484, 490, "STEP-SHAPED TOOL", 16, bold=True)
    body += word(42, 561, "Show two placements; choose one for your idea.", 16)
    body += box(42, 593, 710, 171, "white", data_canvas="IDEA 1")
    body += word(60, 635, "IDEA 1", 18, bold=True)
    body += box(42, 786, 710, 171, "white", data_canvas="IDEA 2")
    body += word(60, 828, "IDEA 2", 18, bold=True)
    body += word(42, 999, "Name visible edge/gap and actual handler or PLAN ONLY.", 14)
    return page("Check L: Robot Postcard", "Fresh separate round and step-shaped forms; two blank ideas.", body)


PAGES = {
    "source-choice": source_choice,
    "contour-silhouette": contour_silhouette,
    "gap-between": gap_between,
    "layer-order": layer_order,
    "material-choice": material_choice,
    "artwork-record": artwork_record,
    "check-k-paper-fan": check_k,
    "check-l-robot-postcard": check_l,
}


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()
