#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Original A4 factor and fraction aids; default compares SVG source read-only."""
from __future__ import annotations

import argparse
import subprocess
from html import escape
from pathlib import Path

HERE = Path(__file__).resolve().parent
RIGHTS = "SubjectNest by NeuroForgeIO Pty Ltd · CC BY 4.0 · 2026"


def txt(x: float, y: float, value: str, size: float = 4, 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 line(x1: float, y1: float, x2: float, y2: float, width: float = .45) -> str:
    return f'<path d="M{x1} {y1} L{x2} {y2}" fill="none" stroke="black" stroke-width="{width}"/>'


def page(title: str, desc: str, body: list[str]) -> str:
    return "\n".join([
        '<svg xmlns="http://www.w3.org/2000/svg" width="210mm" height="297mm" viewBox="0 0 210 297" role="img" aria-labelledby="title desc">',
        f'<title id="title">{escape(title)}</title>',
        f'<desc id="desc">{escape(desc)}</desc>',
        '<rect width="210" height="297" fill="white"/>',
        '<g fill="black" font-family="DejaVu Sans, sans-serif">',
        txt(12, 19, title, 6.4, True), line(12, 25, 198, 25), *body,
        line(12, 281, 198, 281), txt(12, 289, RIGHTS, 2.8),
        '</g>', '</svg>', ''
    ])


def factor_array_lab() -> str:
    body = [txt(12, 36, 'Choose a total. Show arrays or record factor pairs. Use positive whole numbers.', 3.65),
            txt(12, 48, 'Total N = ______', 4.8, True),
            txt(113, 48, 'Grid: 10 by 10 blank cells', 3.65)]
    x0, y0, cell = 13, 56, 8.4
    for i in range(11):
        body += [line(x0 + i * cell, y0, x0 + i * cell, y0 + 10 * cell, .35),
                 line(x0, y0 + i * cell, x0 + 10 * cell, y0 + i * cell, .35)]
    body += [txt(109, 67, 'Distinct factor pairs:', 4, True),
             txt(109, 83, '____ × ____', 4.1), txt(109, 100, '____ × ____', 4.1),
             txt(109, 117, '____ × ____', 4.1), txt(109, 134, '____ × ____', 4.1),
             txt(12, 154, 'More pairs / how I know I have found them all:', 3.9, True),
             line(12, 168, 198, 168), line(12, 180, 198, 180),
             txt(12, 197, 'Prime? [  ]  Composite? [  ]  Square? [  ]  Neither prime nor composite? [  ]', 3.25),
             txt(12, 211, 'A square may also be composite. A factor list needs a reason.', 3.6),
             txt(12, 229, 'My exact evidence:', 4.1, True),
             line(12, 241, 198, 241), line(12, 253, 198, 253),
             txt(12, 268, 'For long rows such as 1 × 23, write the pair; the 10 × 10 grid is optional.', 3.3)]
    return page('Year 6 maths · factor-array investigation',
                'Original black-on-white A4 investigation sheet. A blank 10 by 10 grid sits left of four blank factor-pair lines. Below are blank lines for a completeness reason, prime/composite/square/neither checkboxes, and exact evidence. A note says the grid is optional for rows longer than ten. No number or answer is filled in.', body)


def square_factor_strip() -> str:
    body = [txt(12, 36, 'Fill each total and explain both its square and factor properties.', 3.9),
            txt(12, 49, 'The same number may have two labels. One has a special case.', 3.7)]
    for x, label in ((14, 'Side n'), (55, 'n × n'), (102, 'Total'), (142, 'Prime / composite / neither')):
        body.append(txt(x, 66, label, 3.65, True))
    body += [line(12, 71, 198, 71, .8)]
    for i, n in enumerate(range(1, 8)):
        y = 83 + i * 25
        body += [txt(16, y, str(n), 5, True), txt(55, y, f'{n} × {n} = ______', 3.9),
                 txt(103, y, '______', 3.9), txt(143, y, '________________', 3.45),
                 line(12, y + 6, 198, y + 6, .35)]
    body += [txt(12, 267, 'Why is 1 different? __________________________________________', 3.6)]
    return page('Year 6 maths · square and factor strip',
                'Seven-row blank table for positive whole side lengths 1 through 7. Each row has n times n with a blank answer, a blank total, and a blank classification line. It prompts why 1 is different. No square totals or classification answers are printed.', body)


def ticks(x0: float, x1: float, y: float, gaps: int, middle: bool = False) -> list[str]:
    out = [line(x0, y, x1, y, .85)]
    for i in range(gaps + 1):
        x = x0 + (x1 - x0) * i / gaps
        height = 7 if i in (0, gaps) or (middle and i == gaps // 2) else 4
        out.append(line(x, y - height, x, y + height, .65 if height == 7 else .4))
    return out


def fraction_lines() -> str:
    body = [txt(12, 36, 'Count equal GAPS, not tick marks. Label equivalent names at one point.', 3.8, True),
            txt(12, 52, 'A · zero to one: 12 equal gaps', 4.5, True),
            *ticks(18, 192, 76, 12),
            txt(15, 95, '0', 4.3, True), txt(188, 95, '1', 4.3, True),
            txt(12, 114, 'My mark(s) and reason:', 3.9), line(12, 127, 198, 127),
            txt(12, 148, 'B · zero to two: 12 equal gaps in EACH unit, 24 in all', 4.2, True),
            *ticks(18, 192, 174, 24, middle=True),
            txt(15, 193, '0', 4.3, True), txt(102, 193, '1', 4.3, True),
            txt(188, 193, '2', 4.3, True),
            txt(12, 213, 'My equivalent twelfth names:', 3.9), line(12, 226, 198, 226),
            txt(12, 246, 'These two printed lines have DIFFERENT physical scales.', 3.75, True),
            txt(12, 258, 'Compare values within a line; do not transfer centimetres between lines.', 3.55),
            txt(12, 270, 'The unit from 0 to 1 in line B equals the unit from 1 to 2 in B.', 3.4)]
    return page('Year 6 maths · same-whole fraction number lines',
                'Two original blank horizontal number lines on one A4 page. Line A spans 0 to 1 in 12 equal gaps. Line B spans 0 to 2 in 24 equal gaps, with 1 at the middle twelfth gap. No interior fraction is marked. A note warns that the printed lines have different physical scales. Response lines are blank.', body)


def equal_whole_bars() -> str:
    body = [txt(12, 36, 'Every bar is the SAME-LENGTH whole. Shade or place a fraction mark.', 4, True),
            txt(12, 48, 'The partitions within each bar are equal. No answer is pre-shaded.', 3.65)]
    for y, parts, label in ((67, 2, 'halves'), (112, 3, 'thirds'),
                            (157, 4, 'quarters'), (202, 12, 'twelfths')):
        body += [txt(15, y - 5, label, 4.4, True),
                 line(18, y, 186, y, .8), line(18, y + 24, 186, y + 24, .8)]
        for i in range(parts + 1):
            x = 18 + 168 * i / parts
            body.append(line(x, y, x, y + 24, .6))
    body += [txt(12, 251, 'Same position, two names:', 4.1, True),
             line(67, 253, 198, 253),
             txt(12, 269, 'Compare shaded LENGTHS only when the whole bars match.', 3.65)]
    return page('Year 6 maths · equal-whole fraction bars',
                'Four equal-length, unshaded rectangular bars aligned at the same left and right endpoints. They are divided into 2, 3, 4 and 12 equal parts respectively. A blank line below asks for two names at the same position. No fraction answer is shown; a note says whole bars must match.', body)


PAGES = {"factor-array-lab": factor_array_lab, "square-factor-strip": square_factor_strip,
         "fraction-lines": fraction_lines, "equal-whole-bars": equal_whole_bars}


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--write", action="store_true")
    args = parser.parse_args()
    for name, make in PAGES.items():
        svg = HERE / f"{name}.svg"
        expected = make().encode("utf-8")
        if args.write:
            svg.write_bytes(expected)
            subprocess.run(["rsvg-convert", "-f", "pdf", "-o", str(svg.with_suffix('.pdf')), str(svg)], check=True)
        elif not svg.is_file() or svg.read_bytes() != expected:
            raise SystemExit(f"FAIL: {svg.name} missing or differs from drawing source")
    print("PASS: four original A4 SVG aids" + (" and PDF exports" if args.write else " match drawing source"))


if __name__ == "__main__":
    main()
