<?php
ob_start();
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    ob_clean();
    header('Content-Type: text/html; charset=UTF-8');
    die('<style>body{font-family:sans-serif;padding:30px;background:#fef2f2;color:#dc2626}</style><h3>Error generating invoice</h3><p>' . htmlspecialchars("[$errno] $errstr in $errfile:$errline") . '</p><p><a href="index.php">← Back</a></p>');
});
set_exception_handler(function($e) {
    ob_clean();
    header('Content-Type: text/html; charset=UTF-8');
    die('<style>body{font-family:sans-serif;padding:30px;background:#fef2f2;color:#dc2626}</style><h3>Error generating invoice</h3><p>' . htmlspecialchars($e->getMessage()) . '</p><p><a href="index.php">← Back</a></p>');
});

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    header('Location: index.php'); exit;
}

require_once __DIR__ . '/lib/fpdf.php';

// ─── XLSX Parser (native PHP — no Composer) ───────────────────────────────────

function xlsxParse(string $path): array
{
    if (!class_exists('ZipArchive')) {
        throw new RuntimeException('PHP ZipArchive extension is required.');
    }
    $zip = new ZipArchive();
    if ($zip->open($path) !== true) {
        throw new RuntimeException('Cannot open the Excel file. Make sure it is a valid .xlsx.');
    }

    // Shared strings
    $strings = [];
    $sstXml  = $zip->getFromName('xl/sharedStrings.xml');
    if ($sstXml) {
        $sst = new SimpleXMLElement($sstXml);
        foreach ($sst->si as $si) {
            if (count($si->r) > 0) {
                $t = '';
                foreach ($si->r as $r) $t .= (string)$r->t;
            } else {
                $t = (string)$si->t;
            }
            $strings[] = $t;
        }
    }

    // Sheet names + relationship IDs
    $wbXml = $zip->getFromName('xl/workbook.xml');
    if (!$wbXml) throw new RuntimeException('Invalid Excel file: missing workbook.xml');
    $wb = new SimpleXMLElement($wbXml);

    $relsXml = $zip->getFromName('xl/_rels/workbook.xml.rels');
    $rIdToFile = [];
    if ($relsXml) {
        $rels = new SimpleXMLElement($relsXml);
        foreach ($rels->Relationship as $rel) {
            if (strpos((string)$rel['Type'], 'worksheet') !== false) {
                $rIdToFile[(string)$rel['Id']] = (string)$rel['Target'];
            }
        }
    }

    $result = [];
    $i = 0;
    foreach ($wb->sheets->sheet as $sheet) {
        $name = (string)$sheet['name'];
        $rNS  = $sheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
        $rId  = $rNS ? (string)$rNS['id'] : 'rId' . ($i + 1);
        $file = $rIdToFile[$rId] ?? ('worksheets/sheet' . ($i + 1) . '.xml');
        $file = 'xl/' . $file;

        $sheetXml = $zip->getFromName($file);
        if (!$sheetXml) { $i++; continue; }

        $xml  = new SimpleXMLElement($sheetXml);
        $rows = [];
        foreach ($xml->sheetData->row as $row) {
            $arr = [];
            foreach ($row->c as $cell) {
                // Column index from cell ref (e.g. "B3" → 1)
                preg_match('/^([A-Z]+)/', (string)$cell['r'], $m);
                $col = 0;
                foreach (str_split($m[1]) as $ch) {
                    $col = $col * 26 + (ord($ch) - 64);
                }
                $col--;
                while (count($arr) < $col) $arr[] = '';

                $type = (string)$cell['t'];
                $val  = (string)$cell->v;
                if ($type === 's')         $val = $strings[(int)$val] ?? '';
                elseif ($type === 'inlineStr') $val = (string)$cell->is->t;
                elseif ($type === 'b')     $val = ($val === '1') ? 'TRUE' : 'FALSE';
                $arr[] = $val;
            }
            if (!empty(array_filter($arr))) $rows[] = $arr;
        }
        $result[$name] = $rows;
        $i++;
    }
    $zip->close();
    return $result;
}

