<?php
/**
 * PacificStaffIdPdf — CR80 staff ID card renderer.
 *
 * Two modes:
 *   render($driver)                 → single-card PDF (86 × 54 mm × 2 pages: front, back)
 *   renderSheet($drivers)           → A4 sheet with N cards in a 2 × 5 grid (up to 10 per page)
 *
 * Card contents match the approved design mockup (see the "Pacific Staff ID"
 * artifact for the visual reference).
 *
 * QR CODE:
 *   Rendered by the bundled QrCode class (V4-L, 33 × 33 modules) — see
 *   /app/lib/qrcode/QrCode.php. Each dark module is drawn as its own FPDF
 *   Rect so the code stays crisp at any zoom level.
 */
class PacificStaffIdPdf extends FPDF
{
    // Pacific palette (matches web + invoice PDFs)
    private const C_BRAND    = [ 38,  38, 245];
    private const C_BRAND_D  = [ 20,  20, 214];
    private const C_INK      = [ 15,  18,  32];
    private const C_INK_SOFT = [ 33,  38,  59];
    private const C_MUTED    = [107, 116, 132];
    private const C_RULE     = [226, 231, 239];
    private const C_TINT     = [235, 239, 250];
    private const C_GROUND   = [245, 247, 251];

    // Card dimensions (CR80 landscape)
    private const CARD_W = 85.6;
    private const CARD_H = 53.98;

    public function __construct(string $orientation = 'L', string $unit = 'mm', $size = [self::CARD_W, self::CARD_H])
    {
        if (!defined('FPDF_FONTPATH')) define('FPDF_FONTPATH', ROOT . '/fonts/fpdf/');
        if (!class_exists('QrCode', false)) {
            require_once ROOT . '/app/lib/qrcode/QrCode.php';
        }
        parent::__construct($orientation, $unit, $size);
        $this->AddFont('OpenSans-Regular', '', 'OpenSans-Regular.php');
        $this->AddFont('OpenSans-Bold',    '', 'opensans-bold.php');
        $this->SetMargins(0, 0, 0);
        $this->SetAutoPageBreak(false);
    }

    public function Header(): void {}
    public function Footer(): void {}

    /* ══════════════════════════════════════════════════════════════════
       Public entry — one card, front then back
       ══════════════════════════════════════════════════════════════════ */
    public function render(array $d): void
    {
        $this->SetTitle($this->safe('Staff ID · ' . ($d['full_name'] ?? '')));
        $this->SetAuthor($this->safe(defined('COMPANY_NAME') ? COMPANY_NAME : 'Pacific Group'));

        // Front page
        $this->AddPage('L', [self::CARD_W, self::CARD_H]);
        $this->drawCardFront($d, 0, 0);

        // Back page
        $this->AddPage('L', [self::CARD_W, self::CARD_H]);
        $this->drawCardBack($d, 0, 0);
    }

    /* ══════════════════════════════════════════════════════════════════
       Public entry — A4 sheet with up to 10 cards (fronts) + optional back
       sheet. Useful for batch printing on badge-perforated laser stock.
       ══════════════════════════════════════════════════════════════════ */
    public function renderSheet(array $drivers): void
    {
        // Custom A4 constructor path — recreate a fresh PDF at A4
        $this->SetTitle($this->safe('Staff IDs · sheet'));

        // A4 = 210 × 297 mm. Grid: 2 cols × 5 rows, 8mm outer margin, 4mm gutter.
        // Card 85.6 × 53.98; 2 cols = 175.2; usable = 210-16 = 194; leftover 18.8 → 9.4mm gutter each side of gap.
        $cols = 2; $rows = 5;
        $marginX = ((210 - ($cols * self::CARD_W)) / ($cols + 1));
        $marginY = ((297 - ($rows * self::CARD_H)) / ($rows + 1));

        $chunks = array_chunk($drivers, $cols * $rows);
        foreach ($chunks as $page) {
            $this->AddPage('P', 'A4');
            foreach ($page as $i => $d) {
                $col = $i % $cols;
                $row = intdiv($i, $cols);
                $x = $marginX + $col * (self::CARD_W + $marginX);
                $y = $marginY + $row * (self::CARD_H + $marginY);
                $this->drawCardFront($d, $x, $y);
                // Hairline crop marks (helps trim on a laser printer without badge stock)
                $this->drawCropMarks($x, $y);
            }
        }
        // Second sheet: same layout, but backs (skipped if there's only one driver — not worth a whole page)
        if (count($drivers) > 0) {
            foreach ($chunks as $page) {
                $this->AddPage('P', 'A4');
                foreach ($page as $i => $d) {
                    // Mirror the column order so the back aligns with the front after duplex printing
                    $col = ($cols - 1) - ($i % $cols);
                    $row = intdiv($i, $cols);
                    $x = $marginX + $col * (self::CARD_W + $marginX);
                    $y = $marginY + $row * (self::CARD_H + $marginY);
                    $this->drawCardBack($d, $x, $y);
                    $this->drawCropMarks($x, $y);
                }
            }
        }
    }

