"""Writes an electronic invoice: a PDF/A-3 file carrying its own XML.

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

This is what the whole library is for. A person opens the file and sees an
invoice; an accounting system opens the same file and reads the XML sealed
inside it. Neither can be separated from the other, which is the point: the page
and the data are one document, and cannot disagree.

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

The page is written in the language `HQF_PDF_LANG` names. The XML is not: it is read
by an accounting system rather than by a person, its element names come from a
standard, and the same file goes to a buyer in any country. So the words a person
reads are held in `Words`, once per language, and the attachment stands apart from
them.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# When the invoice was issued. The library never reads the clock — a document that
# stamped itself with the time would be a different file on every build — so the caller
# says when, and here the caller is an example.
ISSUED = "2026-07-14T09:30:00+02:00"

# The invoice, as a machine reads it: a Factur-X MINIMUM profile document, the least the
# standard allows, and enough to show the shape of the thing. A real invoice carries
# more, and carries it under the same rules.
INVOICE_XML = """<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice
    xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
    xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
    xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
  <rsm:ExchangedDocumentContext>
    <ram:GuidelineSpecifiedDocumentContextParameter>
      <ram:ID>urn:factur-x.eu:1p0:minimum</ram:ID>
    </ram:GuidelineSpecifiedDocumentContextParameter>
  </rsm:ExchangedDocumentContext>
  <rsm:ExchangedDocument>
    <ram:ID>1964413</ram:ID>
    <ram:TypeCode>380</ram:TypeCode>
    <ram:IssueDateTime>
      <udt:DateTimeString format="102">20260714</udt:DateTimeString>
    </ram:IssueDateTime>
  </rsm:ExchangedDocument>
  <rsm:SupplyChainTradeTransaction>
    <ram:ApplicableHeaderTradeAgreement>
      <ram:SellerTradeParty>
        <ram:Name>Olivier Pons</ram:Name>
        <ram:SpecifiedLegalOrganization>
          <ram:ID schemeID="0002">123456789</ram:ID>
        </ram:SpecifiedLegalOrganization>
        <ram:PostalTradeAddress>
          <ram:CountryID>FR</ram:CountryID>
        </ram:PostalTradeAddress>
      </ram:SellerTradeParty>
      <ram:BuyerTradeParty>
        <ram:Name>ACME Ltd</ram:Name>
      </ram:BuyerTradeParty>
    </ram:ApplicableHeaderTradeAgreement>
    <ram:ApplicableHeaderTradeDelivery/>
    <ram:ApplicableHeaderTradeSettlement>
      <ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
      <ram:SpecifiedTradeSettlementHeaderMonetarySummation>
        <ram:TaxBasisTotalAmount>4250.00</ram:TaxBasisTotalAmount>
        <ram:TaxTotalAmount currencyID="EUR">850.00</ram:TaxTotalAmount>
        <ram:GrandTotalAmount>5100.00</ram:GrandTotalAmount>
        <ram:DuePayableAmount>5100.00</ram:DuePayableAmount>
      </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
    </ram:ApplicableHeaderTradeSettlement>
  </rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>
"""

# The invoice's number, which the page shows and the XML carries under `ram:ID`. One
# number, written once on each side.
NUMBER = "1964413"

# What each of the three billed lines comes to, in the order they are set. These are the
# figures the XML totals, so they read the same wherever the page is read.
AMOUNTS = ["4 250.00 EUR", "850.00 EUR", "5 100.00 EUR"]

# How many characters a billed line runs to: its label, a space, the leader dots, a
# space, then its amount. The dots take whatever is left, so the amounts end on the same
# character however long the label before them is.
LINE_CHARS = 56

# The size each line of the page is set at, in points, in the order they are drawn.
SIZES = [16.0, 11.0, 11.0, 11.0, 12.0]

# How far in from the left edge of the sheet every line is set, in points.
LEFT = 72.0


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

    What is not language stays out of it: the invoice's number, who issued it and the
    three amounts read the same wherever the page is read, and so does every word of
    the XML.
    """

    # What stands before the invoice's number, at the head of the page and in the
    # document's own title.
    number_label: str
    # What the file says it holds.
    subject: str
    # The line that gives the day it was issued and the term it falls due in.
    dates: str
    # What each billed line is called, in the order `AMOUNTS` prices them.
    items: tuple[str, str, str]

    def title(self) -> str:
        """What the page is headed by, which is also what the file says of itself."""
        return f"{self.number_label} {NUMBER}"

    def lines(self) -> list[str]:
        """The lines the page shows, which say what the XML says."""
        lines = [self.title(), self.dates]
        for item, amount in zip(self.items, AMOUNTS):
            dots = max(LINE_CHARS - len(item) - len(amount) - 2, 0)
            lines.append(f"{item} {'.' * dots} {amount}")
        return lines


# The page in English.
ENGLISH = Words(
    number_label="Invoice No.",
    subject="An electronic invoice, in the Factur-X format",
    dates="Issued 14 July 2026 — due within 30 days",
    items=("Rendering engine development", "VAT at 20 %", "Total due"),
)

# The page in French.
FRENCH = Words(
    number_label="Facture n°",
    subject="Une facture électronique, au format Factur-X",
    dates="Émise le 14 juillet 2026 — payable sous 30 jours",
    items=("Développement du moteur de rendu", "TVA 20 %", "Total à payer"),
)

# 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}


def invoice_page(handle: hqf_pdf.FontHandle, words: Words) -> hqf_pdf.Page:
    """The page a person reads, saying what the attached XML says."""
    content = hqf_pdf.Content()

    top = 760.0
    for line, size in zip(words.lines(), SIZES):
        content.draw_text(handle, size, LEFT, top, line)
        top -= size * 2.0

    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("facturx.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.title(),
            author="Olivier Pons",
            subject=words.subject,
            producer="hqf-pdf",
            created=ISSUED,
            invoice=hqf_pdf.Invoice(
                "factur-x.xml", hqf_pdf.InvoiceProfile.Minimum, "1.0"
            ),
        )
    )

    # What the attachment *is* to the document is not decoration, and has no sensible
    # default: `Data` is right for the profiles that carry only part of the invoice, and
    # `Alternative` for those that carry all of it. This one is MINIMUM, which carries
    # part.
    document.attach(
        hqf_pdf.Attachment.invoice(
            INVOICE_XML.encode(), hqf_pdf.Relationship.Data, ISSUED
        )
    )

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

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


if __name__ == "__main__":
    main()