// ─── Extract invoice fields ────────────────────────────────────────────────────

function extractData(array $sheets): array
{
    $keys   = array_keys($sheets);
    $sheet1 = $sheets[$keys[0]] ?? [];
    $sheet2 = isset($keys[1]) ? $sheets[$keys[1]] : [];

    // Build normalised key → value map
    $map = [];
    foreach ($sheet1 as $row) {
        if (count($row) < 2) continue;
        $k = strtolower(trim(preg_replace('/[^a-z0-9]+/i', '_', $row[0])));
        $map[$k] = trim((string)($row[1] ?? ''));
    }

    $get = function(array $aliases) use ($map): string {
        foreach ($aliases as $a) {
            $k = strtolower(preg_replace('/[^a-z0-9]+/i', '_', $a));
            if (isset($map[$k]) && $map[$k] !== '') return $map[$k];
        }
        return '';
    };

    // Services from sheet 2
    $services = [];
    foreach ($sheet2 as $row) {
        if (empty($row[0])) continue;
        $first = strtolower(trim($row[0]));
        if (in_array($first, ['description', 'descripcion', 'desc'])) continue;
        $services[] = [
            'description' => trim($row[0]),
            'qty'         => trim($row[1] ?? '1'),
            'amount'      => (float) str_replace(['$', ',', ' '], '', $row[2] ?? '0'),
        ];
    }

    return [
        'invoice_number'   => $get(['Invoice Number','Invoice #','Invoice No']),
        'invoice_date'     => $get(['Invoice Date','Date']),
        'due_date'         => $get(['Due Date']),
        'container_number' => $get(['Container Number','Container #']),
        'booking_number'   => $get(['Booking Number','Booking #']),
        'reference_number' => $get(['Reference Number','Reference #']),
        'bill_to_name'     => $get(['Bill To Name','Client Name','Customer']),
        'bill_to_addr1'    => $get(['Bill To Address 1','Bill To Address1']),
        'bill_to_addr2'    => $get(['Bill To Address 2','Bill To Address2']),
        'bill_to_phone'    => $get(['Bill To Phone','Phone']),
        'container'        => $get(['Container']),
        'size'             => $get(['Size']),
        'type'             => $get(['Type']),
        'bol'              => $get(['BOL','Bill of Lading']),
        'pickup_date'      => $get(['Pickup Date','Pickup']),
        'delivery_date'    => $get(['Delivery Date','Delivery']),
        'return_date'      => $get(['Return Date','Return']),
        'origin_name'      => $get(['Origin Name','Origin']),
        'origin_addr1'     => $get(['Origin Address 1','Origin Address1']),
        'origin_addr2'     => $get(['Origin Address 2','Origin Address2']),
        'dest_name'        => $get(['Destination Name','Destination']),
        'dest_addr1'       => $get(['Destination Address 1','Destination Address1']),
        'dest_addr2'       => $get(['Destination Address 2','Destination Address2']),
        'notes'            => $get(['Notes','Comments','Remarks']),
        'services'         => $services,
    ];
}

// ─── PDF Class ────────────────────────────────────────────────────────────────

class CoarcaInvoicePDF extends FPDF
{
    const OR_R = 232;
    const OR_G = 100;
    const OR_B = 10;

    private float $lm   = 15.0;
    private float $rm   = 15.0;
    private float $pw   = 215.9;
    private float $cw;

    public function __construct()
    {
        parent::__construct('P', 'mm', [215.9, 279.4]);
        $this->cw = $this->pw - $this->lm - $this->rm; // 185.9mm
        $this->SetMargins($this->lm, 12.0, $this->rm);
        $this->SetAutoPageBreak(false, 0);
        $this->SetFont('Helvetica', '', 9);
    }

    // ─── Color helpers ───────────────────────────────────────────────────────