    /* ── Front face ────────────────────────────────────────────────── */
    private function drawCardFront(array $d, float $x, float $y): void
    {
        $w = self::CARD_W; $h = self::CARD_H;

        // Card ground (white). Explicit rectangle for the sheet layout.
        $this->SetFillColor(255, 255, 255);
        $this->Rect($x, $y, $w, $h, 'F');

        // ── Header strip (brand blue, 11mm) ──
        $this->SetFillColor(...self::C_BRAND);
        $this->Rect($x, $y, $w, 11, 'F');

        // Mark "P" inside a hexagonal outline (drawn with lines)
        $this->SetDrawColor(255, 255, 255);
        $this->SetLineWidth(0.5);
        $mx = $x + 4; $my = $y + 2; $ms = 7;   // mark bounding box
        $this->drawHex($mx + $ms/2, $my + $ms/2, $ms/2 - 0.4);
        $this->SetTextColor(255, 255, 255);
        $this->SetFont('OpenSans-Bold', '', 8);
        $this->Text($mx + 2.2, $my + $ms - 1.7, 'P');

        // Wordmark
        $this->SetFont('OpenSans-Bold', '', 8);
        $this->Text($x + 13, $y + 7.2, $this->safe('PACIFIC GROUP'));

        // STAFF ID chip on the right
        $this->SetFont('OpenSans-Bold', '', 6);
        $chipText = 'STAFF ID';
        $chipW = $this->GetStringWidth($chipText) + 4;
        $chipX = $x + $w - $chipW - 3;
        $chipY = $y + 3;
        $this->SetDrawColor(255, 255, 255, 200);
        $this->Rect($chipX, $chipY, $chipW, 5, 'D');
        $this->Text($chipX + 2, $chipY + 3.7, $chipText);

        // ── Photo (18 × 24 mm, 3:4 ratio) ──
        $px = $x + 4; $py = $y + 14;
        $pw = 18; $ph = 24;
        $photoUrl = trim((string)($d['photo_url'] ?? ''));
        $localPath = $this->localPhotoPath($photoUrl);
        if ($localPath && is_file($localPath)) {
            @$this->Image($localPath, $px, $py, $pw, $ph);
        } else {
            // Placeholder — gradient rectangle + user silhouette hint
            $this->SetFillColor(...self::C_TINT);
            $this->Rect($px, $py, $pw, $ph, 'F');
            $this->SetTextColor(...self::C_MUTED);
            $this->SetFont('OpenSans-Regular', '', 5);
            $this->Text($px + 4.5, $py + $ph - 1.5, 'PHOTO');
        }

        // ── Details column ──
        $dx = $px + $pw + 3;

        // Role eyebrow
        $this->SetFont('OpenSans-Regular', '', 5.5);
        $this->SetTextColor(...self::C_MUTED);
        $this->Text($dx, $y + 16.5, $this->safe('DRIVER'));

        // Name (bold, 12pt for readability)
        $this->SetFont('OpenSans-Bold', '', 11);
        $this->SetTextColor(...self::C_INK);
        $this->Text($dx, $y + 22, $this->safe(strtoupper($d['full_name'] ?? '—')));

        // Employee no
        $this->SetFont('OpenSans-Regular', '', 7);
        $this->SetTextColor(...self::C_BRAND_D);
        $this->Text($dx, $y + 26.5, $this->safe($d['employee_no'] ?? '—'));

        // Meta grid: Valid from / Expires / Card no
        [$validFrom, $expires] = $this->validity();
        $cardNo = $this->cardNo((int)$d['c0d']);

        $this->drawMetaRow($dx, $y + 32.0, 'VALID FROM', $validFrom);
        $this->drawMetaRow($dx, $y + 36.5, 'EXPIRES',    $expires);
        $this->drawMetaRow($dx, $y + 41.0, 'CARD NO',    $cardNo);

        // ── Footer strip ──
        $this->SetFillColor(...self::C_GROUND);
        $this->Rect($x, $y + $h - 5, $w, 5, 'F');
        $this->SetDrawColor(...self::C_RULE);
        $this->SetLineWidth(0.1);
        $this->Line($x, $y + $h - 5, $x + $w, $y + $h - 5);
        $this->SetFont('OpenSans-Regular', '', 5);
        $this->SetTextColor(...self::C_MUTED);
        $this->Text($x + 3, $y + $h - 1.6, $this->safe('ISSUED BY HR'));
        $rightText = 'www.pacificgroup.co.tz';
        $rw = $this->GetStringWidth($rightText);
        $this->Text($x + $w - $rw - 3, $y + $h - 1.6, $rightText);
    }

