<?php
/**
 * Branch ID Card Controller — Modern Vertical Design + Bulk Print
 *
 * Features:
 *   - index()      → List students with "Generate ID Card" button
 *   - generate()   → Generate single ID card PDF (vertical, maroon design)
 *   - bulkPrint()  → Generate multiple ID cards on A4 page (2 per page)
 *
 * Design (per attached image):
 *   - Top: Maroon background with institute name + logo
 *   - Gold diagonal stripe
 *   - Center: Circular photo with gold border
 *   - Name (bold, maroon, centered)
 *   - ID details (left-aligned, black text)
 *   - Footer: Signature (left) + QR code (right)
 *   - Hole punch indicator at top
 *
 * URL prefix: /branch/id-cards/*
 */

namespace BranchAdmin\Controllers;

use BranchAdmin\Controllers\BranchAdminController;

// ─── Load FPDF BEFORE declaring CircleFPDF (PHP needs it at class-declaration time) ───
if (!class_exists('FPDF')) {
    $fpdfCandidates = [
        dirname(dirname(dirname(__DIR__))) . '/fpdf/fpdf.php',
        dirname(dirname(dirname(dirname(__DIR__)))) . '/fpdf/fpdf.php',
        $_SERVER['DOCUMENT_ROOT'] . '/fpdf/fpdf.php',
        $_SERVER['DOCUMENT_ROOT'] . '/gmind/fpdf/fpdf.php',
        '/home/advaysin/public_html/gmind/fpdf/fpdf.php',
        '/home/advaysin/public_html/fpdf/fpdf.php',
    ];
    foreach ($fpdfCandidates as $fpdfPath) {
        if (file_exists($fpdfPath)) {
            require_once $fpdfPath;
            break;
        }
    }
}

/**
 * Custom FPDF extension with Circle() and Ellipse() support.
 * Must be declared OUTSIDE of any class/method (PHP restriction).
 * FPDF must be loaded above before this class declaration.
 */