    private function setOrange(bool $fill = true): void
    {
        if ($fill) $this->SetFillColor(self::OR_R, self::OR_G, self::OR_B);
        $this->SetDrawColor(self::OR_R, self::OR_G, self::OR_B);
        $this->SetTextColor(255, 255, 255);
    }

    private function setNormal(): void
    {
        $this->SetFillColor(255, 255, 255);
        $this->SetDrawColor(180, 180, 180);
        $this->SetTextColor(40, 40, 40);
    }

    private function setDark(): void
    {
        $this->SetTextColor(20, 20, 20);
    }

    // ─── Section header bar (orange bg, white text) ──────────────────────────

    private function sectionBar(float $x, float $y, float $w, string $label): float
    {
        $this->setOrange();
        $this->SetFont('Helvetica', 'B', 8.5);
        $this->SetXY($x, $y);
        $this->Cell($w, 6.0, $label, 0, 0, 'L', true);
        $this->setNormal();
        return $y + 6.0;
    }

    // ─── Draw a labelled data row inside a box ───────────────────────────────

    private function dataRow(float $x, float $y, float $w, string $label, string $val, float $h = 6.5): float
    {
        $this->SetFont('Helvetica', '', 8.5);
        $this->SetTextColor(40, 40, 40);
        $this->SetXY($x + 2, $y);
        $this->Cell($w - 2, $h, $label . ' ' . $val, 0, 0, 'L');
        return $y + $h;
    }

    // ─── Draw outer border of a box ──────────────────────────────────────────

    private function drawBox(float $x, float $y, float $w, float $h): void
    {
        $this->SetDrawColor(180, 180, 180);
        $this->Rect($x, $y, $w, $h, 'D');
    }

    // ─────────────────────────────────────────────────────────────────────────
    // SECTIONS
    // ─────────────────────────────────────────────────────────────────────────

    public function build(array $d): void
    {
        $this->AddPage();
        $y = 12.0;

        $y = $this->drawHeaderSection($d, $y);
        $y += 2.5;

        $y = $this->drawShipmentMovement($d, $y);
        $y += 2.5;

        $y = $this->drawOriginDest($d, $y);
        $y += 2.5;

        $y = $this->drawServiceDetails($d, $y);
        $y += 1.5;

        $y = $this->drawNotes($d, $y);
        $y += 2.0;

        $y = $this->drawTotalDue($d, $y);
        $y += 2.5;

        $y = $this->drawPaymentInfo($y);
        $y += 2.5;

        $this->drawFooter($y);
    }

    // ── 1. HEADER + BILL TO ──────────────────────────────────────────────────
    //   Left:  Logo + Company info
    //   Right: Invoice Information box  +  Bill To box (below)

