<?php

namespace App\Services\Billing;

use TCPDF;

/**
 * The page furniture: the logo block at the top and the footer at the bottom.
 *
 * A subclass because TCPDF draws headers and footers through overridden
 * methods, and the footer has to repeat on every page — an invoice that runs to
 * two pages must carry the company's register and VAT numbers on both, or the
 * second page is not part of a valid invoice.
 *
 * The body is not built here. It is a Blade template rendered to HTML and
 * handed to writeHTML(), because a table of line items expressed as a hundred
 * Cell() calls with millimetre coordinates is a table nobody will ever adjust.
 */
final class InvoiceDocument extends TCPDF
{
    /** @var array<string, mixed> */
    private array $issuer = [];

    private string $logoFile = '';

    private string $pageLabel = 'Seite';

    /**
     * What to call the issuer's own VAT number in the footer.
     *
     * It used to be printed bare, between the register number and the court, and
     * a bare `ATU12345678` is only recognisable to somebody who already knows
     * what they are looking at. An auditor scanning a document for "UID" found
     * the RECIPIENT's, which is labelled, and not ours — so the one number an
     * Austrian invoice is legally worthless without read as a reference of some
     * kind. The document was valid; it was unreadable.
     */
    private string $vatLabel = 'UID';

    /** @param array<string, mixed> $issuer */
    public function setIssuer(array $issuer, string $logoFile = '', string $pageLabel = 'Seite', string $vatLabel = 'UID'): void
    {
        $this->issuer = $issuer;
        $this->logoFile = $logoFile;
        $this->pageLabel = $pageLabel;
        $this->vatLabel = $vatLabel;
    }

    public function Header(): void // phpcs:ignore
    {
        if ($this->logoFile === '' || ! is_file($this->logoFile)) {
            return;
        }

        // Top right, height-constrained. Width 0 lets TCPDF keep the aspect
        // ratio — a logo squeezed to a fixed box is worse than none.
        $this->Image($this->logoFile, 140, 12, 0, 18, '', '', 'T', false, 300, 'R');
    }

    public function Footer(): void // phpcs:ignore
    {
        $this->SetY(-26);
        $this->SetFont('dejavusans', '', 6.5);
        $this->SetTextColor(110, 110, 122);

        $columns = self::footerColumns($this->issuer, $this->vatLabel);

        // A hairline above it, the same one the totals block uses, so the page
        // reads as one document rather than two halves.
        $this->SetDrawColor(233, 233, 238);
        $this->Line(20, $this->GetY() - 3, 190, $this->GetY() - 3);

        $width = 42.5;

        foreach ($columns as $index => $lines) {
            $this->SetXY(20 + ($index * $width), -24);
            $this->MultiCell($width - 2, 3, implode("\n", $lines), 0, 'L', false, 0, '', '', true, 0, false, true, 0, 'T');
        }

        $this->SetXY(150, -30);
        $this->SetFont('dejavusans', '', 6.5);
        $this->Cell(40, 3, $this->pageLabel.' '.$this->getAliasNumPage().'/'.$this->getAliasNbPages(), 0, 0, 'R');
    }

    /**
     * What the four footer columns SAY, apart from where they are drawn.
     *
     * Separated from Footer() because the two are different kinds of decision and
     * only one of them can be read: what belongs on the bottom of an invoice is a
     * legal question with a right answer, while millimetre coordinates are a
     * layout. Inside a TCPDF method the answer is only observable by rendering a
     * PDF and un-subsetting its fonts, which is why the VAT number went unlabelled
     * for months without anything noticing.
     *
     * Empty entries are dropped rather than printed as blank lines: a company with
     * no register number should not have a gap where one would be.
     *
     * @param  array<string, mixed>  $issuer
     * @return array<int, array<int, string>>
     */
    public static function footerColumns(array $issuer, string $vatLabel = 'UID'): array
    {
        $columns = [
            [
                $issuer['name'] ?? '',
                $issuer['address'] ?? '',
                trim(($issuer['postcode'] ?? '').' '.($issuer['city'] ?? '')),
            ],
            [
                $issuer['phone'] ?? '',
                $issuer['email'] ?? '',
                $issuer['website'] ?? '',
            ],
            [
                $issuer['register_number'] ?? '',
                // Labelled, unlike its neighbours, because this is the one the
                // reader is looking FOR — and an unlabelled ATU number between a
                // register number and a court reads as a reference of some kind.
                self::labelled($vatLabel, $issuer['vat_id'] ?? ''),
                $issuer['register_court'] ?? '',
            ],
            [
                $issuer['bank_name'] ?? '',
                $issuer['iban'] ?? '',
                $issuer['bic'] ?? '',
            ],
        ];

        return array_map(
            fn (array $lines) => array_values(array_filter(
                array_map(fn ($line) => trim((string) $line), $lines),
                fn (string $line) => $line !== '',
            )),
            $columns,
        );
    }

    /**
     * "UID: ATU12345678", or nothing at all when there is no number.
     *
     * An empty string rather than a label on its own, so a company with no VAT
     * number recorded keeps the blank line it had before this label existed.
     */
    private static function labelled(string $label, mixed $value): string
    {
        $value = trim((string) $value);

        return $value === '' ? '' : $label.': '.$value;
    }
}