    /* ── Back face ─────────────────────────────────────────────────── */
    private function drawCardBack(array $d, float $x, float $y): void
    {
        $w = self::CARD_W; $h = self::CARD_H;

        // Card ground
        $this->SetFillColor(255, 255, 255);
        $this->Rect($x, $y, $w, $h, 'F');

        // Top eyebrow line + card no on right
        $this->SetFont('OpenSans-Bold', '', 5.5);
        $this->SetTextColor(...self::C_BRAND_D);
        $this->Text($x + 3, $y + 4.5, $this->safe('DRIVER LICENCE & EMERGENCY DETAILS'));

        $cardNo = $this->cardNo((int)$d['c0d']);
        $this->SetFont('OpenSans-Regular', '', 5.5);
        $this->SetTextColor(...self::C_MUTED);
        $cnW = $this->GetStringWidth($cardNo);
        $this->Text($x + $w - $cnW - 3, $y + 4.5, $this->safe($cardNo));

        // Rule under the header
        $this->SetDrawColor(...self::C_RULE);
        $this->SetLineWidth(0.15);
        $this->Line($x + 3, $y + 6, $x + $w - 3, $y + 6);

        // ── Licence details grid ──
        $rx = $x + 3; $ry = $y + 10;
        $this->drawMetaRow($rx, $ry + 0,  'LICENCE NO',  $d['licence_no'] ?? '—');
        $this->drawMetaRow($rx, $ry + 4,  'CLASS',       $d['licence_class'] ?? '—');
        $this->drawMetaRow($rx, $ry + 8,  'LICENCE EXP', $this->fmtDate($d['licence_expires'] ?? ''));
        $this->drawMetaRow($rx, $ry + 12, 'NIDA',        $d['id_no'] ?? '—');
        $this->drawMetaRow($rx, $ry + 16, 'BLOOD GROUP', $d['blood_group'] ?? '—');
        $this->drawMetaRow($rx, $ry + 20, 'DOB',         $this->fmtDate($d['dob'] ?? ''));

        // ── QR block on the right ──
        $qrX = $x + $w - 22; $qrY = $y + 8;
        $qrSize = 19;
        $this->drawQr($qrX, $qrY, $qrSize, $this->verifyUrl($cardNo));
        $this->SetFont('OpenSans-Regular', '', 4.5);
        $this->SetTextColor(...self::C_MUTED);
        $this->Text($qrX + 3.8, $qrY + $qrSize + 2, $this->safe('SCAN · VERIFY'));

        // ── Emergency block ──
        $exY = $y + 36;
        $this->SetFillColor(...self::C_TINT);
        $this->Rect($x + 3, $exY, $w - 6, 8, 'F');
        $this->SetFont('OpenSans-Bold', '', 5);
        $this->SetTextColor(...self::C_BRAND_D);
        $this->Text($x + 5, $exY + 2.6, $this->safe('IN CASE OF EMERGENCY'));

        $emName = trim((string)($d['next_of_kin_name']  ?? '')) ?: '—';
        $emPhone= trim((string)($d['next_of_kin_phone'] ?? '')) ?: '—';
        $this->SetFont('OpenSans-Bold', '', 7);
        $this->SetTextColor(...self::C_INK);
        $this->Text($x + 5, $exY + 6.2, $this->safe($emName));
        $this->SetFont('OpenSans-Regular', '', 6);
        $this->SetTextColor(...self::C_BRAND_D);
        $nx = $x + 5 + $this->GetStringWidth($this->safe($emName)) + 2;
        $this->Text($nx, $exY + 6.2, $this->safe('· ' . $emPhone));

        // ── Bottom brand strip ──
        $this->SetFillColor(...self::C_BRAND);
        $this->Rect($x, $y + $h - 5, $w, 5, 'F');
        $this->SetFont('OpenSans-Regular', '', 4.5);
        $this->SetTextColor(255, 255, 255);
        $this->Text($x + 3, $y + $h - 1.6, $this->safe('IF FOUND, RETURN TO PACIFIC GROUP'));

        $phone = '+255 22 000 0000';
        $this->SetFont('OpenSans-Bold', '', 5);
        $pw = $this->GetStringWidth($phone);
        $this->Text($x + $w - $pw - 3, $y + $h - 1.6, $this->safe($phone));
    }

