#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Generate original, text-selectable A4 Foundation maths aids and cutout cards."""
from __future__ import annotations

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

HERE = Path(__file__).resolve().parent
INK = "#18364a"
BLUE = "#eaf4f8"
GREEN = "#eef7ec"
PEACH = "#fff0e4"


def box(x: float, y: float, w: float, h: float, fill: str = "white", r: int = 12) -> str:
    return (f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{r}" '
            f'fill="{fill}" stroke="{INK}" stroke-width="2"/>')


def word(x: float, y: float, value: str, size: int = 20, weight: int = 400) -> str:
    return (f'<text x="{x}" y="{y}" fill="{INK}" font-family="DejaVu Sans" '
            f'font-size="{size}" font-weight="{weight}">{escape(value)}</text>')


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


def page(title: str, desc: 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(desc)}</desc>'
            '<rect width="794" height="1123" fill="#ffffff"/>'
            f'{word(45, 68, title, 27, 700)}'
            f'{word(45, 104, desc, 13)}'
            f'{body}{line(45, 1052, 749, 1052)}'
            f'{word(45, 1080, "SubjectNest · original Foundation maths aid · CC BY 4.0", 11)}'
            '</svg>\n')


def sort_mat() -> str:
    b = word(45, 160, "Ask the given question before sorting.", 19, 700)
    b += box(45, 195, 332, 620, BLUE) + box(417, 195, 332, 620, GREEN)
    b += word(70, 236, "GROUP 1", 23, 700) + word(442, 236, "GROUP 2", 23, 700)
    b += word(70, 272, "Visible rule: __________", 15)
    b += word(442, 272, "Visible rule: __________", 15)
    for x in (70, 442):
        for y in (395, 530, 665):
            b += line(x, y, x + 275, y)
    b += box(45, 840, 704, 155, PEACH)
    b += word(67, 884, "Count GROUP 1: ____     Count GROUP 2: ____", 18)
    b += word(67, 924, "Which group has more cards? Show counted evidence.", 16)
    b += word(67, 961, "On THESE cards, I can say: ______________", 16)
    return page("Sort by the question", "Fictional picture data: use only the drawn feature.", b)


def count_board() -> str:
    b = word(45, 160, "Move or mark each card once into a counted place.", 18, 700)
    b += box(45, 195, 333, 640, BLUE) + box(416, 195, 333, 640, GREEN)
    b += word(70, 236, "GROUP 1", 22, 700) + word(440, 236, "GROUP 2", 22, 700)
    for col in (45, 416):
        for row in range(4):
            b += line(col + 24, 345 + row * 115, col + 309, 345 + row * 115)
    b += box(45, 858, 704, 140, PEACH)
    b += word(70, 898, "Count 1: ____     Count 2: ____", 19)
    b += word(70, 939, "Pair or count: which has more? What remains?", 17)
    return page("Count and compare cards", "Spaces support one-to-one tracking, not guessing from row length.", b)


def align_mat() -> str:
    b = word(45, 160, "Put one end of each real paper strip on START.", 18, 700)
    b += box(45, 205, 704, 594, BLUE)
    b += line(95, 270, 95, 745, 5)
    b += word(110, 288, "START: line up ends here", 19, 700)
    b += line(95, 415, 710, 415) + line(95, 650, 710, 650)
    b += word(120, 388, "STRIP 1", 20, 700)
    b += word(120, 623, "STRIP 2", 20, 700)
    b += box(45, 832, 704, 164, GREEN)
    b += word(70, 874, "Which end extends farther after the starts align?", 17)
    b += word(70, 915, "Longer / shorter / equal: ______________", 18)
    b += word(70, 955, "Do not decide from staggered starting positions.", 14)
    return page("Align ends to compare length", "Use real broad strips; adult prep dimensions are not child questions.", b)


def mm_px(mm: int) -> float:
    return round(mm * 794 / 210, 2)


def strips() -> str:
    b = word(45, 155, "Print at actual A4 size; adult checks lengths before use.", 16, 700)
    cases = [("P166 A", 80), ("P166 B", 125), ("P167 C", 110),
             ("P167 D", 110), ("P167 E", 75), ("P167 F", 115)]
    for i, (label, length) in enumerate(cases):
        y = 193 + i * 127
        b += box(45, y, mm_px(length), 72, BLUE if i % 2 == 0 else GREEN, 0).replace(
            '<rect ', f'<rect data-length-mm="{length}" ', 1)
        b += word(60, y + 46, label, 20, 700)
        b += word(45, y + 103, "Cut on the outer line; keep each strip straight.", 12)
    return page("Practice paper roof strips", "Six broad strips for physical length comparisons; no child ruler task.", b)


