<?php
/**
 * PacificInvoicePdf — branded FPDF invoice for Pacific Group.
 *
 * Uses the client's logo (images/logo.png) top-left, brand-blue accents
 * on the header rule + table thead + totals rule, and Open Sans for body
 * copy (self-hosted in /fonts/fpdf/).
 *
 * Called from CustomerInvoicesController::pdfAction() which streams the
 * output to the browser as an attachment.
 *
 * FPDF is Latin-1 only. Every string that reaches a Cell() call goes
 * through $this->safe() first so unicode dashes, arrows, and non-breaking
 * spaces don't emit `Â·` mojibake.
 */
class PacificInvoicePdf extends FPDF
{
    // Pacific palette expressed in RGB triplets for FPDF's colour APIs.
    private const C_BRAND    = [ 38,  38, 245];   // #2626F5
    private const C_INK      = [ 15,  18,  32];   // #0F1220
    private const C_INK_SOFT = [ 33,  38,  59];   // #21263B
    private const C_MUTED    = [107, 116, 132];   // #6B7484
    private const C_RULE     = [226, 231, 239];   // #E2E7EF
    private const C_ROW_BG   = [251, 252, 254];   // #FBFCFE — very pale zebra

    private array $inv = [];
    private array $lines = [];

    public function __construct()
    {
        // FPDF looks up font definition files under FPDF_FONTPATH. The self-hosted
        // Open Sans + Roboto Condensed sit under /fonts/fpdf/, so we define it here
        // before instantiating (matches the pattern in ServiceReports.php).
        if (!defined('FPDF_FONTPATH')) define('FPDF_FONTPATH', ROOT . '/fonts/fpdf/');
        parent::__construct('P', 'mm', 'A4');
        $this->AddFont('OpenSans-Regular', '', 'OpenSans-Regular.php');
        $this->AddFont('OpenSans-Bold',    '', 'opensans-bold.php');
        $this->SetMargins(15, 15, 15);
        $this->SetAutoPageBreak(true, 20);
    }

    public function setInvoice(array $inv, array $lines): void
    {
        $this->inv   = $inv;
        $this->lines = $lines;
        $this->SetTitle($this->safe('Invoice ' . ($inv['invoice_no'] ?? '')));
        $this->SetAuthor($this->safe(defined('COMPANY_NAME') ? COMPANY_NAME : 'Pacific Group'));
    }

    /* ══════════════════════════════════════════════════════════════════
       Header — logo + wordmark on left, invoice metadata block on right
       ══════════════════════════════════════════════════════════════════ */
    public function Header(): void
    {
        $logo = ROOT . '/images/logo.png';
        if (is_file($logo)) {
            // 2100:609 aspect → 55mm wide ≈ 16mm tall
            $this->Image($logo, 15, 12, 55);
        }

        // Right block: INVOICE title + number + date + due
        $this->SetXY(120, 14);
        $this->SetFont('OpenSans-Bold', '', 22);
        $this->SetTextColor(...self::C_BRAND);
        $this->Cell(75, 8, $this->safe('INVOICE'), 0, 2, 'R');

        $this->SetFont('OpenSans-Bold', '', 12);
        $this->SetTextColor(...self::C_INK);
        $this->Cell(75, 6, $this->safe($this->inv['invoice_no'] ?? ''), 0, 2, 'R');

        $this->SetFont('OpenSans-Regular', '', 9);
        $this->SetTextColor(...self::C_MUTED);
        $this->Cell(75, 5, $this->safe('Issued  ' . $this->fmtDate($this->inv['invoice_date'] ?? '')), 0, 2, 'R');
        if (!empty($this->inv['due_date'])) {
            $this->Cell(75, 5, $this->safe('Due     ' . $this->fmtDate($this->inv['due_date'])), 0, 2, 'R');
        }

        // Brand rule under the header — hairline in Pacific Blue
        $this->SetDrawColor(...self::C_BRAND);
        $this->SetLineWidth(0.8);
        $this->Line(15, 34, 195, 34);
        $this->SetLineWidth(0.1);

        // Cursor advance so page-body starts below the rule
        $this->SetY(40);
    }