    private function drawHeaderSection(array $d, float $y): float
    {
        $x      = $this->lm;
        $colW   = ($this->cw - 6.0) / 2;  // ~89.95mm
        $rx     = $x + $colW + 6.0;
        $startY = $y;

        // ── Left: Logo ───────────────────────────────────────────────────────
        $logoPath = __DIR__ . '/assets/logo.png';
        $cy = $y;
        if (file_exists($logoPath)) {
            $info  = getimagesize($logoPath);
            $logoW = 46.0;
            $logoH = ($info && $info[0] > 0) ? $logoW * ($info[1] / $info[0]) : 18.0;
            // White rect behind logo eliminates any PNG artifact
            $this->SetFillColor(255, 255, 255);
            $this->Rect($x, $cy, $logoW, $logoH, 'F');
            $this->Image($logoPath, $x, $cy, $logoW, $logoH);
            $cy += $logoH + 2;
        } else {
            $this->SetFont('Helvetica', 'B', 26);
            $this->SetTextColor(self::OR_R, self::OR_G, self::OR_B);
            $this->SetXY($x, $cy);
            $this->Cell($colW, 14, 'COARCA', 0, 0, 'L');
            $cy += 15;
        }

        // ── Left: Company info ───────────────────────────────────────────────
        $this->SetFont('Helvetica', 'B', 9);
        $this->SetTextColor(20, 20, 20);
        $this->SetXY($x, $cy);
        $this->Cell($colW, 5.5, 'COARCA LOGISTICS', 0, 2, 'L');
        $this->SetFont('Helvetica', '', 8.5);
        $this->SetTextColor(60, 60, 60);
        foreach (['5001 SW 173rd Ave, Miramar, FL 33029', '754-275-8194', 'sales@coarcalogistics.com'] as $line) {
            $this->SetX($x);
            $this->Cell($colW, 4.5, $line, 0, 2, 'L');
        }
        $leftBottom = $this->GetY();

        // ── Right TOP: Invoice Information ───────────────────────────────────
        $rowH     = 5.5;
        $infoRows = [
            ['Invoice #:',    $d['invoice_number']],
            ['Invoice Date:',  $d['invoice_date']],
            ['Due Date:',     $d['due_date']],
            ['Container #:',  $d['container_number']],
            ['Booking #:',    $d['booking_number']],
            ['Reference #:',  $d['reference_number']],
        ];
        $infoBoxH = 6.0 + count($infoRows) * $rowH;  // 6 + 33 = 39mm

        $ry = $this->sectionBar($rx, $startY, $colW, 'INVOICE INFORMATION');
        foreach ($infoRows as $row) {
            $this->SetFont('Helvetica', '', 8.5);
            $this->SetTextColor(40, 40, 40);
            $this->SetXY($rx + 3, $ry);
            $this->Cell(($colW / 2) - 3, $rowH, $row[0], 0, 0, 'L');
            $this->SetX($rx + $colW / 2);
            $this->Cell($colW / 2, $rowH, $row[1], 0, 0, 'L');
            $ry += $rowH;
        }
        $this->drawBox($rx, $startY, $colW, $infoBoxH);

        // ── Right BOTTOM: Bill To ─────────────────────────────────────────────
        $btY    = $startY + $infoBoxH + 2;
        $btRowH = 5.0;
        $btLines = array_values(array_filter([
            $d['bill_to_name'],
            $d['bill_to_addr1'],
            $d['bill_to_addr2'],
            !empty($d['bill_to_phone']) ? 'Phone: ' . $d['bill_to_phone'] : '',
        ]));
        $btBoxH = 6.0 + max(1, count($btLines)) * $btRowH;

        $by = $this->sectionBar($rx, $btY, $colW, 'BILL TO');
        foreach ($btLines as $i => $line) {
            $this->SetFont('Helvetica', $i === 0 ? 'B' : '', 8.5);
            $this->SetTextColor(40, 40, 40);
            $this->SetXY($rx + 3, $by);
            $this->Cell($colW - 6, $btRowH, $line, 0, 0, 'L');
            $by += $btRowH;
        }
        $this->drawBox($rx, $btY, $colW, $btBoxH);
        $rightBottom = $btY + $btBoxH;

        return max($leftBottom, $rightBottom);
    }

    // ── 3. SHIPMENT SUMMARY + MOVEMENT DETAILS ───────────────────────────────