def transfer() -> str:
    b = word(45, 159, "Adult handles clean water over a stable tray.", 18, 700)
    b += box(45, 198, 704, 245, BLUE)
    b += word(70, 244, "1 · Verify actual cups before the lesson", 21, 700)
    b += word(70, 287, "Fill smaller cup to full usable brim; then pour.", 17)
    b += word(70, 327, "All water must fit with visible room remaining.", 17)
    b += word(70, 368, "If spill or uncertainty: replace and retest.", 16)
    b += box(45, 477, 704, 246, GREEN)
    b += word(70, 523, "2 · Child directs the real transfer", 21, 700)
    b += word(70, 566, "Point, speak, sign or use AAC for each step.", 17)
    b += word(70, 608, "Adult reports the observed space neutrally.", 17)
    b += word(70, 651, "Compare CAPACITY, not cup height in a picture.", 17)
    b += box(45, 758, 704, 236, PEACH)
    b += word(70, 805, "Observed: water fit / did not fit / unclear", 18, 700)
    b += word(70, 850, "The cup that can hold more is: __________", 18)
    b += word(70, 893, "I know because: _______________________", 18)
    b += word(70, 946, "This page alone is not a direct comparison.", 14)
    return page("Capacity needs an actual transfer", "Safe supervised water demonstration; picture size proves nothing.", b)


def balance() -> str:
    b = word(45, 159, "Adult checks a stable school-approved two-pan balance.", 17, 700)
    b += box(45, 195, 704, 430, BLUE)
    b += line(397, 275, 397, 560, 5) + line(155, 341, 640, 341, 5)
    b += line(195, 341, 195, 452) + line(600, 341, 600, 452)
    b += line(100, 452, 290, 452, 4) + line(505, 452, 695, 452, 4)
    b += word(140, 502, "LEFT PAN", 18, 700) + word(540, 502, "RIGHT PAN", 18, 700)
    b += word(73, 579, "Diagram only: observe a REAL pan movement.", 16)
    b += box(45, 660, 704, 336, GREEN)
    b += word(70, 707, "Place broad sealed parcels on opposite pans.", 17)
    b += word(70, 750, "The lower pan shows greater MASS if verified.", 17)
    b += word(70, 798, "Switch parcels: does the same one tip down?", 17)
    b += word(70, 850, "Observed lower pan: _______________", 19)
    b += word(70, 900, "Reason: __________________________", 19)
    b += word(70, 954, "If the device sticks or result is unclear: retest.", 14)
    return page("Mass needs a real balance", "Direct comparison is an observed action, not this diagram.", b)


def checks() -> str:
    b = box(45, 151, 704, 365, BLUE)
    b += word(68, 193, "TEACHER HELD · Check A · Day 165", 20, 700)
    b += word(68, 235, "13 fictional leaf cards: 5 ROUND, 8 NARROW", 18)
    b += word(68, 270, "Use two separate cutout sheets for the card set.", 15)
    b += word(68, 305, "Ask which is more ON THESE CARDS.", 17)
    b += word(68, 347, "No local plant population was observed.", 16)
    b += word(68, 395, "Precount privately; no totals shown to child.", 15)
    b += box(45, 546, 704, 385, GREEN)
    b += word(68, 588, "TEACHER HELD · Check B · Day 170", 20, 700)
    b += word(68, 629, "Length: P 95 mm; Q 145 mm. Align ends.", 17)
    b += word(68, 665, "Capacity: verified R smaller than S; direct pour.", 16)
    b += word(68, 701, "Mass: T 3 sheets; U 15 sheets. Verify balance.", 16)
    b += word(68, 748, "Keep prep quantities/results off child prompt.", 15)
    b += word(68, 795, "Failed physical station = invalid, not child error.", 15)
    b += word(68, 845, "Public key; formative only, not secure exams.", 15)
    b += word(45, 981, "Do not display teacher-held preparation with routine practice.", 14, 700)
    return page("Fresh checks: teacher preparation", "New fictional cards and direct-comparison cases; hold until check.", b)


def flower(x: float, y: float) -> str:
    petals = [(0, -19), (18, -7), (11, 15), (-11, 15), (-18, -7)]
    out = ''.join(f'<circle cx="{x+dx}" cy="{y+dy}" r="8" fill="{PEACH}" '
                  f'stroke="{INK}" stroke-width="2"/>' for dx, dy in petals)
    return out + f'<circle cx="{x}" cy="{y}" r="8" fill="white" stroke="{INK}" stroke-width="2"/>'