    /* ── Meta row helper: LABEL (mono) + value ─────────────────────── */
    private function drawMetaRow(float $x, float $y, string $label, ?string $value): void
    {
        $this->SetFont('OpenSans-Regular', '', 4.5);
        $this->SetTextColor(...self::C_MUTED);
        $this->Text($x, $y, $this->safe($label));

        $this->SetFont('OpenSans-Bold', '', 6);
        $this->SetTextColor(...self::C_INK);
        $this->Text($x + 15, $y, $this->safe((string)($value ?: '—')));
    }

    /* ── Hex outline (brand mark placeholder) ──────────────────────── */
    private function drawHex(float $cx, float $cy, float $r): void
    {
        // 6 vertices, flat-top variant
        $pts = [];
        for ($i = 0; $i < 6; $i++) {
            $a = deg2rad(60 * $i - 30);
            $pts[] = [$cx + $r * cos($a), $cy + $r * sin($a)];
        }
        for ($i = 0; $i < 6; $i++) {
            $j = ($i + 1) % 6;
            $this->Line($pts[$i][0], $pts[$i][1], $pts[$j][0], $pts[$j][1]);
        }
    }

    /**
     * Render a real scannable QR code inside the (x, y, size) block. Adds
     * ~1mm padding so the printed white space acts as a quiet zone (the
     * card's larger surrounding whitespace does the rest).
     *
     * Falls back to a plain outline if encoding throws — the printed URL
     * beneath keeps verification workable in that case.
     */
    private function drawQr(float $x, float $y, float $size, string $data): void
    {
        // White ground behind the QR — guarantees a clean quiet zone even if
        // the block ever ends up over a tinted panel.
        $this->SetFillColor(255, 255, 255);
        $this->Rect($x, $y, $size, $size, 'F');

        $pad   = 1.0;
        $inner = $size - 2 * $pad;

        try {
            $matrix = QrCode::encode($data);
        } catch (Throwable $e) {
            $this->SetDrawColor(...self::C_RULE);
            $this->SetLineWidth(0.15);
            $this->Rect($x, $y, $size, $size, 'D');
            return;
        }

        $count = QrCode::SIZE;
        $step  = $inner / $count;
        // Draw each dark module 1% oversize so adjacent cells overlap and no
        // hairline gap appears under anti-aliasing at low zoom.
        $cell  = $step * 1.02;

        $this->SetFillColor(...self::C_INK);
        for ($r = 0; $r < $count; $r++) {
            for ($c = 0; $c < $count; $c++) {
                if ($matrix[$r][$c] === 1) {
                    $this->Rect($x + $pad + $c * $step, $y + $pad + $r * $step, $cell, $cell, 'F');
                }
            }
        }
    }