    private function drawShipmentMovement(array $d, float $y): float
    {
        $x    = $this->lm;
        $colW = ($this->cw - 5.0) / 2;
        $rx   = $x + $colW + 5.0;
        $rowH = 6.0;

        // Left — Shipment Summary
        $leftRows = [
            ['Container:', $d['container']],
            ['Size:', $d['size']],
            ['Type:', $d['type']],
        ];
        $leftH = 6.0 + count($leftRows) * $rowH;

        // Right — Movement Details
        $rightRows = [
            ['BOL:', $d['bol']],
            ['Pickup:', $d['pickup_date']],
            ['Delivery:', $d['delivery_date']],
            ['Return:', $d['return_date']],
        ];
        $rightH = 6.0 + count($rightRows) * $rowH;

        $boxH = max($leftH, $rightH);

        $lblW = 28.0;                      // fixed label width — enough for "Container:"
        $valW = $colW - $lblW - 5.0;       // value gets the rest, -5 for padding

        // Left box
        $cy = $this->sectionBar($x, $y, $colW, 'SHIPMENT SUMMARY');
        foreach ($leftRows as $r) {
            $this->SetTextColor(40, 40, 40);
            $this->SetFont('Helvetica', 'B', 8.0);
            $this->SetXY($x + 3, $cy);
            $this->Cell($lblW, $rowH, $r[0], 0, 0, 'L');
            $this->SetFont('Helvetica', '', 8.0);
            $this->SetX($x + 3 + $lblW);
            $this->Cell($valW, $rowH, $r[1], 0, 0, 'L');
            $cy += $rowH;
        }
        $this->drawBox($x, $y, $colW, $boxH);

        // Right box
        $cy = $this->sectionBar($rx, $y, $colW, 'MOVEMENT DETAILS');
        foreach ($rightRows as $r) {
            $this->SetTextColor(40, 40, 40);
            $this->SetFont('Helvetica', 'B', 8.0);
            $this->SetXY($rx + 3, $cy);
            $this->Cell($lblW, $rowH, $r[0], 0, 0, 'L');
            $this->SetFont('Helvetica', '', 8.0);
            $this->SetX($rx + 3 + $lblW);
            $this->Cell($valW, $rowH, $r[1], 0, 0, 'L');
            $cy += $rowH;
        }
        $this->drawBox($rx, $y, $colW, $boxH);

        return $y + $boxH;
    }

    // ── 4. ORIGIN + DESTINATION ──────────────────────────────────────────────

    private function drawOriginDest(array $d, float $y): float
    {
        $x    = $this->lm;
        $colW = ($this->cw - 5.0) / 2;
        $rx   = $x + $colW + 5.0;
        $rowH = 6.0;

        $leftRows  = array_values(array_filter([$d['origin_name'], $d['origin_addr1'], $d['origin_addr2']]));
        $rightRows = array_values(array_filter([$d['dest_name'],   $d['dest_addr1'],   $d['dest_addr2']]));

        $boxH = 6.0 + max(max(count($leftRows), count($rightRows)), 1) * $rowH;

        // Left — Port
        $cy = $this->sectionBar($x, $y, $colW, 'PORT');
        foreach ($leftRows as $line) {
            $this->SetFont('Helvetica', '', 8.5);
            $this->SetTextColor(40, 40, 40);
            $this->SetXY($x + 3, $cy);
            $this->Cell($colW - 3, $rowH, $line, 0, 0, 'L');
            $cy += $rowH;
        }
        $this->drawBox($x, $y, $colW, $boxH);

        // Right — Shipper/Consignee
        $cy = $this->sectionBar($rx, $y, $colW, 'SHIPPER/CONSIGNEE');
        foreach ($rightRows as $line) {
            $this->SetFont('Helvetica', '', 8.5);
            $this->SetTextColor(40, 40, 40);
            $this->SetXY($rx + 3, $cy);
            $this->Cell($colW - 3, $rowH, $line, 0, 0, 'L');
            $cy += $rowH;
        }
        $this->drawBox($rx, $y, $colW, $boxH);

        return $y + $boxH;
    }

    // ── 5. SERVICE DETAILS ───────────────────────────────────────────────────

