#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Generate original deterministic A4 SVG/PDF Year 8 English 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


def txt(x: float, y: float, value: str, size: float = 4.1, bold: bool = False) -> str:
    return f'<text x="{x}" y="{y}" font-size="{size}" font-weight="{700 if bold else 400}">{escape(value)}</text>'


def box(x: float, y: float, width: float, height: float, fill: str = "#fffefa") -> str:
    return f'<rect x="{x}" y="{y}" width="{width}" height="{height}" rx="2.5" fill="{fill}" stroke="#284c62" stroke-width="0.6"/>'


def write_line(x: float, y: float, end: float) -> str:
    return f'<path d="M{x} {y}H{end}" stroke="#446e84" stroke-width="0.38"/>'


def page(title: str, description: str, parts: list[str]) -> str:
    return f'''<svg xmlns="http://www.w3.org/2000/svg" width="210mm" height="297mm" viewBox="0 0 210 297" role="img" aria-labelledby="title desc">
<title id="title">{escape(title)}</title><desc id="desc">{escape(description)}</desc>
<rect width="210" height="297" fill="#fffefa"/>
<g font-family="DejaVu Sans, sans-serif" fill="#173b50">{''.join(parts)}</g>
<text x="13" y="288" font-family="DejaVu Sans, sans-serif" font-size="3.3" fill="#173b50">SubjectNest · original Year 8 English aid · CC BY 4.0 · neuroforge.io</text>
</svg>
'''


def equivalence_evidence() -> str:
    parts = [txt(13, 18, "Same paper model, different form", 7.8, True),
             txt(13, 27, "Read every part before deciding what the claim can reach.", 4.1),
             box(13, 39, 184, 60, "#edf4f8"), txt(18, 50, "TWO WORDINGS OF ONE STATED RULE", 5, True),
             txt(18, 64, "12 + 18n + 12  =  24 + 18n", 6, True),
             txt(18, 76, "left margin + panels + right margin", 4),
             txt(18, 89, "Both are centimetres in the paper design note.", 3.9),
             box(13, 108, 184, 49, "#f5f0fa"), txt(18, 120, "SOURCE EXAMPLE", 5, True),
             txt(18, 133, "At n = 4: 24 + 18 × 4 = 96 cm.", 4.5),
             txt(18, 146, "Drawn rail = 96 cm. No physical rail was tested.", 4),
             box(13, 166, 184, 69, "#fff5e9"), txt(18, 178, "CLAIM / REACH / LIMIT", 5, True),
             txt(18, 190, "What the model supports:", 4.1, True), write_line(82, 191, 192),
             txt(18, 204, "What it cannot establish:", 4.1, True), write_line(83, 205, 192),
             txt(18, 218, "One next test:", 4.1, True), write_line(58, 219, 192),
             txt(13, 254, "Keep MODEL and REAL evidence visibly separate.", 4.3, True),
             txt(13, 268, "The word 'fit' needs its source and conditions.", 4)]
    return page("Year 8 equivalence and evidence mat", "Top: two equivalent wordings 12 plus 18n plus 12 equals 24 plus 18n for an invented paper model. Middle: n equals 4 gives 96 cm, matching a drawn rail only. Bottom: blank supported claim, unestablished claim and next test fields. Full linear and tactile description in TEXT-ALTERNATIVES.md.", parts)


def hybrid_source() -> str:
    parts = [txt(13, 18, "A headline beside its source", 8, True),
             txt(13, 27, "Read the invitation, the model and the limit separately.", 4.1),
             box(13, 40, 184, 43, "#f2edf8"), txt(18, 52, "1 · HEADLINE / PROMISE", 5.1, True),
             txt(18, 64, "What does a reader expect? Quote the exact words.", 4), write_line(18, 75, 192),
             box(13, 91, 184, 43, "#edf4f8"), txt(18, 103, "2 · TECHNICAL LINE", 5.1, True),
             txt(18, 115, "What unit, variable and condition are actually stated?", 4), write_line(18, 126, 192),
             box(13, 142, 184, 43, "#eef6ee"), txt(18, 154, "3 · SUPPORTED RESULT", 5.1, True),
             txt(18, 166, "Name the model and cite one calculation or phrase.", 4), write_line(18, 177, 192),
             box(13, 193, 184, 43, "#fff5e8"), txt(18, 205, "4 · LIMIT + NEXT QUESTION", 5.1, True),
             txt(18, 217, "What has not been checked for a real reader?", 4), write_line(18, 228, 192),
             txt(13, 254, "Reader test: can someone identify the source and scope?", 4.2, True),
             txt(13, 268, "A confident headline is not a second measurement.", 4)]
    return page("Year 8 hybrid text source map", "Four numbered top-to-bottom fields: headline promise, technical line, supported result and limit with next question. Each has one blank line. Reader test asks whether someone can identify source and scope. Full text and tactile version in TEXT-ALTERNATIVES.md.", parts)


def line_meaning() -> str:
    parts = [txt(13, 18, "One line, two kinds of meaning", 8, True),
             txt(13, 27, "Use exact words; different supported readings can stand.", 4.1),
             box(13, 40, 184, 53, "#edf4f8"), txt(18, 52, "LITERAL LINE IN A MODEL", 5.1, True),
             txt(18, 65, "A drawn rail or paper margin can be measured.", 4.1),
             txt(18, 78, "Source detail:", 4.1, True), write_line(55, 79, 192),
             box(13, 103, 184, 66, "#f2edf8"), txt(18, 115, "LINE AS IMAGE / VIEWPOINT", 5.1, True),
             txt(18, 128, "Story: an edge can become an invitation.", 4.1),
             txt(18, 140, "Poem: a line can look like a gate or sky.", 4.1),
             txt(18, 155, "Exact phrase and possible effect:", 4.1, True), write_line(95, 156, 192),
             box(13, 180, 184, 62, "#fff5e9"), txt(18, 192, "PUNCTUATION CHOICE", 5.1, True),
             txt(18, 204, "Dash — pause or turn. Semicolon ; close linked ideas.", 4.1),
             txt(18, 217, "My original two-line choice / revision:", 4, True),
             write_line(18, 229, 192), write_line(18, 237, 192),
             txt(13, 258, "Ask: which word or mark changed the reader's view?", 4.1, True),
             txt(13, 271, "Do not turn a literary image into a real site claim.", 4)]
    return page("Year 8 line and meaning literary mat", "Three sections: literal measured line in a paper model, line as story or poem image with source phrase, and punctuation choice with two blank writing lines. Dash can pause or turn a thought; semicolon links complete ideas. Full linear and tactile version in TEXT-ALTERNATIVES.md.", parts)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--output-dir", type=Path, default=HERE)
    args = parser.parse_args()
    args.output_dir.mkdir(parents=True, exist_ok=True)
    environment = {**os.environ, "SOURCE_DATE_EPOCH": "1790640000"}  # 2026-09-29 UTC
    for stem, maker in (("equivalence-evidence", equivalence_evidence),
                        ("hybrid-source-map", hybrid_source), ("line-meaning", line_meaning)):
        source = args.output_dir / f"{stem}.svg"
        source.write_text(maker(), encoding="utf-8")
        subprocess.run(["rsvg-convert", "-f", "pdf", "-o", str(source.with_suffix(".pdf")), str(source)],
                       check=True, env=environment)
        print(source.name, source.with_suffix(".pdf").name)


if __name__ == "__main__":
    main()
