# SPDX-License-Identifier: Apache-2.0
"""Generate original single-page A4 SVG/PDF Foundation mathematics aids."""

from __future__ import annotations

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

HERE = Path(__file__).resolve().parent
INK = "#1a3444"
PALE_BLUE = "#eaf4f8"
PALE_GREEN = "#eff6ed"
PALE_PEACH = "#fff2e9"


def rect(x: int, y: int, w: int, h: int, fill: str = "white", **attrs: str) -> str:
    """Draw a high-contrast rectangle with optional card data."""
    other = " ".join(
        f'{key.replace("_", "-")}="{escape(value)}"' for key, value in attrs.items()
    )
    return (
        f'<rect x="{x}" y="{y}" width="{w}" height="{h}" '
        f'fill="{fill}" stroke="{INK}" stroke-width="2" {other}/>'
    )


def text(x: int, y: int, value: str, size: int = 19, bold: bool = False) -> str:
    """Draw PDF-selectable text with bundled-font attribution in pack."""
    return (
        f'<text x="{x}" y="{y}" fill="{INK}" font-family="DejaVu Sans" '
        f'font-size="{size}" font-weight="{700 if bold else 400}">'
        f"{escape(value)}</text>"
    )


def path(d: str, width: int = 2) -> str:
    """Draw a non-colour-only separator or icon boundary."""
    return f'<path d="{d}" fill="none" stroke="{INK}" stroke-width="{width}"/>'


def circle(cx: int, cy: int, r: int, **attrs: str) -> str:
    """Draw a distinct outlined count mark."""
    other = " ".join(
        f'{key.replace("_", "-")}="{escape(value)}"' for key, value in attrs.items()
    )
    return (
        f'<circle cx="{cx}" cy="{cy}" r="{r}" fill="white" '
        f'stroke="{INK}" stroke-width="2" {other}/>'
    )


def bus_picture(x: int, y: int) -> str:
    """Draw a small original side-view bus outline."""
    body = rect(x, y, 112, 43)
    body += rect(x + 13, y + 9, 29, 15)
    body += rect(x + 52, y + 9, 29, 15)
    body += circle(x + 24, y + 46, 8)
    body += circle(x + 88, y + 46, 8)
    return body


def bike_picture(x: int, y: int) -> str:
    """Draw an original two-wheel cycle outline."""
    body = circle(x + 17, y + 42, 17)
    body += circle(x + 96, y + 42, 17)
    body += path(
        f"M{x + 17} {y + 42} L{x + 46} {y + 14} "
        f"L{x + 70} {y + 42} L{x + 17} {y + 42} "
        f"L{x + 56} {y + 42} L{x + 46} {y + 14} "
        f"M{x + 70} {y + 42} L{x + 96} {y + 42} "
        f"M{x + 88} {y + 10} L{x + 96} {y + 42}",
        3,
    )
    return body


def star_picture(x: int, y: int) -> str:
    """Draw a five-point outlined star distinct from a plain straight stroke."""
    return path(
        f"M{x + 18} {y} L{x + 23} {y + 12} L{x + 36} {y + 13} "
        f"L{x + 26} {y + 22} L{x + 30} {y + 35} "
        f"L{x + 18} {y + 28} L{x + 6} {y + 35} "
        f"L{x + 10} {y + 22} L{x} {y + 13} "
        f"L{x + 13} {y + 12} Z",
        3,
    )


def page(title: str, description: str, content: str) -> str:
    """Wrap the original aid in a physical A4 page with SVG metadata."""
    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"{text(43, 65, title, 27, True)}"
        f"{text(43, 102, description, 13)}"
        f"{content}"
        f"{path('M43 1052 L751 1052')}"
        f"{text(43, 1080, 'SubjectNest original Foundation maths aid · CC BY 4.0', 11)}"
        "</svg>\n"
    )


def sort_mat() -> str:
    """Make the given-question two-column source and sort mat."""
    body = text(43, 150, "GIVEN QUESTION: Which picture is more in THIS set?", 18, True)
    body += rect(43, 178, 708, 95, PALE_PEACH)
    body += text(61, 215, "SOURCE: exact supplied sheet or packet", 17, True)
    body += text(61, 247, "Count each source picture once. Move it to DONE.", 15)
    for x, name, fill in ((43, "TYPE A", PALE_BLUE), (405, "TYPE B", PALE_GREEN)):
        body += rect(x, 307, 346, 525, fill)
        body += text(x + 25, 350, name, 22, True)
        body += text(x + 25, 789, "Count: ____", 18)
    body += rect(43, 864, 708, 135)
    body += text(61, 910, "DONE: ____ of ____ source pictures accounted for", 17)
    body += text(61, 949, "More in this set: ______ because ______", 16)
    return page(
        "A given question, one supplied source",
        "Label both types; place each item once; compare only the supplied items.",
        body,
    )