    /* ══════════════════════════════════════════════════════════════════
       Footer — page number + payment note + company line
       ══════════════════════════════════════════════════════════════════ */
    public function Footer(): void
    {
        $this->SetY(-16);

        // Thin rule
        $this->SetDrawColor(...self::C_RULE);
        $this->Line(15, $this->GetY(), 195, $this->GetY());

        $this->Ln(2);
        $this->SetFont('OpenSans-Regular', '', 8);
        $this->SetTextColor(...self::C_MUTED);
        $company = defined('COMPANY_NAME') ? COMPANY_NAME : 'Pacific Group Company Limited';
        $this->Cell(90, 5, $this->safe($company), 0, 0, 'L');
        $this->Cell(90, 5, $this->safe('Page ' . $this->PageNo() . ' / {nb}'), 0, 0, 'R');
    }

    /* ══════════════════════════════════════════════════════════════════
       Public entry: render the full document
       ══════════════════════════════════════════════════════════════════ */
    public function render(): void
    {
        $this->AliasNbPages();
        $this->AddPage();
        $this->drawBillTo();
        $this->drawLinesTable();
        $this->drawTotals();
        $this->drawFooterNote();
        if (($this->inv['status'] ?? '') === 'void')          $this->stamp('VOID', [220, 38, 38]);
        elseif (($this->inv['status'] ?? '') === 'paid')      $this->stamp('PAID', [14, 159, 110]);
    }

    /* ── Bill-to block ─────────────────────────────────────────────── */
    private function drawBillTo(): void
    {
        $y = $this->GetY();
        $this->SetFont('OpenSans-Regular', '', 8);
        $this->SetTextColor(...self::C_MUTED);
        $this->Cell(0, 4, $this->safe('BILL TO'), 0, 1, 'L');

        $this->SetFont('OpenSans-Bold', '', 12);
        $this->SetTextColor(...self::C_INK);
        $this->Cell(0, 6, $this->safe($this->inv['customer_name'] ?? '—'), 0, 1, 'L');

        $this->SetFont('OpenSans-Regular', '', 9);
        $this->SetTextColor(...self::C_INK_SOFT);
        if (!empty($this->inv['customer_phone'])) $this->Cell(0, 4.5, $this->safe($this->inv['customer_phone']), 0, 1, 'L');
        if (!empty($this->inv['customer_email'])) $this->Cell(0, 4.5, $this->safe($this->inv['customer_email']), 0, 1, 'L');

        $addr = [];
        if (!empty($this->inv['physical_address'])) $addr[] = $this->inv['physical_address'];
        if (!empty($this->inv['customer_city']))    $addr[] = $this->inv['customer_city'];
        foreach ($addr as $line) $this->Cell(0, 4.5, $this->safe($line), 0, 1, 'L');

        // Meta strip on the right side of the same block
        $this->SetXY(120, $y);
        $this->SetFont('OpenSans-Regular', '', 8);
        $this->SetTextColor(...self::C_MUTED);
        $this->Cell(75, 4, $this->safe('TERMS'), 0, 2, 'R');
        $this->SetFont('OpenSans-Regular', '', 9);
        $this->SetTextColor(...self::C_INK);
        $this->Cell(75, 5, $this->safe($this->inv['payment_terms'] ?? ($this->inv['currency'] ?? 'TZS')), 0, 2, 'R');
        $this->SetFont('OpenSans-Regular', '', 8);
        $this->SetTextColor(...self::C_MUTED);
        $this->Cell(75, 4, $this->safe('CURRENCY'), 0, 2, 'R');
        $this->SetFont('OpenSans-Regular', '', 9);
        $this->SetTextColor(...self::C_INK);
        $this->Cell(75, 5, $this->safe($this->inv['currency'] ?? 'TZS'), 0, 2, 'R');

        $this->Ln(6);
    }