if (!class_exists('BranchAdmin\Controllers\CircleFPDF') && class_exists('FPDF')) {
    class CircleFPDF extends \FPDF {
        /**
         * Draw a circle.
         * @param float $x Center X
         * @param float $y Center Y
         * @param float $r Radius
         * @param string $style 'D' = draw, 'F' = fill, 'DF' = both
         */
        public function Circle($x, $y, $r, $style = 'D'): void
        {
            $this->Ellipse($x, $y, $r, $r, $style);
        }

        /**
         * Draw an ellipse using 4 bezier curves.
         */
        public function Ellipse($x, $y, $rx, $ry, $style = 'D'): void
        {
            if ($style === 'F') {
                $op = 'f';
            } elseif ($style === 'DF' || $style === 'FD') {
                $op = 'B';
            } else {
                $op = 'S';
            }
            $k = $this->k;
            $h = $this->h;

            $stream = sprintf('%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c',
                ($x + $rx) * $k, ($h - $y) * $k,
                ($x + $rx) * $k, ($h - ($y - $ry * 0.5523)) * $k,
                ($x + $rx * 0.5523) * $k, ($h - ($y - $ry)) * $k,
                $x * $k, ($h - ($y - $ry)) * $k
            );
            $stream .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F c',
                ($x - $rx * 0.5523) * $k, ($h - ($y - $ry)) * $k,
                ($x - $rx) * $k, ($h - ($y - $ry * 0.5523)) * $k,
                ($x - $rx) * $k, ($h - $y) * $k
            );
            $stream .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F c',
                ($x - $rx) * $k, ($h - ($y + $ry * 0.5523)) * $k,
                ($x - $rx * 0.5523) * $k, ($h - ($y + $ry)) * $k,
                $x * $k, ($h - ($y + $ry)) * $k
            );
            $stream .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F c %s',
                ($x + $rx * 0.5523) * $k, ($h - ($y + $ry)) * $k,
                ($x + $rx) * $k, ($h - ($y + $ry * 0.5523)) * $k,
                ($x + $rx) * $k, ($h - $y) * $k,
                $op
            );
            $this->_out($stream);
        }

        /**
         * Rounded Rectangle (bonus — useful for card corners).
         */
        public function RoundedRect($x, $y, $w, $h, $r, $style = 'D'): void
        {
            $k = $this->k;
            $hp = $this->h;
            $op = ($style === 'F') ? 'f' : (($style === 'FD' || $style === 'DF') ? 'B' : 'S');

            $stream = sprintf('%.3F %.3F m', ($x + $r) * $k, ($hp - $y) * $k);
            $stream .= sprintf(' %.3F %.3F l', ($x + $w - $r) * $k, ($hp - $y) * $k);
            $stream .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F c',
                ($x + $w - $r * 0.5523) * $k, ($hp - $y) * $k,
                ($x + $w) * $k, ($hp - ($y - $r * 0.5523)) * $k,
                ($x + $w) * $k, ($hp - ($y + $r)) * $k
            );
            $stream .= sprintf(' %.3F %.3F l', ($x + $w) * $k, ($hp - ($y + $h - $r)) * $k);
            $stream .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F c',
                ($x + $w) * $k, ($hp - ($y + $h - $r * 0.5523)) * $k,
                ($x + $w - $r * 0.5523) * $k, ($hp - ($y + $h)) * $k,
                ($x + $w - $r) * $k, ($hp - ($y + $h)) * $k
            );
            $stream .= sprintf(' %.3F %.3F l', ($x + $r) * $k, ($hp - ($y + $h)) * $k);
            $stream .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F c',
                ($x + $r * 0.5523) * $k, ($hp - ($y + $h)) * $k,
                $x * $k, ($hp - ($y + $h - $r * 0.5523)) * $k,
                $x * $k, ($hp - ($y + $h - $r)) * $k
            );
            $stream .= sprintf(' %.3F %.3F l', $x * $k, ($hp - ($y + $r)) * $k);
            $stream .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F c',
                $x * $k, ($hp - ($y + $r * 0.5523)) * $k,
                ($x + $r * 0.5523) * $k, ($hp - $y) * $k,
                ($x + $r) * $k, ($hp - $y) * $k
            );
            $stream .= ' ' . $op;
            $this->_out($stream);
        }
    }
}

class BranchIdCardController extends BranchAdminController
{
    /**
     * List all students with "Generate ID Card" + checkbox for bulk print.
     */
    public function index(): void
    {
        requireBranchAdmin();
        $branchId = $this->getBranchId();

        $search = trim($_GET['search'] ?? '');
        $courseFilter = trim($_GET['course_id'] ?? '');

        $sql = "SELECT s.*, u.name, u.email, u.phone, u.photo,
                       c.name as course_name, c.duration,
                       b.name as branch_name, b.branch_code
                FROM students s
                INNER JOIN users u ON s.user_id = u.id
                LEFT JOIN courses c ON s.course_id = c.id
                LEFT JOIN branches b ON s.branch_id = b.id
                WHERE s.branch_id = :bid AND s.status = 'active'";
        $params = [':bid' => $branchId];

        if ($search !== '') {
            $sql .= " AND (u.name LIKE :q OR s.enrollment_no LIKE :q OR u.phone LIKE :q)";
            $params[':q'] = "%{$search}%";
        }
        if ($courseFilter !== '') {
            $sql .= " AND s.course_id = :cid";
            $params[':cid'] = $courseFilter;
        }
        $sql .= " ORDER BY u.name ASC LIMIT 500";

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        $students = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];

        $courses = [];
        try {
            $courses = $this->db->query("SELECT id, name FROM courses WHERE active=1 ORDER BY name")->fetchAll(\PDO::FETCH_ASSOC) ?: [];
        } catch (\Throwable $e) {}