    private function drawServiceDetails(array $d, float $y): float
    {
        $x     = $this->lm;
        $cw    = $this->cw;
        $rowH  = 5.5;

        // Heading
        $this->SetFont('Helvetica', 'BI', 9.5);
        $this->SetTextColor(40, 40, 40);
        $this->SetXY($x, $y);
        $this->Cell($cw, 5.5, 'SERVICE DETAILS', 0, 2, 'L');
        $y = $this->GetY();

        // Column widths
        $descW   = $cw * 0.72;  // ~133.8mm
        $qtyW    = $cw * 0.10;  // ~18.6mm
        $amountW = $cw - $descW - $qtyW; // ~33.5mm

        // Header row (orange)
        $this->setOrange();
        $this->SetFont('Helvetica', 'B', 8.5);
        $this->SetXY($x, $y);
        $this->Cell($descW,   $rowH, '  DESCRIPTION', 0, 0, 'L', true);
        $this->SetX($x + $descW);
        $this->Cell($qtyW,    $rowH, 'QTY',    0, 0, 'C', true);
        $this->SetX($x + $descW + $qtyW);
        $this->Cell($amountW, $rowH, 'AMOUNT', 0, 0, 'R', true);
        $y += $rowH;

        // Service rows
        $this->SetFont('Helvetica', '', 8.5);
        $this->SetTextColor(40, 40, 40);
        $this->SetDrawColor(220, 220, 220);

        foreach ($d['services'] as $i => $svc) {
            $fill = ($i % 2 === 0);
            if ($fill) $this->SetFillColor(250, 250, 250);
            else       $this->SetFillColor(255, 255, 255);

            $lineTotal = $svc['amount'] * max(1, (float)$svc['qty']);

            $this->SetXY($x, $y);
            $this->Cell($descW,   $rowH, '  ' . $svc['description'], 'B', 0, 'L', $fill);
            $this->SetX($x + $descW);
            $this->Cell($qtyW,    $rowH, $svc['qty'],                 'B', 0, 'C', $fill);
            $this->SetX($x + $descW + $qtyW);
            $this->Cell($amountW, $rowH, '$' . number_format($lineTotal, 2), 'B', 0, 'R', $fill);
            $y += $rowH;
        }

        // Outer border around whole table
        $tableH = $rowH + count($d['services']) * $rowH;
        $tableY = $y - $tableH;
        $this->SetDrawColor(180, 180, 180);
        $this->Rect($x, $tableY, $cw, $tableH, 'D');

        return $y;
    }

    // ── 6. NOTES ─────────────────────────────────────────────────────────────

    private function drawNotes(array $d, float $y): float
    {
        $x = $this->lm;

        $this->SetFont('Helvetica', 'BI', 9.5);
        $this->SetTextColor(40, 40, 40);
        $this->SetXY($x, $y);
        $this->Cell($this->cw, 5.5, 'NOTES', 0, 2, 'L');
        $y = $this->GetY();

        if (!empty($d['notes'])) {
            $this->SetFont('Helvetica', '', 8.5);
            $this->SetTextColor(60, 60, 60);
            $this->SetXY($x, $y);
            $this->MultiCell($this->cw, 5.0, $d['notes'], 0, 'L');
            $y = $this->GetY() + 1.5;
        }

        return $y;
    }

    // ── 7. TOTAL DUE ─────────────────────────────────────────────────────────

    private function drawTotalDue(array $d, float $y): float
    {
        $x    = $this->lm;
        $cw   = $this->cw;
        $boxH = 17.0;

        $total = array_sum(array_map(fn($s) => $s['amount'] * max(1, (float)$s['qty']), $d['services']));

        // Orange box
        $this->SetFillColor(self::OR_R, self::OR_G, self::OR_B);
        $this->Rect($x, $y, $cw, $boxH, 'F');

        // "TOTAL DUE" label
        $this->SetFont('Helvetica', 'B', 9);
        $this->SetTextColor(255, 255, 255);
        $this->SetXY($x, $y + 1.0);
        $this->Cell($cw, 5.5, 'TOTAL DUE', 0, 2, 'C');

        // Amount
        $this->SetFont('Helvetica', 'B', 18);
        $this->SetXY($x, $y + 7.5);
        $this->Cell($cw, 8, '$' . number_format($total, 2), 0, 0, 'C');

        $this->setNormal();
        return $y + $boxH;
    }

    // ── 8. PAYMENT INFORMATION ───────────────────────────────────────────────