    /* ── Line items table ──────────────────────────────────────────── */
    private function drawLinesTable(): void
    {
        // Column widths sum to 180 (matches usable width)
        $w = ['n' => 8, 'desc' => 82, 'qty' => 12, 'unit' => 22, 'net' => 22, 'vat' => 16, 'total' => 18];

        // Header row — brand blue background, white text
        $this->SetFont('OpenSans-Bold', '', 8.5);
        $this->SetFillColor(...self::C_BRAND);
        $this->SetTextColor(255, 255, 255);
        $this->SetDrawColor(...self::C_BRAND);
        $this->Cell($w['n'],     7, $this->safe('#'),            0, 0, 'C', true);
        $this->Cell($w['desc'],  7, $this->safe('DESCRIPTION'),  0, 0, 'L', true);
        $this->Cell($w['qty'],   7, $this->safe('QTY'),          0, 0, 'R', true);
        $this->Cell($w['unit'],  7, $this->safe('UNIT PRICE'),   0, 0, 'R', true);
        $this->Cell($w['net'],   7, $this->safe('NET'),          0, 0, 'R', true);
        $this->Cell($w['vat'],   7, $this->safe('VAT'),          0, 0, 'R', true);
        $this->Cell($w['total'], 7, $this->safe('TOTAL'),        0, 1, 'R', true);

        // Rows
        $this->SetFont('OpenSans-Regular', '', 9);
        $this->SetTextColor(...self::C_INK);
        $this->SetDrawColor(...self::C_RULE);

        foreach ($this->lines as $i => $ln) {
            $rowBg = ($i % 2 === 0);
            if ($rowBg) $this->SetFillColor(...self::C_ROW_BG);
            $this->SetTextColor(...self::C_MUTED);
            $this->Cell($w['n'],     6, $this->safe((string)($ln['line_no'] ?? ($i + 1))), 'B', 0, 'C', $rowBg);
            $this->SetTextColor(...self::C_INK);
            // Description may be long — MultiCell handling could be added; for now truncate to fit
            $desc = $this->truncate($ln['description'] ?? '', 60);
            $this->Cell($w['desc'],  6, $this->safe($desc), 'B', 0, 'L', $rowBg);
            $this->Cell($w['qty'],   6, $this->safe($this->fmtQty((float)($ln['quantity'] ?? 0))), 'B', 0, 'R', $rowBg);
            $this->Cell($w['unit'],  6, $this->safe($this->fmtMoney((float)($ln['unit_price'] ?? 0))), 'B', 0, 'R', $rowBg);
            $this->Cell($w['net'],   6, $this->safe($this->fmtMoney((float)($ln['net_amount'] ?? 0))), 'B', 0, 'R', $rowBg);
            $this->Cell($w['vat'],   6, $this->safe($this->fmtMoney((float)($ln['vat_amount'] ?? 0))), 'B', 0, 'R', $rowBg);
            $this->SetFont('OpenSans-Bold', '', 9);
            $this->Cell($w['total'], 6, $this->safe($this->fmtMoney((float)($ln['total_amount'] ?? 0))), 'B', 1, 'R', $rowBg);
            $this->SetFont('OpenSans-Regular', '', 9);
        }

        $this->Ln(4);
    }

    /* ── Totals block ──────────────────────────────────────────────── */
    private function drawTotals(): void
    {
        $labelW = 40; $valueW = 40;
        $x = 195 - ($labelW + $valueW);  // right-align to page

        $currency = $this->inv['currency'] ?? 'TZS';
        $subtotal = (float)($this->inv['subtotal']     ?? 0);
        $vat      = (float)($this->inv['vat_amount']   ?? 0);
        $total    = (float)($this->inv['total_amount'] ?? 0);
        $paid     = (float)($this->inv['amount_paid']  ?? 0);
        $due      = $total - $paid;

        $this->SetX($x);
        $this->SetFont('OpenSans-Regular', '', 9);
        $this->SetTextColor(...self::C_MUTED);
        $this->Cell($labelW, 6, $this->safe('Subtotal'),   0, 0, 'R');
        $this->SetTextColor(...self::C_INK);
        $this->Cell($valueW, 6, $this->safe($currency . ' ' . $this->fmtMoney($subtotal)), 0, 1, 'R');

        $this->SetX($x);
        $this->SetTextColor(...self::C_MUTED);
        $this->Cell($labelW, 6, $this->safe('VAT'), 0, 0, 'R');
        $this->SetTextColor(...self::C_INK);
        $this->Cell($valueW, 6, $this->safe($currency . ' ' . $this->fmtMoney($vat)), 0, 1, 'R');

        // Total due — bold rule above in brand blue
        $this->SetDrawColor(...self::C_BRAND);
        $this->SetLineWidth(0.6);
        $this->Line($x, $this->GetY() + 1, 195, $this->GetY() + 1);
        $this->SetLineWidth(0.1);
        $this->Ln(2);

        $this->SetX($x);
        $this->SetFont('OpenSans-Bold', '', 12);
        $this->SetTextColor(...self::C_INK);
        $this->Cell($labelW, 8, $this->safe('TOTAL DUE'), 0, 0, 'R');
        $this->SetTextColor(...self::C_BRAND);
        $this->Cell($valueW, 8, $this->safe($currency . ' ' . $this->fmtMoney($total)), 0, 1, 'R');

        if ($paid > 0) {
            $this->SetX($x);
            $this->SetFont('OpenSans-Regular', '', 9);
            $this->SetTextColor( 14, 159, 110);   // fresh green
            $this->Cell($labelW, 6, $this->safe('Amount paid'), 0, 0, 'R');
            $this->Cell($valueW, 6, $this->safe($currency . ' ' . $this->fmtMoney($paid)), 0, 1, 'R');

            $this->SetX($x);
            $this->SetFont('OpenSans-Bold', '', 10);
            $this->SetTextColor(...($due > 0.005 ? [220, 38, 38] : [14, 159, 110]));
            $this->Cell($labelW, 7, $this->safe('Outstanding'), 0, 0, 'R');
            $this->Cell($valueW, 7, $this->safe($currency . ' ' . $this->fmtMoney($due)), 0, 1, 'R');
        }

        $this->Ln(8);
    }

