"""Writes an archival cover sheet whose metadata carries fields of its own: a
records office's reference, department, retention date and classification.

The Python twin of the `write_own_schema` example in Rust: the same document,
through the binding rather than through the library directly.

PDF/A accepts metadata no standard ever heard of, but not silently. A property
from outside the standard schemas travels with a description of itself — what it
is called, how it is to be read, who it is written for, and what it means — or
the file is not conformant. One schema says all of that, and the library writes
both halves: the properties into the packet, the description into the extension
block.

The page prints the very fields the packet declares, from one list read twice, so
that what a person reads and what a machine reads cannot drift apart.

The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks which
set the sheet is drawn up in. The names the properties are written under are not
words: they are what a machine looks them up by, and they are spelled the same
way whoever reads the sheet. What each of them says and what each of them means
are words, and travel into the packet with the page.

Whether the result is really PDF/A-3 is not for us to say. `veraPDF` says, and
`scripts/check_pdfa.sh` asks it.

Usage: python examples/write_own_schema.py [out.pdf] [font.ttf]
       HQF_PDF_LANG=fr python examples/write_own_schema.py
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import NamedTuple

import _language
import _licence
import _out

import hqf_pdf

# When the sheet was drawn up. The library never reads the clock, so the caller says
# when.
ISSUED = "2026-07-14T09:30:00+02:00"

# The namespace the office's properties live in. It is the office's own, and resolves to
# nothing: a namespace names a set of properties, it is not an address anything fetches.
NAMESPACE = "https://hqf-pdf.example/ns/records/1.0/"

# The prefix the office's properties are written with.
PREFIX = "rec"


class Field(NamedTuple):
    """One field of the office's own: what it is written under, how a validator is to
    read it, and who it is written for.

    What it says and what it means are words, and stand in `Words` under the same order.
    """

    name: str
    value_type: hqf_pdf.XmpValueType
    category: hqf_pdf.XmpCategory


# The fields the sheet carries. The page draws them and the packet declares them, from
# this one list.
FIELDS = (
    Field("Reference", hqf_pdf.XmpValueType.Text, hqf_pdf.XmpCategory.External),
    Field("Department", hqf_pdf.XmpValueType.Text, hqf_pdf.XmpCategory.External),
    Field("RetainUntil", hqf_pdf.XmpValueType.Date, hqf_pdf.XmpCategory.Internal),
    Field(
        "Classification", hqf_pdf.XmpValueType.Text, hqf_pdf.XmpCategory.External
    ),
    Field("Sheets", hqf_pdf.XmpValueType.Integer, hqf_pdf.XmpCategory.Internal),
)


@dataclass(frozen=True)
class Words:
    """Every word the sheet is drawn up in, in one language."""

    # What the sheet is called, on the page and in the file's own title.
    title: str
    # The paragraph under the title.
    prose: str
    # The heading over what the sheet says, and the one over what the packet declares of
    # it.
    headings: tuple[str, str]
    # What this sheet says for each field of `FIELDS`. A reference, a date and a count
    # are not words; a department and how widely a dossier may be circulated are.
    values: tuple[str, str, str, str, str]
    # What each field of `FIELDS` means, which is what the packet carries beside it so
    # that a validator will take it.
    descriptions: tuple[str, str, str, str, str]
    # The note at the foot of the sheet.
    note: str
    # What the records office calls its own metadata, for a person reading the packet.
    schema: str
    # What the file says it is about.
    subject: str

    def document_title(self) -> str:
        """What the file calls itself: what the sheet is, then the reference it is filed
        under."""
        return f"{self.title} {self.values[0]}"

    def declared_schema(self) -> hqf_pdf.XmpSchema:
        """The schema the packet declares, built from the fields the page draws."""
        properties = [
            hqf_pdf.XmpProperty(
                field.name,
                value,
                description,
                value_type=field.value_type,
                category=field.category,
            )
            for field, value, description in zip(
                FIELDS, self.values, self.descriptions
            )
        ]
        return hqf_pdf.XmpSchema(self.schema, NAMESPACE, PREFIX, properties)


# The sheet in English.
ENGLISH = Words(
    title="Records office cover sheet",
    prose="The fields below are the records office's own. No part of the PDF "
    "standard knows what a retention date is, so the file explains itself: "
    "each field arrives in the metadata packet with its name, the way it is "
    "to be read, who it is written for, and what it means. An archival "
    "validator accepts them on that description, and refuses metadata that "
    "turns up without one.",
    headings=(
        "What the sheet says",
        "What the packet declares of each of them",
    ),
    values=(
        "RO-2026-004318",
        "Planning",
        "2056-12-31T23:59:59+01:00",
        "Public",
        "1",
    ),
    descriptions=(
        "The reference the records office files the dossier under",
        "The department the dossier came from",
        "The date the dossier may be destroyed, and not before",
        "How widely the dossier may be circulated",
        "How many sheets the dossier holds",
    ),
    note="Every value above is written twice: once on this page, and once in the "
    "packet a machine reads, under the prefix the schema names. Reading the "
    "file's metadata gives back this table, field for field.",
    schema="Records Office Schema",
    subject="An archival sheet carrying metadata of its own",
)

# The sheet in French.
FRENCH = Words(
    title="Bordereau du service d'archives",
    prose="Les champs ci-dessous appartiennent au service d'archives. Aucune "
    "partie de la norme PDF ne sait ce qu'est une date de conservation, donc le "
    "fichier s'explique : chaque champ arrive dans le paquet de métadonnées avec "
    "son nom, la façon dont il doit être lu, pour qui il est écrit et ce qu'il "
    "veut dire. Un validateur d'archivage les accepte sur cette description, et "
    "refuse une donnée qui se présente sans elle.",
    headings=(
        "Ce que dit le bordereau",
        "Ce que le paquet déclare de chacun",
    ),
    values=(
        "RO-2026-004318",
        "Urbanisme",
        "2056-12-31T23:59:59+01:00",
        "Public",
        "1",
    ),
    descriptions=(
        "La référence sous laquelle le service d'archives classe le dossier",
        "Le service d'où vient le dossier",
        "La date à partir de laquelle le dossier peut être détruit",
        "Jusqu'où le dossier peut être diffusé",
        "Combien de feuilles contient le dossier",
    ),
    note="Chaque valeur ci-dessus est écrite deux fois : une fois sur cette page, "
    "une fois dans le paquet que lit une machine, sous le préfixe que nomme le "
    "schéma. Relire les métadonnées du fichier redonne ce tableau, champ pour "
    "champ.",
    schema="Schéma du service d'archives",
    subject="Un bordereau d'archives qui porte ses propres métadonnées",
)

# Every language the example is written in. A language is added by writing its own set
# of words and naming it here.
WORDS = {_language.ENGLISH: ENGLISH, _language.FRENCH: FRENCH}

# The left edge the sheet is set from.
X = 72.0

# The room the sheet has across the page.
WIDTH = 451.0

# How far into that room the values stand, leaving the names to their left.
VALUES = 150.0

# Where the first block starts, down from the top of the page.
TOP = 780.0


def block(
    content: hqf_pdf.Content,
    flow: hqf_pdf.TextFlow,
    text: str,
    top: float,
) -> float:
    """Draws `text` in `flow` at `top`, and hands back the top of whatever follows
    it."""
    lines = flow.break_lines(text, WIDTH)
    flow.draw(content, lines, X, top, WIDTH)
    return top - flow.height(lines)


def cover_sheet(handle: hqf_pdf.FontHandle, words: Words) -> hqf_pdf.Page:
    """The sheet: what it is, what it says about itself, and how a reader may check."""
    content = hqf_pdf.Content()

    title = hqf_pdf.TextFlow(handle, 20.0, space_after=14.0)
    prose = hqf_pdf.TextFlow(
        handle,
        10.5,
        leading=15.0,
        align=hqf_pdf.Align.Justify,
        space_after=16.0,
    )
    heading = hqf_pdf.TextFlow(handle, 12.0, space_before=20.0, space_after=8.0)
    row = hqf_pdf.TextFlow(
        handle,
        11.0,
        leading=17.0,
        tab_method=hqf_pdf.TabMethod.Ruler,
        tab_ruler=[VALUES],
    )
    declared = hqf_pdf.TextFlow(handle, 9.5, leading=14.0).hanging_list(0.0, VALUES)
    note = hqf_pdf.TextFlow(handle, 9.0, leading=13.0, space_before=18.0)

    top = block(content, title, words.title, TOP)
    top = block(content, prose, words.prose, top)

    top = block(content, heading, words.headings[0], top)
    values = "\n".join(
        f"{field.name}\t{value}" for field, value in zip(FIELDS, words.values)
    )
    top = block(content, row, values, top)

    top = block(content, heading, words.headings[1], top)
    rows = "\n".join(
        f"{PREFIX}:{field.name}\t{field.value_type.as_str()} · "
        f"{field.category.as_str()} · {description}"
        for field, description in zip(FIELDS, words.descriptions)
    )
    top = block(content, declared, rows, top)

    block(content, note, words.note, top)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    return page


def main() -> None:
    language = _language.from_environment()
    words = _language.words_of(WORDS, language)

    # A named file is written as named; the default one carries the language, so the two
    # languages do not overwrite each other in `tmp/`.
    out = _out.output_path(Path(_language.file_name("own_schema.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_conformance(hqf_pdf.PdfA.A3B)
    document.set_metadata(
        hqf_pdf.Metadata(
            title=words.document_title(),
            author="Olivier Pons",
            subject=words.subject,
            producer="hqf-pdf",
            created=ISSUED,
            schemas=[words.declared_schema()],
        )
    )

    handle = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
    document.add_page(cover_sheet(handle, words))

    written = document.write(out)
    print(f"wrote {out}: {written} bytes")


if __name__ == "__main__":
    main()