def leaf(x: float, y: float, kind: str) -> str:
    if kind in ("round", "oval", "smooth"):
        rx, ry = (54, 55) if kind == "round" else (68, 44)
        return (f'<ellipse cx="{x}" cy="{y}" rx="{rx}" ry="{ry}" fill="{GREEN}" '
                f'stroke="{INK}" stroke-width="4"/>'
                f'{line(x, y + ry, x, y + ry + 18, 3)}')
    if kind in ("pointed", "narrow", "spear"):
        return (f'<path d="M{x} {y-61} Q{x+53} {y-9} {x} {y+59} '
                f'Q{x-53} {y-9} {x} {y-61} Z" fill="{GREEN}" '
                f'stroke="{INK}" stroke-width="4"/>'
                f'{line(x, y + 59, x, y + 75, 3)}')
    if kind == "jagged":
        pts = [(0, -62), (21, -42), (36, -46), (42, -23), (66, -8),
               (43, 8), (47, 28), (22, 32), (0, 59), (-22, 32),
               (-47, 28), (-43, 8), (-66, -8), (-42, -23), (-36, -46), (-21, -42)]
        s = ' '.join(f'{x+dx},{y+dy}' for dx, dy in pts)
        return (f'<polygon points="{s}" fill="{GREEN}" stroke="{INK}" stroke-width="4"/>'
                f'{line(x, y + 59, x, y + 75, 3)}')
    raise ValueError(kind)


def card_sheet(title: str, desc: str, kinds: list[tuple[str, bool]], first_index: int = 1,
               rows: int = 4) -> str:
    b = word(45, 149, "Cut on outer boxes. Shapes are imagined drawings.", 15, 700)
    h = 174 if rows == 4 else 232
    step = 201 if rows == 4 else 260
    for i, (kind, has_flower) in enumerate(kinds):
        col, row = i % 3, i // 3
        x, y = 45 + col * 240, 178 + row * step
        b += box(x, y, 224, h, "white", 0)
        cy = y + (77 if rows == 4 else 100)
        b += f'<g data-kind="{kind}" data-flower="{int(has_flower)}">'
        b += leaf(x + 112, cy, kind)
        if has_flower:
            b += flower(x + 172, y + 37)
        b += '</g>'
        b += word(x + 15, y + h - 16, f"CARD {first_index+i}", 12, 700)
    return page(title, desc, b)


def cards_161() -> str:
    kinds = [(k, False) for k in ("round", "pointed", "round", "round", "pointed",
                                   "round", "pointed", "round", "round", "pointed", "round")]
    return card_sheet("P161 · fictional leaf cards", "Eleven original cards; sort by round or pointed outline.", kinds)


def cards_162() -> str:
    flags = [True, False, True, True, False, True, False, True, False]
    return card_sheet("P162 · flower-symbol cards", "Nine imaginary cards; sort symbol drawn or not drawn.",
                      [("oval", f) for f in flags])


def cards_163() -> str:
    kinds = ["smooth", "jagged", "smooth", "smooth", "jagged", "smooth",
             "smooth", "jagged", "smooth", "smooth", "jagged", "smooth"]
    return card_sheet("P163 · drawn edge cards", "Twelve imaginary cards; sort smooth or jagged visible edge.",
                      [(k, False) for k in kinds])


def cards_164() -> str:
    kinds = [("oval", True), ("spear", False), ("oval", False), ("oval", True),
             ("spear", False), ("oval", False), ("spear", True), ("oval", False),
             ("spear", False), ("oval", False)]
    return card_sheet("P164 · two-feature card set", "Same ten fictional cards can be sorted for two questions.", kinds)


def cards_165a() -> str:
    kinds = ["round", "narrow", "narrow", "round", "narrow", "narrow", "round"]
    return card_sheet("CHECK A · card set 1 of 2", "Teacher held. Fictional outlines; keep off practice tables.",
                      [(k, False) for k in kinds], 1, 3)


def cards_165b() -> str:
    kinds = ["narrow", "round", "narrow", "narrow", "round", "narrow"]
    return card_sheet("CHECK A · card set 2 of 2", "Teacher held. Combine with sheet 1; pre-count privately.",
                      [(k, False) for k in kinds], 8, 3)


PAGES = {
    "feature-sort-mat": sort_mat,
    "count-compare-board": count_board,
    "align-ends-mat": align_mat,
    "practice-strips": strips,
    "transfer-station": transfer,
    "balance-observation": balance,
    "teacher-held-checks": checks,
    "practice-observation-161": cards_161,
    "practice-observation-162": cards_162,
    "practice-observation-163": cards_163,
    "practice-observation-164": cards_164,
    "fresh-observation-165-a": cards_165a,
    "fresh-observation-165-b": cards_165b,
}


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")
        subprocess.run(["rsvg-convert", "-f", "pdf", "-o", str(pdf), str(svg)], check=True)
        print(stem)


if __name__ == "__main__":
    main()
