<?php

namespace App\Libraries;

/**
 * Minimal multi-page PDF table writer using PDF core fonts (Helvetica).
 */
class SimpleTablePdf
{
    private string $title;
    private ?string $subtitle;
    /** @var list<string> */
    private array $pages = [];
    private string $buffer = '';
    private float $y = 0;
    private int $pageNo = 0;

    private const PAGE_W = 842.0; // A4 landscape
    private const PAGE_H = 595.0;
    private const MARGIN = 36.0;
    private const ROW_H = 14.0;
    private const FONT_SIZE = 8.0;
    private const TITLE_SIZE = 14.0;

    public function __construct(string $title, ?string $subtitle = null)
    {
        $this->title    = $title;
        $this->subtitle = $subtitle;
        $this->newPage(true);
    }

    /**
     * @param list<string>                         $headers
     * @param list<list<string|int|float|null>>    $rows
     */
    public function addSection(?string $sectionTitle, array $headers, array $rows): void
    {
        if ($headers === []) {
            return;
        }

        $colCount = max(1, count($headers));
        $usable   = self::PAGE_W - (2 * self::MARGIN);
        $colW     = $usable / $colCount;

        if ($sectionTitle !== null && $sectionTitle !== '') {
            $this->ensureSpace(24);
            $this->writeText(self::MARGIN, $this->y, $sectionTitle, 10, true);
            $this->y -= 16;
        }

        $this->ensureSpace(self::ROW_H * 2);
        $this->drawHeaderRow($headers, $colW);
        $this->y -= self::ROW_H;

        foreach ($rows as $row) {
            $this->ensureSpace(self::ROW_H + 2);
            $this->drawDataRow($headers, $row, $colW);
            $this->y -= self::ROW_H;
        }

        $this->y -= 10;
    }

    public function render(): string
    {
        $this->flushPage();

        $objects = [];
        $objects[] = '<< /Type /Catalog /Pages 2 0 R >>';

        $kids = [];
        $pageCount = count($this->pages);
        for ($i = 0; $i < $pageCount; $i++) {
            $kids[] = (3 + ($i * 2)) . ' 0 R';
        }
        $objects[] = '<< /Type /Pages /Kids [' . implode(' ', $kids) . '] /Count ' . $pageCount . ' >>';

        $fontObjNum = 3 + ($pageCount * 2);
        for ($i = 0; $i < $pageCount; $i++) {
            $contentObj = 4 + ($i * 2);
            $pageObjNum = 3 + ($i * 2);
            $objects[]  = sprintf(
                '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 %.2F %.2F] /Contents %d 0 R /Resources << /Font << /F1 %d 0 R >> >> >>',
                self::PAGE_W,
                self::PAGE_H,
                $contentObj,
                $fontObjNum
            );
            $stream = $this->pages[$i];
            $objects[] = '<< /Length ' . strlen($stream) . " >>\nstream\n" . $stream . "\nendstream";
        }

        $objects[] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>';

        $pdf = "%PDF-1.4\n";
        $offsets = [0];
        foreach ($objects as $i => $obj) {
            $offsets[$i + 1] = strlen($pdf);
            $pdf .= ($i + 1) . " 0 obj\n" . $obj . "\nendobj\n";
        }

        $xrefPos = strlen($pdf);
        $count   = count($objects) + 1;
        $pdf .= "xref\n0 {$count}\n";
        $pdf .= "0000000000 65535 f \n";
        for ($i = 1; $i < $count; $i++) {
            $pdf .= sprintf("%010d 00000 n \n", $offsets[$i]);
        }
        $pdf .= "trailer\n<< /Size {$count} /Root 1 0 R >>\n";
        $pdf .= "startxref\n{$xrefPos}\n%%EOF";

        return $pdf;
    }

    private function newPage(bool $withTitle): void
    {
        if ($this->buffer !== '') {
            $this->flushPage();
        }

        $this->pageNo++;
        $this->buffer = "BT /F1 " . self::FONT_SIZE . " Tf ET\n";
        $this->y      = self::PAGE_H - self::MARGIN;

        if ($withTitle) {
            $this->writeText(self::MARGIN, $this->y, $this->title, self::TITLE_SIZE, true);
            $this->y -= 18;
            if ($this->subtitle !== null && $this->subtitle !== '') {
                $this->writeText(self::MARGIN, $this->y, $this->subtitle, 9, false);
                $this->y -= 14;
            }
            $this->writeText(self::MARGIN, $this->y, 'Generated ' . date('Y-m-d H:i'), 8, false);
            $this->y -= 20;
        } else {
            $this->writeText(self::MARGIN, $this->y, $this->title . ' (continued)', 10, true);
            $this->y -= 18;
        }

        $this->writeText(self::PAGE_W - self::MARGIN - 40, self::MARGIN - 16, 'Page ' . $this->pageNo, 8, false);
    }

    private function flushPage(): void
    {
        if ($this->buffer === '') {
            return;
        }
        $this->pages[] = $this->buffer;
        $this->buffer  = '';
    }

    private function ensureSpace(float $needed): void
    {
        if ($this->y - $needed < self::MARGIN + 20) {
            $this->newPage(false);
        }
    }

    /**
     * @param list<string> $headers
     */
    private function drawHeaderRow(array $headers, float $colW): void
    {
        $x = self::MARGIN;
        foreach ($headers as $header) {
            $this->writeText($x + 2, $this->y, $this->truncate((string) $header, $colW), self::FONT_SIZE, true);
            $x += $colW;
        }
        $this->line(self::MARGIN, $this->y - 3, self::PAGE_W - self::MARGIN, $this->y - 3);
    }

    /**
     * @param list<string>                      $headers
     * @param list<string|int|float|null>       $row
     */
    private function drawDataRow(array $headers, array $row, float $colW): void
    {
        $x = self::MARGIN;
        foreach ($headers as $i => $_) {
            $value = $row[$i] ?? '';
            $this->writeText($x + 2, $this->y, $this->truncate((string) $value, $colW), self::FONT_SIZE, false);
            $x += $colW;
        }
    }

    private function writeText(float $x, float $y, string $text, float $size, bool $bold): void
    {
        // Core Helvetica has no bold variant object — simulate with slight overprint offset.
        $escaped = $this->escape($text);
        $this->buffer .= sprintf("BT /F1 %.2F Tf 1 0 0 1 %.2F %.2F Tm (%s) Tj ET\n", $size, $x, $y, $escaped);
        if ($bold) {
            $this->buffer .= sprintf("BT /F1 %.2F Tf 1 0 0 1 %.2F %.2F Tm (%s) Tj ET\n", $size, $x + 0.35, $y, $escaped);
        }
    }

    private function line(float $x1, float $y1, float $x2, float $y2): void
    {
        $this->buffer .= sprintf("%.2F %.2F m %.2F %.2F l S\n", $x1, $y1, $x2, $y2);
    }

    private function escape(string $text): string
    {
        // WinAnsi-ish: strip characters Helvetica cannot encode.
        $text = str_replace(["\\", '(', ')'], ['\\\\', '\\(', '\\)'], $text);
        $text = preg_replace('/[^\x20-\x7E]/', '?', $text) ?? $text;

        return $text;
    }

    private function truncate(string $text, float $colW): string
    {
        $maxChars = max(4, (int) floor($colW / 4.5));
        if (strlen($text) <= $maxChars) {
            return $text;
        }

        return substr($text, 0, $maxChars - 3) . '...';
    }
}