    /* ── Crop marks for A4-sheet trimming ──────────────────────────── */
    private function drawCropMarks(float $x, float $y): void
    {
        $l = 2;   // mark length
        $g = 0.5; // gap from card edge
        $this->SetDrawColor(...self::C_RULE);
        $this->SetLineWidth(0.1);
        $w = self::CARD_W; $h = self::CARD_H;
        // Top-left
        $this->Line($x - $g - $l, $y,          $x - $g,      $y);
        $this->Line($x,          $y - $g - $l, $x,          $y - $g);
        // Top-right
        $this->Line($x + $w + $g,    $y,          $x + $w + $g + $l, $y);
        $this->Line($x + $w,          $y - $g - $l, $x + $w,          $y - $g);
        // Bottom-left
        $this->Line($x - $g - $l, $y + $h,     $x - $g,      $y + $h);
        $this->Line($x,          $y + $h + $g, $x,          $y + $h + $g + $l);
        // Bottom-right
        $this->Line($x + $w + $g,    $y + $h,     $x + $w + $g + $l, $y + $h);
        $this->Line($x + $w,          $y + $h + $g, $x + $w,          $y + $h + $g + $l);
    }

    /* ── Helpers ───────────────────────────────────────────────────── */
    public function cardNo(int $driverId): string
    {
        return sprintf('PGC-%04d-A', $driverId);
    }

    /** [validFrom, expires] — today and today + 24 months. */
    public function validity(): array
    {
        $from = new DateTimeImmutable('today');
        $to   = $from->modify('+24 months');
        return [$from->format('d M Y'), $to->format('d M Y')];
    }

    private function verifyUrl(string $cardNo): string
    {
        // Prefer the live host + PROOT when we're inside a web request so
        // links open on the same server the card was printed from. Fall back
        // to the prod URL for CLI (batch printing / cron / tests).
        if (!empty($_SERVER['HTTP_HOST'])) {
            $scheme = (($_SERVER['HTTPS'] ?? '') === 'on') ? 'https' : 'http';
            $host   = $scheme . '://' . $_SERVER['HTTP_HOST'] . PROOT;
        } else {
            $host = 'https://pacificgroup.co.tz/';
        }
        return $host . 'Verify/' . rawurlencode($cardNo);
    }

    private function localPhotoPath(string $url): ?string
    {
        if ($url === '') return null;
        // Convert /pacific_logistics/uploads/... URL back to a local disk path.
        $proot = rtrim(PROOT, '/');
        if (str_starts_with($url, $proot . '/')) {
            $rel = substr($url, strlen($proot) + 1);
            $p   = ROOT . '/' . $rel;
            return is_file($p) ? $p : null;
        }
        // Absolute URL — fall back to null (FPDF can't fetch remotes here).
        return null;
    }

    private function fmtDate(?string $ymd): string
    {
        if (!$ymd) return '—';
        $t = strtotime($ymd);
        return $t ? date('d M Y', $t) : $ymd;
    }

    /**
     * Down-convert UTF-8 → cp1252 for FPDF (same helper as invoice PDF).
     */
    private function safe($s): string
    {
        $s = (string)$s;
        $s = strtr($s, [
            "\u{2013}" => '-', "\u{2014}" => '-',
            "\u{2018}" => "'", "\u{2019}" => "'",
            "\u{201C}" => '"', "\u{201D}" => '"',
            "\u{2026}" => '...', "\u{2192}" => '->',
            "\u{00A0}" => ' ',  "\u{00B7}" => '.',
        ]);
        return @iconv('UTF-8', 'CP1252//TRANSLIT//IGNORE', $s) ?: $s;
    }
}
