<?php
// ============================================================
//  lib/qr_invite.php  — QR generation + ticket composer
//  PHP 8.1+ safe: all GD function arguments explicitly cast to int
// ============================================================

// Suppress deprecation notices from any third-party QR code libraries
// that may be installed on the server (e.g. t0k4rt/phpqrcode)
$_prev_er = error_reporting(error_reporting() & ~E_DEPRECATED & ~E_NOTICE);

// ── QR library loader ─────────────────────────────────────────
// Loads our bundled qrlib.php with error isolation.
// If the server has t0k4rt phpqrcode auto-loaded (via Composer etc.),
// the class_exists() check prevents double-loading.
if (!class_exists('QRcode')) {
    $__qrLib = BASE_PATH . '/lib/phpqrcode/qrlib.php';
    if (file_exists($__qrLib)) {
        // Suppress any deprecation/notice output from the included file
        $__prev = error_reporting(E_ERROR | E_PARSE);
        require $__qrLib;
        error_reporting($__prev);
    }
    unset($__qrLib, $__prev);
}

// Final fallback: if QRcode still not available, define a minimal stub
if (!class_exists('QRcode')) {
    if (!defined('QR_ECLEVEL_L')) define('QR_ECLEVEL_L', 0);
    if (!defined('QR_ECLEVEL_M')) define('QR_ECLEVEL_M', 1);
    if (!defined('QR_ECLEVEL_Q')) define('QR_ECLEVEL_Q', 2);
    if (!defined('QR_ECLEVEL_H')) define('QR_ECLEVEL_H', 3);

    class QRcode {
        /** @param string|false $outfile */
        public static function png(
            string       $text,
            string|false $outfile      = false,
            int          $level        = 3,
            int          $size         = 8,
            int          $margin       = 2,
            bool         $saveAndPrint = false
        ): void {
            $dim = ($size + $margin * 2) * 21;
            $img = imagecreatetruecolor($dim, $dim);
            $w   = imagecolorallocate($img, 255, 255, 255);
            $b   = imagecolorallocate($img, 0, 0, 0);
            imagefill($img, 0, 0, $w);
            imagestring($img, 2, 4, 4,  'NO QR LIB', $b);
            imagestring($img, 1, 4, 18, substr($text, -12), $b);
            if ($outfile) imagepng($img, $outfile);
            imagedestroy($img);
        }
    }
}

// ──────────────────────────────────────────────────────────────
//  generateQR()
// ──────────────────────────────────────────────────────────────
function generateQR(string $token, string $eventSlug): string {
    $safeSlug = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $eventSlug ?: 'preview');
    $dir      = QR_PATH . '/' . $safeSlug;
    if (!is_dir($dir)) mkdir($dir, 0755, true);

    $file      = $dir . '/' . $token . '.png';
    $isPreview = str_starts_with($token, 'preview_');

    if (!file_exists($file) || $isPreview) {
        $url = BASE_URL . '/api/checkin.php?token=' . urlencode($token);
        QRcode::png($url, $file, QR_ECLEVEL_H, 8, 2);
    }
    return $file;
}