    private function drawPaymentInfo(float $y): float
    {
        $x = $this->lm;

        $this->SetFont('Helvetica', 'BI', 9.5);
        $this->SetTextColor(40, 40, 40);
        $this->SetXY($x, $y);
        $this->Cell($this->cw, 5.5, 'PAYMENT INFORMATION', 0, 2, 'L');
        $y = $this->GetY();

        $this->SetFont('Helvetica', '', 8.5);
        $this->SetTextColor(40, 40, 40);
        $this->SetXY($x, $y);
        $this->Cell($this->cw, 5.0,
            'Bank: Chase Bank    Routing: 267084131    Account: 506582185', 0, 2, 'L');
        $this->SetX($x);
        $this->Cell($this->cw, 5.0,
            'Zelle: coarca.log@gmail.com    Remittance: sales@coarcalogistics.com', 0, 2, 'L');

        return $this->GetY();
    }

    // ── 9. FOOTER ────────────────────────────────────────────────────────────

    private function drawFooter(float $y): void
    {
        $this->SetFont('Helvetica', '', 8.5);
        $this->SetTextColor(80, 80, 80);
        $this->SetXY($this->lm, $y);
        $this->Cell($this->cw, 6, 'Thank you for your business.', 0, 0, 'L');
    }
}

// ─── Main: handle POST form ───────────────────────────────────────────────────

if (empty($_POST['invoice_number']) && empty($_POST['bill_to_name'])) {
    header('Location: index.php'); exit;
}

// Strip tags, collapse newlines/tabs to space — safe for FPDF Cell()
$clean = fn(string $k): string => trim(preg_replace('/[\r\n\t]+/', ' ', strip_tags($_POST[$k] ?? '')));

// Build services from parallel arrays
$services = [];
$descs  = $_POST['svc_desc'] ?? [];
$qtys   = $_POST['svc_qty']  ?? [];
$amts   = $_POST['svc_amt']  ?? [];
foreach ($descs as $i => $desc) {
    if (trim($desc) === '') continue;
    $services[] = [
        'description' => trim(strip_tags($desc)),
        'qty'         => trim($qtys[$i] ?? '1'),
        'amount'      => (float)($amts[$i] ?? 0),
    ];
}

$data = [
    'invoice_number'   => $clean('invoice_number'),
    'invoice_date'     => $clean('invoice_date'),
    'due_date'         => $clean('due_date'),
    'container_number' => $clean('container_number'),
    'booking_number'   => $clean('booking_number'),
    'reference_number' => $clean('reference_number'),
    'bill_to_name'     => $clean('bill_to_name'),
    'bill_to_addr1'    => $clean('bill_to_addr1'),
    'bill_to_addr2'    => $clean('bill_to_addr2'),
    'bill_to_phone'    => $clean('bill_to_phone'),
    'container'        => $clean('container'),
    'size'             => $clean('size'),
    'type'             => $clean('type'),
    'bol'              => $clean('bol'),
    'pickup_date'      => $clean('pickup_date'),
    'delivery_date'    => $clean('delivery_date'),
    'return_date'      => $clean('return_date'),
    'origin_name'      => $clean('origin_name'),
    'origin_addr1'     => $clean('origin_addr1'),
    'origin_addr2'     => $clean('origin_addr2'),
    'dest_name'        => $clean('dest_name'),
    'dest_addr1'       => $clean('dest_addr1'),
    'dest_addr2'       => $clean('dest_addr2'),
    'notes'            => $clean('notes'),
    'services'         => $services,
];

// Generate PDF
$pdf = new CoarcaInvoicePDF();
$pdf->build($data);

$invoiceNum = trim($data['invoice_number'] ?: 'invoice');
$filename   = 'INV # ' . $invoiceNum . '.pdf';

$pdfContent = $pdf->Output('S');

ob_clean();
header('Content-Type: application/pdf');
// filename* (RFC 5987) is prioritized by modern browsers and handles # correctly
header('Content-Disposition: attachment; filename="' . addslashes($filename) . '"; filename*=UTF-8\'\'' . rawurlencode($filename));
header('Content-Length: ' . strlen($pdfContent));
header('Cache-Control: private, max-age=0, must-revalidate');
echo $pdfContent;