    /* ── Footer note (payment instructions etc.) ───────────────────── */
    private function drawFooterNote(): void
    {
        if (empty($this->inv['footer_note'])) return;
        $this->SetDrawColor(...self::C_RULE);
        $this->Line(15, $this->GetY(), 195, $this->GetY());
        $this->Ln(3);
        $this->SetFont('OpenSans-Regular', '', 8);
        $this->SetTextColor(...self::C_MUTED);
        $this->Cell(0, 4, $this->safe('PAYMENT INSTRUCTIONS'), 0, 1, 'L');
        $this->SetFont('OpenSans-Regular', '', 9);
        $this->SetTextColor(...self::C_INK_SOFT);
        $this->MultiCell(0, 5, $this->safe($this->inv['footer_note']), 0, 'L');
    }

    /* ── VOID / PAID stamp overlay ─────────────────────────────────── */
    private function stamp(string $word, array $rgb): void
    {
        $this->SetFont('OpenSans-Bold', '', 60);
        $this->SetTextColor(...$rgb);
        // Rotate 25° counter-clockwise around a central point
        $angle = 25;
        $x = 55; $y = 200;
        $this->Rotate($angle, $x, $y);
        $this->Text($x, $y, $this->safe($word));
        $this->Rotate(0);
    }

    /* ── Rotate helper (missing from stock FPDF) ───────────────────── */
    private function Rotate(float $angle, ?float $x = null, ?float $y = null): void
    {
        if ($x === null) $x = $this->GetX();
        if ($y === null) $y = $this->GetY();
        // Restore any previous rotation state
        static $active = false;
        if ($active) { $this->_out('Q'); $active = false; }
        if ($angle === 0.0) return;
        $angle = $angle * M_PI / 180;
        $c = cos($angle); $s = sin($angle);
        $cx = $x * $this->k; $cy = ($this->h - $y) * $this->k;
        $this->_out(sprintf('q %.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm',
            $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy));
        $active = true;
    }

    /* ── Formatting helpers ────────────────────────────────────────── */
    private function fmtMoney(float $v): string
    {
        return number_format($v, 2, '.', ',');
    }
    private function fmtQty(float $v): string
    {
        return number_format($v, ($v == (int)$v ? 0 : 2), '.', ',');
    }
    private function fmtDate(string $ymd): string
    {
        if (!$ymd) return '—';
        $t = strtotime($ymd);
        return $t ? date('d M Y', $t) : $ymd;
    }
    private function truncate(string $s, int $n): string
    {
        return mb_strlen($s) > $n ? mb_substr($s, 0, $n - 1) . '…' : $s;
    }

    /**
     * Down-convert UTF-8 to Windows-1252 (FPDF's supported encoding) so
     * unicode dashes, arrows, non-breaking spaces don't emit mojibake.
     */
    private function safe($s): string
    {
        $s = (string)$s;
        // Common substitutions for characters that don't exist in cp1252
        $s = strtr($s, [
            "\u{2013}" => '-',     // en dash
            "\u{2014}" => '-',     // em dash
            "\u{2018}" => "'",     // left single quote
            "\u{2019}" => "'",     // right single quote
            "\u{201C}" => '"',     // left double quote
            "\u{201D}" => '"',     // right double quote
            "\u{2026}" => '...',   // ellipsis
            "\u{2192}" => '->',    // right arrow
            "\u{00A0}" => ' ',     // non-breaking space
            "\u{00B7}" => '.',     // middle dot
        ]);
        return @iconv('UTF-8', 'CP1252//TRANSLIT//IGNORE', $s) ?: $s;
    }
}