// ──────────────────────────────────────────────────────────────
//  buildInvite()  — main entry point
// ──────────────────────────────────────────────────────────────
function buildInvite(array $guest, array $event): string {
    $slug = preg_replace('/[^a-zA-Z0-9_\-]/', '_',
        $event['slug'] ?? ('event-' . ($event['id'] ?? 'preview')));

    $dir = INVITE_PATH . '/' . $slug;
    if (!is_dir($dir)) mkdir($dir, 0755, true);

    $outFile = $dir . '/' . $guest['token'] . '.png';

    // Decode layout
    $layout = null;
    if (!empty($event['ticket_layout'])) {
        $layout = is_string($event['ticket_layout'])
            ? json_decode($event['ticket_layout'], true)
            : $event['ticket_layout'];
    }

    // Resolve template image.
    // 'ticket_template' is the current field (set via Ticket Designer).
    // 'invite_template' is DEPRECATED — kept only as a fallback so events
    // created before the Ticket Designer still render. Do not remove.
    $tplPath = null;
    foreach (['ticket_template', 'invite_template'] as $field) {
        if (!empty($event[$field])) {
            $resolved = resolveUploadPath($event[$field]);
            if ($resolved) { $tplPath = $resolved; break; }
        }
    }

    // Build canvas
    $W = $H = 0;
    $canvas = null;

    if ($tplPath) {
        $ext = strtolower(pathinfo($tplPath, PATHINFO_EXTENSION));
        $src = match($ext) {
            'jpg','jpeg' => @imagecreatefromjpeg($tplPath),
            'png'        => @imagecreatefrompng($tplPath),
            default      => false
        };
        if ($src) {
            $W = imagesx($src);
            $H = imagesy($src);
            $canvas = imagecreatetruecolor($W, $H);
            imagealphablending($canvas, false);
            imagesavealpha($canvas, true);
            $transparent = imagecolorallocatealpha($canvas, 0, 0, 0, 127);
            imagefill($canvas, 0, 0, $transparent);
            imagealphablending($canvas, true);
            imagecopyresampled($canvas, $src, 0, 0, 0, 0, $W, $H, $W, $H);
            imagedestroy($src);
        }
    }

    if (!$canvas) {
        $W = 900; $H = 600;
        $canvas = buildDefaultCanvas($W, $H, $event, $guest);
    }

    // Generate QR code
    $qrFile = generateQR($guest['token'], $slug);

    // Verify QR file is a valid non-empty PNG before loading
    $qrSrc = null;
    if (file_exists($qrFile) && filesize($qrFile) > 50) {
        $qrSrc = @imagecreatefrompng($qrFile);
    }

    // If QR load failed, generate a visible fallback square
    if (!$qrSrc) {
        $qrSrc = imagecreatetruecolor(200, 200);
        $qrBg  = imagecolorallocate($qrSrc, 255, 255, 255);
        $qrFg  = imagecolorallocate($qrSrc, 30, 30, 30);
        imagefill($qrSrc, 0, 0, $qrBg);
        // Draw a simple border so it's visible
        imagerectangle($qrSrc, 5, 5, 194, 194, $qrFg);
        imagerectangle($qrSrc, 15, 15, 184, 184, $qrFg);
        imagestring($qrSrc, 2, 55, 90, 'QR CODE', $qrFg);
        // Save it so next call doesn't regenerate
        if ($qrFile) imagepng($qrSrc, $qrFile);
    }

    if ($layout && isset($layout['qr'])) {
        // ── Designer layout ───────────────────────────────────
        $q       = $layout['qr'];
        $qrX     = intval($q['x']       ?? 0);
        $qrY     = intval($q['y']       ?? 0);
        $qrSize  = intval($q['size']    ?? 160);
        $padding = intval($q['padding'] ?? 6);

        if ($padding > 0) {
            $white = imagecolorallocate($canvas, 255, 255, 255);
            imagefilledrectangle(
                $canvas,
                $qrX - $padding, $qrY - $padding,
                $qrX + $qrSize + $padding, $qrY + $qrSize + $padding,
                $white
            );
        }
        if ($qrSrc) {
            imagecopyresampled(
                $canvas, $qrSrc,
                $qrX, $qrY, 0, 0,
                $qrSize, $qrSize,
                imagesx($qrSrc), imagesy($qrSrc)
            );
            imagedestroy($qrSrc);
        }

        // ── Name text ─────────────────────────────────────────
        if (isset($layout['name'])) {
            $n = $layout['name'];
            // All values from JSON: force to int/string — never pass raw JSON float to GD
            $nameX    = intval($n['x']        ?? 40);
            $nameY    = intval($n['y']        ?? 40);
            $maxW     = intval($n['maxWidth'] ?? 400);
            $fontSize = intval($n['fontSize'] ?? 32);
            $colorHex = (string)($n['color']     ?? '#ffffff');
            $align    = (string)($n['align']     ?? 'center');
            $bold     = ((string)($n['fontStyle'] ?? 'bold')) === 'bold';
            $fontFile = (string)($n['fontFile']  ?? '');

            $rgb = hexToRgb($colorHex);
            $col = imagecolorallocate($canvas, $rgb[0], $rgb[1], $rgb[2]);

            $drawn = drawTextTTF(
                $canvas, (string)$guest['name'],
                $nameX, $nameY, $maxW, $fontSize,
                $col, $align, $bold, $fontFile
            );
            if (!$drawn) {
                drawTextGD(
                    $canvas, (string)$guest['name'],
                    $nameX, $nameY, $maxW, $fontSize, $col, $align
                );
            }
        }

    } else {
        // ── Legacy: fixed bottom-right QR ─────────────────────
        $qrSize = 160;
        $qrX    = $W - $qrSize - 30;
        $qrY    = $H - $qrSize - 30;
        $white  = imagecolorallocate($canvas, 255, 255, 255);
        imagefilledrectangle($canvas,
            $qrX - 8, $qrY - 8,
            $qrX + $qrSize + 8, $qrY + $qrSize + 8,
            $white
        );
        imagecopyresampled(
            $canvas, $qrSrc ?? imagecreatetruecolor(1,1),
            $qrX, $qrY, 0, 0,
            $qrSize, $qrSize,
            $qrSrc ? imagesx($qrSrc) : 1,
            $qrSrc ? imagesy($qrSrc) : 1
        );
        if ($qrSrc) imagedestroy($qrSrc);

        $black = imagecolorallocate($canvas, 20, 20, 20);
        imagestring($canvas, 3, $qrX, $qrY + $qrSize + 12,
            mb_strtoupper(mb_substr((string)$guest['name'], 0, 24)), $black);
    }

    imagepng($canvas, $outFile, 8);
    imagedestroy($canvas);
    return $outFile;
}