def compare_board() -> str:
    """Make two rows of paired count slots with labels independent of colour."""
    body = text(43, 155, "Pair one item with one item, or track each once.", 18, True)
    body += text(43, 190, "Group A", 19, True)
    body += text(43, 560, "Group B", 19, True)
    for group, base_y, fill in (("A", 218, PALE_BLUE), ("B", 588, PALE_GREEN)):
        for number in range(20):
            x = 43 + (number % 10) * 71
            y = base_y + (number // 10) * 130
            body += rect(x, y, 64, 104, fill, data_slot=f"{group}{number + 1}")
            body += text(x + 15, y + 128, str(number + 1), 11)
    body += rect(43, 928, 708, 80, PALE_PEACH)
    body += text(58, 961, "A: ____    B: ____    More: ____    Why? ______", 16)
    body += text(58, 989, "Spacing is not an extra object.", 13)
    return page(
        "One-to-one comparison board",
        "Twenty labelled spaces per group; use only as many as the source has.",
        body,
    )


def recheck_mat() -> str:
    """Make honest first idea, physical check and confirmed/revised claim spaces."""
    body = text(43, 154, "The first idea may be confirmed or changed.", 18, True)
    for y, title, fill, hint in (
        (
            190,
            "1  FIRST IDEA / CLAIM",
            PALE_PEACH,
            "What did the card say or what did I predict?",
        ),
        (
            452,
            "2  WHAT I CHECKED",
            PALE_BLUE,
            "Move, pair or count each source object once.",
        ),
        (
            714,
            "3  NOW I CAN SAY",
            PALE_GREEN,
            "Confirmed / revised because the objects show ...",
        ),
    ):
        body += rect(43, y, 708, 220, fill)
        body += text(61, y + 42, title, 20, True)
        body += text(61, y + 78, hint, 16)
    body += text(43, 990, "Exact source kept the same?  YES / RESET IT", 16)
    return page(
        "First idea · physical check · now",
        "A checked idea need not change; capture the actual source and evidence.",
        body,
    )


def change_mat() -> str:
    """Make a three-part add/remove model with visible OUT area."""
    body = text(43, 150, "Build the story before reading the claim card.", 18, True)
    for y, title, fill in (
        (183, "START: what was there?", PALE_BLUE),
        (420, "CHANGE: add or take away?", PALE_PEACH),
        (657, "END: what is still in the model?", PALE_GREEN),
    ):
        body += rect(43, y, 708, 205, fill)
        body += text(61, y + 42, title, 20, True)
        body += text(61, y + 177, "Count: ____", 16)
    body += rect(43, 895, 708, 110)
    body += text(61, 934, "OUT (for take-away): ____   Claim card: ____", 16)
    body += text(61, 971, "Checked result: ____   Same / needs repair", 16)
    return page(
        "Start · change · end model",
        "Keep the starting, added or removed, and ending objects traceable.",
        body,
    )


def route_cards() -> str:
    """Make the exact ten two-attribute fictional route pictures for Day 183."""
    kinds = ["BUS+STAR"] * 2 + ["BUS+PLAIN"] * 4 + ["BIKE+STAR"] + ["BIKE+PLAIN"] * 3
    body = text(43, 145, "Same ten cards; reset before the second question.", 17, True)
    body += text(43, 175, "1 Which vehicle icon is more?  2 Which mark is more?", 15)
    for index, kind in enumerate(kinds):
        x = 43 + (index % 2) * 359
        y = 218 + (index // 2) * 154
        body += rect(
            x,
            y,
            345,
            142,
            PALE_BLUE if index % 2 == 0 else PALE_GREEN,
            data_card_kind=kind,
        )
        vehicle, mark = kind.split("+")
        body += text(x + 24, y + 56, vehicle, 24, True)
        body += text(x + 24, y + 103, mark, 21)
        picture = (
            bus_picture(x + 199, y + 22)
            if vehicle == "BUS"
            else bike_picture(x + 199, y + 17)
        )
        body += f'<g data-icon-kind="{vehicle}">{picture}</g>'
        mark_picture = (
            star_picture(x + 240, y + 87)
            if mark == "STAR"
            else path(f"M{x + 241} {y + 106} L{x + 278} {y + 106}", 5)
        )
        body += f'<g data-mark-kind="{mark}">{mark_picture}</g>'
    body += text(
        43, 1020, "Cut only with adult/local safety review; a sheet is enough.", 13
    )
    return page(
        "Two given questions · ten route cards",
        "Invented symbols; no child or real travel information is collected.",
        body,
    )


def check_a() -> str:
    """Make a standalone new thirteen-icon picture source without a printed answer."""
    kinds = [
        "ARC",
        "BAR",
        "ARC",
        "ARC",
        "BAR",
        "ARC",
        "ARC",
        "BAR",
        "ARC",
        "ARC",
        "BAR",
        "ARC",
        "ARC",
    ]
    body = text(
        43, 147, "Given question: Which icon appears more on THIS sheet?", 17, True
    )
    body += text(
        43, 177, "Each wide rectangle is one source picture. Sort every one.", 15
    )
    for index, kind in enumerate(kinds):
        x = 43 + (index % 3) * 239
        y = 217 + (index // 3) * 155
        body += rect(
            x,
            y,
            224,
            141,
            PALE_GREEN if index % 2 == 0 else PALE_BLUE,
            data_card_kind=kind,
        )
        if kind == "ARC":
            pictogram = path(
                f"M{x + 39} {y + 70} Q{x + 112} {y - 8} {x + 185} {y + 70}",
                7,
            )
        else:
            pictogram = rect(x + 42, y + 48, 140, 20, INK)
        body += f'<g data-icon-kind="{kind}">{pictogram}</g>'
        body += text(x + 64, y + 113, kind, 22, True)
    body += text(
        43,
        1021,
        "Give this source only at Check A; a child may point instead of cut.",
        12,
    )
    return page(
        "Check A · fresh picture source",
        "Thirteen fictional craft-label pictures with ARC or BAR outlines and words.",
        body,
    )


def check_b() -> str:
    """Make a separate fresh row-comparison and add-to source without worked answers."""
    body = text(
        43, 143, "Story 1: count or pair actual marks, not paper width.", 17, True
    )
    body += rect(43, 176, 708, 375, PALE_BLUE)
    body += text(59, 211, "ROW A · compact", 18, True)
    for index in range(12):
        x = 66 + (index % 12) * 42
        body += circle(x, 256, 16, data_group="A")
    body += text(59, 354, "ROW B · spread", 18, True)
    for index in range(8):
        x = 66 + index * 91
        body += circle(x, 413, 16, data_group="B")
    body += text(43, 601, "Story 2: START + ADD; test the display card.", 17, True)
    body += rect(43, 630, 340, 305, PALE_GREEN)
    body += rect(411, 630, 340, 305, PALE_PEACH)
    body += text(59, 670, "START", 20, True)
    body += text(427, 670, "ADD", 20, True)
    for index in range(4):
        body += rect(
            63 + (index % 2) * 143,
            710 + (index // 2) * 90,
            112,
            67,
            "white",
            data_group="START",
        )
        body += text(93 + (index % 2) * 143, 755 + (index // 2) * 90, "ICON", 14)
    for index in range(5):
        body += rect(
            427 + (index % 2) * 143,
            710 + (index // 2) * 69,
            112,
            60,
            "white",
            data_group="ADD",
        )
        body += text(458 + (index % 2) * 143, 749 + (index // 2) * 69, "ICON", 14)
    body += rect(43, 962, 708, 71)
    body += text(
        59, 1004, "DISPLAY CLAIM: 8 at the end. Check with the icons.", 16, True
    )
    return page(
        "Check B · two fresh paper stories",
        "Separate comparison rows and a visible add-to model with a claim card.",
        body,
    )


def blank_cards() -> str:
    """Make twelve broad blank reusable cards without answer labels."""
    body = text(
        43, 147, "Draw one icon or mark per card; use a sheet if safer.", 17, True
    )
    for index in range(12):
        x = 43 + (index % 3) * 239
        y = 190 + (index // 3) * 200
        body += rect(x, y, 224, 184, "white", data_card_kind="blank")
        body += text(x + 27, y + 52, "ONE ITEM", 14)
    body += text(
        43, 1020, "Adult-managed broad cards; do not use small loose counters.", 13
    )
    return page(
        "Twelve broad blank source cards",
        "Draw one picture per card and keep the supplied source complete.",
        body,
    )


PAGES = {
    "given-question-sort-mat": sort_mat,
    "one-to-one-compare-board": compare_board,
    "first-check-now-mat": recheck_mat,
    "start-change-end-mat": change_mat,
    "two-question-picture-cards": route_cards,
    "check-a-picture-sheet": check_a,
    "check-b-model-sheet": check_b,
    "blank-source-cards": blank_cards,
}


def main() -> None:
    """Write SVG and render matching PDF using installed librsvg."""
    for stem, maker in PAGES.items():
        svg_path = HERE / f"{stem}.svg"
        svg_path.write_text(maker(), encoding="utf-8")
        subprocess.run(
            [
                "rsvg-convert",
                "-f",
                "pdf",
                "-o",
                str(HERE / f"{stem}.pdf"),
                str(svg_path),
            ],
            check=True,
        )
        print(f"WROTE {stem}.svg/.pdf")


if __name__ == "__main__":
    main()