        $this->branchAdminView('id_cards/index', [
            'title'        => 'ID Cards',
            'currentPage'  => 'id_cards',
            'students'     => $students,
            'courses'      => $courses,
            'search'       => $search,
            'courseFilter' => $courseFilter,
            'csrfToken'    => $this->csrfToken ?? '',
        ]);
    }

    /**
     * Generate single ID card PDF.
     */
    public function generate(array $params): void
    {
        requireBranchAdmin();
        $branchId = $this->getBranchId();
        $id = (int)($params['id'] ?? 0);

        $student = $this->fetchStudent($id, $branchId);
        if (!$student) {
            flash('error', 'Student not found.');
            redirect(BASE_PATH . '/branch/id-cards');
        }

        $this->generateIdCardPdf([$student], 1);
    }

    /**
     * Bulk print — multiple ID cards on A4 page.
     */
    public function bulkPrint(): void
    {
        requireBranchAdmin();
        $this->validateCsrf();
        $branchId = $this->getBranchId();

        $selectedIds = $_POST['selected'] ?? [];
        if (is_string($selectedIds)) {
            $selectedIds = explode(',', $selectedIds);
        }
        $selectedIds = array_filter(array_map('intval', $selectedIds));

        if (empty($selectedIds)) {
            flash('error', '❌ No students selected. Please select at least one.');
            redirect(BASE_PATH . '/branch/id-cards');
        }

        $students = [];
        foreach ($selectedIds as $sid) {
            $s = $this->fetchStudent($sid, $branchId);
            if ($s) $students[] = $s;
        }

        if (empty($students)) {
            flash('error', '❌ No valid students found.');
            redirect(BASE_PATH . '/branch/id-cards');
        }

        $this->generateIdCardPdf($students, count($students));
    }

    // ════════════════════════════════════════════════════════════════════════
    // PRIVATE HELPERS
    // ════════════════════════════════════════════════════════════════════════

    private function fetchStudent(int $id, int $branchId): ?array
    {
        $stmt = $this->db->prepare(
            "SELECT s.*, u.name, u.email, u.phone, u.photo,
                    c.name as course_name, c.duration,
                    b.name as branch_name, b.branch_code, b.address, b.city, b.state, b.phone as branch_phone
             FROM students s
             INNER JOIN users u ON s.user_id = u.id
             LEFT JOIN courses c ON s.course_id = c.id
             LEFT JOIN branches b ON s.branch_id = b.id
             WHERE s.id = :sid AND s.branch_id = :bid LIMIT 1"
        );
        $stmt->execute([':sid' => $id, ':bid' => $branchId]);
        return $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;
    }

    /**
     * Generate ID card PDF — vertical card design (85.6mm × 54mm).
     * For single card: 1 card on A4 (centered, ready to print)
     * For bulk: 3 cards per row on A4
     */
    private function generateIdCardPdf(array $students, int $totalSelected): void
    {
        // Load FPDF
        if (!class_exists('FPDF')) {
            $candidates = [
                dirname(dirname(dirname(__DIR__))) . '/fpdf/fpdf.php',
                dirname(dirname(dirname(dirname(__DIR__)))) . '/fpdf/fpdf.php',
                $_SERVER['DOCUMENT_ROOT'] . '/fpdf/fpdf.php',
                $_SERVER['DOCUMENT_ROOT'] . '/gmind/fpdf/fpdf.php',
                '/home/advaysin/public_html/gmind/fpdf/fpdf.php',
                '/home/advaysin/public_html/fpdf/fpdf.php',
            ];
            foreach ($candidates as $path) {
                if (file_exists($path)) {
                    require_once $path;
                    break;
                }
            }
        }

        if (!class_exists('FPDF')) {
            flash('error', 'FPDF library not found at /gmind/fpdf/fpdf.php');
            redirect(BASE_PATH . '/branch/id-cards');
        }

        // Card dimensions — standard CR80 portrait: 54mm × 85.6mm
        $cardW = 54;
        $cardH = 85.6;

        if ($totalSelected === 1) {
            // Single card — use A4 portrait, center the card
            $pdf = new CircleFPDF('P', 'mm', 'A4');
            $pdf->SetMargins(0, 0, 0);
            $pdf->SetAutoPageBreak(false);
            $pdf->AddPage();
            // Center horizontally: (210 - 54) / 2 = 78
            // Center vertically: (297 - 85.6) / 2 = 105.7
            $this->drawIdCard($pdf, $students[0], 78, 105.7, $cardW, $cardH);
            $downloadName = 'ID_Card_' . preg_replace('/[^a-zA-Z0-9]/', '_', $students[0]['name'] ?? 'student') . '.pdf';
        } else {
            // Bulk print — 3 cards per row on A4 portrait
            // A4 portrait: 210mm × 297mm
            // 3 cards per row: 3 × 54 = 162mm + 2 gaps × 10mm = 182mm (fits in 210)
            // Rows per page: floor((297 - 20) / (85.6 + 10)) = floor(277 / 95.6) = 2 rows → 6 cards per page
            $pdf = new CircleFPDF('P', 'mm', 'A4');  // Portrait
            $pdf->SetMargins(0, 0, 0);
            $pdf->SetAutoPageBreak(false);

            $marginX = 14;
            $marginY = 12;
            $gapX = 10;
            $gapY = 10;
            $cardsPerRow = 3;
            $cardsPerCol = 3;  // 3 rows fit in A4 portrait
            $cardsPerPage = $cardsPerRow * $cardsPerCol;  // 9 per page

            $cardIndex = 0;
            foreach ($students as $student) {
                $positionInPage = $cardIndex % $cardsPerPage;
                if ($positionInPage === 0) {
                    $pdf->AddPage();
                }

                $col = $positionInPage % $cardsPerRow;
                $row = floor($positionInPage / $cardsPerRow);

                $x = $marginX + $col * ($cardW + $gapX);
                $y = $marginY + $row * ($cardH + $gapY);

                $this->drawIdCard($pdf, $student, $x, $y, $cardW, $cardH);
                $cardIndex++;
            }
            $downloadName = 'ID_Cards_Bulk_' . count($students) . '_students_' . date('Y-m-d') . '.pdf';
        }

        header('Content-Type: application/pdf');
        header('Content-Disposition: attachment; filename="' . $downloadName . '"');
        header('Cache-Control: private, max-age=0, must-revalidate');
        while (ob_get_level()) ob_end_clean();
        $pdf->Output('D', $downloadName);
        exit;
    }

    /**
     * Draw a single ID card (portrait, 54mm × 85.6mm).
     * Design: Maroon top, gold stripe, circular photo (truly round via GD), name, details, QR + signature.
     */
    private function drawIdCard(\FPDF $pdf, array $student, float $x, float $y, float $w, float $h): void
    {
        // ─── Card border (subtle gray) ────────────────────────────────────
        $pdf->SetDrawColor(180, 180, 180);
        $pdf->SetLineWidth(0.3);
        $pdf->Rect($x, $y, $w, $h, 'D');
        $pdf->SetLineWidth(0.2);

        // ─── Top maroon section (compact — just enough for 3 lines) ────────
        $topH = 18;  // Fixed 18mm height (was 28% = ~24mm)
        $pdf->SetFillColor(109, 33, 38);  // Deep maroon
        $pdf->Rect($x, $y, $w, $topH, 'F');

        // ─── Gold horizontal stripe (separator) ───────────────────────────
        $stripeY = $y + $topH;
        $pdf->SetFillColor(212, 175, 55);  // Gold
        $pdf->Rect($x, $stripeY, $w, 1.5, 'F');

        // ─── Institute name (white, centered in maroon section) ───────────
        $branchName = $student['branch_name'] ?? 'GMIND INSTITUTE';
        if (strlen($branchName) > 20) {
            $branchName = substr($branchName, 0, 20);
        }
        $pdf->SetTextColor(255, 255, 255);
        $pdf->SetFont('Arial', 'B', 7);
        $pdf->SetXY($x, $y + 3);
        $pdf->Cell($w, 4, strtoupper($branchName), 0, 1, 'C');

        // Subtitle
        $pdf->SetFont('Arial', '', 5);
        $pdf->SetTextColor(255, 220, 180);
        $pdf->SetXY($x, $y + 7);
        $pdf->Cell($w, 3, 'STUDENT ID CARD', 0, 1, 'C');

        // Branch code
        $pdf->SetFont('Arial', '', 5);
        $pdf->SetTextColor(255, 255, 255);
        $pdf->SetXY($x, $y + 10);
        $pdf->Cell($w, 3, 'Code: ' . ($student['branch_code'] ?? '—'), 0, 1, 'C');

        // ─── Circular photo (centered in WHITE body section, below gold stripe) ──
        // ✅ Photo placed ENTIRELY in white body section so white corners are invisible
        $photoCenterX = $x + ($w / 2);
        $photoRadius = 12;
        // Photo center = gold stripe + 3mm gap + radius
        $photoCenterY = $stripeY + 3 + $photoRadius;

        // White background circle (same as card body — invisible edges)
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Circle($photoCenterX, $photoCenterY, $photoRadius + 2, 'F');

        // Gold border ring
        $pdf->SetDrawColor(212, 175, 55);  // Gold
        $pdf->SetLineWidth(1);
        $pdf->Circle($photoCenterX, $photoCenterY, $photoRadius + 1, 'D');

        // ✅ Generate truly circular photo using GD library
        $photoPath = $this->resolvePhotoPath($student['photo'] ?? '');
        $circularPhotoPath = $this->createCircularPhoto($photoPath, $photoRadius);

        if ($circularPhotoPath) {
            try {
                $imgSize = $photoRadius * 2;
                $pdf->Image($circularPhotoPath, $photoCenterX - $photoRadius, $photoCenterY - $photoRadius, $imgSize, $imgSize);
                @unlink($circularPhotoPath);  // Cleanup temp file
            } catch (\Throwable $e) {
                $this->drawPhotoPlaceholder($pdf, $photoCenterX, $photoCenterY, $photoRadius);
            }
        } else {
            $this->drawPhotoPlaceholder($pdf, $photoCenterX, $photoCenterY, $photoRadius);
        }

        $pdf->SetLineWidth(0.2);

        // ─── Name (bold, maroon, centered) ────────────────────────────────
        $nameY = $photoCenterY + $photoRadius + 2;
        $studentName = $student['name'] ?? 'Student Name';
        if (strlen($studentName) > 22) {
            $studentName = substr($studentName, 0, 22);
        }
        $pdf->SetTextColor(109, 33, 38);  // Maroon
        $pdf->SetFont('Arial', 'B', 8);
        $pdf->SetXY($x, $nameY);
        $pdf->Cell($w, 4, strtoupper($studentName), 0, 1, 'C');

        // ─── ID Details (left-aligned, black text) ────────────────────────
        $detailsY = $nameY + 5;
        $detailX = $x + 3;
        $lineHeight = 3.8;
        $labelW = 16;
        $valueW = $w - $labelW - 6;

        // Helper function to draw a detail row
        $drawDetail = function($pdf, $y, $label, $value, $detailX, $labelW, $valueW, $lineHeight) {
            $pdf->SetXY($detailX, $y);
            $pdf->SetFont('Arial', 'B', 6);
            $pdf->SetTextColor(80, 80, 80);
            $pdf->Cell($labelW, $lineHeight, $label . ':', 0, 0);
            $pdf->SetFont('Arial', '', 6);
            $pdf->SetTextColor(0, 0, 0);
            if (strlen($value) > 28) $value = substr($value, 0, 28);
            $pdf->Cell($valueW, $lineHeight, ' ' . $value, 0, 1);
        };

        $drawDetail($pdf, $detailsY, 'ID No', $student['enrollment_no'] ?? '—', $detailX, $labelW, $valueW, $lineHeight);
        $drawDetail($pdf, $detailsY + $lineHeight, 'Course', $student['course_name'] ?? '—', $detailX, $labelW, $valueW, $lineHeight);
        $drawDetail($pdf, $detailsY + ($lineHeight * 2), 'Duration', $student['duration'] ?? '—', $detailX, $labelW, $valueW, $lineHeight);
        $drawDetail($pdf, $detailsY + ($lineHeight * 3), 'Phone', $student['phone'] ?? '—', $detailX, $labelW, $valueW, $lineHeight);

        $dob = !empty($student['dob']) ? date('d/m/Y', strtotime($student['dob'])) : '—';
        $drawDetail($pdf, $detailsY + ($lineHeight * 4), 'D.O.B', $dob, $detailX, $labelW, $valueW, $lineHeight);

        $addr = trim(($student['address'] ?? '') . ', ' . ($student['city'] ?? ''));
        if (strlen($addr) > 25) $addr = substr($addr, 0, 25) . '...';
        $drawDetail($pdf, $detailsY + ($lineHeight * 5), 'Address', $addr, $detailX, $labelW, $valueW, $lineHeight);

        // ─── Footer: QR code (right) + Signature (left) ───────────────────
        $footerY = $y + $h - 14;

        // QR code (bottom-right)
        $qrSize = 12;
        $qrX = $x + $w - $qrSize - 3;
        $qrY = $footerY;

        $qrTempPath = null;
        try {
            $verifyUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http') . '://'
                       . ($_SERVER['HTTP_HOST'] ?? 'localhost')
                       . (defined('BASE_PATH') ? BASE_PATH : '/gmind')
                       . '/verify/student/' . urlencode($student['enrollment_no'] ?? '');
            $qrApiUrl = 'https://quickchart.io/qr?text=' . urlencode($verifyUrl) . '&size=120&margin=1';

            $qrTempPath = sys_get_temp_dir() . '/qr_idcard_' . ($student['id'] ?? 'tmp') . '_' . time() . '_' . rand(1000,9999) . '.png';
            $qrData = @file_get_contents($qrApiUrl);
            if ($qrData !== false && strlen($qrData) > 100) {
                file_put_contents($qrTempPath, $qrData);
            } else {
                $qrTempPath = null;
            }
        } catch (\Throwable $e) {
            $qrTempPath = null;
        }

        if ($qrTempPath && file_exists($qrTempPath)) {
            // Gold border around QR
            $pdf->SetDrawColor(212, 175, 55);
            $pdf->SetLineWidth(0.5);
            $pdf->Rect($qrX - 0.5, $qrY - 0.5, $qrSize + 1, $qrSize + 1, 'D');
            $pdf->Image($qrTempPath, $qrX, $qrY, $qrSize, $qrSize);
            @unlink($qrTempPath);
        } else {
            $pdf->SetDrawColor(180, 180, 180);
            $pdf->Rect($qrX, $qrY, $qrSize, $qrSize, 'D');
            $pdf->SetFont('Arial', '', 5);
            $pdf->SetTextColor(180, 180, 180);
            $pdf->SetXY($qrX, $qrY + 4);
            $pdf->Cell($qrSize, 4, 'QR', 0, 0, 'C');
        }
        $pdf->SetLineWidth(0.2);

        // Signature line (left of QR)
        $sigX = $x + 3;
        $sigY = $footerY + 8;
        $sigW = $qrX - $sigX - 3;
        $pdf->SetDrawColor(80, 80, 80);
        $pdf->SetLineWidth(0.2);
        $pdf->Line($sigX, $sigY, $sigX + $sigW, $sigY);
        $pdf->SetFont('Arial', '', 5);
        $pdf->SetTextColor(80, 80, 80);
        $pdf->SetXY($sigX, $sigY + 1);
        $pdf->Cell($sigW, 3, 'Authorized Signatory', 0, 0, 'C');

        // Validity text
        $validity = !empty($student['admission_date']) ? date('m/Y', strtotime($student['admission_date'])) . ' - ' . date('m/Y', strtotime('+1 year')) : '—';
        $pdf->SetXY($sigX, $sigY + 4);
        $pdf->SetFont('Arial', '', 5);
        $pdf->SetTextColor(100, 100, 100);
        $pdf->Cell($sigW, 3, 'Valid: ' . $validity, 0, 0, 'C');
    }

    /**
     * Draw a placeholder for photo (when no photo available).
     */
    private function drawPhotoPlaceholder(\FPDF $pdf, float $cx, float $cy, float $r): void
    {
        $pdf->SetFillColor(240, 240, 240);
        $pdf->Circle($cx, $cy, $r - 0.5, 'F');
        $pdf->SetTextColor(180, 180, 180);
        $pdf->SetFont('Arial', 'B', 14);
        $pdf->SetXY($cx - 5, $cy - 4);
        $pdf->Cell(10, 8, '?', 0, 0, 'C');
    }

    /**
     * Create a truly circular photo using GD library.
     * Returns path to temp JPEG file with circular crop (white background outside circle), or null on failure.
     *
     * ✅ FIX: Uses WHITE background (not transparent) + JPEG output because:
     *   - FPDF renders transparent PNG as black background
     *   - FPDF handles JPEG much better than PNG
     *   - White circle edges blend perfectly with card's white body
     */
    private function createCircularPhoto(?string $photoPath, float $radiusMm): ?string
    {
        if (empty($photoPath) || !file_exists($photoPath)) return null;
        if (!function_exists('imagecreatefromjpeg')) return null;

        try {
            $info = @getimagesize($photoPath);
            if (!$info) return null;

            $mime = $info['mime'] ?? '';
            switch ($mime) {
                case 'image/jpeg': $src = imagecreatefromjpeg($photoPath); break;
                case 'image/png':  $src = imagecreatefrompng($photoPath);  break;
                case 'image/gif':  $src = imagecreatefromgif($photoPath);  break;
                default: return null;
            }
            if (!$src) return null;

            $srcW = imagesx($src);
            $srcH = imagesy($src);
            $size = 240;  // High resolution for crisp print

            // ✅ WHITE background (FPDF transparent PNG support नहीं करता)
            $dst = imagecreatetruecolor($size, $size);
            $white = imagecolorallocate($dst, 255, 255, 255);
            imagefilledrectangle($dst, 0, 0, $size, $size, $white);

            // Center crop — सबसे छोटी dimension लेकर square बनाएँ
            $minDim = min($srcW, $srcH);
            $srcX = (int)(($srcW - $minDim) / 2);
            $srcY = (int)(($srcH - $minDim) / 2);

            // Photo को dst पर copy + resize करें
            imagecopyresampled($dst, $src, 0, 0, $srcX, $srcY, $size, $size, $minDim, $minDim);
            imagedestroy($src);

            // ✅ Circle के बाहर WHITE fill करें (transparent नहीं)
            // Pixel-by-pixel approach — FPDF के लिए सबसे reliable
            $cx = $size / 2;
            $cy = $size / 2;
            $r  = $size / 2;

            for ($px = 0; $px < $size; $px++) {
                for ($py = 0; $py < $size; $py++) {
                    $dist = sqrt(($px - $cx) ** 2 + ($py - $cy) ** 2);
                    if ($dist > $r) {
                        // Circle के बाहर = white pixel
                        imagesetpixel($dst, $px, $py, $white);
                    }
                }
            }

            // ✅ JPEG save करें (FPDF JPEG को बहुत better handle करता है)
            $tempPath = sys_get_temp_dir() . '/circular_photo_' . uniqid() . '.jpg';
            imagejpeg($dst, $tempPath, 92);  // 92% quality
            imagedestroy($dst);

            return file_exists($tempPath) ? $tempPath : null;

        } catch (\Throwable $e) {
            error_log('createCircularPhoto error: ' . $e->getMessage());
            return null;
        }
    }

    /**
     * Resolve student photo path (try multiple locations).
     */
    private function resolvePhotoPath(string $photoPath): ?string
    {
        if (empty($photoPath)) return null;
        if ($photoPath[0] === '/' && file_exists($photoPath)) return realpath($photoPath);

        $candidates = [
            $_SERVER['DOCUMENT_ROOT'] . '/' . ltrim($photoPath, '/'),
            $_SERVER['DOCUMENT_ROOT'] . '/gmind/' . ltrim($photoPath, '/'),
            '/home/advaysin/public_html/' . ltrim($photoPath, '/'),
            '/home/advaysin/public_html/gmind/' . ltrim($photoPath, '/'),
            dirname(dirname(dirname(__DIR__))) . '/' . ltrim($photoPath, '/'),
        ];
        foreach ($candidates as $c) {
            if (file_exists($c)) return realpath($c);
        }
        return null;
    }
}