// ──────────────────────────────────────────────────────────────
//  TTF text rendering
// ──────────────────────────────────────────────────────────────
function drawTextTTF(
    GdImage $canvas,
    string  $text,
    int     $x,
    int     $y,
    int     $maxW,
    int     $fontSize,
    int     $color,
    string  $align,
    bool    $bold,
    string  $explicitFont = ''
): bool {
    $font = resolveFontFile($explicitFont, $bold);
    if (!$font) return false;
    return renderTTFLines($canvas, $text, $font, $x, $y, $maxW, $fontSize, $color, $align);
}

function resolveFontFile(string $explicit, bool $bold): string|false {
    $candidates = [];

    // 1. Explicitly selected font from designer
    if ($explicit) {
        $candidates[] = BASE_PATH . '/lib/fonts/' . basename($explicit);
    }
    // 2. Custom uploaded fonts in lib/fonts/
    $candidates[] = BASE_PATH . '/lib/fonts/font-bold.ttf';
    $candidates[] = BASE_PATH . '/lib/fonts/font.ttf';

    // 3. System fonts
    $candidates = array_merge($candidates, [
        '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
        '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
        '/usr/share/fonts/truetype/freefont/FreeSansBold.ttf',
        '/usr/share/fonts/truetype/freefont/FreeSans.ttf',
        '/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf',
        '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf',
        '/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf',
        '/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf',
        'C:/Windows/Fonts/arialbd.ttf',
        'C:/Windows/Fonts/arial.ttf',
    ]);

    foreach ($candidates as $path) {
        if (!file_exists($path)) continue;
        // Verify the font actually works with GD before returning it
        $test = @imagettfbbox(12, 0, $path, 'A');
        if (is_array($test)) return $path;
    }
    return false;
}

function renderTTFLines(
    GdImage $canvas,
    string  $text,
    string  $font,
    int     $x,
    int     $y,
    int     $maxW,
    int     $fontSize,
    int     $color,
    string  $align
): bool {
    $lines = wrapTextTTF($text, $font, $fontSize, $maxW);
    $lineH = (int) round($fontSize * 1.3);

    foreach ($lines as $i => $line) {
        $box = @imagettfbbox($fontSize, 0, $font, $line);
        // imagettfbbox returns false if font is invalid — fall back to GD
        if (!is_array($box) || !isset($box[0], $box[4])) {
            return false;
        }
        $lineW = (int) abs($box[4] - $box[0]);

        $drawX = match ($align) {
            'center' => (int) round($x + ($maxW - $lineW) / 2),
            'right'  => (int) ($x + $maxW - $lineW),
            default  => $x,
        };
        $drawY = (int) ($y + $fontSize + $i * $lineH);

        $result = @imagettftext($canvas, $fontSize, 0, $drawX, $drawY, $color, $font, $line);
        if ($result === false) return false;
    }
    return true;
}

function wrapTextTTF(string $text, string $font, int $fontSize, int $maxW): array {
    if ($maxW <= 0) return [$text];
    // Verify font works before trying to wrap
    $test = @imagettfbbox($fontSize, 0, $font, 'A');
    if (!is_array($test)) return [$text]; // font broken — return unwrapped, caller will fall back

    $words = explode(' ', $text);
    $lines = [];
    $line  = '';
    foreach ($words as $word) {
        $probe = $line ? "$line $word" : $word;
        $box   = @imagettfbbox($fontSize, 0, $font, $probe);
        if (!is_array($box) || !isset($box[0], $box[4])) {
            // Can't measure — just accumulate
            $line = $probe;
            continue;
        }
        $w = (int) abs($box[4] - $box[0]);
        if ($w <= $maxW || !$line) {
            $line = $probe;
        } else {
            $lines[] = $line;
            $line    = $word;
        }
    }
    if ($line !== '') $lines[] = $line;
    return $lines ?: [$text];
}

// ──────────────────────────────────────────────────────────────
//  GD bitmap fallback (no TTF font available)
// ──────────────────────────────────────────────────────────────
function drawTextGD(
    GdImage $canvas,
    string  $text,
    int     $x,
    int     $y,
    int     $maxW,
    int     $fontSize,
    int     $color,
    string  $align
): void {
    $gdFont = match (true) {
        $fontSize >= 40 => 5,
        $fontSize >= 28 => 4,
        $fontSize >= 18 => 3,
        $fontSize >= 12 => 2,
        default         => 1,
    };
    $charW  = (int) imagefontwidth($gdFont);
    $charH  = (int) imagefontheight($gdFont);
    $cols   = max(1, (int) ($maxW / max(1, $charW)));
    $lines  = mb_str_split($text, $cols) ?: [$text];

    foreach ($lines as $i => $line) {
        $lineW  = $charW * mb_strlen($line);
        $drawX  = match ($align) {
            'center' => (int) round($x + ($maxW - $lineW) / 2),
            'right'  => (int) ($x + $maxW - $lineW),
            default  => $x,
        };
        $drawY = (int) ($y + $i * ($charH + 4));
        imagestring($canvas, $gdFont, $drawX, $drawY, $line, $color);
    }
}

// ──────────────────────────────────────────────────────────────
//  Helpers
// ──────────────────────────────────────────────────────────────
function hexToRgb(string $hex): array {
    $hex = ltrim($hex, '#');
    if (strlen($hex) === 3) {
        $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
    }
    return [
        (int) hexdec(substr($hex, 0, 2)),
        (int) hexdec(substr($hex, 2, 2)),
        (int) hexdec(substr($hex, 4, 2)),
    ];
}

function buildDefaultCanvas(int $W, int $H, array $event, array $guest): GdImage {
    $c    = imagecreatetruecolor($W, $H);
    $bg   = imagecolorallocate($c, 12, 12, 18);
    $gold = imagecolorallocate($c, 197, 158, 89);
    $wht  = imagecolorallocate($c, 240, 235, 220);

    imagefill($c, 0, 0, $bg);
    imagefilledrectangle($c, 0, 0, 6, $H, $gold);
    imagefilledrectangle($c, $W - 6, 0, $W, $H, $gold);

    imagestring($c, 5, 40, 60,
        mb_strtoupper(mb_substr((string)($event['title'] ?? ''), 0, 30)), $gold);
    imagestring($c, 3, 40, 90,
        (string)($event['venue'] ?? ''), $wht);

    $ds = !empty($event['event_date'])
        ? date('l, F j, Y', strtotime($event['event_date']))
        : '';
    imagestring($c, 3, 40, 114, $ds, $wht);
    imageline($c, 40, 170, $W - 240, 170, $gold);
    imagestring($c, 5, 40, 200, 'INVITED GUEST', $gold);
    imagestring($c, 4, 40, 228, (string)($guest['name'] ?? ''), $wht);

    if (!empty($guest['table_number'])) {
        imagestring($c, 3, 40, 264, 'Table: ' . $guest['table_number'], $wht);
    }
    if (!empty($guest['category'])) {
        imagestring($c, 3, 40, 288, 'Category: ' . $guest['category'], $wht);
    }
    return $c;
}
