<?php
// Prevent double-including this mega file (avoids function redeclare fatals)
if (defined('APP_BOOTSTRAPPED')) { return; }
define('APP_BOOTSTRAPPED', 1);

// Honttech Registration System with Admin & Student Portals
// PHP 8.2+ Compatible

// Start output buffering at the very beginning
ob_start(static function ($buffer) {
    $sample = ltrim((string)$buffer);
	
    if ($sample === '') return $buffer;
    $looksHtml = (stripos($sample, '<!doctype') !== false) || (stripos($sample, '<html') !== false) || (stripos($sample, '<body') !== false);
    if (!$looksHtml) return $buffer;
    return str_replace(['Honttech'], ['HontTech'], $buffer);
});

// Environment-aware error handling. Do not expose PHP warnings, SQL errors, paths, or secrets to end users.
$__honttechDebug = false;
try {
    $__debugEnv = getenv('APP_DEBUG');
    $__honttechDebug = is_string($__debugEnv) && in_array(strtolower(trim($__debugEnv)), ['1', 'true', 'yes', 'on'], true);
} catch (Throwable $e) {
    $__honttechDebug = false;
}
error_reporting(E_ALL);
ini_set('log_errors', '1');
ini_set('display_errors', $__honttechDebug ? '1' : '0');
ini_set('display_startup_errors', $__honttechDebug ? '1' : '0');
if (!defined('APP_DEBUG')) {
    define('APP_DEBUG', $__honttechDebug);
}

// Start session safely (XAMPP/shared-hosting compatible)
// Fixes session_start() permission errors caused by an unwritable php.ini session.save_path such as \xampp\tmp.
if (!function_exists('honttechEnsureWritableDirectory')) {
    function honttechEnsureWritableDirectory(string $dir): bool {
        $dir = rtrim($dir, "\\/");
        if ($dir === '') {
            return false;
        }

        if (!is_dir($dir)) {
            @mkdir($dir, 0775, true);
        }

        if (!is_dir($dir) || !is_writable($dir)) {
            return false;
        }

        try {
            $testFile = $dir . DIRECTORY_SEPARATOR . '.session_write_test_' . bin2hex(random_bytes(6));
        } catch (Throwable $e) {
            $testFile = $dir . DIRECTORY_SEPARATOR . '.session_write_test_' . uniqid('', true);
        }

        $ok = @file_put_contents($testFile, 'ok', LOCK_EX) !== false;
        if ($ok) {
            @unlink($testFile);
            // Prevent direct web browsing of the session folder on Apache/shared hosting.
            $htaccess = $dir . DIRECTORY_SEPARATOR . '.htaccess';
            if (!is_file($htaccess)) {
                @file_put_contents($htaccess, "Deny from all\n", LOCK_EX);
            }
        }
        return $ok;
    }
}

if (!function_exists('honttechSessionWritablePath')) {
    function honttechSessionWritablePath(): string {
        $paths = [
            __DIR__ . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'sessions',
            __DIR__ . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'sessions',
            __DIR__ . DIRECTORY_SEPARATOR . 'sessions',
        ];

        $currentSavePath = (string)session_save_path();
        if ($currentSavePath !== '') {
            // PHP may store session.save_path as "N;/path" or "N;MODE;/path". Keep only the real directory.
            $parts = explode(';', $currentSavePath);
            $currentSavePath = trim((string)end($parts));
            if ($currentSavePath !== '') {
                $paths[] = $currentSavePath;
            }
        }

        $systemTemp = rtrim((string)sys_get_temp_dir(), "\\/");
        if ($systemTemp !== '') {
            $paths[] = $systemTemp . DIRECTORY_SEPARATOR . 'honttech_sessions';
        }

        foreach (array_unique($paths) as $path) {
            if (honttechEnsureWritableDirectory($path)) {
                return $path;
            }
        }

        return '';
    }
}

if (!function_exists('honttechStartSessionSafely')) {
    function honttechStartSessionSafely(): void {
        if (session_status() === PHP_SESSION_ACTIVE) {
            return;
        }

        if (headers_sent($file, $line)) {
            error_log('Unable to start session because headers were already sent in ' . $file . ' on line ' . $line . '.');
            if (!isset($_SESSION) || !is_array($_SESSION)) {
                $_SESSION = [];
            }
            return;
        }

        $sessionPath = honttechSessionWritablePath();
        if ($sessionPath !== '') {
            @ini_set('session.save_path', $sessionPath);
            @session_save_path($sessionPath);
        }

        @ini_set('session.use_only_cookies', '1');
        @ini_set('session.use_strict_mode', '1');
        @ini_set('session.cookie_httponly', '1');
        @ini_set('session.cookie_samesite', 'Lax');
        @ini_set('session.gc_maxlifetime', '7200');
        if (session_name() === 'PHPSESSID') {
            @session_name('HONTTECHSID');
        }

        $secureCookie = (!empty($_SERVER['HTTPS']) && strtolower((string)$_SERVER['HTTPS']) !== 'off')
            || ((string)($_SERVER['SERVER_PORT'] ?? '') === '443')
            || (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https');
        $cookieParams = session_get_cookie_params();
        session_set_cookie_params([
            'lifetime' => (int)($cookieParams['lifetime'] ?? 0),
            'path' => (string)($cookieParams['path'] ?? '/'),
            'domain' => (string)($cookieParams['domain'] ?? ''),
            'secure' => $secureCookie,
            'httponly' => true,
            'samesite' => 'Lax',
        ]);

        if (!@session_start()) {
            $fallbackPath = rtrim(__DIR__, "\\/") . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'sessions';
            if ($fallbackPath !== $sessionPath && honttechEnsureWritableDirectory($fallbackPath)) {
                @ini_set('session.save_path', $fallbackPath);
                @session_save_path($fallbackPath);
                @session_start();
            }
        }

        if (session_status() !== PHP_SESSION_ACTIVE) {
            error_log('Unable to start PHP session. Please make the application storage/sessions directory writable.');
            if (!isset($_SESSION) || !is_array($_SESSION)) {
                $_SESSION = [];
            }
        }
    }
}


if (!function_exists('honttechAppEnv')) {
    function honttechAppEnv(string $key, $default = '') {
        $value = getenv($key);
        if ($value === false && isset($_SERVER[$key])) {
            $value = $_SERVER[$key];
        }
        if ($value === false && isset($_ENV[$key])) {
            $value = $_ENV[$key];
        }
        if ($value === false || $value === null) {
            return $default;
        }
        return $value;
    }
}

if (!function_exists('honttechIsHttpsRequest')) {
    function honttechIsHttpsRequest(): bool {
        return (!empty($_SERVER['HTTPS']) && strtolower((string)$_SERVER['HTTPS']) !== 'off')
            || ((string)($_SERVER['SERVER_PORT'] ?? '') === '443')
            || (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https');
    }
}

if (!function_exists('honttechApplySecurityHeaders')) {
    function honttechApplySecurityHeaders(): void {
        if (headers_sent()) return;
        header('X-Content-Type-Options: nosniff');
        header('X-Frame-Options: SAMEORIGIN');
        header('Referrer-Policy: strict-origin-when-cross-origin');
        header('X-Permitted-Cross-Domain-Policies: none');
        header('Permissions-Policy: geolocation=(), microphone=(), camera=(self), payment=(self)');
        header("Content-Security-Policy: frame-ancestors 'self'; base-uri 'self'; object-src 'none'");
        if (honttechIsHttpsRequest()) {
            header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
        }
    }
}


if (!function_exists('honttechGenerateTemporaryPassword')) {
    function honttechGenerateTemporaryPassword(int $length = 14): string {
        $length = max(12, min(64, $length));
        $alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789@$!%*?&';
        $max = strlen($alphabet) - 1;
        $password = '';
        for ($i = 0; $i < $length; $i++) {
            try {
                $password .= $alphabet[random_int(0, $max)];
            } catch (Throwable $e) {
                $password .= $alphabet[mt_rand(0, $max)];
            }
        }
        return $password;
    }
}

if (!function_exists('honttechSecureToken')) {
    function honttechSecureToken(int $bytes = 32): string {
        $bytes = max(16, min(64, $bytes));
        try {
            return bin2hex(random_bytes($bytes));
        } catch (Throwable $e) {
            return hash('sha256', uniqid('honttech', true) . microtime(true) . mt_rand());
        }
    }
}

if (!function_exists('honttechHardenWritableDirectory')) {
    function honttechHardenWritableDirectory(string $dir, bool $denyAll = false): void {
        $dir = rtrim($dir, "\\/");
        if ($dir === '') return;
        if (!is_dir($dir)) {
            @mkdir($dir, 0775, true);
        }
        if (!is_dir($dir)) return;
        $htaccess = $dir . DIRECTORY_SEPARATOR . '.htaccess';
        if ($denyAll) {
            $rules = "Require all denied\nDeny from all\n";
        } else {
            $rules = "Options -Indexes\n";
            $rules .= "<FilesMatch \"\\.(php|php[0-9]|phtml|phar|cgi|pl|py|jsp|asp|aspx|sh|bash|exe|dll)$\">\n";
            $rules .= "    Require all denied\n";
            $rules .= "    Deny from all\n";
            $rules .= "</FilesMatch>\n";
            $rules .= "<IfModule mod_headers.c>\n    Header set X-Content-Type-Options \"nosniff\"\n</IfModule>\n";
        }
        if (!is_file($htaccess) || trim((string)@file_get_contents($htaccess)) === '') {
            @file_put_contents($htaccess, $rules, LOCK_EX);
        }
    }
}

honttechApplySecurityHeaders();

honttechStartSessionSafely();

// Prevent mobile/browser caching (fix application form not sticking on phones)
if (!headers_sent()) {
    header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
    header("Cache-Control: post-check=0, pre-check=0", false);
    header("Pragma: no-cache");
    header("Expires: 0");
}


// Superadmin impersonation: return to original Superadmin session
if ((isset($_GET['return_superadmin']) || isset($_GET['return_admin'])) && (isset($_SESSION['superadmin_impersonation_backup']) || isset($_SESSION['impersonator_admin_id']))) {
    $backup = $_SESSION['superadmin_impersonation_backup'] ?? null;
    $superadminId = 0;
    if (is_array($backup) && !empty($backup['user_id'])) {
        $superadminId = (int)$backup['user_id'];
        foreach ($backup as $k => $v) {
            $_SESSION[$k] = $v;
        }
    } else {
        $superadminId = (int)($_SESSION['impersonator_admin_id'] ?? 0);
        if ($superadminId > 0) {
            $_SESSION['user_id'] = $superadminId;
            $_SESSION['user_type'] = 'admin';
        }
    }

    unset($_SESSION['impersonator_admin_id'], $_SESSION['is_impersonating'], $_SESSION['impersonation_target_user_id'], $_SESSION['impersonation_target_user_type'], $_SESSION['superadmin_impersonation_backup'], $_SESSION['student_id'], $_SESSION['registration_no']);
    if ($superadminId > 0 && isset($pdo) && ($pdo instanceof PDO) && function_exists('portalImpersonationAuditLog')) {
        portalImpersonationAuditLog($pdo, $superadminId, 0, 'return', 'return');
    }
    redirect('?page=admin_dashboard');
}


// -------------------------
// SAFE HELPERS
// -------------------------
if (!function_exists('e')) {
    function e($value): string {
        return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
    }
}
if (!function_exists('full_name')) {
    function full_name(array $row): string {
        $surname = trim((string)($row['surname'] ?? ''));
        $first = trim((string)($row['first_name'] ?? $row['firstname'] ?? ''));
        $other = trim((string)($row['other_name'] ?? $row['othername'] ?? ''));
        $name = $surname;
        if ($first !== '') $name .= ($name !== '' ? ', ' : '') . $first;
        if ($other !== '') $name .= ' ' . $other;
        return trim($name);
    }
}

if (!function_exists('portalReceiptEmbeddedStyleBlock')) {
    function portalReceiptEmbeddedStyleBlock(): string {
        return <<<'CSS'
html,body{margin:0;padding:0;background:#f3f4f6;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:#111827;}
.wrap{padding:14px;}
.receipt-container{max-width:820px;margin:0 auto;padding:18px 20px 16px;border:1px solid #dfe6ee;border-radius:12px;background:#fff;box-shadow:0 8px 24px rgba(15,23,42,.06);overflow:hidden;}
.receipt-container .doc-stamp{display:none !important;}
.receipt-header{position:relative;text-align:center;margin-bottom:14px;border-bottom:1px solid #e5e7eb;padding:4px 96px 12px;min-height:90px;}
.receipt-header .logo-area{height:0;margin:0 !important;}
.receipt-header .logo-area .logo-display,
.receipt-header .logo-area .logo-placeholder{position:absolute;top:2px;width:72px;height:72px;object-fit:contain;}
.receipt-header .logo-area > *:first-child{left:0;}
.receipt-header .logo-area > *:last-child{right:0;}
.logo-display{object-fit:contain;}
.logo-placeholder{border:1px dashed #cbd5e1;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:10px;color:#64748b;background:#fff;}
.receipt-header h2{margin:0;font-size:20px;line-height:1.12;font-weight:700;}
.receipt-header h3{margin:4px 0 0;font-size:16px;line-height:1.18;font-weight:600;}
.receipt-header p{margin:4px 0 0;font-size:13px;line-height:1.24;color:#4b5563;}
.receipt-header h4{margin:10px 0 0;font-size:15px;line-height:1.2;font-weight:700;}
.receipt-body{margin-bottom:0;}
.receipt-body p{margin:0 0 5px;font-size:13px;line-height:1.38;}
.receipt-meta{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:24px;row-gap:8px;align-items:start;margin:0 0 12px !important;}
.receipt-meta-col{min-width:0;}
.receipt-meta-col p:last-child{margin-bottom:0;}
.receipt-body .row{margin-bottom:12px !important;}
.receipt-body .table-responsive{margin-bottom:12px !important;overflow:visible;}
.table{width:100%;border-collapse:collapse;font-size:13px;margin-bottom:0;}
.table th,.table td{border:1px solid #dfe6ee;padding:9px 10px;vertical-align:top;}
.table thead th{background:#f8fafc;}
.qr-code{text-align:center;margin:10px 0 0;}
.qr-code p{margin:0 0 6px;}
.qr-pack{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap;}
.qr-code img{max-width:150px;width:100%;height:auto;border:1px solid #e5e7eb;border-radius:10px;background:#fff;padding:6px;}
.qr-stamp{width:118px;height:118px;object-fit:contain;opacity:.92;background:transparent;border:none !important;padding:0 !important;}
.receipt-footer{margin-top:14px;border-top:1px solid #e5e7eb;padding-top:10px;text-align:center;}
.receipt-footer p{margin:0 0 4px;font-size:12px;line-height:1.35;color:#374151;}
@page{size:A4;margin:9mm 8mm;}
@media (max-width:767.98px){
  .wrap{padding:8px;}
  .receipt-container{padding:14px 14px 12px;}
  .receipt-header{padding:2px 72px 10px;min-height:72px;}
  .receipt-header .logo-area .logo-display,
  .receipt-header .logo-area .logo-placeholder{width:52px;height:52px;}
  .receipt-header h2{font-size:17px;}
  .receipt-header h3{font-size:14px;}
  .receipt-header p{font-size:12px;}
  .receipt-header h4{font-size:14px;}
  .receipt-meta{grid-template-columns:1fr;column-gap:0;row-gap:6px;}
}
@media print{
  html,body{background:#fff !important;}
  .wrap,.card,.card-body{padding:0 !important;margin:0 !important;border:none !important;box-shadow:none !important;}
  .card-header,.no-print,.navbar,.sidebar,.topbar,.main-header,.footer{display:none !important;}
  .receipt-container{max-width:none;border:none;box-shadow:none;padding:6mm 5mm 4mm;}
  .receipt-header{margin-bottom:10px;padding:2px 84px 10px;min-height:78px;}
  .receipt-header .logo-area .logo-display,
  .receipt-header .logo-area .logo-placeholder{width:64px;height:64px;top:1px;}
  .receipt-header h2{font-size:19px;}
  .receipt-header h3{font-size:15px;}
  .receipt-header p{font-size:12px;}
  .receipt-header h4{font-size:14px;margin-top:8px;}
  .receipt-body p{font-size:12px;margin-bottom:4px;}
  .receipt-meta{grid-template-columns:repeat(2,minmax(0,1fr));column-gap:20px;row-gap:6px;}
  .table{font-size:12px;}
  .table th,.table td{padding:7px 8px;}
  .qr-code{margin-top:8px;}
  .qr-code img{max-width:118px;padding:4px;}
  .qr-stamp{width:96px;height:96px;}
  .receipt-body .row,
  .receipt-body .table-responsive,
  .qr-code,
  .receipt-footer{page-break-inside:avoid;break-inside:avoid-page;}
  .receipt-footer{margin-top:10px;padding-top:8px;}
}
CSS;
    }
}

if (!function_exists('bestPaymentType')) {
    function bestPaymentType(array $tx): string {
        $displayType = (string)($tx['transaction_type'] ?? '');
        $refStr = (string)($tx['reference'] ?? '');

        // Prefer receipt_data payment_type for assigned/custom payments
        if ($displayType === '' && !empty($tx['receipt_data'])) {
            $rd = json_decode((string)$tx['receipt_data'], true);
            if (is_array($rd)) {
                if (!empty($rd['payment_type'])) $displayType = (string)$rd['payment_type'];
                elseif (!empty($rd['custom_name'])) $displayType = (string)$rd['custom_name'];
                elseif (!empty($rd['name'])) $displayType = (string)$rd['name'];
            }
        }

        // Infer from reference for wallet/manual legacy rows
        if ($displayType === '' && $refStr !== '') {
            $r = strtolower($refStr);

            if (str_starts_with($r, 'wallet_acceptance')) $displayType = 'Acceptance Fee';
            elseif (str_starts_with($r, 'wallet_hostel')) $displayType = 'Hostel Fee';
            elseif (str_starts_with($r, 'wallet_school')) $displayType = 'School Fee';
            elseif (str_starts_with($r, 'wallet_registration')) $displayType = 'Registration Fee';
            elseif (str_starts_with($r, 'wallet_assigned')) $displayType = 'Assigned Payment';
            elseif (str_starts_with($r, 'wallet_funding')) $displayType = 'Wallet Funding';

            elseif (str_starts_with($r, 'manual_acceptance')) $displayType = 'Acceptance Fee';
            elseif (str_starts_with($r, 'manual_hostel')) $displayType = 'Hostel Fee';
            elseif (str_starts_with($r, 'manual_school')) $displayType = 'School Fee';
            elseif (str_starts_with($r, 'manual_registration')) $displayType = 'Registration Fee';
            elseif (str_starts_with($r, 'manual_assigned')) $displayType = 'Assigned Payment';
        }

        // Add installment / one-time clarity for School Fee
        if ($displayType === 'School Fee') {
            $ti = (int)($tx['total_installments'] ?? 1);
            $in = (int)($tx['installment_number'] ?? 1);
            if ($ti > 1) $displayType = 'School Fee ' . portalOrdinalLabel($in) . ' Installment';
            else $displayType .= ' (One-time)';
        }

        return trim($displayType) !== '' ? trim($displayType) : 'Payment';
    }
}

if (!function_exists('flash')) {
    function flash(string $type, string $msg): void {
        $_SESSION['__flash'][] = ['type'=>$type,'msg'=>$msg];
    }
}
if (!function_exists('renderFlashes')) {
    function renderFlashes(): void {
        $fl = $_SESSION['__flash'] ?? [];
        if (!is_array($fl) || !$fl) return;
        foreach ($fl as $f) {
            $t = $f['type'] ?? 'info';
            $m = $f['msg'] ?? '';
            echo '<div class="alert alert-' . e($t) . ' alert-dismissible fade show" role="alert">' . e($m)
               . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
        }
        unset($_SESSION['__flash']);
    }
}

if (!function_exists('portalColumnExistsSafe')) {
    function portalColumnExistsSafe(PDO $pdo, string $table, string $column): bool {
        try {
            $stmt = $pdo->prepare("SHOW COLUMNS FROM `$table` LIKE ?");
            $stmt->execute([$column]);
            return $stmt->rowCount() > 0;
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('portalEnsureStudentFullNameSupport')) {
    function portalEnsureStudentFullNameSupport(PDO $pdo): void {
        try {
            if (!portalColumnExistsSafe($pdo, 'students', 'full_name')) {
                $pdo->exec("ALTER TABLE students ADD COLUMN full_name VARCHAR(160) NULL AFTER other_name");
            }
        } catch (Throwable $e) {}

        try {
            $pdo->exec("UPDATE students
                SET full_name = TRIM(CONCAT_WS(' ',
                    NULLIF(TRIM(COALESCE(surname, '')), ''),
                    NULLIF(TRIM(COALESCE(first_name, '')), ''),
                    NULLIF(TRIM(COALESCE(other_name, '')), '')
                ))
                WHERE COALESCE(TRIM(full_name), '') = ''");
        } catch (Throwable $e) {}

        try {
            if (portalColumnExistsSafe($pdo, 'students', 'full_name') && function_exists('portalIndexExistsSafe') && !portalIndexExistsSafe($pdo, 'students', 'idx_students_full_name')) {
                $pdo->exec("ALTER TABLE students ADD INDEX idx_students_full_name (full_name)");
            }
        } catch (Throwable $e) {}
    }
}


if (!function_exists('portalEnsureRegistrableCourseScopeSchema')) {
    function portalEnsureRegistrableCourseScopeSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS registrable_courses (
                id INT AUTO_INCREMENT PRIMARY KEY,
                course_code VARCHAR(30) NOT NULL,
                course_title VARCHAR(255) NOT NULL,
                credit_unit INT NOT NULL DEFAULT 0,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                mode_of_study VARCHAR(100) NULL,
                level VARCHAR(10) NULL,
                semester ENUM('First','Second') NOT NULL DEFAULT 'First',
                session_name VARCHAR(30) NULL,
                year VARCHAR(10) NULL,
                course_type ENUM('C','E') NOT NULL DEFAULT 'C',
                registrable ENUM('Yes','No') NOT NULL DEFAULT 'Yes',
                lecturer_staff_id INT NULL,
                is_general_course TINYINT(1) NOT NULL DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_rc_filter (faculty_id, department_id, programme_id, level, semester, session_name, year),
                INDEX idx_rc_code (course_code),
                INDEX idx_rc_scope_lookup (faculty_id, department_id, programme_id, session_name, semester, level, year, registrable, is_general_course),
                INDEX idx_rc_scope_unique_hint (course_code, faculty_id, department_id, programme_id, session_name, semester, level, year, is_general_course)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        $columnSql = [
            'credit_unit' => "ALTER TABLE registrable_courses ADD COLUMN credit_unit INT NOT NULL DEFAULT 0 AFTER course_title",
            'faculty_id' => "ALTER TABLE registrable_courses ADD COLUMN faculty_id INT NULL AFTER credit_unit",
            'department_id' => "ALTER TABLE registrable_courses ADD COLUMN department_id INT NULL AFTER faculty_id",
            'programme_id' => "ALTER TABLE registrable_courses ADD COLUMN programme_id INT NULL AFTER department_id",
            'mode_of_study' => "ALTER TABLE registrable_courses ADD COLUMN mode_of_study VARCHAR(100) NULL AFTER programme_id",
            'level' => "ALTER TABLE registrable_courses ADD COLUMN level VARCHAR(10) NULL AFTER mode_of_study",
            'semester' => "ALTER TABLE registrable_courses ADD COLUMN semester ENUM('First','Second') NOT NULL DEFAULT 'First' AFTER level",
            'session_name' => "ALTER TABLE registrable_courses ADD COLUMN session_name VARCHAR(30) NULL AFTER semester",
            'year' => "ALTER TABLE registrable_courses ADD COLUMN year VARCHAR(10) NULL AFTER session_name",
            'course_type' => "ALTER TABLE registrable_courses ADD COLUMN course_type ENUM('C','E') NOT NULL DEFAULT 'C' AFTER year",
            'registrable' => "ALTER TABLE registrable_courses ADD COLUMN registrable ENUM('Yes','No') NOT NULL DEFAULT 'Yes' AFTER course_type",
            'lecturer_staff_id' => "ALTER TABLE registrable_courses ADD COLUMN lecturer_staff_id INT NULL AFTER registrable",
            'is_general_course' => "ALTER TABLE registrable_courses ADD COLUMN is_general_course TINYINT(1) NOT NULL DEFAULT 0 AFTER lecturer_staff_id",
            'created_at' => "ALTER TABLE registrable_courses ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP AFTER is_general_course",
            'updated_at' => "ALTER TABLE registrable_courses ADD COLUMN updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP AFTER created_at",
        ];
        foreach ($columnSql as $column => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, 'registrable_courses', $column)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) {}
        }

        try {
            if (portalColumnExistsSafe($pdo, 'registrable_courses', 'credit_load') && portalColumnExistsSafe($pdo, 'registrable_courses', 'credit_unit')) {
                $pdo->exec("UPDATE registrable_courses SET credit_unit = credit_load WHERE (credit_unit IS NULL OR credit_unit = 0) AND credit_load IS NOT NULL");
            }
        } catch (Throwable $e) {}
        try {
            if (portalColumnExistsSafe($pdo, 'registrable_courses', 'credit') && portalColumnExistsSafe($pdo, 'registrable_courses', 'credit_unit')) {
                $pdo->exec("UPDATE registrable_courses SET credit_unit = credit WHERE (credit_unit IS NULL OR credit_unit = 0) AND credit IS NOT NULL");
            }
        } catch (Throwable $e) {}

        $indexSql = [
            "ALTER TABLE registrable_courses ADD INDEX idx_rc_filter (faculty_id, department_id, programme_id, level, semester, session_name, year)",
            "ALTER TABLE registrable_courses ADD INDEX idx_rc_code (course_code)",
            "ALTER TABLE registrable_courses ADD INDEX idx_rc_scope_lookup (faculty_id, department_id, programme_id, session_name, semester, level, year, registrable, is_general_course)",
            "ALTER TABLE registrable_courses ADD INDEX idx_rc_sync_scope (course_code, session_name, semester, level, year, faculty_id, department_id, programme_id)",
            "ALTER TABLE registrable_courses ADD INDEX idx_rc_scope_unique_hint (course_code, faculty_id, department_id, programme_id, session_name, semester, level, year, is_general_course)",
            "ALTER TABLE registrable_courses ADD INDEX idx_rc_filter_mode (faculty_id, department_id, programme_id, mode_of_study, level, semester, session_name, year)",
            "ALTER TABLE registrable_courses ADD INDEX idx_rc_scope_lookup_mode (faculty_id, department_id, programme_id, mode_of_study, session_name, semester, level, year, registrable, is_general_course)",
            "ALTER TABLE registrable_courses ADD INDEX idx_rc_sync_scope_mode (course_code, mode_of_study, session_name, semester, level, year, faculty_id, department_id, programme_id)"
        ];
        foreach ($indexSql as $sql) {
            try { $pdo->exec($sql); } catch (Throwable $e) {}
        }

        try {
            $pdo->exec("UPDATE registrable_courses rc INNER JOIN departments d ON d.id = rc.department_id
                SET rc.faculty_id = d.faculty_id
                WHERE rc.department_id IS NOT NULL AND rc.department_id <> 0 AND (rc.faculty_id IS NULL OR rc.faculty_id = 0)");
        } catch (Throwable $e) {}
        try {
            $pdo->exec("UPDATE registrable_courses rc
                INNER JOIN programmes p ON p.id = rc.programme_id
                INNER JOIN departments d ON d.id = p.department_id
                SET rc.department_id = p.department_id,
                    rc.faculty_id = d.faculty_id
                WHERE rc.programme_id IS NOT NULL AND rc.programme_id <> 0
                  AND ((rc.department_id IS NULL OR rc.department_id = 0) OR (rc.faculty_id IS NULL OR rc.faculty_id = 0))");
        } catch (Throwable $e) {}
        try {
            if (portalColumnExistsSafe($pdo, 'registrable_courses', 'mode_of_study')) {
                $pdo->exec("UPDATE registrable_courses rc
                    LEFT JOIN programmes p ON p.id = rc.programme_id
                    SET rc.mode_of_study = CASE
                        WHEN COALESCE(TRIM(rc.mode_of_study), '') <> '' THEN TRIM(rc.mode_of_study)
                        WHEN p.id IS NOT NULL AND COALESCE(p.mode_of_study_type, '') = 'Others' AND COALESCE(TRIM(p.mode_of_study_custom), '') <> '' THEN TRIM(p.mode_of_study_custom)
                        WHEN p.id IS NOT NULL AND COALESCE(p.mode_of_study_type, '') IN ('Full-Time', 'Part-Time') THEN p.mode_of_study_type
                        WHEN LOWER(COALESCE(p.name, '')) LIKE '%weekend%' THEN 'Weekend'
                        WHEN LOWER(COALESCE(p.name, '')) LIKE '%sandwich%' THEN 'Sandwich'
                        WHEN LOWER(COALESCE(p.name, '')) LIKE '%distance%' THEN 'Distance Learning'
                        WHEN LOWER(COALESCE(p.name, '')) LIKE '%part%' OR LOWER(COALESCE(p.name, '')) LIKE '%pde%' THEN 'Part-Time'
                        ELSE 'Full-Time'
                    END
                    WHERE COALESCE(TRIM(rc.mode_of_study), '') = ''");
            }
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalNormalizeAcademicLookupValue')) {
    function portalNormalizeAcademicLookupValue($value): string {
        $value = html_entity_decode((string)$value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $value = str_replace(["\xC2\xA0", '’', '‘', '“', '”', '`'], [' ', "'", "'", '"', '"', "'"], $value);
        $value = trim($value, " \t\n\r\0\x0B\"'");
        $value = str_ireplace('&', ' and ', $value);
        $value = str_replace(['/', '\\', '-', '_', '–', '—', '.', ',', ':', ';', '(', ')', '[', ']', '{', '}'], ' ', $value);
        $value = preg_replace('/[^\\p{L}\\p{N} ]+/u', ' ', $value) ?: $value;
        $value = preg_replace('/\\s+/u', ' ', trim($value)) ?: trim($value);
        return function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
    }
}

if (!function_exists('portalAcademicLookupVariants')) {
    function portalAcademicLookupVariants($value, string $type = 'generic'): array {
        $base = portalNormalizeAcademicLookupValue($value);
        if ($base === '') return [];
        $variants = [$base];
        $prefixPatterns = [
            '/^(school|faculty)\s+of\s+/u',
            '/^(school|faculty)\s+/u',
            '/^(department|course of study)\s+of\s+/u',
            '/^(department|course of study)\s+/u',
            '/^(programme|program)\s+of\s+/u',
            '/^(programme|program)\s+/u',
        ];
        foreach ($prefixPatterns as $pattern) {
            $stripped = preg_replace($pattern, '', $base);
            $stripped = preg_replace('/\s+/u', ' ', trim((string)$stripped)) ?: trim((string)$stripped);
            if ($stripped !== '' && $stripped !== $base) {
                $variants[] = $stripped;
            }
        }
        if ($type === 'faculty') {
            foreach ([$base, ...$variants] as $candidate) {
                if ($candidate === '') continue;
                $variants[] = 'school of ' . $candidate;
                $variants[] = 'faculty of ' . $candidate;
            }
        }
        return array_values(array_unique(array_filter(array_map(static function($item) {
            return preg_replace('/\s+/u', ' ', trim((string)$item)) ?: trim((string)$item);
        }, $variants))));
    }
}

if (!function_exists('portalAcademicLookupSuggestion')) {
    function portalAcademicLookupSuggestion($value, array $rows, string $nameKey = 'name'): string {
        $needle = portalNormalizeAcademicLookupValue($value);
        if ($needle === '' || !$rows) return '';
        $bestName = '';
        $bestScore = PHP_INT_MAX;
        foreach ($rows as $row) {
            $label = trim((string)($row[$nameKey] ?? ''));
            if ($label === '') continue;
            $candidateVariants = portalAcademicLookupVariants($label, 'generic');
            if (!$candidateVariants) continue;
            foreach ($candidateVariants as $candidate) {
                $score = function_exists('levenshtein') ? levenshtein($needle, $candidate) : abs(strlen($needle) - strlen($candidate));
                if ($score < $bestScore) {
                    $bestScore = $score;
                    $bestName = $label;
                }
            }
        }
        if ($bestName === '') return '';
        $threshold = max(2, (int)floor(max(strlen($needle), strlen((string)$bestName)) * 0.45));
        return $bestScore <= $threshold ? $bestName : '';
    }
}

if (!function_exists('portalFacultyNameById')) {
    function portalFacultyNameById(PDO $pdo, int $facultyId, string $fallback = ''): string {
        if ($facultyId <= 0) return $fallback;
        static $cache = [];
        if (array_key_exists($facultyId, $cache)) return (string)$cache[$facultyId];
        try {
            $stmt = $pdo->prepare("SELECT name FROM faculties WHERE id = ? LIMIT 1");
            $stmt->execute([$facultyId]);
            $cache[$facultyId] = trim((string)($stmt->fetchColumn() ?: ''));
        } catch (Throwable $e) {
            $cache[$facultyId] = $fallback;
        }
        return (string)($cache[$facultyId] !== '' ? $cache[$facultyId] : $fallback);
    }
}

if (!function_exists('portalResolveFacultyRecordByText')) {
    function portalResolveFacultyRecordByText(PDO $pdo, $value): array {
        $raw = trim((string)$value);
        $result = ['id' => 0, 'name' => '', 'suggestion' => '', 'matched_by' => ''];
        if ($raw === '') return $result;
        if (ctype_digit($raw)) {
            try {
                $st = $pdo->prepare("SELECT id, name FROM faculties WHERE id = ? LIMIT 1");
                $st->execute([(int)$raw]);
                $row = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                if ($row) {
                    return ['id' => (int)($row['id'] ?? 0), 'name' => trim((string)($row['name'] ?? '')), 'suggestion' => '', 'matched_by' => 'id'];
                }
            } catch (Throwable $e) {}
        }
        static $rows = null;
        if ($rows === null) {
            try {
                $rows = $pdo->query("SELECT id, name FROM faculties ORDER BY name ASC, id ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) {
                $rows = [];
            }
        }
        foreach ($rows as $row) {
            $name = trim((string)($row['name'] ?? ''));
            if ($name !== '' && strcasecmp($name, $raw) === 0) {
                return ['id' => (int)($row['id'] ?? 0), 'name' => $name, 'suggestion' => '', 'matched_by' => 'exact'];
            }
        }
        $needleVariants = portalAcademicLookupVariants($raw, 'faculty');
        if ($needleVariants) {
            $matches = [];
            foreach ($rows as $row) {
                $rowVariants = portalAcademicLookupVariants((string)($row['name'] ?? ''), 'faculty');
                if ($rowVariants && array_intersect($needleVariants, $rowVariants)) {
                    $matches[] = $row;
                }
            }
            if (count($matches) === 1) {
                $row = $matches[0];
                return ['id' => (int)($row['id'] ?? 0), 'name' => trim((string)($row['name'] ?? '')), 'suggestion' => '', 'matched_by' => 'normalized'];
            }
        }
        $result['suggestion'] = portalAcademicLookupSuggestion($raw, $rows, 'name');
        return $result;
    }
}

if (!function_exists('portalResolveDepartmentRecordByText')) {
    function portalResolveDepartmentRecordByText(PDO $pdo, $value, int $facultyId = 0): array {
        $raw = trim((string)$value);
        $result = ['id' => 0, 'name' => '', 'faculty_id' => 0, 'suggestion' => '', 'matched_by' => '', 'found_elsewhere' => null];
        if ($raw === '') return $result;
        static $rows = null;
        if ($rows === null) {
            try {
                $rows = $pdo->query("SELECT id, faculty_id, name FROM departments ORDER BY name ASC, id ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) {
                $rows = [];
            }
        }
        if (ctype_digit($raw)) {
            foreach ($rows as $row) {
                if ((int)($row['id'] ?? 0) === (int)$raw && ($facultyId <= 0 || (int)($row['faculty_id'] ?? 0) === $facultyId)) {
                    return ['id' => (int)($row['id'] ?? 0), 'name' => trim((string)($row['name'] ?? '')), 'faculty_id' => (int)($row['faculty_id'] ?? 0), 'suggestion' => '', 'matched_by' => 'id', 'found_elsewhere' => null];
                }
            }
        }
        $scopedRows = array_values(array_filter($rows, static function(array $row) use ($facultyId): bool {
            return $facultyId <= 0 || (int)($row['faculty_id'] ?? 0) === $facultyId;
        }));
        foreach ($scopedRows as $row) {
            $name = trim((string)($row['name'] ?? ''));
            if ($name !== '' && strcasecmp($name, $raw) === 0) {
                return ['id' => (int)($row['id'] ?? 0), 'name' => $name, 'faculty_id' => (int)($row['faculty_id'] ?? 0), 'suggestion' => '', 'matched_by' => 'exact', 'found_elsewhere' => null];
            }
        }
        $needleVariants = portalAcademicLookupVariants($raw, 'department');
        if ($needleVariants) {
            $matches = [];
            foreach ($scopedRows as $row) {
                $rowVariants = portalAcademicLookupVariants((string)($row['name'] ?? ''), 'department');
                if ($rowVariants && array_intersect($needleVariants, $rowVariants)) {
                    $matches[] = $row;
                }
            }
            if (count($matches) === 1) {
                $row = $matches[0];
                return ['id' => (int)($row['id'] ?? 0), 'name' => trim((string)($row['name'] ?? '')), 'faculty_id' => (int)($row['faculty_id'] ?? 0), 'suggestion' => '', 'matched_by' => 'normalized', 'found_elsewhere' => null];
            }
        }
        if ($facultyId > 0) {
            foreach ($rows as $row) {
                $rowVariants = portalAcademicLookupVariants((string)($row['name'] ?? ''), 'department');
                if ($rowVariants && array_intersect($needleVariants, $rowVariants)) {
                    $result['found_elsewhere'] = [
                        'id' => (int)($row['id'] ?? 0),
                        'name' => trim((string)($row['name'] ?? '')),
                        'faculty_id' => (int)($row['faculty_id'] ?? 0),
                        'faculty_name' => portalFacultyNameById($pdo, (int)($row['faculty_id'] ?? 0)),
                    ];
                    break;
                }
            }
        }
        $result['suggestion'] = portalAcademicLookupSuggestion($raw, $scopedRows ?: $rows, 'name');
        return $result;
    }
}

if (!function_exists('portalResolveProgrammeRecordByText')) {
    function portalResolveProgrammeRecordByText(PDO $pdo, $value, int $departmentId = 0): array {
        $raw = trim((string)$value);
        $result = ['id' => 0, 'name' => '', 'department_id' => 0, 'suggestion' => '', 'matched_by' => '', 'found_elsewhere' => null];
        if ($raw === '') return $result;
        static $rows = null;
        if ($rows === null) {
            try {
                $rows = $pdo->query("SELECT id, department_id, name FROM programmes ORDER BY name ASC, id ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) {
                $rows = [];
            }
        }
        if (ctype_digit($raw)) {
            foreach ($rows as $row) {
                if ((int)($row['id'] ?? 0) === (int)$raw && ($departmentId <= 0 || (int)($row['department_id'] ?? 0) === $departmentId)) {
                    return ['id' => (int)($row['id'] ?? 0), 'name' => trim((string)($row['name'] ?? '')), 'department_id' => (int)($row['department_id'] ?? 0), 'suggestion' => '', 'matched_by' => 'id', 'found_elsewhere' => null];
                }
            }
        }
        $scopedRows = array_values(array_filter($rows, static function(array $row) use ($departmentId): bool {
            return $departmentId <= 0 || (int)($row['department_id'] ?? 0) === $departmentId;
        }));
        foreach ($scopedRows as $row) {
            $name = trim((string)($row['name'] ?? ''));
            if ($name !== '' && strcasecmp($name, $raw) === 0) {
                return ['id' => (int)($row['id'] ?? 0), 'name' => $name, 'department_id' => (int)($row['department_id'] ?? 0), 'suggestion' => '', 'matched_by' => 'exact', 'found_elsewhere' => null];
            }
        }
        $needleVariants = portalAcademicLookupVariants($raw, 'programme');
        if ($needleVariants) {
            $matches = [];
            foreach ($scopedRows as $row) {
                $rowVariants = portalAcademicLookupVariants((string)($row['name'] ?? ''), 'programme');
                if ($rowVariants && array_intersect($needleVariants, $rowVariants)) {
                    $matches[] = $row;
                }
            }
            if (count($matches) === 1) {
                $row = $matches[0];
                return ['id' => (int)($row['id'] ?? 0), 'name' => trim((string)($row['name'] ?? '')), 'department_id' => (int)($row['department_id'] ?? 0), 'suggestion' => '', 'matched_by' => 'normalized', 'found_elsewhere' => null];
            }
        }
        if ($departmentId > 0) {
            foreach ($rows as $row) {
                $rowVariants = portalAcademicLookupVariants((string)($row['name'] ?? ''), 'programme');
                if ($rowVariants && array_intersect($needleVariants, $rowVariants)) {
                    $elsewhereDepartmentId = (int)($row['department_id'] ?? 0);
                    $result['found_elsewhere'] = [
                        'id' => (int)($row['id'] ?? 0),
                        'name' => trim((string)($row['name'] ?? '')),
                        'department_id' => $elsewhereDepartmentId,
                        'department_name' => portalDepartmentNameById($pdo, $elsewhereDepartmentId),
                    ];
                    break;
                }
            }
        }
        $result['suggestion'] = portalAcademicLookupSuggestion($raw, $scopedRows ?: $rows, 'name');
        return $result;
    }
}

if (!function_exists('portalResolveFacultyIdByText')) {
    function portalResolveFacultyIdByText(PDO $pdo, $value): int {
        $match = portalResolveFacultyRecordByText($pdo, $value);
        return (int)($match['id'] ?? 0);
    }
}

if (!function_exists('portalResolveDepartmentIdByText')) {
    function portalResolveDepartmentIdByText(PDO $pdo, $value, int $facultyId = 0): int {
        $match = portalResolveDepartmentRecordByText($pdo, $value, $facultyId);
        return (int)($match['id'] ?? 0);
    }
}

if (!function_exists('portalResolveProgrammeIdByText')) {
    function portalResolveProgrammeIdByText(PDO $pdo, $value, int $departmentId = 0): int {
        $match = portalResolveProgrammeRecordByText($pdo, $value, $departmentId);
        return (int)($match['id'] ?? 0);
    }
}

if (!function_exists('portalNormalizeAcademicOptionName')) {
    function portalNormalizeAcademicOptionName($value): string {
        $name = trim((string)$value);
        if ($name === '') return '';
        $name = preg_replace('/\s+/u', ' ', $name) ?: $name;
        return function_exists('mb_strtolower') ? mb_strtolower($name, 'UTF-8') : strtolower($name);
    }
}

if (!function_exists('portalNormalizeProgrammeDisplayName')) {
    function portalNormalizeProgrammeDisplayName($value): string {
        $name = trim((string)$value);
        if ($name === '') return '';
        $name = preg_replace('/\s+/u', ' ', $name) ?: $name;
        $flatten = static function(string $input): string {
            $input = function_exists('mb_strtolower') ? mb_strtolower($input, 'UTF-8') : strtolower($input);
            $input = str_replace(['–', '—', '_'], ['-', '-', ' '], $input);
            $input = preg_replace('/[\/\-]+/u', ' ', $input) ?: $input;
            $input = preg_replace('/\s+/u', ' ', trim($input)) ?: trim($input);
            return $input;
        };
        $flat = $flatten($name);
        $modeOnly = ['full time', 'part time', 'others', 'other', 'sandwich', 'distance', 'distance learning'];
        if (in_array($flat, $modeOnly, true)) {
            return '';
        }
        if (preg_match('/^(.*?)(?:\s*[\/\-]?\s*)(full[\s-]*time|part[\s-]*time|sandwich|distance(?:\s+learning)?|others?)$/iu', $name, $m)) {
            $base = trim((string)($m[1] ?? ''));
            $base = preg_replace('/\s+/u', ' ', $base) ?: $base;
            if ($base !== '') {
                $name = trim($base, " 	

 
-/");
            } else {
                return '';
            }
        }
        return trim((string)$name);
    }
}
if (!function_exists('portalProgrammeScopeNameById')) {
    function portalProgrammeScopeNameById(PDO $pdo, int $programmeId): string {
        static $cache = [];
        if ($programmeId <= 0) return '';
        if (array_key_exists($programmeId, $cache)) {
            return (string)$cache[$programmeId];
        }
        try {
            $stmt = $pdo->prepare("SELECT name FROM programmes WHERE id = ? LIMIT 1");
            $stmt->execute([$programmeId]);
            $cache[$programmeId] = portalNormalizeProgrammeDisplayName((string)($stmt->fetchColumn() ?: ''));
        } catch (Throwable $e) {
            $cache[$programmeId] = '';
        }
        return (string)$cache[$programmeId];
    }
}
if (!function_exists('portalProgrammesShareScopeName')) {
    function portalProgrammesShareScopeName(PDO $pdo, int $leftProgrammeId, int $rightProgrammeId): bool {
        if ($leftProgrammeId > 0 && $leftProgrammeId === $rightProgrammeId) return true;
        if ($leftProgrammeId <= 0 || $rightProgrammeId <= 0) return false;
        $left = portalProgrammeScopeNameById($pdo, $leftProgrammeId);
        $right = portalProgrammeScopeNameById($pdo, $rightProgrammeId);
        return $left !== '' && $right !== '' && strcasecmp($left, $right) === 0;
    }
}
if (!function_exists('portalFetchDistinctProgrammeOptions')) {
    function portalFetchDistinctProgrammeOptions(PDO $pdo): array {
        $rows = [];
        try {
            $sql = "SELECT p.id,
                           COALESCE(d.faculty_id, p.faculty_id, 0) AS faculty_id,
                           COALESCE(p.department_id, 0) AS department_id,
                           TRIM(COALESCE(p.name, '')) AS name,
                           COALESCE(p.mode_of_study_type, '') AS mode_of_study_type,
                           COALESCE(p.mode_of_study_custom, '') AS mode_of_study_custom
                    FROM programmes p
                    LEFT JOIN departments d ON d.id = p.department_id
                    WHERE COALESCE(p.is_active, 1) = 1
                      AND TRIM(COALESCE(p.name, '')) <> ''
                    ORDER BY TRIM(COALESCE(p.name, '')) ASC, p.id ASC";
            $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $rows = [];
        }

        $bestByKey = [];
        foreach ($rows as $row) {
            $rawName = trim((string)($row['name'] ?? ''));
            $displayName = portalNormalizeProgrammeDisplayName($rawName);
            $norm = portalNormalizeAcademicOptionName($displayName);
            if ($norm === '') continue;
            $facultyId = (int)($row['faculty_id'] ?? 0);
            $departmentId = (int)($row['department_id'] ?? 0);
            $key = $facultyId . '|' . $departmentId . '|' . $norm;
            $row['id'] = (int)($row['id'] ?? 0);
            $row['faculty_id'] = $facultyId;
            $row['department_id'] = $departmentId;
            $row['raw_name'] = $rawName;
            $row['name'] = $displayName;
            $row['mode_of_study'] = portalProgrammeModeOfStudyDisplay($row, 'Full-Time');
            $score = 0;
            if (strcasecmp($rawName, $displayName) === 0) $score += 100;
            if ($departmentId > 0) $score += 10;
            if ($facultyId > 0) $score += 5;
            $row['__score'] = $score;
            if (!isset($bestByKey[$key])) {
                $bestByKey[$key] = $row;
                continue;
            }
            $existing = $bestByKey[$key];
            $replace = false;
            if ((int)($row['__score'] ?? 0) > (int)($existing['__score'] ?? 0)) {
                $replace = true;
            } elseif ((int)($row['__score'] ?? 0) === (int)($existing['__score'] ?? 0) && (int)($row['id'] ?? 0) < (int)($existing['id'] ?? 0)) {
                $replace = true;
            }
            if ($replace) {
                $bestByKey[$key] = $row;
            }
        }
        $unique = array_values($bestByKey);
        usort($unique, static function(array $a, array $b): int {
            return strcasecmp((string)($a['name'] ?? ''), (string)($b['name'] ?? ''));
        });
        foreach ($unique as &$row) {
            unset($row['__score']);
        }
        unset($row);
        return $unique;
    }
}

if (!function_exists('portalFilterProgrammeOptionsForScope')) {
    function portalFilterProgrammeOptionsForScope(array $rows, int $facultyId = 0, int $departmentId = 0): array {
        $filtered = [];
        $seen = [];
        foreach ($rows as $row) {
            $rowFaculty = (int)($row['faculty_id'] ?? 0);
            $rowDepartment = (int)($row['department_id'] ?? 0);
            if ($departmentId > 0 && $rowDepartment !== $departmentId) continue;
            if ($departmentId <= 0 && $facultyId > 0 && $rowFaculty !== $facultyId) continue;
            $name = trim((string)($row['name'] ?? ''));
            $norm = portalNormalizeAcademicOptionName($name);
            if ($norm === '') continue;
            if (isset($seen[$norm])) continue;
            $seen[$norm] = true;
            $filtered[] = [
                'id' => (int)($row['id'] ?? 0),
                'faculty_id' => $rowFaculty,
                'department_id' => $rowDepartment,
                'name' => preg_replace('/\s+/u', ' ', $name) ?: $name,
                'mode_of_study' => trim((string)($row['mode_of_study'] ?? portalProgrammeModeOfStudyDisplay((array)$row, 'Full-Time'))),
            ];
        }
        usort($filtered, static function(array $a, array $b): int {
            return strcasecmp((string)($a['name'] ?? ''), (string)($b['name'] ?? ''));
        });
        return $filtered;
    }
}


if (!function_exists('portalDepartmentNameById')) {
    function portalDepartmentNameById(PDO $pdo, int $departmentId, string $fallback = ''): string {
        if ($departmentId <= 0) return $fallback;
        static $cache = [];
        if (array_key_exists($departmentId, $cache)) return (string)$cache[$departmentId];
        try {
            $stmt = $pdo->prepare("SELECT name FROM departments WHERE id = ? LIMIT 1");
            $stmt->execute([$departmentId]);
            $cache[$departmentId] = trim((string)($stmt->fetchColumn() ?: ''));
        } catch (Throwable $e) {
            $cache[$departmentId] = $fallback;
        }
        return (string)($cache[$departmentId] !== '' ? $cache[$departmentId] : $fallback);
    }
}
if (!function_exists('portalProgrammeSelectorOptions')) {
    function portalProgrammeSelectorOptions(PDO $pdo, int $facultyId = 0, int $departmentId = 0): array {
        try {
            return portalFilterProgrammeOptionsForScope(portalFetchDistinctProgrammeOptions($pdo), $facultyId, $departmentId);
        } catch (Throwable $e) {
            return [];
        }
    }
}
if (!function_exists('portalProgrammeOptionSelected')) {
    function portalProgrammeOptionSelected(PDO $pdo, int $selectedProgrammeId, int $optionProgrammeId): bool {
        if ($selectedProgrammeId > 0 && $selectedProgrammeId === $optionProgrammeId) return true;
        if ($selectedProgrammeId <= 0 || $optionProgrammeId <= 0) return false;
        return portalProgrammesShareScopeName($pdo, $selectedProgrammeId, $optionProgrammeId);
    }
}
if (!function_exists('portalProgrammeSelectorLabelById')) {
    function portalProgrammeSelectorLabelById(PDO $pdo, int $programmeId, int $facultyId = 0, int $departmentId = 0, string $fallback = 'All Programmes'): string {
        if ($programmeId <= 0) return $fallback;
        foreach (portalProgrammeSelectorOptions($pdo, $facultyId, $departmentId) as $row) {
            if (portalProgrammeOptionSelected($pdo, $programmeId, (int)($row['id'] ?? 0))) {
                $label = trim((string)($row['name'] ?? ''));
                if ($label !== '') return $label;
            }
        }
        $label = trim((string)portalProgrammeScopeNameById($pdo, $programmeId));
        return $label !== '' ? $label : $fallback;
    }
}
if (!function_exists('portalModeOfStudyOptionValues')) {
    function portalModeOfStudyOptionValues(PDO $pdo): array {
        $values = ['Full-Time' => 'Full-Time', 'Part-Time' => 'Part-Time'];
        try {
            foreach (portalFetchDistinctProgrammeOptions($pdo) as $row) {
                $mode = trim((string)($row['mode_of_study'] ?? portalProgrammeModeOfStudyDisplay((array)$row, 'Full-Time')));
                if ($mode !== '') $values[$mode] = $mode;
            }
        } catch (Throwable $e) {}
        try {
            $stmt = $pdo->query("SELECT DISTINCT TRIM(mode_of_study) AS mode_of_study FROM students WHERE COALESCE(TRIM(mode_of_study),'') <> '' ORDER BY mode_of_study ASC");
            while ($stmt && ($row = $stmt->fetch(PDO::FETCH_ASSOC))) {
                $mode = trim((string)($row['mode_of_study'] ?? ''));
                if ($mode !== '') $values[$mode] = $mode;
            }
        } catch (Throwable $e) {}
        ksort($values, SORT_NATURAL | SORT_FLAG_CASE);
        return array_values($values);
    }
}

if (!function_exists('portalRegistrableCourseModeOfStudyOptionValues')) {
    function portalRegistrableCourseModeOfStudyOptionValues(PDO $pdo): array {
        $seed = ['Full-Time', 'Part-Time', 'Distance Learning', 'Weekend', 'Sandwich', 'Others'];
        $values = [];
        foreach ($seed as $opt) {
            $values[$opt] = $opt;
        }
        try {
            foreach (portalModeOfStudyOptionValues($pdo) as $opt) {
                $opt = trim((string)$opt);
                if ($opt === '') continue;
                $values[$opt] = $opt;
            }
        } catch (Throwable $e) {}
        $ordered = [];
        foreach ($seed as $opt) {
            if (isset($values[$opt])) {
                $ordered[] = $values[$opt];
                unset($values[$opt]);
            }
        }
        ksort($values, SORT_NATURAL | SORT_FLAG_CASE);
        foreach ($values as $opt) {
            if (!in_array($opt, $ordered, true)) {
                $ordered[] = $opt;
            }
        }
        return $ordered;
    }
}

if (!function_exists('portalNormalizeRegistrableCourseModeOfStudy')) {
    function portalNormalizeRegistrableCourseModeOfStudy($value, string $fallback = 'Full-Time'): string {
        $mode = trim((string)$value);
        $mode = preg_replace('/\s+/u', ' ', $mode) ?: $mode;
        if ($mode === '') return $fallback;
        $key = strtolower($mode);
        $map = [
            'full time' => 'Full-Time',
            'full-time' => 'Full-Time',
            'part time' => 'Part-Time',
            'part-time' => 'Part-Time',
            'distance' => 'Distance Learning',
            'distance learning' => 'Distance Learning',
            'weekend' => 'Weekend',
            'sandwich' => 'Sandwich',
            'other' => 'Others',
            'others' => 'Others',
        ];
        return $map[$key] ?? $mode;
    }
}

if (!function_exists('portalNormalizeProgrammeModeOfStudyInput')) {
    function portalNormalizeProgrammeModeOfStudyInput($type, $custom = ''): array {
        $type = trim((string)$type);
        $allowed = ['Full-Time', 'Part-Time', 'Others'];
        if (!in_array($type, $allowed, true)) $type = 'Full-Time';
        $custom = trim((string)$custom);
        $custom = preg_replace('/
|
|
/u', ' ', $custom) ?: $custom;
        $custom = preg_replace('/\s+/u', ' ', $custom) ?: $custom;
        if ($type === 'Others') {
            if ($custom === '') {
                throw new RuntimeException('Please enter the custom Mode of Study name when Others is selected.');
            }
            return ['type' => 'Others', 'custom' => $custom, 'display' => $custom];
        }
        return ['type' => $type, 'custom' => null, 'display' => $type];
    }
}

if (!function_exists('portalProgrammeModeOfStudyDisplay')) {
    function portalProgrammeModeOfStudyDisplay(array $row, string $fallback = 'Full-Time'): string {
        $type = trim((string)($row['programme_mode_of_study_type'] ?? $row['mode_of_study_type'] ?? ''));
        $custom = trim((string)($row['programme_mode_of_study_custom'] ?? $row['mode_of_study_custom'] ?? ''));
        if ($type === 'Others' && $custom !== '') return $custom;
        if (in_array($type, ['Full-Time', 'Part-Time'], true)) return $type;
        $stored = trim((string)($row['programme_mode_of_study'] ?? $row['mode_of_study'] ?? ''));
        if ($stored !== '') return $stored;
        $hay = strtolower(trim((string)($row['programme_name_cache'] ?? $row['programme_name'] ?? $row['programme_text'] ?? $row['programme'] ?? $row['name'] ?? '')));
        if ($hay !== '') {
            if (str_contains($hay, 'part') || str_contains($hay, 'pde')) return 'Part-Time';
            if (str_contains($hay, 'sandwich')) return 'Sandwich';
            if (str_contains($hay, 'distance')) return 'Distance Learning';
        }
        return $fallback;
    }
}

if (!function_exists('portalEnsureProgrammeModeOfStudySupport')) {
    function portalEnsureProgrammeModeOfStudySupport(PDO $pdo): void {
        try { if (!portalColumnExistsSafe($pdo, 'programmes', 'mode_of_study_type')) { $pdo->exec("ALTER TABLE programmes ADD COLUMN mode_of_study_type VARCHAR(20) NOT NULL DEFAULT 'Full-Time' AFTER name"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'programmes', 'mode_of_study_custom')) { $pdo->exec("ALTER TABLE programmes ADD COLUMN mode_of_study_custom VARCHAR(180) NULL AFTER mode_of_study_type"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'programmes') && !portalIndexExistsSafe($pdo, 'programmes', 'idx_prog_mode')) { $pdo->exec("CREATE INDEX idx_prog_mode ON programmes(mode_of_study_type)"); } } catch (Throwable $e) {}
        try {
            $pdo->exec("UPDATE programmes
                SET
                    mode_of_study_type = CASE
                        WHEN COALESCE(TRIM(mode_of_study_type),'') <> '' THEN mode_of_study_type
                        WHEN LOWER(COALESCE(name, '')) LIKE '%part%' OR LOWER(COALESCE(name, '')) LIKE '%pde%' THEN 'Part-Time'
                        WHEN LOWER(COALESCE(name, '')) LIKE '%sandwich%' OR LOWER(COALESCE(name, '')) LIKE '%distance%' THEN 'Others'
                        ELSE 'Full-Time'
                    END,
                    mode_of_study_custom = CASE
                        WHEN COALESCE(TRIM(mode_of_study_custom),'') <> '' THEN mode_of_study_custom
                        WHEN (LOWER(COALESCE(name, '')) LIKE '%sandwich%' OR LOWER(COALESCE(name, '')) LIKE '%distance%') THEN TRIM(name)
                        ELSE mode_of_study_custom
                    END
                WHERE COALESCE(TRIM(mode_of_study_type),'') = '' OR COALESCE(TRIM(mode_of_study_custom),'') = ''");
        } catch (Throwable $e) {}
        try {
            $pdo->exec("UPDATE students s
                LEFT JOIN programmes p ON p.id = s.programme_id
                SET s.mode_of_study = CASE
                    WHEN p.id IS NOT NULL AND COALESCE(p.mode_of_study_type, '') = 'Others' AND COALESCE(TRIM(p.mode_of_study_custom), '') <> '' THEN TRIM(p.mode_of_study_custom)
                    WHEN p.id IS NOT NULL AND COALESCE(p.mode_of_study_type, '') IN ('Full-Time', 'Part-Time') THEN p.mode_of_study_type
                    WHEN LOWER(COALESCE(s.programme, '')) LIKE '%part%' OR LOWER(COALESCE(s.programme, '')) LIKE '%pde%' THEN 'Part-Time'
                    WHEN LOWER(COALESCE(s.programme, '')) LIKE '%sandwich%' THEN 'Sandwich'
                    WHEN LOWER(COALESCE(s.programme, '')) LIKE '%distance%' THEN 'Distance Learning'
                    ELSE 'Full-Time'
                END
                WHERE COALESCE(TRIM(s.mode_of_study), '') = ''");
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalProgrammeModeOfStudyValueById')) {
    function portalProgrammeModeOfStudyValueById(PDO $pdo, int $programmeId, string $fallback = 'Full-Time'): string {
        if ($programmeId <= 0) return $fallback;
        portalEnsureProgrammeModeOfStudySupport($pdo);
        try {
            $st = $pdo->prepare("SELECT mode_of_study_type, mode_of_study_custom, name FROM programmes WHERE id = ? LIMIT 1");
            $st->execute([$programmeId]);
            $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            return portalProgrammeModeOfStudyDisplay($row, $fallback);
        } catch (Throwable $e) {
            return $fallback;
        }
    }
}

if (!function_exists('portalSynchronizeProgrammeModeOfStudyAcrossStudents')) {
    function portalSynchronizeProgrammeModeOfStudyAcrossStudents(PDO $pdo, int $programmeId = 0): void {
        portalEnsureProgrammeModeOfStudySupport($pdo);
        try {
            $sql = "UPDATE students s
                INNER JOIN programmes p ON p.id = s.programme_id
                SET s.mode_of_study = CASE
                    WHEN COALESCE(p.mode_of_study_type, '') = 'Others' AND COALESCE(TRIM(p.mode_of_study_custom), '') <> '' THEN TRIM(p.mode_of_study_custom)
                    WHEN COALESCE(p.mode_of_study_type, '') IN ('Full-Time', 'Part-Time') THEN p.mode_of_study_type
                    ELSE COALESCE(NULLIF(TRIM(s.mode_of_study), ''), 'Full-Time')
                END";
            $params = [];
            if ($programmeId > 0) {
                $sql .= " WHERE s.programme_id = ?";
                $params[] = $programmeId;
            }
            $st = $pdo->prepare($sql);
            $st->execute($params);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalEnsureApplicationAcademicFieldSupport')) {
    function portalEnsureApplicationAcademicFieldSupport(PDO $pdo): void {
        portalEnsureStudentAcademicMappingSupport($pdo);
        portalEnsureProgrammeModeOfStudySupport($pdo);
        try { if (!portalColumnExistsSafe($pdo, 'students', 'program')) { $pdo->exec("ALTER TABLE students ADD COLUMN program VARCHAR(180) NULL AFTER programme"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'students') && !portalIndexExistsSafe($pdo, 'students', 'idx_students_application_academics')) { $pdo->exec("CREATE INDEX idx_students_application_academics ON students(faculty_id, department_id, programme_id)"); } } catch (Throwable $e) {}
        try {
            $pdo->exec("UPDATE students
                SET program = COALESCE(NULLIF(TRIM(program), ''), NULLIF(TRIM(programme_name_cache), ''), NULLIF(TRIM(programme), ''))
                WHERE program IS NULL OR TRIM(program) = ''");
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalValidateApplicationAcademicSelection')) {
    function portalValidateApplicationAcademicSelection(PDO $pdo, int $facultyId, int $departmentId, int $programmeId): array {
        portalEnsureApplicationAcademicFieldSupport($pdo);

        if ($facultyId <= 0) {
            throw new RuntimeException('School is required. Please select a valid school.');
        }
        if ($departmentId <= 0) {
            throw new RuntimeException('Course of Study is required. Please select a valid course of study.');
        }
        if ($programmeId <= 0) {
            throw new RuntimeException('Program is required. Please select a valid program.');
        }

        $faculty = null;
        $department = null;
        $programme = null;

        try {
            $st = $pdo->prepare("SELECT id, name FROM faculties WHERE id = ? LIMIT 1");
            $st->execute([$facultyId]);
            $faculty = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$faculty) {
                throw new RuntimeException('Invalid school selected.');
            }

            $st = $pdo->prepare("SELECT id, faculty_id, name FROM departments WHERE id = ? LIMIT 1");
            $st->execute([$departmentId]);
            $department = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$department) {
                throw new RuntimeException('Invalid course of study selected.');
            }
            $departmentFacultyId = (int)($department['faculty_id'] ?? 0);
            if ($departmentFacultyId !== $facultyId) {
                throw new RuntimeException('Invalid selection combination. The selected course of study does not belong to the chosen school.');
            }

            $st = $pdo->prepare("SELECT p.id,
                                        COALESCE(NULLIF(p.faculty_id, 0), d.faculty_id, 0) AS faculty_id,
                                        p.department_id,
                                        p.name,
                                        p.mode_of_study_type,
                                        p.mode_of_study_custom
                                 FROM programmes p
                                 LEFT JOIN departments d ON d.id = p.department_id
                                 WHERE p.id = ?
                                 LIMIT 1");
            $st->execute([$programmeId]);
            $programme = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$programme) {
                throw new RuntimeException('Invalid program selected.');
            }

            $programmeDepartmentId = (int)($programme['department_id'] ?? 0);
            $programmeFacultyId = (int)($programme['faculty_id'] ?? 0);
            if ($programmeFacultyId <= 0 && $programmeDepartmentId === $departmentId) {
                $programmeFacultyId = $departmentFacultyId;
                $programme['faculty_id'] = $programmeFacultyId;
            }

            if ($programmeDepartmentId !== $departmentId || $programmeFacultyId !== $facultyId) {
                throw new RuntimeException('Invalid selection combination. The selected program does not match the chosen school and course of study.');
            }
        } catch (RuntimeException $e) {
            throw $e;
        } catch (Throwable $e) {
            throw new RuntimeException('Unable to validate your school, course of study, and program selection right now.');
        }

        return [
            'faculty_id' => $facultyId,
            'department_id' => $departmentId,
            'programme_id' => $programmeId,
            'school' => trim((string)($faculty['name'] ?? '')),
            'course_of_study' => trim((string)($department['name'] ?? '')),
            'programme' => trim((string)($programme['name'] ?? '')),
            'mode_of_study' => portalProgrammeModeOfStudyDisplay((array)$programme),
        ];
    }
}

if (!function_exists('portalStudentApplicationAcademicState')) {
    function portalStudentApplicationAcademicState(PDO $pdo, array $student): array {
        portalEnsureApplicationAcademicFieldSupport($pdo);

        $studentId = (int)($student['id'] ?? 0);
        $academic = portalResolveStudentAcademicProfile($pdo, $student);

        $facultyId = (int)($student['faculty_id'] ?? 0);
        $departmentId = (int)($student['department_id'] ?? 0);
        $programmeId = (int)($student['programme_id'] ?? 0);

        if ($facultyId <= 0) {
            $facultyId = portalResolveFacultyIdByText($pdo, $student['school'] ?? $student['school_name'] ?? $student['faculty'] ?? $academic['school'] ?? '');
        }
        if ($departmentId <= 0) {
            $departmentId = portalResolveDepartmentIdByText($pdo, $student['course_of_study'] ?? $student['course_in_view'] ?? $student['department'] ?? $academic['course_of_study'] ?? '', $facultyId);
        }
        if ($programmeId <= 0) {
            $programmeId = portalResolveProgrammeIdByText($pdo, $student['program'] ?? $student['programme_name_cache'] ?? $student['programme'] ?? $academic['programme'] ?? '', $departmentId);
        }

        $school = trim((string)($student['school'] ?? $student['school_name'] ?? $academic['school'] ?? ''));
        $courseOfStudy = trim((string)($student['course_of_study'] ?? $student['course_in_view'] ?? $academic['course_of_study'] ?? ''));
        $programme = trim((string)($student['program'] ?? $student['programme_name_cache'] ?? $student['programme'] ?? $academic['programme'] ?? ''));
        $modeOfStudy = trim((string)($student['mode_of_study'] ?? ''));

        try {
            if ($facultyId > 0 && $school === '') {
                $st = $pdo->prepare("SELECT name FROM faculties WHERE id = ? LIMIT 1");
                $st->execute([$facultyId]);
                $school = trim((string)($st->fetchColumn() ?: ''));
            }
            if ($departmentId > 0 && $courseOfStudy === '') {
                $st = $pdo->prepare("SELECT name FROM departments WHERE id = ? LIMIT 1");
                $st->execute([$departmentId]);
                $courseOfStudy = trim((string)($st->fetchColumn() ?: ''));
            }
            if ($programmeId > 0 && $programme === '') {
                $st = $pdo->prepare("SELECT name FROM programmes WHERE id = ? LIMIT 1");
                $st->execute([$programmeId]);
                $programme = trim((string)($st->fetchColumn() ?: ''));
            }
            if ($programmeId > 0 && $modeOfStudy === '') {
                $modeOfStudy = portalProgrammeModeOfStudyValueById($pdo, $programmeId, 'Full-Time');
            }
        } catch (Throwable $e) {}

        try {
            $faculties = $pdo->query("SELECT id, name FROM faculties WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) { $faculties = []; }
        try {
            $departments = $pdo->query("SELECT id, faculty_id, name FROM departments WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) { $departments = []; }
        try {
            $programmes = $pdo->query("SELECT id, faculty_id, department_id, name, mode_of_study_type, mode_of_study_custom FROM programmes WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) { $programmes = []; }

        if ($studentId > 0) {
            try {
                $st = $pdo->prepare("UPDATE students SET faculty_id = COALESCE(?, faculty_id), department_id = COALESCE(?, department_id), programme_id = COALESCE(?, programme_id), faculty = COALESCE(?, faculty), school_name = COALESCE(?, school_name), school = COALESCE(?, school), department = COALESCE(?, department), course_in_view = COALESCE(?, course_in_view), course_of_study = COALESCE(?, course_of_study), programme = COALESCE(?, programme), programme_name_cache = COALESCE(?, programme_name_cache), program = COALESCE(?, program), mode_of_study = COALESCE(?, mode_of_study) WHERE id = ?");
                $st->execute([
                    $facultyId > 0 ? $facultyId : null,
                    $departmentId > 0 ? $departmentId : null,
                    $programmeId > 0 ? $programmeId : null,
                    $school !== '' && $school !== '—' ? $school : null,
                    $school !== '' && $school !== '—' ? $school : null,
                    $school !== '' && $school !== '—' ? $school : null,
                    $courseOfStudy !== '' && $courseOfStudy !== '—' ? $courseOfStudy : null,
                    $courseOfStudy !== '' && $courseOfStudy !== '—' ? $courseOfStudy : null,
                    $courseOfStudy !== '' && $courseOfStudy !== '—' ? $courseOfStudy : null,
                    $programme !== '' && $programme !== '—' ? $programme : null,
                    $programme !== '' && $programme !== '—' ? $programme : null,
                    $programme !== '' && $programme !== '—' ? $programme : null,
                    $modeOfStudy !== '' ? $modeOfStudy : null,
                    $studentId
                ]);
            } catch (Throwable $e) {}
        }

        return [
            'faculty_id' => $facultyId,
            'department_id' => $departmentId,
            'programme_id' => $programmeId,
            'school' => $school,
            'course_of_study' => $courseOfStudy,
            'programme' => $programme,
            'mode_of_study' => $modeOfStudy !== '' ? $modeOfStudy : 'Full-Time',
            'faculties' => $faculties,
            'departments' => $departments,
            'programmes' => $programmes,
        ];
    }
}


if (!function_exists('portalEnsureApplicationGraceSupport')) {
    function portalEnsureApplicationGraceSupport(PDO $pdo): void {
        portalEnsureApplicationAcademicFieldSupport($pdo);

        $studentColumns = [
            "application_max_edit_count" => "ALTER TABLE students ADD COLUMN application_max_edit_count INT NOT NULL DEFAULT 3 AFTER application_edit_count",
            "application_extra_grace_count" => "ALTER TABLE students ADD COLUMN application_extra_grace_count INT NOT NULL DEFAULT 0 AFTER application_max_edit_count",
            "application_grace_used_count" => "ALTER TABLE students ADD COLUMN application_grace_used_count INT NOT NULL DEFAULT 0 AFTER application_extra_grace_count",
            "application_granted_by_admin_id" => "ALTER TABLE students ADD COLUMN application_granted_by_admin_id INT NULL AFTER application_grace_used_count",
            "application_granted_at" => "ALTER TABLE students ADD COLUMN application_granted_at DATETIME NULL AFTER application_granted_by_admin_id",
            "application_last_grace_used_at" => "ALTER TABLE students ADD COLUMN application_last_grace_used_at DATETIME NULL AFTER application_granted_at",
        ];
        foreach ($studentColumns as $column => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, 'students', $column)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) {}
        }

        try {
            if (portalTableExistsSafe($pdo, 'students') && !portalIndexExistsSafe($pdo, 'students', 'idx_students_application_grace')) {
                $pdo->exec("CREATE INDEX idx_students_application_grace ON students(application_submitted, application_edit_count, application_max_edit_count, application_extra_grace_count)");
            }
        } catch (Throwable $e) {}

        try {
            $pdo->exec("
                CREATE TABLE IF NOT EXISTS application_resubmission_audit (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    student_id INT NOT NULL,
                    admin_id INT NULL,
                    action_type VARCHAR(50) NOT NULL DEFAULT 'grant',
                    previous_extra_count INT NOT NULL DEFAULT 0,
                    new_extra_count INT NOT NULL DEFAULT 0,
                    grace_used_count INT NOT NULL DEFAULT 0,
                    note VARCHAR(255) NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    INDEX idx_app_resub_student (student_id),
                    INDEX idx_app_resub_admin (admin_id),
                    INDEX idx_app_resub_action (action_type)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
            ");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("
                UPDATE students
                SET application_max_edit_count = CASE
                        WHEN application_max_edit_count IS NULL OR application_max_edit_count < 3 THEN 3
                        ELSE application_max_edit_count
                    END,
                    application_extra_grace_count = GREATEST(COALESCE(application_extra_grace_count, 0), 0),
                    application_grace_used_count = GREATEST(
                        COALESCE(application_edit_count, 0) - COALESCE(NULLIF(application_max_edit_count, 0), 3),
                        0
                    )
            ");
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalApplicationResubmissionState')) {
    function portalApplicationResubmissionState(PDO $pdo, $studentOrId): array {
        portalEnsureApplicationGraceSupport($pdo);

        $student = null;
        if (is_array($studentOrId)) {
            $student = $studentOrId;
        } else {
            $studentId = (int)$studentOrId;
            try {
                $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $st->execute([$studentId]);
                $student = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) {
                $student = [];
            }
        }

        $baseLimit = max(3, (int)($student['application_max_edit_count'] ?? 3));
        $usedCount = max(0, (int)($student['application_edit_count'] ?? 0));
        $submitted = !empty($student['application_submitted']) && (int)$student['application_submitted'] === 1;
        $extraCount = max(0, (int)($student['application_extra_grace_count'] ?? 0));
        $totalAllowed = $baseLimit + $extraCount;
        $remainingTotal = max(0, $totalAllowed - $usedCount);
        $graceUsed = max(0, $usedCount - $baseLimit);
        $graceRemaining = max(0, $extraCount - $graceUsed);
        $canEdit = $usedCount < $totalAllowed;
        $reopened = $submitted && $graceRemaining > 0;

        $message = '';
        if ($reopened) {
            $message = 'Your application has been reopened for resubmission.';
        } elseif (!$canEdit && $extraCount > 0) {
            $message = 'Your grace resubmission attempts have been exhausted.';
        } elseif (!$canEdit) {
            $message = 'Your application is locked for further modifications.';
        }

        return [
            'student_id' => (int)($student['id'] ?? 0),
            'submitted' => $submitted,
            'base_limit' => $baseLimit,
            'used_count' => $usedCount,
            'extra_grace_count' => $extraCount,
            'grace_used' => $graceUsed,
            'grace_remaining' => $graceRemaining,
            'remaining_total' => $remainingTotal,
            'total_allowed' => $totalAllowed,
            'can_edit' => $canEdit,
            'reopened' => $reopened,
            'message' => $message,
            'granted_by_admin_id' => (int)($student['application_granted_by_admin_id'] ?? 0),
            'granted_at' => (string)($student['application_granted_at'] ?? ''),
            'last_grace_used_at' => (string)($student['application_last_grace_used_at'] ?? ''),
        ];
    }
}

if (!function_exists('portalLogApplicationResubmissionAudit')) {
    function portalLogApplicationResubmissionAudit(PDO $pdo, int $studentId, int $adminId, string $actionType, int $previousExtraCount, int $newExtraCount, int $graceUsedCount = 0, string $note = ''): void {
        portalEnsureApplicationGraceSupport($pdo);
        try {
            $st = $pdo->prepare("INSERT INTO application_resubmission_audit (student_id, admin_id, action_type, previous_extra_count, new_extra_count, grace_used_count, note) VALUES (?, ?, ?, ?, ?, ?, ?)");
            $st->execute([$studentId, $adminId > 0 ? $adminId : null, $actionType, max(0, $previousExtraCount), max(0, $newExtraCount), max(0, $graceUsedCount), $note !== '' ? $note : null]);
        } catch (Throwable $e) {}
    }
}


if (!function_exists('portalAdminSubmittedApplicationEditAllowed')) {
    function portalAdminSubmittedApplicationEditAllowed(PDO $pdo): bool {
        if (!isAdmin()) return false;
        if (isSuperadmin($pdo)) return true;
        if (function_exists('hasStaffPermission')) {
            return hasStaffPermission($pdo, 'academics_manage');
        }
        return false;
    }
}

if (!function_exists('portalAdminSubmittedApplicationEditToken')) {
    function portalAdminSubmittedApplicationEditToken(): string {
        if (empty($_SESSION['portal_admin_application_edit_token'])) {
            try {
                $_SESSION['portal_admin_application_edit_token'] = bin2hex(random_bytes(32));
            } catch (Throwable $e) {
                $_SESSION['portal_admin_application_edit_token'] = honttechSecureToken(32);
            }
        }
        return (string)$_SESSION['portal_admin_application_edit_token'];
    }
}

if (!function_exists('portalAdminSubmittedApplicationEditTokenValid')) {
    function portalAdminSubmittedApplicationEditTokenValid(string $token): bool {
        $session = (string)($_SESSION['portal_admin_application_edit_token'] ?? '');
        return $session !== '' && $token !== '' && hash_equals($session, $token);
    }
}

if (!function_exists('portalAdminStudentStatusUpdateAllowed')) {
    function portalAdminStudentStatusUpdateAllowed(PDO $pdo): bool {
        if (!isAdmin()) return false;
        if (isSuperadmin($pdo)) return true;
        if (function_exists('hasStaffPermission')) {
            return hasStaffPermission($pdo, 'academics_manage');
        }
        return true;
    }
}

if (!function_exists('portalStudentStatusUpdateToken')) {
    function portalStudentStatusUpdateToken(): string {
        if (empty($_SESSION['portal_student_status_update_token'])) {
            try {
                $_SESSION['portal_student_status_update_token'] = bin2hex(random_bytes(32));
            } catch (Throwable $e) {
                $_SESSION['portal_student_status_update_token'] = honttechSecureToken(32);
            }
        }
        return (string)$_SESSION['portal_student_status_update_token'];
    }
}

if (!function_exists('portalStudentStatusUpdateTokenValid')) {
    function portalStudentStatusUpdateTokenValid(string $token): bool {
        $session = (string)($_SESSION['portal_student_status_update_token'] ?? '');
        return $session !== '' && $token !== '' && hash_equals($session, $token);
    }
}

if (!function_exists('portalStudentAccountStatusAllowedValues')) {
    function portalStudentAccountStatusAllowedValues(): array {
        return ['Active', 'Suspended', 'Blocked'];
    }
}

if (!function_exists('portalNormalizeStudentAccountStatus')) {
    function portalNormalizeStudentAccountStatus($value, string $fallback = 'Active'): string {
        $status = trim((string)$value);
        $allowed = portalStudentAccountStatusAllowedValues();
        return in_array($status, $allowed, true) ? $status : $fallback;
    }
}

if (!function_exists('portalSafeReturnUrl')) {
    function portalSafeReturnUrl($url, string $fallback = '?page=admin_students'): string {
        $url = trim((string)$url);
        if ($url !== '' && str_starts_with($url, '?') && stripos($url, 'page=') !== false) {
            return $url;
        }
        return $fallback;
    }
}

if (!function_exists('portalRenderAdminSubmittedApplicationEditor')) {
    function portalRenderAdminSubmittedApplicationEditor(PDO $pdo, array $student, string $returnPage = 'admin_applications'): void {
        if (!isAdmin()) return;

        $studentId = (int)($student['id'] ?? 0);
        if ($studentId <= 0) return;

        if (!portalAdminSubmittedApplicationEditAllowed($pdo)) {
            echo '<div class="alert alert-warning mt-4">You can view this submitted form, but you do not have permission to edit it.</div>';
            return;
        }

        portalEnsureApplicationAcademicFieldSupport($pdo);
        $academicState = portalStudentApplicationAcademicState($pdo, $student);
        $selectedFacultyId = (int)($academicState['faculty_id'] ?? 0);
        $selectedDepartmentId = (int)($academicState['department_id'] ?? 0);
        $selectedProgrammeId = (int)($academicState['programme_id'] ?? 0);
        $faculties = $academicState['faculties'] ?? [];
        $departments = $academicState['departments'] ?? [];
        $programmes = $academicState['programmes'] ?? [];
        $availableLevels = portalAcademicLevelOptions($pdo);
        $selectedLevel = trim((string)($student['level'] ?? ''));
        if ($selectedLevel === '') { $selectedLevel = portalStudentResolvedCurrentLevel($pdo, $student, (string)($student['session'] ?? portalCurrentAcademicSessionName($pdo))); }

        $olevelRows = [];
        try {
            if (portalTableExistsSafe($pdo, 'student_olevel_subjects')) {
                $st = $pdo->prepare("SELECT subject_name, grade FROM student_olevel_subjects WHERE student_id = ? ORDER BY id ASC");
                $st->execute([$studentId]);
                $olevelRows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            }
        } catch (Throwable $e) {
            $olevelRows = [];
        }
        if (!$olevelRows) {
            foreach (['subject1', 'subject2', 'subject3'] as $field) {
                $name = trim((string)($student[$field] ?? ''));
                if ($name !== '') {
                    $olevelRows[] = ['subject_name' => $name, 'grade' => ''];
                }
            }
        }
        if (!$olevelRows) {
            $olevelRows = [['subject_name' => '', 'grade' => '']];
        }

        $educationHistory = [];
        try {
            $st = $pdo->prepare("SELECT institution_name, from_date, to_date, qualification FROM education_history WHERE student_id = ? ORDER BY id ASC");
            $st->execute([$studentId]);
            $educationHistory = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $educationHistory = [];
        }
        if (!$educationHistory) {
            $educationHistory = [['institution_name' => '', 'from_date' => '', 'to_date' => '', 'qualification' => '']];
        }

        $examResults = [];
        try {
            $st = $pdo->prepare("SELECT exam_type, exam_type_other, exam_date, exam_number, subjects_grades, credentials_attached FROM examination_results WHERE student_id = ? ORDER BY id ASC");
            $st->execute([$studentId]);
            $examResults = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $examResults = [];
        }
        if (!$examResults) {
            $examResults = [[
                'exam_type' => '',
                'exam_type_other' => '',
                'exam_date' => '',
                'exam_number' => '',
                'subjects_grades' => '',
                'credentials_attached' => 1
            ]];
        }

        $referee = [];
        try {
            $st = $pdo->prepare("SELECT * FROM referees WHERE student_id = ? ORDER BY id DESC LIMIT 1");
            $st->execute([$studentId]);
            $referee = $st->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $referee = [];
        }

        $documents = [];
        try {
            $st = $pdo->prepare("SELECT * FROM student_documents WHERE student_id = ? ORDER BY id DESC");
            $st->execute([$studentId]);
            $documents = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $documents = [];
        }

        $editorId = 'adminApplicationEditorPane' . $studentId;
        $facultyIdField = $editorId . '_faculty';
        $departmentIdField = $editorId . '_department';
        $programmeIdField = $editorId . '_programme';
        $eduBodyId = $editorId . '_education_body';
        $examBodyId = $editorId . '_exam_body';
        $olevelBodyId = $editorId . '_olevel_body';
        $docsBodyId = $editorId . '_docs_body';
        $token = portalAdminSubmittedApplicationEditToken();

        $openEditor = ((string)($_GET['edit'] ?? '') === '1');
        $departmentsJson = json_encode(array_values($departments), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        $programmesJson = json_encode(array_values($programmes), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        ?>
        <div class="card border-primary-subtle mt-4" id="adminApplicationEditor">
            <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
                <div>
                    <h5 class="mb-0">Submitted Form Editor</h5>
                    <div class="text-muted small">Admin can update this student's submitted form here without reopening the student-side submission lock.</div>
                </div>
                <button class="btn btn-primary btn-sm" type="button" data-bs-toggle="collapse" data-bs-target="#<?php echo e($editorId); ?>" aria-expanded="<?php echo $openEditor ? 'true' : 'false'; ?>">
                    <i class="bi bi-pencil-square"></i> <?php echo $openEditor ? 'Hide' : 'Edit'; ?> Submitted Form
                </button>
            </div>
            <div id="<?php echo e($editorId); ?>" class="collapse <?php echo $openEditor ? 'show' : ''; ?>">
                <div class="card-body">
                    <div class="alert alert-info">
                        Student-side final submission and admission locks remain intact. This editor applies an admin-only update directly to the current submitted record and reloads the fresh values immediately after save.
                    </div>

                    <form method="post" enctype="multipart/form-data" class="row g-3">
                        <input type="hidden" name="action" value="admin_update_submitted_application">
                        <input type="hidden" name="student_id" value="<?php echo (int)$studentId; ?>">
                        <input type="hidden" name="return_page" value="<?php echo e($returnPage); ?>">
                        <input type="hidden" name="return_action" value="view">
                        <input type="hidden" name="return_id" value="<?php echo (int)$studentId; ?>">
                        <input type="hidden" name="admin_application_edit_token" value="<?php echo e($token); ?>">
                        <input type="hidden" name="applicant_signature_data" value="<?php echo e((string)($student['signature_data'] ?? '')); ?>">
                        <input type="hidden" name="ref_signature_data" value="<?php echo e((string)($referee['signature_data'] ?? '')); ?>">

                        <div class="col-12">
                            <div class="form-section">
                                <h3>Personal & Academic Information</h3>
                                <div class="row">
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Surname</label>
                                        <input type="text" class="form-control" name="surname" value="<?php echo e((string)($student['surname'] ?? '')); ?>" required>
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">First Name</label>
                                        <input type="text" class="form-control" name="first_name" value="<?php echo e((string)($student['first_name'] ?? '')); ?>" required>
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Other Name</label>
                                        <input type="text" class="form-control" name="other_name" value="<?php echo e((string)($student['other_name'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Maiden Name</label>
                                        <input type="text" class="form-control" name="maiden_name" value="<?php echo e((string)($student['maiden_name'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Sex</label>
                                        <?php $sexValue = trim((string)($student['sex'] ?? '')); ?>
                                        <select class="form-select" name="sex" required>
                                            <option value="">Select</option>
                                            <option value="Male" <?php echo $sexValue === 'Male' ? 'selected' : ''; ?>>Male</option>
                                            <option value="Female" <?php echo $sexValue === 'Female' ? 'selected' : ''; ?>>Female</option>
                                        </select>
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Date of Birth</label>
                                        <input type="date" class="form-control" name="date_of_birth" value="<?php echo e((string)($student['date_of_birth'] ?? '')); ?>" required>
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Nationality</label>
                                        <input type="text" class="form-control" name="nationality" value="<?php echo e((string)($student['nationality'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">State of Origin</label>
                                        <input type="text" class="form-control" name="state_of_origin" value="<?php echo e((string)($student['state_of_origin'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Local Government Area</label>
                                        <input type="text" class="form-control" name="lga" value="<?php echo e((string)($student['lga'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Mobile Phone</label>
                                        <input type="text" class="form-control" name="mobile_phone" value="<?php echo e((string)($student['mobile_phone'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Residential Phone</label>
                                        <input type="text" class="form-control" name="residential_phone" value="<?php echo e((string)($student['residential_phone'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-4 mb-3">
                                        <label class="form-label">Email</label>
                                        <input type="email" class="form-control" name="email" value="<?php echo e((string)($student['email'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-3 mb-3">
                                        <label class="form-label">School</label>
                                        <select class="form-select" name="faculty_id" id="<?php echo e($facultyIdField); ?>" required>
                                            <option value="">Select School</option>
                                            <?php foreach ($faculties as $facultyRow): ?>
                                                <option value="<?php echo (int)($facultyRow['id'] ?? 0); ?>" <?php echo (int)($facultyRow['id'] ?? 0) === $selectedFacultyId ? 'selected' : ''; ?>>
                                                    <?php echo e((string)($facultyRow['name'] ?? '')); ?>
                                                </option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-3 mb-3">
                                        <label class="form-label">Course of Study</label>
                                        <select class="form-select" name="department_id" id="<?php echo e($departmentIdField); ?>" required>
                                            <option value="">Select Course of Study</option>
                                            <?php foreach ($departments as $deptRow): ?>
                                                <?php if ($selectedFacultyId > 0 && (int)($deptRow['faculty_id'] ?? 0) !== $selectedFacultyId) continue; ?>
                                                <option value="<?php echo (int)($deptRow['id'] ?? 0); ?>" <?php echo (int)($deptRow['id'] ?? 0) === $selectedDepartmentId ? 'selected' : ''; ?>>
                                                    <?php echo e((string)($deptRow['name'] ?? '')); ?>
                                                </option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-3 mb-3">
                                        <label class="form-label mb-1">Programme</label>
                                        <select class="form-select" name="programme_id" id="<?php echo e($programmeIdField); ?>" required>
                                            <option value="">Select Programme</option>
                                            <?php foreach ($programmes as $programmeRow): ?>
                                                <?php if ($selectedDepartmentId > 0 && (int)($programmeRow['department_id'] ?? 0) !== $selectedDepartmentId) continue; ?>
                                                <option value="<?php echo (int)($programmeRow['id'] ?? 0); ?>" <?php echo (int)($programmeRow['id'] ?? 0) === $selectedProgrammeId ? 'selected' : ''; ?>>
                                                    <?php echo e((string)($programmeRow['name'] ?? '')); ?>
                                                </option>
                                            <?php endforeach; ?>
                                        </select>
                                        <div class="form-text">Mode of Study remains synchronized with the selected programme.</div>
                                    </div>
                                    <div class="col-md-3 mb-3">
                                        <label class="form-label">Level</label>
                                        <select class="form-select" name="level" required>
                                            <option value="">Select Level</option>
                                            <?php foreach ($availableLevels as $levelOption): ?>
                                                <?php $levelOption = trim((string)$levelOption); if ($levelOption === '') continue; ?>
                                                <option value="<?php echo e($levelOption); ?>" <?php echo ((string)$selectedLevel === (string)$levelOption) ? 'selected' : ''; ?>><?php echo e($levelOption); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-12 mb-3">
                                        <label class="form-label">Permanent Address</label>
                                        <textarea class="form-control" name="permanent_address" rows="3"><?php echo e((string)($student['permanent_address'] ?? '')); ?></textarea>
                                    </div>
                                    <div class="col-md-12 mb-3">
                                        <label class="form-label">Extra Curricular Activities</label>
                                        <textarea class="form-control" name="extra_curricular" rows="2"><?php echo e((string)($student['extra_curricular'] ?? '')); ?></textarea>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="col-12">
                            <div class="form-section">
                                <h3>Next of Kin & Sponsor</h3>
                                <div class="row">
                                    <div class="col-md-6 mb-3">
                                        <label class="form-label">Next of Kin / Parent Name & Address</label>
                                        <textarea class="form-control" name="nok_name" rows="3"><?php echo e((string)($student['nok_name'] ?? '')); ?></textarea>
                                    </div>
                                    <div class="col-md-6 mb-3">
                                        <label class="form-label">Next of Kin Telephone</label>
                                        <input type="text" class="form-control" name="nok_phone" value="<?php echo e((string)($student['nok_phone'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-6 mb-3">
                                        <label class="form-label">Sponsor Name & Address</label>
                                        <textarea class="form-control" name="sponsor_name" rows="3"><?php echo e((string)($student['sponsor_name'] ?? '')); ?></textarea>
                                    </div>
                                    <div class="col-md-3 mb-3">
                                        <label class="form-label">Sponsor Telephone</label>
                                        <input type="text" class="form-control" name="sponsor_phone" value="<?php echo e((string)($student['sponsor_phone'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-3 mb-3">
                                        <label class="form-label">Sponsor Occupation</label>
                                        <input type="text" class="form-control" name="sponsor_occupation" value="<?php echo e((string)($student['sponsor_occupation'] ?? '')); ?>">
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="col-12">
                            <div class="form-section">
                                <div class="d-flex justify-content-between align-items-center mb-2">
                                    <h3 class="mb-0">O'Level Subjects</h3>
                                    <button type="button" class="btn btn-sm btn-outline-primary" id="<?php echo e($olevelBodyId); ?>_add">Add Subject</button>
                                </div>
                                <div class="table-responsive">
                                    <table class="table table-bordered align-middle">
                                        <thead>
                                            <tr>
                                                <th style="width:60%;">Subject</th>
                                                <th style="width:30%;">Grade</th>
                                                <th style="width:10%;"></th>
                                            </tr>
                                        </thead>
                                        <tbody id="<?php echo e($olevelBodyId); ?>">
                                            <?php foreach ($olevelRows as $olevelRow): ?>
                                                <tr>
                                                    <td><input type="text" class="form-control" name="subject_name[]" value="<?php echo e((string)($olevelRow['subject_name'] ?? '')); ?>"></td>
                                                    <td><input type="text" class="form-control" name="subject_grade[]" value="<?php echo e((string)($olevelRow['grade'] ?? '')); ?>"></td>
                                                    <td class="text-center"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('tr').remove();">×</button></td>
                                                </tr>
                                            <?php endforeach; ?>
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                        </div>

                        <div class="col-12">
                            <div class="form-section">
                                <div class="d-flex justify-content-between align-items-center mb-2">
                                    <h3 class="mb-0">Education History</h3>
                                    <button type="button" class="btn btn-sm btn-outline-primary" id="<?php echo e($eduBodyId); ?>_add">Add School</button>
                                </div>
                                <div class="table-responsive">
                                    <table class="table table-bordered align-middle">
                                        <thead>
                                            <tr>
                                                <th>Institution</th>
                                                <th>From</th>
                                                <th>To</th>
                                                <th>Qualification</th>
                                                <th></th>
                                            </tr>
                                        </thead>
                                        <tbody id="<?php echo e($eduBodyId); ?>">
                                            <?php foreach ($educationHistory as $eduRow): ?>
                                                <tr>
                                                    <td><input type="text" class="form-control" name="school_name[]" value="<?php echo e((string)($eduRow['institution_name'] ?? '')); ?>"></td>
                                                    <td><input type="date" class="form-control" name="school_from[]" value="<?php echo e((string)($eduRow['from_date'] ?? '')); ?>"></td>
                                                    <td><input type="date" class="form-control" name="school_to[]" value="<?php echo e((string)($eduRow['to_date'] ?? '')); ?>"></td>
                                                    <td><input type="text" class="form-control" name="school_qual[]" value="<?php echo e((string)($eduRow['qualification'] ?? '')); ?>"></td>
                                                    <td class="text-center"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('tr').remove();">×</button></td>
                                                </tr>
                                            <?php endforeach; ?>
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                        </div>

                        <div class="col-12">
                            <div class="form-section">
                                <div class="d-flex justify-content-between align-items-center mb-2">
                                    <h3 class="mb-0">Examination Results</h3>
                                    <button type="button" class="btn btn-sm btn-outline-primary" id="<?php echo e($examBodyId); ?>_add">Add Result</button>
                                </div>
                                <div class="table-responsive">
                                    <table class="table table-bordered align-middle">
                                        <thead>
                                            <tr>
                                                <th>Exam Type</th>
                                                <th>Exam Date</th>
                                                <th>Exam Number</th>
                                                <th>Subjects / Grades</th>
                                                <th>Attached</th>
                                                <th></th>
                                            </tr>
                                        </thead>
                                        <tbody id="<?php echo e($examBodyId); ?>">
                                            <?php foreach ($examResults as $examIndex => $examRow): ?>
                                                <?php $examTypeValue = strtoupper(trim((string)($examRow['exam_type'] ?? ''))); ?>
                                                <tr>
                                                    <td>
                                                        <select class="form-select exam-type-select" name="exam_type[]">
                                                            <option value="">Select</option>
                                                            <?php foreach (['WAEC','NECO','NABTEB','GCE','OTHER'] as $examTypeOption): ?>
                                                                <option value="<?php echo e($examTypeOption); ?>" <?php echo $examTypeValue === $examTypeOption ? 'selected' : ''; ?>><?php echo e($examTypeOption); ?></option>
                                                            <?php endforeach; ?>
                                                        </select>
                                                        <input type="text" class="form-control mt-2 exam-type-other" name="exam_type_other[]" value="<?php echo e((string)($examRow['exam_type_other'] ?? '')); ?>" placeholder="Specify other exam type" <?php echo $examTypeValue === 'OTHER' ? '' : 'style="display:none;"'; ?>>
                                                    </td>
                                                    <td><input type="date" class="form-control" name="exam_date[]" value="<?php echo e((string)($examRow['exam_date'] ?? '')); ?>"></td>
                                                    <td><input type="text" class="form-control" name="exam_no[]" value="<?php echo e((string)($examRow['exam_number'] ?? '')); ?>"></td>
                                                    <td><textarea class="form-control" name="exam_grades[]" rows="2"><?php echo e((string)($examRow['subjects_grades'] ?? '')); ?></textarea></td>
                                                    <td class="text-center"><input type="checkbox" class="form-check-input" name="exam_attached[<?php echo (int)$examIndex; ?>]" value="1" <?php echo !empty($examRow['credentials_attached']) ? 'checked' : ''; ?>></td>
                                                    <td class="text-center"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('tr').remove();">×</button></td>
                                                </tr>
                                            <?php endforeach; ?>
                                        </tbody>
                                    </table>
                                </div>
                                <div class="form-text">Use the Subjects / Grades field to keep the exact result text already used in your current workflow.</div>
                            </div>
                        </div>

                        <div class="col-12">
                            <div class="form-section">
                                <h3>Referee Information</h3>
                                <div class="row">
                                    <div class="col-md-6 mb-3">
                                        <label class="form-label">Referee Full Name</label>
                                        <input type="text" class="form-control" name="ref_name" value="<?php echo e((string)($referee['full_name'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-6 mb-3">
                                        <label class="form-label">Occupation</label>
                                        <input type="text" class="form-control" name="ref_occ" value="<?php echo e((string)($referee['occupation'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-6 mb-3">
                                        <label class="form-label">Post Held</label>
                                        <input type="text" class="form-control" name="ref_post" value="<?php echo e((string)($referee['post_held'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-6 mb-3">
                                        <label class="form-label">Date</label>
                                        <input type="date" class="form-control" name="ref_date" value="<?php echo e((string)($referee['date'] ?? '')); ?>">
                                    </div>
                                    <div class="col-md-12 mb-3">
                                        <label class="form-label">Address</label>
                                        <textarea class="form-control" name="ref_address" rows="3"><?php echo e((string)($referee['address'] ?? '')); ?></textarea>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="col-12">
                            <div class="form-section">
                                <div class="d-flex justify-content-between align-items-center mb-2">
                                    <h3 class="mb-0">Uploads</h3>
                                    <button type="button" class="btn btn-sm btn-outline-primary" id="<?php echo e($docsBodyId); ?>_add">Add Supporting Document</button>
                                </div>
                                <div class="row">
                                    <div class="col-md-6 mb-3">
                                        <label class="form-label">Replace Profile Image (optional)</label>
                                        <input type="file" class="form-control" name="profile_image" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx">
                                    </div>
                                    <div class="col-md-6 mb-3">
                                        <label class="form-label">Replace Passport Photograph (optional)</label>
                                        <input type="file" class="form-control" name="passport_photo" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx">
                                    </div>
                                </div>
                                <div class="table-responsive">
                                    <table class="table table-bordered align-middle">
                                        <thead>
                                            <tr>
                                                <th>Document Type</th>
                                                <th>Upload File</th>
                                                <th></th>
                                            </tr>
                                        </thead>
                                        <tbody id="<?php echo e($docsBodyId); ?>">
                                            <tr>
                                                <td><input type="text" class="form-control" name="document_type[]" value="other" placeholder="e.g. NIN Slip"></td>
                                                <td><input type="file" class="form-control" name="documents[]" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"></td>
                                                <td class="text-center"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('tr').remove();">×</button></td>
                                            </tr>
                                        </tbody>
                                    </table>
                                </div>
                                <?php if (!empty($documents)): ?>
                                    <div class="small text-muted">Existing uploads</div>
                                    <div class="list-group mb-2">
                                        <?php foreach ($documents as $doc): ?>
                                            <div class="list-group-item d-flex justify-content-between align-items-center flex-wrap gap-2">
                                                <span><strong><?php echo e((string)($doc['document_name'] ?? 'Document')); ?></strong> <span class="badge bg-secondary ms-2"><?php echo e((string)($doc['document_type'] ?? 'other')); ?></span></span>
                                                <a href="<?php echo e((string)($doc['file_path'] ?? '#')); ?>" target="_blank" class="btn btn-sm btn-outline-primary">View</a>
                                            </div>
                                        <?php endforeach; ?>
                                    </div>
                                <?php endif; ?>
                                <div class="form-text">Existing applicant and referee signatures are preserved automatically unless the student later replaces them from the regular submission workflow.</div>
                            </div>
                        </div>

                        <div class="col-12 d-flex flex-wrap gap-2">
                            <button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> Save Submitted Form Changes</button>
                            <a href="?page=<?php echo e($returnPage); ?>&action=view&id=<?php echo (int)$studentId; ?>" class="btn btn-outline-secondary">Cancel</a>
                        </div>
                    </form>
                </div>
            </div>
        </div>

        <script>
        (function(){
            const editorKey = <?php echo json_encode($editorId); ?>;
            if (window['__' + editorKey + '_bound']) return;
            window['__' + editorKey + '_bound'] = true;

            const departments = <?php echo $departmentsJson ?: '[]'; ?>;
            const programmes = <?php echo $programmesJson ?: '[]'; ?>;
            const facultySelect = document.getElementById(<?php echo json_encode($facultyIdField); ?>);
            const departmentSelect = document.getElementById(<?php echo json_encode($departmentIdField); ?>);
            const programmeSelect = document.getElementById(<?php echo json_encode($programmeIdField); ?>);
            const educationBody = document.getElementById(<?php echo json_encode($eduBodyId); ?>);
            const examBody = document.getElementById(<?php echo json_encode($examBodyId); ?>);
            const olevelBody = document.getElementById(<?php echo json_encode($olevelBodyId); ?>);
            const docsBody = document.getElementById(<?php echo json_encode($docsBodyId); ?>);

            function rebuildDepartments(selectedValue) {
                if (!departmentSelect) return;
                const facultyId = String((facultySelect && facultySelect.value) || '');
                const current = String(selectedValue || '');
                departmentSelect.innerHTML = '<option value="">Select Course of Study</option>';
                departments.forEach(function(row){
                    if (facultyId !== '' && String(row.faculty_id || '') !== facultyId) return;
                    const opt = document.createElement('option');
                    opt.value = String(row.id || '');
                    opt.textContent = String(row.name || '');
                    if (opt.value === current) opt.selected = true;
                    departmentSelect.appendChild(opt);
                });
            }

            function rebuildProgrammes(selectedValue) {
                if (!programmeSelect) return;
                const departmentId = String((departmentSelect && departmentSelect.value) || '');
                const current = String(selectedValue || '');
                programmeSelect.innerHTML = '<option value="">Select Programme</option>';
                programmes.forEach(function(row){
                    if (departmentId !== '' && String(row.department_id || '') !== departmentId) return;
                    const opt = document.createElement('option');
                    opt.value = String(row.id || '');
                    opt.textContent = String(row.name || '');
                    if (opt.value === current) opt.selected = true;
                    programmeSelect.appendChild(opt);
                });
            }

            if (facultySelect) {
                facultySelect.addEventListener('change', function(){
                    rebuildDepartments('');
                    rebuildProgrammes('');
                });
            }
            if (departmentSelect) {
                departmentSelect.addEventListener('change', function(){
                    rebuildProgrammes('');
                });
            }

            function addRow(targetBody, html) {
                if (!targetBody) return;
                const temp = document.createElement('tbody');
                temp.innerHTML = html.trim();
                if (temp.firstElementChild) targetBody.appendChild(temp.firstElementChild);
            }

            const addEduBtn = document.getElementById(<?php echo json_encode($eduBodyId . '_add'); ?>);
            if (addEduBtn) {
                addEduBtn.addEventListener('click', function(){
                    addRow(educationBody, `
                        <tr>
                            <td><input type="text" class="form-control" name="school_name[]" value=""></td>
                            <td><input type="date" class="form-control" name="school_from[]" value=""></td>
                            <td><input type="date" class="form-control" name="school_to[]" value=""></td>
                            <td><input type="text" class="form-control" name="school_qual[]" value=""></td>
                            <td class="text-center"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('tr').remove();">×</button></td>
                        </tr>
                    `);
                });
            }

            const addOlevelBtn = document.getElementById(<?php echo json_encode($olevelBodyId . '_add'); ?>);
            if (addOlevelBtn) {
                addOlevelBtn.addEventListener('click', function(){
                    addRow(olevelBody, `
                        <tr>
                            <td><input type="text" class="form-control" name="subject_name[]" value=""></td>
                            <td><input type="text" class="form-control" name="subject_grade[]" value=""></td>
                            <td class="text-center"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('tr').remove();">×</button></td>
                        </tr>
                    `);
                });
            }

            const addDocBtn = document.getElementById(<?php echo json_encode($docsBodyId . '_add'); ?>);
            if (addDocBtn) {
                addDocBtn.addEventListener('click', function(){
                    addRow(docsBody, `
                        <tr>
                            <td><input type="text" class="form-control" name="document_type[]" value="other" placeholder="e.g. NIN Slip"></td>
                            <td><input type="file" class="form-control" name="documents[]" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"></td>
                            <td class="text-center"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('tr').remove();">×</button></td>
                        </tr>
                    `);
                });
            }

            function bindExamTypeToggles(scope) {
                (scope || document).querySelectorAll('.exam-type-select').forEach(function(select){
                    if (select.dataset.bound === '1') return;
                    select.dataset.bound = '1';
                    const other = select.parentElement ? select.parentElement.querySelector('.exam-type-other') : null;
                    const sync = function(){
                        if (!other) return;
                        other.style.display = String(select.value || '').toUpperCase() === 'OTHER' ? '' : 'none';
                    };
                    select.addEventListener('change', sync);
                    sync();
                });
            }

            const addExamBtn = document.getElementById(<?php echo json_encode($examBodyId . '_add'); ?>);
            if (addExamBtn) {
                addExamBtn.addEventListener('click', function(){
                    addRow(examBody, `
                        <tr>
                            <td>
                                <select class="form-select exam-type-select" name="exam_type[]">
                                    <option value="">Select</option>
                                    <option value="WAEC">WAEC</option>
                                    <option value="NECO">NECO</option>
                                    <option value="NABTEB">NABTEB</option>
                                    <option value="GCE">GCE</option>
                                    <option value="OTHER">OTHER</option>
                                </select>
                                <input type="text" class="form-control mt-2 exam-type-other" name="exam_type_other[]" value="" placeholder="Specify other exam type" style="display:none;">
                            </td>
                            <td><input type="date" class="form-control" name="exam_date[]" value=""></td>
                            <td><input type="text" class="form-control" name="exam_no[]" value=""></td>
                            <td><textarea class="form-control" name="exam_grades[]" rows="2"></textarea></td>
                            <td class="text-center"><input type="checkbox" class="form-check-input" name="exam_attached[]" value="1" checked></td>
                            <td class="text-center"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('tr').remove();">×</button></td>
                        </tr>
                    `);
                    bindExamTypeToggles(examBody);
                });
            }

            rebuildDepartments(<?php echo json_encode((string)$selectedDepartmentId); ?>);
            rebuildProgrammes(<?php echo json_encode((string)$selectedProgrammeId); ?>);
            bindExamTypeToggles(document.getElementById(<?php echo json_encode($editorId); ?>) || document);
        })();
        </script>
        <?php
    }
}


if (!function_exists('portalRenderAdminApplicationAcademicControls')) {
    function portalRenderAdminApplicationAcademicControls(PDO $pdo, array $student, string $returnPage = 'admin_students'): void {
        if (!isAdmin()) return;
        if (!portalAdminSubmittedApplicationEditAllowed($pdo)) return;
        portalEnsureApplicationGraceSupport($pdo);
        $academicState = portalStudentApplicationAcademicState($pdo, $student);
        $resubmission = portalApplicationResubmissionState($pdo, $student);
        $studentId = (int)($student['id'] ?? 0);

        $faculties = $academicState['faculties'] ?? [];
        $selectedFacultyId = (int)($academicState['faculty_id'] ?? 0);
        $selectedDepartmentId = (int)($academicState['department_id'] ?? 0);
        $selectedProgrammeId = (int)($academicState['programme_id'] ?? 0);

        $grantAdminName = '';
        $grantAdminId = (int)($resubmission['granted_by_admin_id'] ?? 0);
        if ($grantAdminId > 0) {
            try {
                $stAdmin = $pdo->prepare("SELECT COALESCE(NULLIF(TRIM(full_name),''), NULLIF(TRIM(username),''), CONCAT('Admin #', id)) FROM admins WHERE id = ? LIMIT 1");
                $stAdmin->execute([$grantAdminId]);
                $grantAdminName = trim((string)($stAdmin->fetchColumn() ?: ''));
            } catch (Throwable $e) {
                $grantAdminName = '';
            }
        }
        ?>
        <?php renderFlashes(); ?>
        <div class="row mt-4">
            <div class="col-12 col-xl-7 mb-3">
                <div class="card border-primary-subtle h-100">
                    <div class="card-header bg-light">
                        <strong>Application Academic Mapping</strong>
                    </div>
                    <div class="card-body">
                        <p class="text-muted small mb-3">Manage the student's School, Course of Study, and Program here. The same linked academic options used on the student application form are applied below.</p>
                        <?php $mappingFormId = 'applicationAcademicMappingForm' . (int)$studentId; ?>
                        <form method="post" class="row g-3" id="<?php echo e($mappingFormId); ?>" data-academic-mapping-form="1">
                            <input type="hidden" name="action" value="admin_update_student_application_academics">
                            <input type="hidden" name="student_id" value="<?php echo (int)$studentId; ?>">
                            <input type="hidden" name="return_page" value="<?php echo e($returnPage); ?>">
                            <input type="hidden" name="return_action" value="view">
                            <input type="hidden" name="return_id" value="<?php echo (int)$studentId; ?>">
                            <input type="hidden" name="original_faculty_id" value="<?php echo (int)$selectedFacultyId; ?>">
                            <input type="hidden" name="original_department_id" value="<?php echo (int)$selectedDepartmentId; ?>">
                            <input type="hidden" name="original_programme_id" value="<?php echo (int)$selectedProgrammeId; ?>">

                            <div class="col-md-4">
                                <label class="form-label">School</label>
                                <select class="form-select" name="faculty_id" required>
                                    <option value="">Select School</option>
                                    <?php foreach ($faculties as $facultyRow): ?>
                                        <option value="<?php echo (int)($facultyRow['id'] ?? 0); ?>" <?php echo ((int)($facultyRow['id'] ?? 0) === $selectedFacultyId) ? 'selected' : ''; ?>>
                                            <?php echo e((string)($facultyRow['name'] ?? '')); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                            </div>

                            <div class="col-md-4">
                                <label class="form-label">Course of Study</label>
                                <select class="form-select" name="department_id" required>
                                    <option value="">Select Course of Study</option>
                                    <?php if ($selectedDepartmentId > 0): ?>
                                        <option value="<?php echo $selectedDepartmentId; ?>" selected><?php echo e((string)($academicState['course_of_study'] ?? '')); ?></option>
                                    <?php endif; ?>
                                </select>
                            </div>

                            <div class="col-md-4">
                                <label class="form-label">Program</label>
                                <select class="form-select" name="programme_id" required>
                                    <option value="">Select Program</option>
                                    <?php if ($selectedProgrammeId > 0): ?>
                                        <option value="<?php echo $selectedProgrammeId; ?>" selected><?php echo e((string)($academicState['programme'] ?? '')); ?></option>
                                    <?php endif; ?>
                                </select>
                            </div>

                            <div class="col-12">
                                <div class="d-flex flex-wrap gap-2 justify-content-between align-items-center">
                                    <div class="small text-muted">
                                        Current values:
                                        <strong><?php echo e((string)($academicState['school'] ?? 'Not Set')); ?></strong>,
                                        <strong><?php echo e((string)($academicState['course_of_study'] ?? 'Not Set')); ?></strong>,
                                        <strong><?php echo e((string)($academicState['programme'] ?? 'Not Set')); ?></strong>,
                                        <strong><?php echo e((string)($academicState['mode_of_study'] ?? 'Full-Time')); ?></strong>
                                    </div>
                                    <button type="submit" class="btn btn-primary">Save Academic Fields</button>
                                </div>
                            </div>

                            <div class="col-12" data-superadmin-password-wrap="1" style="display:none;">
                                <div class="alert alert-warning mb-0">
                                    <div class="fw-semibold mb-2"><i class="bi bi-shield-lock"></i> Superadmin password confirmation required</div>
                                    <div class="small mb-3">Changing this student's School, Course of Study, or Program requires confirmation from the currently logged-in Superadmin before the academic mapping can be saved.</div>
                                    <label class="form-label">Superadmin Password</label>
                                    <input type="password" class="form-control" name="superadmin_password_confirm" autocomplete="current-password" placeholder="Enter current Superadmin password to continue">
                                    <div class="form-text">No password prompt is required when there is no actual change in this section.</div>
                                </div>
                            </div>
                        </form>
                        <script>
                        (function(){
                            const form = document.getElementById(<?php echo json_encode($mappingFormId); ?>);
                            if (!form || form.dataset.superadminAcademicBound === '1') return;
                            form.dataset.superadminAcademicBound = '1';
                            const faculty = form.querySelector('select[name="faculty_id"]');
                            const department = form.querySelector('select[name="department_id"]');
                            const programme = form.querySelector('select[name="programme_id"]');
                            const passwordWrap = form.querySelector('[data-superadmin-password-wrap="1"]');
                            const passwordInput = form.querySelector('input[name="superadmin_password_confirm"]');
                            const originals = {
                                faculty_id: String((form.querySelector('input[name="original_faculty_id"]') || {}).value || ''),
                                department_id: String((form.querySelector('input[name="original_department_id"]') || {}).value || ''),
                                programme_id: String((form.querySelector('input[name="original_programme_id"]') || {}).value || '')
                            };
                            function valueOf(el){ return String((el && el.value) || ''); }
                            function changed(){
                                return valueOf(faculty) !== originals.faculty_id
                                    || valueOf(department) !== originals.department_id
                                    || valueOf(programme) !== originals.programme_id;
                            }
                            function syncPromptVisibility(forceShow){
                                if (!passwordWrap) return;
                                const shouldShow = !!forceShow || changed();
                                passwordWrap.style.display = shouldShow ? '' : 'none';
                                if (!shouldShow && passwordInput) passwordInput.value = '';
                            }
                            [faculty, department, programme].forEach(function(el){
                                if (!el) return;
                                el.addEventListener('change', function(){ syncPromptVisibility(false); });
                            });
                            form.addEventListener('submit', function(ev){
                                if (!changed()) {
                                    syncPromptVisibility(false);
                                    return;
                                }
                                syncPromptVisibility(true);
                                if (!passwordInput || !String(passwordInput.value || '').trim()) {
                                    ev.preventDefault();
                                    if (passwordInput) {
                                        passwordInput.focus();
                                        try { passwordInput.scrollIntoView({behavior:'smooth', block:'center'}); } catch (e) {}
                                    }
                                }
                            });
                        })();
                        </script>
                    </div>
                </div>
            </div>

            <div class="col-12 col-xl-5 mb-3">
                <div class="card border-warning-subtle h-100">
                    <div class="card-header bg-light d-flex justify-content-between align-items-center">
                        <strong>Grace Resubmission Control</strong>
                        <span class="badge bg-<?php echo $resubmission['can_edit'] ? 'success' : 'secondary'; ?>">
                            <?php echo $resubmission['can_edit'] ? 'Editable' : 'Locked'; ?>
                        </span>
                    </div>
                    <div class="card-body">
                        <div class="small mb-3">
                            <div><strong>Original submission state:</strong> <?php echo $resubmission['submitted'] ? 'Final submission already done' : 'Not finally submitted'; ?></div>
                            <div><strong>Used edits:</strong> <?php echo (int)$resubmission['used_count']; ?> / <?php echo (int)$resubmission['base_limit']; ?> base edits</div>
                            <div><strong>Grace used:</strong> <?php echo (int)$resubmission['grace_used']; ?></div>
                            <div><strong>Grace remaining:</strong> <?php echo (int)$resubmission['grace_remaining']; ?></div>
                            <?php if (!empty($resubmission['granted_at'])): ?>
                                <div><strong>Last grace update:</strong> <?php echo e(date('d M Y h:i A', strtotime((string)$resubmission['granted_at']))); ?></div>
                            <?php endif; ?>
                            <?php if ($grantAdminName !== ''): ?>
                                <div><strong>Granted by:</strong> <?php echo e($grantAdminName); ?></div>
                            <?php endif; ?>
                            <?php if (!empty($resubmission['last_grace_used_at'])): ?>
                                <div><strong>Last grace usage:</strong> <?php echo e(date('d M Y h:i A', strtotime((string)$resubmission['last_grace_used_at']))); ?></div>
                            <?php endif; ?>
                        </div>

                        <form method="post" class="row g-3">
                            <input type="hidden" name="action" value="admin_update_student_grace_resubmission">
                            <input type="hidden" name="student_id" value="<?php echo (int)$studentId; ?>">
                            <input type="hidden" name="return_page" value="<?php echo e($returnPage); ?>">
                            <input type="hidden" name="return_action" value="view">
                            <input type="hidden" name="return_id" value="<?php echo (int)$studentId; ?>">

                            <div class="col-12">
                                <label class="form-label">Additional Grace Attempts</label>
                                <input type="number" min="0" step="1" class="form-control" name="extra_grace_count" value="<?php echo (int)($resubmission['extra_grace_count'] ?? 0); ?>" required>
                                <div class="form-text">Set the total additional edit/resubmission attempts for this student. Use <strong>0</strong> to revoke extra grace.</div>
                            </div>

                            <div class="col-12">
                                <label class="form-label">Admin Note (optional)</label>
                                <input type="text" class="form-control" name="grace_note" maxlength="255" placeholder="Reason for granting or adjusting grace">
                            </div>

                            <div class="col-12 d-flex flex-wrap gap-2">
                                <button type="submit" class="btn btn-warning">Save Grace Setting</button>
                                <button type="submit" class="btn btn-outline-danger" name="extra_grace_count" value="0" onclick="return confirm('Revoke all extra grace attempts for this student?');">Revoke Grace</button>
                            </div>
                        </form>

                        <?php if (!empty($resubmission['message'])): ?>
                            <div class="alert alert-info mt-3 mb-0"><?php echo e((string)$resubmission['message']); ?></div>
                        <?php endif; ?>

                        <?php
                        $lateSession = portalNormalizeSessionNameSafe((string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)));
                        $lateSemester = portalNormalizeSemesterName((string)($student['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
                        $lateBalanceInfo = portalActiveLateFeeBalanceForPeriod($pdo, (int)$studentId, $lateSession, $lateSemester);
                        ?>
                        <hr class="my-3">
                        <h6 class="mb-2">School Fee Late Fee</h6>
                        <div class="small mb-3">Current late-fee balance for <?php echo e($lateSession); ?> / <?php echo e($lateSemester); ?>: <strong>₦<?php echo number_format((float)($lateBalanceInfo['amount'] ?? 0), 2); ?></strong></div>
                        <div class="d-flex flex-wrap gap-2">
                            <form method="post">
                                <input type="hidden" name="action" value="recalculate_student_late_fee">
                                <input type="hidden" name="student_id" value="<?php echo (int)$studentId; ?>">
                                <input type="hidden" name="session_name" value="<?php echo e($lateSession); ?>">
                                <input type="hidden" name="semester" value="<?php echo e($lateSemester); ?>">
                                <button type="submit" class="btn btn-outline-danger btn-sm">Recalculate Late Fee</button>
                            </form>
                            <form method="post" onsubmit="return confirm('Waive active late fee for this student?');">
                                <input type="hidden" name="action" value="waive_student_late_fee">
                                <input type="hidden" name="student_id" value="<?php echo (int)$studentId; ?>">
                                <input type="hidden" name="session_name" value="<?php echo e($lateSession); ?>">
                                <input type="hidden" name="semester" value="<?php echo e($lateSemester); ?>">
                                <button type="submit" class="btn btn-outline-secondary btn-sm">Waive / Remove Late Fee</button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <?php
    }
}


if (!function_exists('portalNormalizeRegistrableCoursePayload')) {
    function portalNormalizeRegistrableCoursePayload(PDO $pdo, array $payload): array {
        $facultyId = (int)($payload['faculty_id'] ?? 0);
        $departmentId = (int)($payload['department_id'] ?? 0);
        $programmeId = (int)($payload['programme_id'] ?? 0);

        $departmentRow = null;
        $programmeRow = null;
        $modeOfStudy = portalNormalizeRegistrableCourseModeOfStudy((string)($payload['mode_of_study'] ?? ''), '');

        if ($programmeId > 0) {
            $st = $pdo->prepare("SELECT p.id, p.department_id, d.faculty_id FROM programmes p INNER JOIN departments d ON d.id = p.department_id WHERE p.id = ? LIMIT 1");
            $st->execute([$programmeId]);
            $programmeRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$programmeRow) {
                throw new RuntimeException('Programme-specific course mapping is invalid.');
            }
            $departmentId = (int)($programmeRow['department_id'] ?? 0);
            $facultyId = (int)($programmeRow['faculty_id'] ?? 0);
            if ($modeOfStudy === '') {
                $modeOfStudy = portalNormalizeRegistrableCourseModeOfStudy(portalProgrammeModeOfStudyValueById($pdo, $programmeId, 'Full-Time'), 'Full-Time');
            }
        }

        if ($departmentId > 0) {
            $st = $pdo->prepare("SELECT id, faculty_id FROM departments WHERE id = ? LIMIT 1");
            $st->execute([$departmentId]);
            $departmentRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$departmentRow) {
                throw new RuntimeException('Department-specific course mapping is invalid.');
            }
            if ($facultyId > 0 && (int)($departmentRow['faculty_id'] ?? 0) !== $facultyId) {
                throw new RuntimeException('A department-specific course cannot appear in another department.');
            }
            $facultyId = (int)($departmentRow['faculty_id'] ?? 0);
        }

        if ($facultyId > 0) {
            $st = $pdo->prepare("SELECT id FROM faculties WHERE id = ? LIMIT 1");
            $st->execute([$facultyId]);
            if (!(int)($st->fetchColumn() ?: 0)) {
                throw new RuntimeException('Please select a valid faculty before assigning this course.');
            }
        }

        if ($modeOfStudy === '') {
            $modeOfStudy = 'Full-Time';
        }

        return [
            'id' => (int)($payload['id'] ?? 0),
            'course_code' => strtoupper(trim((string)($payload['course_code'] ?? ''))),
            'course_title' => trim((string)($payload['course_title'] ?? '')),
            'credit_unit' => max(0, (int)($payload['credit_unit'] ?? 0)),
            'faculty_id' => $facultyId,
            'department_id' => $departmentId,
            'programme_id' => $programmeId,
            'mode_of_study' => $modeOfStudy,
            'level' => trim((string)($payload['level'] ?? '')),
            'semester' => in_array((string)($payload['semester'] ?? 'First'), ['First','Second'], true) ? (string)$payload['semester'] : 'First',
            'session_name' => trim((string)($payload['session_name'] ?? '')),
            'year' => trim((string)($payload['year'] ?? '')),
            'course_type' => in_array((string)($payload['course_type'] ?? 'C'), ['C','E'], true) ? (string)$payload['course_type'] : 'C',
            'registrable' => in_array((string)($payload['registrable'] ?? 'Yes'), ['Yes','No'], true) ? (string)$payload['registrable'] : 'Yes',
            'lecturer_staff_id' => max(0, (int)($payload['lecturer_staff_id'] ?? 0)),
            'is_general_course' => !empty($payload['is_general_course']) ? 1 : 0,
        ];
    }
}

if (!function_exists('portalRegistrableCourseScopeDepth')) {
    function portalRegistrableCourseScopeDepth(array $payload): int {
        if ((int)($payload['programme_id'] ?? 0) > 0) return 3;
        if ((int)($payload['department_id'] ?? 0) > 0) return 2;
        if ((int)($payload['faculty_id'] ?? 0) > 0) return 1;
        return 0;
    }
}

if (!function_exists('portalRegistrableCourseMappingsOverlap')) {
    function portalRegistrableCourseMappingsOverlap(array $a, array $b): bool {
        if (strtoupper(trim((string)($a['course_code'] ?? ''))) !== strtoupper(trim((string)($b['course_code'] ?? '')))) return false;
        if (trim((string)($a['session_name'] ?? '')) !== trim((string)($b['session_name'] ?? ''))) return false;
        if (trim((string)($a['semester'] ?? '')) !== trim((string)($b['semester'] ?? ''))) return false;
        if (trim((string)($a['level'] ?? '')) !== trim((string)($b['level'] ?? ''))) return false;
        if (trim((string)($a['year'] ?? '')) !== trim((string)($b['year'] ?? ''))) return false;

        $modeA = portalNormalizeRegistrableCourseModeOfStudy((string)($a['mode_of_study'] ?? ''), 'Full-Time');
        $modeB = portalNormalizeRegistrableCourseModeOfStudy((string)($b['mode_of_study'] ?? ''), 'Full-Time');
        if ($modeA !== '' && $modeB !== '' && strcasecmp($modeA, $modeB) !== 0) return false;

        $aFaculty = (int)($a['faculty_id'] ?? 0);
        $bFaculty = (int)($b['faculty_id'] ?? 0);
        if ($aFaculty <= 0 || $bFaculty <= 0 || $aFaculty !== $bFaculty) return false;

        $aDept = (int)($a['department_id'] ?? 0);
        $bDept = (int)($b['department_id'] ?? 0);
        $aProg = (int)($a['programme_id'] ?? 0);
        $bProg = (int)($b['programme_id'] ?? 0);

        if ($aProg > 0 && $bProg > 0) return $aProg === $bProg;
        if ($aProg > 0 && $bDept > 0) return $aDept > 0 && $aDept === $bDept;
        if ($bProg > 0 && $aDept > 0) return $bDept > 0 && $aDept === $bDept;
        if ($aProg > 0 || $bProg > 0) return true;

        if ($aDept > 0 && $bDept > 0) return $aDept === $bDept;
        if ($aDept > 0 || $bDept > 0) return true;

        return true;
    }
}

if (!function_exists('portalValidateRegistrableCoursePayload')) {
    function portalValidateRegistrableCoursePayload(PDO $pdo, array $payload, int $excludeId = 0): array {
        portalEnsureRegistrableCourseScopeSchema($pdo);
        $norm = portalNormalizeRegistrableCoursePayload($pdo, $payload);

        if ($norm['course_code'] === '' || $norm['course_title'] === '') {
            throw new RuntimeException('Course code and title are required.');
        }
        if ($norm['session_name'] === '' || $norm['semester'] === '' || $norm['level'] === '' || $norm['year'] === '') {
            throw new RuntimeException('Session, semester, and level are required.');
        }
        if ($norm['faculty_id'] <= 0 && $norm['department_id'] <= 0 && $norm['programme_id'] <= 0) {
            throw new RuntimeException('Please select a valid faculty before assigning this course.');
        }
        if ($norm['is_general_course'] === 1 && $norm['faculty_id'] <= 0 && $norm['department_id'] <= 0 && $norm['programme_id'] <= 0) {
            throw new RuntimeException('A general course must still be scoped to a valid faculty, department, or programme.');
        }

        $st = $pdo->prepare("SELECT id, is_general_course FROM registrable_courses
            WHERE UPPER(TRIM(course_code)) = ?
              AND COALESCE(faculty_id, 0) = ?
              AND COALESCE(department_id, 0) = ?
              AND COALESCE(programme_id, 0) = ?
              AND COALESCE(mode_of_study, '') = ?
              AND COALESCE(session_name, '') = ?
              AND COALESCE(semester, '') = ?
              AND COALESCE(level, '') = ?
              AND COALESCE(year, '') = ?
              AND id <> ?
            ORDER BY id ASC LIMIT 1");
        $st->execute([
            $norm['course_code'],
            $norm['faculty_id'],
            $norm['department_id'],
            $norm['programme_id'],
            $norm['mode_of_study'],
            $norm['session_name'],
            $norm['semester'],
            $norm['level'],
            $norm['year'],
            $excludeId,
        ]);
        $dup = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        if ($dup) {
            if ((int)($dup['is_general_course'] ?? 0) !== (int)$norm['is_general_course']) {
                throw new RuntimeException('Admin tried to save a course with conflicting general/non-general settings.');
            }
            throw new RuntimeException('This course is already assigned to the selected faculty/department/programme/session/semester/level.');
        }

        $st = $pdo->prepare("SELECT id, course_code, faculty_id, department_id, programme_id, mode_of_study, level, semester, session_name, year, is_general_course
            FROM registrable_courses
            WHERE UPPER(TRIM(course_code)) = ?
              AND COALESCE(session_name, '') = ?
              AND COALESCE(semester, '') = ?
              AND COALESCE(level, '') = ?
              AND COALESCE(year, '') = ?
              AND id <> ?
            ORDER BY id ASC");
        $st->execute([
            $norm['course_code'],
            $norm['session_name'],
            $norm['semester'],
            $norm['level'],
            $norm['year'],
            $excludeId,
        ]);
        $existingRows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        foreach ($existingRows as $existing) {
            if (!portalRegistrableCourseMappingsOverlap($norm, $existing)) {
                continue;
            }
            if ((int)($existing['is_general_course'] ?? 0) !== (int)$norm['is_general_course']) {
                throw new RuntimeException('Admin tried to save a course with conflicting general/non-general settings.');
            }
            throw new RuntimeException('This course already has a synchronized mapping that overlaps the selected faculty/department/programme/session/semester/level/year scope.');
        }

        return $norm;
    }
}


if (!function_exists('portalNormalizeRegistrableCourseManagerFilter')) {
    function portalNormalizeRegistrableCourseManagerFilter(PDO $pdo, array $input): array {
        portalEnsureRegistrableCourseScopeSchema($pdo);

        $facultyId = max(0, (int)($input['faculty_id'] ?? 0));
        $departmentId = max(0, (int)($input['department_id'] ?? 0));
        $programmeId = max(0, (int)($input['programme_id'] ?? 0));
        $modeOfStudy = portalNormalizeRegistrableCourseModeOfStudy((string)($input['mode_of_study'] ?? ''), '');
        $level = trim((string)($input['level'] ?? ''));
        $semester = in_array((string)($input['semester'] ?? ''), ['First','Second'], true) ? (string)$input['semester'] : '';
        $sessionName = trim((string)($input['session_name'] ?? ''));
        $year = trim((string)($input['year'] ?? ''));
        $notes = [];

        $activeSessionName = '';
        try { $activeSessionName = trim((string)portalCurrentAcademicSessionName($pdo)); } catch (Throwable $e) { $activeSessionName = ''; }
        if ($activeSessionName === '') {
            $activeSessionName = trim((string)setting('academic_session', ''));
        }

        if ($programmeId > 0) {
            try {
                $st = $pdo->prepare("SELECT p.id, p.department_id, d.faculty_id FROM programmes p INNER JOIN departments d ON d.id = p.department_id WHERE p.id = ? LIMIT 1");
                $st->execute([$programmeId]);
                $programmeRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                if ($programmeRow) {
                    $resolvedDepartmentId = (int)($programmeRow['department_id'] ?? 0);
                    $resolvedFacultyId = (int)($programmeRow['faculty_id'] ?? 0);
                    if ($departmentId > 0 && $resolvedDepartmentId > 0 && $departmentId !== $resolvedDepartmentId) {
                        $notes[] = 'Programme filter was synchronized to its correct department automatically.';
                    }
                    if ($facultyId > 0 && $resolvedFacultyId > 0 && $facultyId !== $resolvedFacultyId) {
                        $notes[] = 'Programme filter was synchronized to its correct faculty automatically.';
                    }
                    $departmentId = $resolvedDepartmentId;
                    $facultyId = $resolvedFacultyId;
                } else {
                    $notes[] = 'Invalid programme filter was ignored.';
                    $programmeId = 0;
                }
            } catch (Throwable $e) {}
        }

        if ($programmeId <= 0 && $departmentId > 0) {
            try {
                $st = $pdo->prepare("SELECT id, faculty_id FROM departments WHERE id = ? LIMIT 1");
                $st->execute([$departmentId]);
                $departmentRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                if ($departmentRow) {
                    $resolvedFacultyId = (int)($departmentRow['faculty_id'] ?? 0);
                    if ($facultyId > 0 && $resolvedFacultyId > 0 && $facultyId !== $resolvedFacultyId) {
                        $notes[] = 'Department filter was synchronized to its correct faculty automatically.';
                    }
                    $facultyId = $resolvedFacultyId;
                } else {
                    $notes[] = 'Invalid department filter was ignored.';
                    $departmentId = 0;
                }
            } catch (Throwable $e) {}
        }

        if ($facultyId > 0) {
            try {
                $st = $pdo->prepare("SELECT id FROM faculties WHERE id = ? LIMIT 1");
                $st->execute([$facultyId]);
                if (!(int)($st->fetchColumn() ?: 0)) {
                    $notes[] = 'Invalid faculty filter was ignored.';
                    $facultyId = 0;
                }
            } catch (Throwable $e) {}
        }

        return [
            'faculty_id' => $facultyId,
            'department_id' => $departmentId,
            'programme_id' => $programmeId,
            'mode_of_study' => $modeOfStudy,
            'level' => $level,
            'semester' => $semester,
            'session_name' => $sessionName,
            'year' => $year,
            'active_session_name' => $activeSessionName,
            'notes' => array_values(array_unique(array_filter($notes))),
        ];
    }
}

if (!function_exists('portalRegistrableCourseManagerRedirectUrl')) {
    function portalRegistrableCourseManagerRedirectUrl(array $filter = []): string {
        $query = ['page' => 'admin_courses_manager'];

        foreach (['faculty_id', 'department_id', 'programme_id'] as $key) {
            $value = max(0, (int)($filter[$key] ?? 0));
            if ($value > 0) {
                $query[$key] = $value;
            }
        }
        foreach (['mode_of_study', 'level', 'semester', 'session_name', 'year'] as $key) {
            $value = trim((string)($filter[$key] ?? ''));
            if ($value !== '') {
                $query[$key] = $value;
            }
        }

        return '?' . http_build_query($query);
    }
}

if (!function_exists('portalRenderRegistrableCourseManagerReturnFilterInputs')) {
    function portalRenderRegistrableCourseManagerReturnFilterInputs(array $filter): void {
        $hiddenMap = [
            '_filter_faculty_id' => max(0, (int)($filter['faculty_id'] ?? 0)),
            '_filter_department_id' => max(0, (int)($filter['department_id'] ?? 0)),
            '_filter_programme_id' => max(0, (int)($filter['programme_id'] ?? 0)),
            '_filter_mode_of_study' => trim((string)($filter['mode_of_study'] ?? '')),
            '_filter_level' => trim((string)($filter['level'] ?? '')),
            '_filter_semester' => trim((string)($filter['semester'] ?? '')),
            '_filter_session_name' => trim((string)($filter['session_name'] ?? '')),
            '_filter_year' => trim((string)($filter['year'] ?? '')),
        ];
        foreach ($hiddenMap as $name => $value) {
            echo '<input type="hidden" name="' . e($name) . '" value="' . e((string)$value) . '">';
        }
    }
}

if (!function_exists('portalRegistrableCourseScopeWhere')) {
    function portalRegistrableCourseScopeWhere(array $context, string $alias = 'registrable_courses'): array {
        $a = trim($alias) !== '' ? trim($alias) . '.' : '';
        $facultyId = (int)($context['faculty_id'] ?? 0);
        $departmentId = (int)($context['department_id'] ?? 0);
        $programmeId = (int)($context['programme_id'] ?? 0);
        $semester = trim((string)($context['semester'] ?? ''));
        $sessionName = trim((string)($context['session_name'] ?? ''));
        $modeOfStudy = portalNormalizeRegistrableCourseModeOfStudy((string)($context['mode_of_study'] ?? ''), '');
        $level = trim((string)($context['level'] ?? ''));
        $year = trim((string)($context['year'] ?? ''));

        $where = [];
        $params = [];

        if (($context['require_registrable'] ?? true) != false) {
            $where[] = "COALESCE({$a}registrable, 'Yes') = 'Yes'";
        }
        if ($semester !== '') { $where[] = "COALESCE({$a}semester, '') = ?"; $params[] = $semester; }
        if ($sessionName !== '') { $where[] = "COALESCE({$a}session_name, '') = ?"; $params[] = $sessionName; }
        if ($modeOfStudy !== '') { $where[] = "(COALESCE(NULLIF(TRIM({$a}mode_of_study), ''), '') = '' OR LOWER(TRIM(COALESCE({$a}mode_of_study, ''))) = LOWER(TRIM(?)))"; $params[] = $modeOfStudy; }
        if ($level !== '') { $where[] = "COALESCE({$a}level, '') = ?"; $params[] = $level; }
        if ($year !== '') { $where[] = "COALESCE({$a}year, '') = ?"; $params[] = $year; }

        if ($programmeId > 0) {
            $where[] = "(
                COALESCE({$a}programme_id, 0) = ?
                OR (COALESCE({$a}programme_id, 0) = 0 AND COALESCE({$a}department_id, 0) = ?)
                OR (COALESCE({$a}programme_id, 0) = 0 AND COALESCE({$a}department_id, 0) = 0 AND COALESCE({$a}faculty_id, 0) = ?)
            )";
            $params[] = $programmeId;
            $params[] = $departmentId;
            $params[] = $facultyId;
        } elseif ($departmentId > 0) {
            $where[] = "(
                COALESCE({$a}department_id, 0) = ?
                OR (COALESCE({$a}department_id, 0) = 0 AND COALESCE({$a}faculty_id, 0) = ?)
            )";
            $params[] = $departmentId;
            $params[] = $facultyId;
        } elseif ($facultyId > 0) {
            $where[] = "COALESCE({$a}faculty_id, 0) = ?";
            $params[] = $facultyId;
        } else {
            $where[] = "1 = 0";
        }

        return ['sql' => implode(' AND ', $where), 'params' => $params];
    }
}

if (!function_exists('portalFetchScopedRegistrableCourses')) {
    function portalFetchScopedRegistrableCourses(PDO $pdo, array $context): array {
        portalEnsureRegistrableCourseScopeSchema($pdo);
        $scope = portalRegistrableCourseScopeWhere($context, 'rc');
        $warnings = [];
        $courses = [];
        try {
            $sql = "SELECT rc.*, f.name AS faculty_name, d.name AS department_name, p.name AS programme_name
                FROM registrable_courses rc
                LEFT JOIN faculties f ON f.id = rc.faculty_id
                LEFT JOIN departments d ON d.id = rc.department_id
                LEFT JOIN programmes p ON p.id = rc.programme_id
                WHERE {$scope['sql']}
                ORDER BY COALESCE(rc.programme_id,0) DESC, COALESCE(rc.department_id,0) DESC, COALESCE(rc.faculty_id,0) DESC, rc.is_general_course DESC, COALESCE(rc.updated_at, rc.created_at) DESC, rc.id DESC";
            $st = $pdo->prepare($sql);
            $st->execute($scope['params']);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];

            $deduped = [];
            foreach ($rows as $row) {
                $key = strtoupper(trim((string)($row['course_code'] ?? '')));
                if ($key === '') {
                    continue;
                }
                if (!isset($deduped[$key])) {
                    $deduped[$key] = $row;
                    continue;
                }
                $warnings[] = 'Duplicate mappings exist for course ' . trim((string)($row['course_code'] ?? '')) . ' in the selected scope. The newest synchronized mapping is being used.';
            }
            $courses = array_values($deduped);
            usort($courses, static function(array $a, array $b): int {
                $typeA = (string)($a['course_type'] ?? 'C');
                $typeB = (string)($b['course_type'] ?? 'C');
                if ($typeA !== $typeB) return strcmp($typeA, $typeB);
                return strcmp((string)($a['course_code'] ?? ''), (string)($b['course_code'] ?? ''));
            });
        } catch (Throwable $e) {
            $warnings[] = 'Synchronized course loading failed. Please try again or contact the administrator.';
        }
        if (!$courses) {
            $warnings[] = 'No synchronized courses are available for the selected student scope.';
        }
        return ['courses' => $courses, 'warnings' => array_values(array_unique(array_filter($warnings)))];
    }
}

if (!function_exists('portalEnsureCourseRegistrationCarryOverItemSupport')) {
    function portalEnsureCourseRegistrationCarryOverItemSupport(PDO $pdo): void {
        try { if (!portalColumnExistsSafe($pdo, 'course_registration_items', 'is_carry_over')) { $pdo->exec("ALTER TABLE course_registration_items ADD COLUMN is_carry_over TINYINT(1) NOT NULL DEFAULT 0 AFTER course_type"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'course_registration_items', 'carry_over_source_session')) { $pdo->exec("ALTER TABLE course_registration_items ADD COLUMN carry_over_source_session VARCHAR(30) NULL AFTER is_carry_over"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'course_registration_items', 'carry_over_source_semester')) { $pdo->exec("ALTER TABLE course_registration_items ADD COLUMN carry_over_source_semester VARCHAR(20) NULL AFTER carry_over_source_session"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'course_registration_items') && !portalIndexExistsSafe($pdo, 'course_registration_items', 'idx_cri_registration_type')) { $pdo->exec("CREATE INDEX idx_cri_registration_type ON course_registration_items(registration_id, is_carry_over)"); } } catch (Throwable $e) {}
    }
}

if (!function_exists('portalFetchSynchronizedCourseRegistrationItems')) {
    function portalFetchSynchronizedCourseRegistrationItems(PDO $pdo, int $registrationId): array {
        if ($registrationId <= 0) return [];
        portalEnsureCourseRegistrationCarryOverItemSupport($pdo);
        try {
            $st = $pdo->prepare("SELECT
                    cri.*,
                    COALESCE(NULLIF(rc.course_code, ''), cri.course_code) AS synced_course_code,
                    COALESCE(NULLIF(rc.course_title, ''), cri.course_title) AS synced_course_title,
                    CASE
                        WHEN rc.id IS NOT NULL AND COALESCE(rc.credit_unit, 0) > 0 THEN rc.credit_unit
                        ELSE cri.credit_unit
                    END AS synced_credit_unit,
                    COALESCE(NULLIF(rc.course_type, ''), cri.course_type) AS synced_course_type,
                    COALESCE(NULLIF(rc.registrable, ''), 'Yes') AS synced_registrable,
                    rc.id AS synced_registrable_course_id
                FROM course_registration_items cri
                LEFT JOIN registrable_courses rc ON rc.id = cri.course_id
                WHERE cri.registration_id = ?
                ORDER BY COALESCE(cri.is_carry_over, 0) ASC,
                         COALESCE(NULLIF(rc.course_code, ''), cri.course_code) ASC");
            $st->execute([$registrationId]);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($rows as &$row) {
                $row['course_code'] = (string)($row['synced_course_code'] ?? $row['course_code'] ?? '');
                $row['course_title'] = (string)($row['synced_course_title'] ?? $row['course_title'] ?? '');
                $row['credit_unit'] = (int)($row['synced_credit_unit'] ?? $row['credit_unit'] ?? 0);
                $row['course_type'] = (string)($row['synced_course_type'] ?? $row['course_type'] ?? 'C');
                $row['is_carry_over'] = !empty($row['is_carry_over']) ? 1 : 0;
                $row['carry_over_source_session'] = trim((string)($row['carry_over_source_session'] ?? ''));
                $row['carry_over_source_semester'] = trim((string)($row['carry_over_source_semester'] ?? ''));
                $row['sync_status'] = ((int)($row['synced_registrable_course_id'] ?? 0) > 0)
                    ? (((string)($row['synced_registrable'] ?? 'Yes') === 'Yes') ? 'active' : 'disabled')
                    : 'orphaned';
            }
            unset($row);
            return $rows;
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalPartitionCourseRegistrationItems')) {
    function portalPartitionCourseRegistrationItems(array $items): array {
        $regular = [];
        $carry = [];
        foreach ($items as $item) {
            if (!empty($item['is_carry_over'])) {
                $carry[] = $item;
            } else {
                $regular[] = $item;
            }
        }
        return ['regular' => $regular, 'carry' => $carry];
    }
}

if (!function_exists('portalFetchEligibleCarryOverCourses')) {
    function portalFetchEligibleCarryOverCourses(PDO $pdo, array $student, string $currentSessionName = '', string $currentSemester = ''): array {
        $studentId = (int)($student['id'] ?? 0);
        if ($studentId <= 0 || !portalTableExistsSafe($pdo, 'results')) return [];

        $statusWhere = "r.status IN ('approved','published')";
        try {
            $hasApproved = (int)($pdo->query("SELECT COUNT(*) FROM results WHERE student_id = " . $studentId . " AND status IN ('approved','published')")->fetchColumn() ?: 0);
            if ($hasApproved <= 0) {
                $statusWhere = "r.status IN ('submitted','approved','published')";
            }
        } catch (Throwable $e) {}

        try {
            $sql = "SELECT
                        r.id AS result_id,
                        r.course_id AS result_course_id,
                        r.session AS source_session,
                        r.semester AS source_semester,
                        r.ca_score,
                        r.exam_score,
                        r.total_score,
                        rc.id AS registrable_course_id,
                        COALESCE(NULLIF(rc.course_code, ''), '') AS course_code,
                        COALESCE(NULLIF(rc.course_title, ''), '') AS course_title,
                        COALESCE(rc.credit_unit, 0) AS credit_unit,
                        COALESCE(NULLIF(rc.course_type, ''), 'C') AS course_type
                    FROM results r
                    LEFT JOIN registrable_courses rc ON rc.id = r.course_id
                    WHERE r.student_id = ?
                      AND {$statusWhere}
                      AND COALESCE(NULLIF(TRIM(COALESCE(rc.course_code, '')), ''), '') <> ''
                    ORDER BY COALESCE(r.session, '') DESC,
                             CASE COALESCE(r.semester, '') WHEN 'First' THEN 1 WHEN 'Second' THEN 2 ELSE 3 END DESC,
                             r.id DESC";
            $st = $pdo->prepare($sql);
            $st->execute([$studentId]);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }

        $eligible = [];
        foreach ($rows as $row) {
            $courseCode = strtoupper(trim((string)($row['course_code'] ?? '')));
            if ($courseCode === '') continue;
            if (array_key_exists($courseCode, $eligible)) continue;
            $ca = (float)($row['ca_score'] ?? 0);
            $exam = (float)($row['exam_score'] ?? 0);
            $total = (float)($row['total_score'] ?? ($ca + $exam));
            $assessment = portalResultAssessment($pdo, $ca, $exam, $total);
            if (strtoupper(trim((string)($assessment['status'] ?? 'PASS'))) === 'PASS') {
                $eligible[$courseCode] = null;
                continue;
            }
            $eligible[$courseCode] = [
                'result_id' => (int)($row['result_id'] ?? 0),
                'course_id' => (int)($row['registrable_course_id'] ?? $row['result_course_id'] ?? 0),
                'course_code' => trim((string)($row['course_code'] ?? '')),
                'course_title' => trim((string)($row['course_title'] ?? '')),
                'credit_unit' => (int)($row['credit_unit'] ?? 0),
                'course_type' => in_array((string)($row['course_type'] ?? 'C'), ['C','E'], true) ? (string)$row['course_type'] : 'C',
                'source_session' => trim((string)($row['source_session'] ?? '')),
                'source_semester' => trim((string)($row['source_semester'] ?? '')),
                'assessment_status' => trim((string)($assessment['status'] ?? 'CARRY OVER')),
                'assessment_remark' => trim((string)($assessment['remark'] ?? 'Carry Over')),
            ];
        }

        $out = [];
        foreach ($eligible as $row) {
            if (!is_array($row)) continue;
            if ((int)($row['course_id'] ?? 0) <= 0) continue;
            if (trim((string)($row['course_code'] ?? '')) === '' || trim((string)($row['course_title'] ?? '')) === '') continue;
            $out[] = $row;
        }

        usort($out, static function(array $a, array $b): int {
            $left = strtoupper(trim((string)($a['course_code'] ?? '')));
            $right = strtoupper(trim((string)($b['course_code'] ?? '')));
            return $left <=> $right;
        });

        return $out;
    }
}

if (!function_exists('portalStudentRegistrableCourseYear')) {
    function portalStudentRegistrableCourseYear(PDO $pdo, array $student): string {
        foreach (['year_of_study', 'study_year', 'current_year', 'year'] as $field) {
            $val = trim((string)($student[$field] ?? ''));
            if ($val !== '') return $val;
        }
        try {
            return (string)portalCompletedProgrammeYears($pdo, $student);
        } catch (Throwable $e) {
            return '1';
        }
    }
}

if (!function_exists('portalStudentFirstNameSql')) {
    function portalStudentFirstNameSql(PDO $pdo, string $alias = 's'): string {
        $prefix = trim($alias) !== '' ? trim($alias) . '.' : '';
        $hasFirstName = portalColumnExistsSafe($pdo, 'students', 'first_name');
        $hasFirstname = portalColumnExistsSafe($pdo, 'students', 'firstname');
        if ($hasFirstName && $hasFirstname) {
            return "COALESCE({$prefix}first_name, {$prefix}firstname)";
        }
        if ($hasFirstName) {
            return "{$prefix}first_name";
        }
        if ($hasFirstname) {
            return "{$prefix}firstname";
        }
        return "''";
    }
}

if (!function_exists('portalSettingValueSafe')) {
    function portalSettingValueSafe(PDO $pdo, string $key, string $default = ''): string {
        try {
            $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = ? LIMIT 1");
            $stmt->execute([$key]);
            $val = $stmt->fetchColumn();
            return $val !== false && $val !== null && $val !== '' ? (string)$val : $default;
        } catch (Throwable $e) {
            return $default;
        }
    }
}

if (!function_exists('portalNotificationSettingValueSafe')) {
    function portalNotificationSettingValueSafe(PDO $pdo, string $key, string $default = ''): string {
        try {
            $stmt = $pdo->query("SELECT * FROM notification_settings ORDER BY id ASC LIMIT 1");
            $row = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : [];
            if (is_array($row) && array_key_exists($key, $row)) {
                $val = $row[$key];
                return $val !== null && $val !== '' ? (string)$val : $default;
            }
        } catch (Throwable $e) {}
        return $default;
    }
}

if (!function_exists('portalSyncStudentFeeVisibilityState')) {
    function portalSyncStudentFeeVisibilityState(PDO $pdo, int $studentId): void {
        if ($studentId <= 0) return;
        $currentSession = trim((string)portalCurrentAcademicSessionName($pdo));
        if ($currentSession === '') return;
        try {
            $stmt = $pdo->prepare("UPDATE students
                SET hostel_fee_paid_session = CASE
                        WHEN hostel_fee_status = 'Paid' AND (hostel_fee_paid_session IS NULL OR hostel_fee_paid_session = '') THEN ?
                        ELSE hostel_fee_paid_session
                    END,
                    school_fee_paid_session = CASE
                        WHEN school_fee_status = 'Paid' AND (school_fee_paid_session IS NULL OR school_fee_paid_session = '') THEN ?
                        ELSE school_fee_paid_session
                    END
                WHERE id = ?");
            $stmt->execute([$currentSession, $currentSession, $studentId]);
        } catch (Throwable $e) {}
    }
}



if (!function_exists('portalFeeWorkflowSettingEnabled')) {
    function portalFeeWorkflowSettingEnabled(PDO $pdo, string $key, string $default = '1'): bool {
        $value = strtolower(trim((string)portalSettingValueSafe($pdo, $key, $default)));
        return in_array($value, ['1', 'true', 'yes', 'on', 'enabled'], true);
    }
}

if (!function_exists('portalEnsureAccommodationVerificationSupport')) {
    function portalEnsureAccommodationVerificationSupport(PDO $pdo): void {
        try {
            if (!portalColumnExistsSafe($pdo, 'students', 'accommodation_verification_status')) {
                $pdo->exec("ALTER TABLE students ADD COLUMN accommodation_verification_status VARCHAR(30) NOT NULL DEFAULT 'Not Required' AFTER accommodation_status");
            }
        } catch (Throwable $e) {}
        try {
            if (!portalColumnExistsSafe($pdo, 'students', 'accommodation_verification_note')) {
                $pdo->exec("ALTER TABLE students ADD COLUMN accommodation_verification_note TEXT NULL AFTER accommodation_verification_status");
            }
        } catch (Throwable $e) {}
        try {
            if (!portalColumnExistsSafe($pdo, 'students', 'accommodation_verified_at')) {
                $pdo->exec("ALTER TABLE students ADD COLUMN accommodation_verified_at DATETIME NULL AFTER accommodation_verification_note");
            }
        } catch (Throwable $e) {}
        try {
            if (!portalColumnExistsSafe($pdo, 'students', 'accommodation_verified_by')) {
                $pdo->exec("ALTER TABLE students ADD COLUMN accommodation_verified_by INT NULL AFTER accommodation_verified_at");
            }
        } catch (Throwable $e) {}
        try {
            if (!portalColumnExistsSafe($pdo, 'students', 'accommodation_last_claimed_at')) {
                $pdo->exec("ALTER TABLE students ADD COLUMN accommodation_last_claimed_at DATETIME NULL AFTER accommodation_verified_by");
            }
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS accommodation_change_requests (
                id INT AUTO_INCREMENT PRIMARY KEY,
                student_id INT NOT NULL,
                requested_status VARCHAR(30) NOT NULL DEFAULT 'Off-Campus',
                session_name VARCHAR(80) NULL,
                semester VARCHAR(40) NULL,
                request_status VARCHAR(30) NOT NULL DEFAULT 'Pending',
                student_note TEXT NULL,
                admin_note TEXT NULL,
                reviewed_by INT NULL,
                reviewed_at DATETIME NULL,
                created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at DATETIME NULL DEFAULT NULL,
                INDEX idx_acr_student (student_id),
                INDEX idx_acr_status (request_status),
                INDEX idx_acr_scope (session_name, semester)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("UPDATE students
                SET accommodation_verification_status = CASE
                    WHEN LOWER(COALESCE(accommodation_status,'')) = 'off-campus' THEN 'Pending'
                    ELSE 'Not Required'
                END
                WHERE COALESCE(TRIM(accommodation_verification_status),'') = ''");
        } catch (Throwable $e) {}

        $defaults = [
            'school_fee_enforcement_on_promotion' => ['enabled', 'Require school-fee compliance after promotion'],
            'school_fee_enforcement_on_session_change' => ['enabled', 'Require school-fee compliance when academic session changes'],
            'fee_difference_auto_calculation' => ['enabled', 'Automatically charge only the difference when a current fee exceeds prior covered amount'],
            'previous_debt_blocking' => ['enabled', 'Block course registration when previous school-fee debt exists'],
            'hostel_fee_depend_on_accommodation_status' => ['enabled', 'Only require hostel fee when accommodation status requires campus accommodation'],
            'off_campus_verification_required' => ['enabled', 'Require admin verification for Off-Campus accommodation claims'],
            'pending_claim_behavior' => ['exempt_until_review', 'How pending Off-Campus claims affect hostel fee: exempt_until_review or require_hostel_until_review'],
            'dashboard_payment_popup_enabled' => ['enabled', 'Show dashboard fee/accommodation action popup when student action is required'],
        ];
        foreach ($defaults as $key => $payload) {
            try { setSystemSettingValue($pdo, $key, portalSettingValueSafe($pdo, $key, (string)$payload[0]), (string)$payload[1]); } catch (Throwable $e) {}
        }
    }
}

if (!function_exists('portalImmediatePreviousSessionName')) {
    function portalImmediatePreviousSessionName(PDO $pdo, array $student, string $currentSession): string {
        $currentSession = portalNormalizeSessionNameSafe($currentSession);
        if ($currentSession === '') return '';
        $currentSort = portalSessionSortValue($currentSession);
        $candidate = '';
        $candidateSort = 0;
        foreach (portalCollectStudentKnownSessions($pdo, $student) as $sessionName) {
            $sessionName = portalNormalizeSessionNameSafe((string)$sessionName);
            if ($sessionName === '' || strcasecmp($sessionName, $currentSession) === 0) continue;
            $sort = portalSessionSortValue($sessionName);
            if ($currentSort > 0 && $sort >= $currentSort) continue;
            if ($sort >= $candidateSort) {
                $candidateSort = $sort;
                $candidate = $sessionName;
            }
        }
        return $candidate;
    }
}

if (!function_exists('portalStudentPreviousSchoolFeeCredit')) {
    function portalStudentPreviousSchoolFeeCredit(PDO $pdo, array $student, string $currentSession, string $semester = ''): array {
        $out = [
            'enabled' => false,
            'source_session' => '',
            'source_paid_amount' => 0.0,
            'credit_amount' => 0.0,
            'message' => '',
        ];
        if (!portalFeeWorkflowSettingEnabled($pdo, 'fee_difference_auto_calculation', '1')) return $out;
        $currentSession = portalNormalizeSessionNameSafe($currentSession);
        if ($currentSession === '') return $out;
        $sourceSession = portalImmediatePreviousSessionName($pdo, $student, $currentSession);
        if ($sourceSession === '') return $out;
        $sourceSummary = portalSchoolFeeSessionSummary($pdo, $student, $sourceSession, $semester);
        if (!empty($sourceSummary['has_outstanding']) || (isset($sourceSummary['paid_in_full']) && !$sourceSummary['paid_in_full'])) return $out;
        $sourcePaidAmount = (float)($sourceSummary['total_paid'] ?? 0);
        if ($sourcePaidAmount <= 0.0001) return $out;
        $currentRequired = (float)portalStudentSchoolFeeAmount($pdo, $student, $currentSession, $semester);
        $creditAmount = min($currentRequired, $sourcePaidAmount);
        if ($creditAmount <= 0.0001) return $out;
        $out['enabled'] = true;
        $out['source_session'] = $sourceSession;
        $out['source_paid_amount'] = round($sourcePaidAmount, 2);
        $out['credit_amount'] = round($creditAmount, 2);
        $out['message'] = 'Previous paid school fee from ' . $sourceSession . ' covers ₦' . number_format($creditAmount, 2) . ' of the current requirement.';
        return $out;
    }
}

if (!function_exists('portalStudentAccommodationVerificationState')) {
    function portalStudentAccommodationVerificationState(PDO $pdo, array $student, string $sessionName = '', string $semesterName = ''): array {
        portalEnsureAccommodationVerificationSupport($pdo);
        $sessionName = portalNormalizeSessionNameSafe($sessionName !== '' ? $sessionName : (string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)));
        $semesterName = portalNormalizeSemesterName($semesterName !== '' ? $semesterName : (string)($student['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
        $status = trim((string)($student['accommodation_verification_status'] ?? ''));
        if ($status === '') {
            $status = strtolower(trim((string)($student['accommodation_status'] ?? 'Campus'))) === 'off-campus' ? 'Pending' : 'Not Required';
        }
        $out = [
            'status' => $status,
            'student_status' => trim((string)($student['accommodation_status'] ?? 'Campus')),
            'admin_note' => trim((string)($student['accommodation_verification_note'] ?? '')),
            'claim_id' => 0,
            'claim_status' => $status,
            'claim_created_at' => trim((string)($student['accommodation_last_claimed_at'] ?? '')),
            'claim_session' => $sessionName,
            'claim_semester' => $semesterName,
        ];
        try {
            $st = $pdo->prepare("SELECT * FROM accommodation_change_requests WHERE student_id = ? ORDER BY id DESC LIMIT 1");
            $st->execute([(int)($student['id'] ?? 0)]);
            $claim = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if ($claim) {
                $out['claim_id'] = (int)($claim['id'] ?? 0);
                $out['claim_status'] = trim((string)($claim['request_status'] ?? $status));
                $out['claim_created_at'] = trim((string)($claim['created_at'] ?? $out['claim_created_at']));
                $out['claim_session'] = trim((string)($claim['session_name'] ?? $sessionName));
                $out['claim_semester'] = trim((string)($claim['semester'] ?? $semesterName));
                if (trim((string)($claim['admin_note'] ?? '')) !== '') {
                    $out['admin_note'] = trim((string)$claim['admin_note']);
                }
            }
        } catch (Throwable $e) {}
        return $out;
    }
}

if (!function_exists('portalShouldRequireHostelFeeForStudent')) {
    function portalShouldRequireHostelFeeForStudent(PDO $pdo, array $student, string $sessionName = '', string $semesterName = ''): bool {
        if (!portalFeeWorkflowSettingEnabled($pdo, 'hostel_fee_depend_on_accommodation_status', '1')) {
            return true;
        }
        $acc = strtolower(trim((string)($student['accommodation_status'] ?? 'Campus')));
        if ($acc !== 'off-campus') return true;
        if (!portalFeeWorkflowSettingEnabled($pdo, 'off_campus_verification_required', '1')) {
            return false;
        }
        $state = portalStudentAccommodationVerificationState($pdo, $student, $sessionName, $semesterName);
        $status = strtolower(trim((string)($state['claim_status'] ?? $state['status'] ?? 'Pending')));
        if ($status === 'approved') return false;
        if ($status === 'rejected') return true;
        $pendingBehavior = strtolower(trim((string)portalSettingValueSafe($pdo, 'pending_claim_behavior', 'exempt_until_review')));
        return $pendingBehavior === 'require_hostel_until_review';
    }
}

if (!function_exists('portalStudentHostelRequirementSummary')) {
    function portalStudentHostelRequirementSummary(PDO $pdo, array $student, string $sessionName = '', string $semesterName = ''): array {
        $sessionName = portalNormalizeSessionNameSafe($sessionName !== '' ? $sessionName : (string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)));
        $semesterName = portalNormalizeSemesterName($semesterName !== '' ? $semesterName : (string)($student['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
        $state = portalStudentAccommodationVerificationState($pdo, $student, $sessionName, $semesterName);
        $requiresHostelFee = portalShouldRequireHostelFeeForStudent($pdo, $student, $sessionName, $semesterName);
        $status = strtolower(trim((string)($state['claim_status'] ?? $state['status'] ?? '')));
        $label = 'Campus Accommodation';
        $message = '';
        if (strtolower(trim((string)($student['accommodation_status'] ?? 'Campus'))) === 'off-campus') {
            if ($status === 'approved') {
                $label = 'Off-Campus Approved';
                $message = 'Your Off-Campus claim has been approved. Hostel fee is not required.';
            } elseif ($status === 'rejected') {
                $label = 'Off-Campus Rejected';
                $message = 'Your Off-Campus claim was rejected. Hostel fee is required where applicable.';
            } elseif ($requiresHostelFee) {
                $label = 'Off-Campus Pending Review';
                $message = 'Your Off-Campus claim is pending review. Hostel fee is still required until admin review is completed.';
            } else {
                $label = 'Off-Campus Pending Review';
                $message = 'Your Off-Campus claim is pending review. Hostel fee is temporarily not required until admin review is completed.';
            }
        }
        return [
            'requires_hostel_fee' => $requiresHostelFee,
            'label' => $label,
            'message' => $message,
            'verification_state' => $state,
        ];
    }
}

if (!function_exists('portalStudentDashboardActionNotice')) {
    function portalStudentDashboardActionNotice(PDO $pdo, array $student): array {
        $sessionName = portalNormalizeSessionNameSafe((string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)));
        if ($sessionName === '') $sessionName = portalNormalizeSessionNameSafe((string)portalCurrentAcademicSessionName($pdo));
        $semesterName = portalNormalizeSemesterName((string)($student['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
        if ($semesterName === '') $semesterName = portalNormalizeSemesterName((string)portalCurrentAcademicSemesterName($pdo));
        $schoolSummary = portalSchoolFeePeriodSummary($pdo, (int)($student['id'] ?? 0), $sessionName, $semesterName, $student);
        $previousDebt = portalStudentOutstandingPreviousSchoolFee($pdo, $student, $sessionName, $semesterName);
        $hostelRequirement = portalStudentHostelRequirementSummary($pdo, $student, $sessionName, $semesterName);
        $hostelStatus = portalHostelStatusForPeriod($pdo, $student, $sessionName, $semesterName);
        $items = [];
        if (!empty($previousDebt['has_outstanding'])) {
            $summary = (array)($previousDebt['summary'] ?? []);
            $items[] = 'Previous session school fee debt from ' . (string)($previousDebt['session_name'] ?? 'a previous session') . ' — outstanding ₦' . number_format((float)($summary['total_due'] ?? $summary['outstanding_amount'] ?? 0), 2);
        }
        if ((float)($schoolSummary['total_due'] ?? 0) > 0.0001) {
            $prefix = ((float)($schoolSummary['carry_forward_credit'] ?? 0) > 0.0001)
                ? 'Pay only the remaining school fee difference'
                : 'Pay school fee';
            $items[] = $prefix . ' for ' . $sessionName . ' — ₦' . number_format((float)($schoolSummary['total_due'] ?? 0), 2);
        }
        if (!empty($hostelRequirement['requires_hostel_fee']) && $hostelStatus !== 'Paid') {
            $hostelAmount = (float)portalStudentManagedFeeAmount($pdo, 'hostel', $student, $sessionName, $semesterName);
            if ($hostelAmount > 0) {
                $items[] = 'Pay hostel fee for ' . $sessionName . ' — ₦' . number_format($hostelAmount, 2);
            }
        }
        if (strtolower(trim((string)($student['accommodation_status'] ?? 'Campus'))) === 'off-campus') {
            $claimStatus = strtolower(trim((string)($hostelRequirement['verification_state']['claim_status'] ?? '')));
            if (in_array($claimStatus, ['pending', 'rejected'], true)) {
                $items[] = 'Update accommodation status / review Off-Campus claim (' . ucfirst($claimStatus) . ')';
            }
        }
        return [
            'show' => portalFeeWorkflowSettingEnabled($pdo, 'dashboard_payment_popup_enabled', '1') && !empty($items),
            'items' => $items,
            'school_summary' => $schoolSummary,
            'previous_debt' => $previousDebt,
            'hostel_requirement' => $hostelRequirement,
            'hostel_status' => $hostelStatus,
            'session_name' => $sessionName,
            'semester_name' => $semesterName,
        ];
    }
}


if (!function_exists('portalLatestApprovedFeeReferenceForPeriod')) {
    function portalLatestApprovedFeeReferenceForPeriod(PDO $pdo, int $studentId, string $transactionType, string $sessionName = '', string $semesterName = '', array $fallbackStudent = []): string {
        if ($studentId <= 0 || trim($transactionType) === '') return '';
        try {
            $stmt = $pdo->prepare("SELECT reference, academic_session, semester, status, payment_date, approval_date, id FROM payment_transactions WHERE student_id = ? AND transaction_type = ? AND status IN ('Approved','Successful') ORDER BY COALESCE(approval_date, payment_date, created_at) DESC, id DESC");
            $stmt->execute([$studentId, $transactionType]);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($rows as $row) {
                if (!portalTransactionMatchesPeriod((array)$row, $sessionName, $semesterName, $fallbackStudent)) continue;
                return trim((string)($row['reference'] ?? ''));
            }
        } catch (Throwable $e) {}
        return '';
    }
}

if (!function_exists('portalResynchronizeStudentFeeRequirements')) {
    function portalResynchronizeStudentFeeRequirements(PDO $pdo, int $studentId, array $overrides = []): array {
        $result = [
            'student_id' => $studentId,
            'session_name' => '',
            'semester' => '',
            'school_fee_amount' => 0.0,
            'hostel_fee_amount' => 0.0,
            'school_status' => 'Not Paid',
            'hostel_status' => 'Not Paid',
            'warnings' => [],
        ];
        if ($studentId <= 0) return $result;
        try {
            $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $st->execute([$studentId]);
            $student = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$student) return $result;

            $studentForCalc = (array)$student;
            foreach (['faculty_id','department_id','programme_id','mode_of_study','level','session','semester','session_name'] as $field) {
                if (array_key_exists($field, $overrides) && $overrides[$field] !== null) {
                    $studentForCalc[$field === 'session_name' ? 'session' : $field] = $overrides[$field];
                }
            }

            $ctx = portalStudentContext($pdo, $studentForCalc);
            $sessionName = portalNormalizeSessionNameSafe((string)($overrides['session_name'] ?? $overrides['session'] ?? ($ctx['session_name'] ?? ($studentForCalc['session'] ?? portalCurrentAcademicSessionName($pdo)))));
            if ($sessionName === '') $sessionName = portalNormalizeSessionNameSafe((string)portalCurrentAcademicSessionName($pdo));
            $semesterName = portalNormalizeSemesterName((string)($overrides['semester'] ?? ($ctx['semester'] ?? ($studentForCalc['semester'] ?? portalCurrentAcademicSemesterName($pdo)))));
            if ($semesterName === '') $semesterName = portalNormalizeSemesterName((string)portalCurrentAcademicSemesterName($pdo));
            if ($semesterName === '') $semesterName = 'First';
            $studentForCalc['session'] = $sessionName;
            $studentForCalc['semester'] = $semesterName;

            $schoolRule = portalStudentSchoolFeeRule($pdo, $studentForCalc, $sessionName, $semesterName);
            $schoolFeeAmount = (float)portalStudentSchoolFeeAmount($pdo, $studentForCalc, $sessionName, $semesterName);
            $schoolSummary = portalSchoolFeePeriodSummary($pdo, $studentId, $sessionName, $semesterName, $studentForCalc);
            $schoolTotalPaid = (float)($schoolSummary['total_paid'] ?? 0);
            $schoolOutstanding = (float)($schoolSummary['total_due'] ?? 0);
            $schoolStatus = 'Not Paid';
            if ($schoolFeeAmount <= 0.0001 && empty($schoolRule['matched'])) {
                $result['warnings'][] = 'No matching school fee found for the new student scope.';
            }
            if ($schoolTotalPaid > 0.0001 && $schoolOutstanding > 0.0001) $schoolStatus = 'Pending';
            elseif ($schoolTotalPaid > 0.0001 && $schoolOutstanding <= 0.0001) $schoolStatus = 'Paid';
            $schoolReference = portalLatestApprovedFeeReferenceForPeriod($pdo, $studentId, 'School Fee', $sessionName, $semesterName, $studentForCalc);
            $schoolPaidSession = ($schoolStatus === 'Paid' && $sessionName !== '') ? $sessionName : null;

            $hostelContext = array_merge($ctx, ['session_name' => $sessionName, 'semester' => $semesterName]);
            $hostelRule = portalManagedFeeRuleForContext($pdo, 'hostel', $hostelContext);
            $hostelFeeAmount = (float)portalStudentManagedFeeAmount($pdo, 'hostel', $studentForCalc, $sessionName, $semesterName);
            $hostelTotalPaid = portalApprovedFeeTransactionsTotal($pdo, $studentId, 'Hostel Fee', $sessionName, $semesterName, 0, $studentForCalc);
            $hostelStatus = 'Not Paid';
            $accommodationStatus = strtolower(trim((string)($student['accommodation_status'] ?? 'Campus')));
            if ($accommodationStatus !== 'off-campus') {
                if ($hostelFeeAmount <= 0.0001 && empty($hostelRule['matched'])) {
                    $result['warnings'][] = 'No matching hostel fee found for the new session scope.';
                }
                if ($hostelTotalPaid > 0.0001 && $hostelFeeAmount > 0.0001 && $hostelTotalPaid + 0.0001 < $hostelFeeAmount) $hostelStatus = 'Pending';
                elseif ($hostelTotalPaid > 0.0001 && ($hostelFeeAmount <= 0.0001 || $hostelTotalPaid + 0.0001 >= $hostelFeeAmount)) $hostelStatus = 'Paid';
            }
            $hostelReference = portalLatestApprovedFeeReferenceForPeriod($pdo, $studentId, 'Hostel Fee', $sessionName, $semesterName, $studentForCalc);
            $hostelPaidSession = ($hostelStatus === 'Paid' && $sessionName !== '') ? $sessionName : null;
            if ($accommodationStatus === 'off-campus') {
                $hostelReference = null;
                $hostelPaidSession = null;
            }

            $upd = $pdo->prepare("UPDATE students SET school_fee_status = ?, school_fee_reference = ?, school_fee_paid_session = ?, hostel_fee_status = ?, hostel_fee_reference = ?, hostel_fee_paid_session = ? WHERE id = ?");
            $upd->execute([
                $schoolStatus,
                $schoolReference !== '' ? $schoolReference : null,
                $schoolPaidSession,
                $hostelStatus,
                ($hostelReference !== '' ? $hostelReference : null),
                $hostelPaidSession,
                $studentId,
            ]);

            $result['session_name'] = $sessionName;
            $result['semester'] = $semesterName;
            $result['school_fee_amount'] = $schoolFeeAmount;
            $result['hostel_fee_amount'] = $hostelFeeAmount;
            $result['school_status'] = $schoolStatus;
            $result['hostel_status'] = $hostelStatus;
        } catch (Throwable $e) {
            $result['warnings'][] = $e->getMessage() !== '' ? $e->getMessage() : 'Unable to re-synchronize student fee requirements right now.';
        }
        return $result;
    }
}


if (!function_exists('portalEnsureBulkPromotionHistorySchema')) {
    function portalEnsureBulkPromotionHistorySchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS promotion_bulk_history (
                id INT AUTO_INCREMENT PRIMARY KEY,
                action_type VARCHAR(40) NOT NULL DEFAULT 'promote',
                performed_by_admin_id INT NULL,
                performed_by_identity VARCHAR(190) NULL,
                acting_admin_name VARCHAR(255) NULL,
                from_level VARCHAR(60) NULL,
                to_level VARCHAR(60) NULL,
                from_session VARCHAR(120) NULL,
                to_session VARCHAR(120) NULL,
                faculty_id INT NULL,
                faculty_name VARCHAR(255) NULL,
                department_id INT NULL,
                department_name VARCHAR(255) NULL,
                programme_id INT NULL,
                programme_name VARCHAR(255) NULL,
                mode_of_study VARCHAR(120) NULL,
                mode_of_study_filter_type VARCHAR(30) NULL,
                matched_count INT NOT NULL DEFAULT 0,
                updated_count INT NOT NULL DEFAULT 0,
                status VARCHAR(30) NOT NULL DEFAULT 'Successful',
                school_fee_resync_triggered TINYINT(1) NOT NULL DEFAULT 0,
                hostel_fee_resync_triggered TINYINT(1) NOT NULL DEFAULT 0,
                warning_count INT NOT NULL DEFAULT 0,
                message TEXT NULL,
                warnings_json LONGTEXT NULL,
                details_json LONGTEXT NULL,
                created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_promotion_history_created (created_at),
                INDEX idx_promotion_history_status (status),
                INDEX idx_promotion_history_to_session (to_session),
                INDEX idx_promotion_history_to_level (to_level),
                INDEX idx_promotion_history_admin (performed_by_admin_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        $cols = [
            'action_type' => "ALTER TABLE promotion_bulk_history ADD COLUMN action_type VARCHAR(40) NOT NULL DEFAULT 'promote' AFTER id",
            'performed_by_admin_id' => "ALTER TABLE promotion_bulk_history ADD COLUMN performed_by_admin_id INT NULL AFTER action_type",
            'performed_by_identity' => "ALTER TABLE promotion_bulk_history ADD COLUMN performed_by_identity VARCHAR(190) NULL AFTER performed_by_admin_id",
            'acting_admin_name' => "ALTER TABLE promotion_bulk_history ADD COLUMN acting_admin_name VARCHAR(255) NULL AFTER performed_by_identity",
            'from_level' => "ALTER TABLE promotion_bulk_history ADD COLUMN from_level VARCHAR(60) NULL AFTER acting_admin_name",
            'to_level' => "ALTER TABLE promotion_bulk_history ADD COLUMN to_level VARCHAR(60) NULL AFTER from_level",
            'from_session' => "ALTER TABLE promotion_bulk_history ADD COLUMN from_session VARCHAR(120) NULL AFTER to_level",
            'to_session' => "ALTER TABLE promotion_bulk_history ADD COLUMN to_session VARCHAR(120) NULL AFTER from_session",
            'faculty_id' => "ALTER TABLE promotion_bulk_history ADD COLUMN faculty_id INT NULL AFTER to_session",
            'faculty_name' => "ALTER TABLE promotion_bulk_history ADD COLUMN faculty_name VARCHAR(255) NULL AFTER faculty_id",
            'department_id' => "ALTER TABLE promotion_bulk_history ADD COLUMN department_id INT NULL AFTER faculty_name",
            'department_name' => "ALTER TABLE promotion_bulk_history ADD COLUMN department_name VARCHAR(255) NULL AFTER department_id",
            'programme_id' => "ALTER TABLE promotion_bulk_history ADD COLUMN programme_id INT NULL AFTER department_name",
            'programme_name' => "ALTER TABLE promotion_bulk_history ADD COLUMN programme_name VARCHAR(255) NULL AFTER programme_id",
            'mode_of_study' => "ALTER TABLE promotion_bulk_history ADD COLUMN mode_of_study VARCHAR(120) NULL AFTER programme_name",
            'mode_of_study_filter_type' => "ALTER TABLE promotion_bulk_history ADD COLUMN mode_of_study_filter_type VARCHAR(30) NULL AFTER mode_of_study",
            'matched_count' => "ALTER TABLE promotion_bulk_history ADD COLUMN matched_count INT NOT NULL DEFAULT 0 AFTER mode_of_study_filter_type",
            'updated_count' => "ALTER TABLE promotion_bulk_history ADD COLUMN updated_count INT NOT NULL DEFAULT 0 AFTER matched_count",
            'status' => "ALTER TABLE promotion_bulk_history ADD COLUMN status VARCHAR(30) NOT NULL DEFAULT 'Successful' AFTER updated_count",
            'school_fee_resync_triggered' => "ALTER TABLE promotion_bulk_history ADD COLUMN school_fee_resync_triggered TINYINT(1) NOT NULL DEFAULT 0 AFTER status",
            'hostel_fee_resync_triggered' => "ALTER TABLE promotion_bulk_history ADD COLUMN hostel_fee_resync_triggered TINYINT(1) NOT NULL DEFAULT 0 AFTER school_fee_resync_triggered",
            'warning_count' => "ALTER TABLE promotion_bulk_history ADD COLUMN warning_count INT NOT NULL DEFAULT 0 AFTER hostel_fee_resync_triggered",
            'message' => "ALTER TABLE promotion_bulk_history ADD COLUMN message TEXT NULL AFTER warning_count",
            'warnings_json' => "ALTER TABLE promotion_bulk_history ADD COLUMN warnings_json LONGTEXT NULL AFTER message",
            'details_json' => "ALTER TABLE promotion_bulk_history ADD COLUMN details_json LONGTEXT NULL AFTER warnings_json",
            'created_at' => "ALTER TABLE promotion_bulk_history ADD COLUMN created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER details_json",
        ];
        foreach ($cols as $column => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, 'promotion_bulk_history', $column)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) {}
        }

        $indexes = [
            "CREATE INDEX idx_promotion_history_created ON promotion_bulk_history(created_at)",
            "CREATE INDEX idx_promotion_history_status ON promotion_bulk_history(status)",
            "CREATE INDEX idx_promotion_history_to_session ON promotion_bulk_history(to_session)",
            "CREATE INDEX idx_promotion_history_to_level ON promotion_bulk_history(to_level)",
            "CREATE INDEX idx_promotion_history_admin ON promotion_bulk_history(performed_by_admin_id)",
        ];
        foreach ($indexes as $sql) {
            try { $pdo->exec($sql); } catch (Throwable $e) {}
        }
    }
}

if (!function_exists('portalInsertBulkPromotionHistory')) {
    function portalInsertBulkPromotionHistory(PDO $pdo, array $payload): int {
        portalEnsureBulkPromotionHistorySchema($pdo);
        $st = $pdo->prepare("INSERT INTO promotion_bulk_history (
            action_type, performed_by_admin_id, performed_by_identity, acting_admin_name,
            from_level, to_level, from_session, to_session,
            faculty_id, faculty_name, department_id, department_name, programme_id, programme_name,
            mode_of_study, mode_of_study_filter_type,
            matched_count, updated_count, status,
            school_fee_resync_triggered, hostel_fee_resync_triggered, warning_count,
            message, warnings_json, details_json
        ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
        $st->execute([
            (string)($payload['action_type'] ?? 'promote'),
            array_key_exists('performed_by_admin_id', $payload) && $payload['performed_by_admin_id'] !== null ? (int)$payload['performed_by_admin_id'] : null,
            trim((string)($payload['performed_by_identity'] ?? '')) ?: null,
            trim((string)($payload['acting_admin_name'] ?? '')) ?: null,
            trim((string)($payload['from_level'] ?? '')) ?: null,
            trim((string)($payload['to_level'] ?? '')) ?: null,
            trim((string)($payload['from_session'] ?? '')) ?: null,
            trim((string)($payload['to_session'] ?? '')) ?: null,
            array_key_exists('faculty_id', $payload) && (int)($payload['faculty_id'] ?? 0) > 0 ? (int)$payload['faculty_id'] : null,
            trim((string)($payload['faculty_name'] ?? '')) ?: null,
            array_key_exists('department_id', $payload) && (int)($payload['department_id'] ?? 0) > 0 ? (int)$payload['department_id'] : null,
            trim((string)($payload['department_name'] ?? '')) ?: null,
            array_key_exists('programme_id', $payload) && (int)($payload['programme_id'] ?? 0) > 0 ? (int)$payload['programme_id'] : null,
            trim((string)($payload['programme_name'] ?? '')) ?: null,
            trim((string)($payload['mode_of_study'] ?? '')) ?: null,
            trim((string)($payload['mode_of_study_filter_type'] ?? '')) ?: null,
            max(0, (int)($payload['matched_count'] ?? 0)),
            max(0, (int)($payload['updated_count'] ?? 0)),
            trim((string)($payload['status'] ?? 'Successful')) ?: 'Successful',
            !empty($payload['school_fee_resync_triggered']) ? 1 : 0,
            !empty($payload['hostel_fee_resync_triggered']) ? 1 : 0,
            max(0, (int)($payload['warning_count'] ?? 0)),
            trim((string)($payload['message'] ?? '')) ?: null,
            !empty($payload['warnings_json']) ? (string)$payload['warnings_json'] : null,
            !empty($payload['details_json']) ? (string)$payload['details_json'] : null,
        ]);
        return (int)$pdo->lastInsertId();
    }
}

if (!function_exists('portalBulkPromotionSafeMessage')) {
    function portalBulkPromotionSafeMessage(string $message, string $fallback = 'Bulk promotion failed during processing.'): string {
        $message = trim($message);
        if ($message === '') return $fallback;
        if (stripos($message, 'SQLSTATE') !== false) return $fallback;
        return $message;
    }
}

if (!function_exists('portalAcademicYearNumber')) {
    function portalAcademicYearNumber(PDO $pdo, ?string $sessionName = null): string {
        $raw = trim((string)$sessionName);
        if ($raw === '') {
            $raw = trim((string)portalSettingValueSafe($pdo, 'academic_session', ''));
        }
        if ($raw !== '' && preg_match('/(20\d{2})/', $raw, $m)) {
            return $m[1];
        }
        return date('Y');
    }
}

if (!function_exists('portalAcronym')) {
    function portalAcronym(string $name, int $fallbackLen = 4): string {
        $name = trim($name);
        if ($name === '') return 'STD';
        $parts = preg_split('/\s+/', preg_replace('/[^A-Za-z0-9 ]+/', ' ', $name)) ?: [];
        $skip = ['OF', 'AND', 'THE', 'FOR', 'IN', 'ON', 'AT', 'TO'];
        $letters = '';
        foreach ($parts as $part) {
            $part = strtoupper(trim($part));
            if ($part === '' || in_array($part, $skip, true)) continue;
            $letters .= substr($part, 0, 1);
        }
        if ($letters !== '') return substr($letters, 0, 8);
        $flat = strtoupper(preg_replace('/[^A-Za-z0-9]+/', '', $name));
        return substr($flat !== '' ? $flat : 'STD', 0, max(3, $fallbackLen));
    }
}


if (!function_exists('portalSanitizeFilenamePart')) {
    function portalSanitizeFilenamePart(string $value, string $fallback = 'NA'): string {
        $value = html_entity_decode(trim(strip_tags($value)), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $value = str_replace(['\\', '/', ':', '*', '?', '"', '<', '>', '|'], '-', $value);
        $value = preg_replace('/[^\pL\pN._-]+/u', '-', $value) ?? '';
        $value = preg_replace('/[-_.]{2,}/', '-', $value) ?? '';
        $value = trim($value, "-_. ");
        return $value !== '' ? $value : $fallback;
    }
}

if (!function_exists('portalNormalizeCourseFormSchoolName')) {
    function portalNormalizeCourseFormSchoolName(string $value): string {
        $value = trim((string)$value);
        if ($value === '') return '';
        $value = preg_replace('/\bSchool\s+of\s+Busines\b/iu', 'School of Business', $value) ?: $value;
        $value = preg_replace('/\s+/u', ' ', $value) ?: $value;
        return trim($value);
    }
}

if (!function_exists('portalCourseFormFilenameLabelPart')) {
    function portalCourseFormFilenameLabelPart(string $value, string $fallback = 'NA'): string {
        $value = html_entity_decode(trim(strip_tags($value)), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $value = str_replace(['\\', '/', ':', '*', '?', '"', '<', '>', '|'], ' ', $value);
        $value = preg_replace('/[\r\n\t]+/u', ' ', $value) ?: $value;
        $value = preg_replace('/[^\pL\pN ._-]+/u', ' ', $value) ?: $value;
        $value = preg_replace('/\s+/u', ' ', $value) ?: $value;
        $value = preg_replace('/[-_.]{2,}/', '-', $value) ?: $value;
        $value = trim($value, " -_.");
        return $value !== '' ? $value : $fallback;
    }
}

if (!function_exists('portalCourseFormDownloadFilename')) {
    function portalCourseFormDownloadFilename(PDO $pdo, array $reg): string {
        $studentName = trim((string)($reg['full_name'] ?? ''));
        if ($studentName === '') {
            $studentName = trim((string)($reg['surname'] ?? '') . ' ' . portalCourseRegistrationOtherNames($reg));
        }
        if ($studentName === '') {
            $studentName = 'Student';
        }

        $profile = [];
        try {
            $profile = portalResolveCourseFormProfile($pdo, $reg);
        } catch (Throwable $e) {
            $profile = [];
        }

        $departmentName = trim((string)($profile['course_in_view'] ?? $profile['course_of_study'] ?? $reg['department_name'] ?? $reg['department'] ?? ''));
        if ($departmentName === '') {
            $departmentName = 'Department';
        }

        $level = trim((string)($reg['level'] ?? ''));
        if ($level === '') $level = 'Level';

        $semester = portalNormalizeSemesterName((string)($reg['semester'] ?? 'First'));
        if ($semester === '') $semester = 'Semester';

        $sessionName = trim((string)($reg['session_name'] ?? ''));
        if ($sessionName === '') $sessionName = trim((string)portalCurrentAcademicSessionName($pdo));
        if ($sessionName === '') $sessionName = date('Y') . '-' . ((int)date('Y') + 1);
        $sessionName = str_replace('/', '-', $sessionName);

        $currentDate = date('Y-m-d');

        $parts = [
            portalCourseFormFilenameLabelPart($studentName, 'Student'),
            portalCourseFormFilenameLabelPart($departmentName, 'Department'),
            portalCourseFormFilenameLabelPart($level, 'Level'),
            portalCourseFormFilenameLabelPart($semester, 'Semester'),
            portalCourseFormFilenameLabelPart($sessionName, date('Y') . '-' . ((int)date('Y') + 1)),
            portalCourseFormFilenameLabelPart($currentDate, date('Y-m-d')),
        ];

        $filename = implode('_', array_filter($parts, static fn($part) => $part !== ''));
        $filename = preg_replace('/\s{2,}/u', ' ', $filename) ?: $filename;
        $filename = trim($filename, '_ ');
        if ($filename === '') $filename = 'course-form-' . (int)($reg['id'] ?? 0);
        if (function_exists('mb_strlen') && function_exists('mb_substr')) {
            if (mb_strlen($filename, 'UTF-8') > 180) $filename = mb_substr($filename, 0, 180, 'UTF-8');
        } elseif (strlen($filename) > 180) {
            $filename = substr($filename, 0, 180);
        }
        return $filename . '.pdf';
    }
}


if (!function_exists('portalPaymentReceiptDownloadFilename')) {
    function portalPaymentReceiptDownloadFilename(array $transaction): string {
        $studentName = trim((string)full_name($transaction));
        if ($studentName === '') {
            $studentName = trim((string)(($transaction['surname'] ?? '') . ' ' . ($transaction['first_name'] ?? '') . ' ' . ($transaction['other_name'] ?? '')));
        }
        if ($studentName === '') $studentName = 'Student';

        $level = trim((string)($transaction['level'] ?? ''));
        if ($level === '') $level = 'Level';

        $receiptRef = trim((string)(portalReceiptNumber($transaction) ?: ($transaction['reference'] ?? '') ?: ($transaction['transaction_id'] ?? 'Receipt')));
        if ($receiptRef === '') $receiptRef = 'Receipt';

        $parts = [
            portalCourseFormFilenameLabelPart($studentName, 'Student'),
            portalCourseFormFilenameLabelPart($level, 'Level'),
            portalCourseFormFilenameLabelPart($receiptRef, 'Receipt'),
        ];

        $filename = implode('_', array_filter($parts, static fn($part) => $part !== ''));
        $filename = preg_replace('/\s{2,}/u', ' ', $filename) ?: $filename;
        $filename = trim($filename, '_ ');
        if ($filename === '') $filename = 'payment-receipt';
        if (function_exists('mb_strlen') && function_exists('mb_substr')) {
            if (mb_strlen($filename, 'UTF-8') > 180) $filename = mb_substr($filename, 0, 180, 'UTF-8');
        } elseif (strlen($filename) > 180) {
            $filename = substr($filename, 0, 180);
        }
        return $filename . '.pdf';
    }
}

if (!function_exists('portalStudentContext')) {
    function portalStudentContext(PDO $pdo, array $student): array {
        $ctx = [
            'faculty_id' => (int)($student['faculty_id'] ?? 0),
            'department_id' => (int)($student['department_id'] ?? 0),
            'programme_id' => (int)($student['programme_id'] ?? 0),
            'faculty_name' => trim((string)($student['faculty'] ?? '')),
            'department_name' => trim((string)($student['department'] ?? '')),
            'programme_name' => trim((string)($student['programme'] ?? '')),
            'faculty_code' => '',
            'level' => trim((string)($student['level'] ?? '')),
            'session_name' => trim((string)($student['session'] ?? '')),
            'semester' => trim((string)($student['semester'] ?? '')),
            'mode_of_study' => trim((string)($student['mode_of_study'] ?? '')),
        ];

        try {
            if ($ctx['faculty_id'] > 0) {
                $stmt = $pdo->prepare("SELECT name, code FROM faculties WHERE id = ? LIMIT 1");
                $stmt->execute([$ctx['faculty_id']]);
                if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    if ($ctx['faculty_name'] === '' && !empty($row['name'])) $ctx['faculty_name'] = (string)$row['name'];
                    if (!empty($row['code'])) $ctx['faculty_code'] = strtoupper(trim((string)$row['code']));
                }
            }
        } catch (Throwable $e) {}

        try {
            if ($ctx['department_id'] > 0) {
                $stmt = $pdo->prepare("SELECT name FROM departments WHERE id = ? LIMIT 1");
                $stmt->execute([$ctx['department_id']]);
                $name = $stmt->fetchColumn();
                if ($ctx['department_name'] === '' && $name) $ctx['department_name'] = (string)$name;
            }
        } catch (Throwable $e) {}

        try {
            if ($ctx['programme_id'] > 0) {
                $stmt = $pdo->prepare("SELECT name FROM programmes WHERE id = ? LIMIT 1");
                $stmt->execute([$ctx['programme_id']]);
                $name = $stmt->fetchColumn();
                if ($ctx['programme_name'] === '' && $name) $ctx['programme_name'] = (string)$name;
            }
        } catch (Throwable $e) {}

        try {
            $courseRegistrationModeSelect = portalColumnExistsSafe($pdo, 'course_registrations', 'mode_of_study')
                ? "cr.mode_of_study AS mode_of_study,"
                : "'' AS mode_of_study,";
            $stmt = $pdo->prepare("
                SELECT cr.session_name, cr.semester, cr.level,
                       cr.faculty_id, cr.department_id, cr.programme_id,
                       {$courseRegistrationModeSelect}
                       f.name AS faculty_name, f.code AS faculty_code,
                       d.name AS department_name, p.name AS programme_name
                FROM course_registrations cr
                LEFT JOIN faculties f ON f.id = cr.faculty_id
                LEFT JOIN departments d ON d.id = cr.department_id
                LEFT JOIN programmes p ON p.id = cr.programme_id
                WHERE cr.student_id = ?
                ORDER BY COALESCE(cr.updated_at, cr.created_at) DESC, cr.id DESC
                LIMIT 1
            ");
            $stmt->execute([(int)($student['id'] ?? 0)]);
            if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                if ($ctx['session_name'] === '' && !empty($row['session_name'])) $ctx['session_name'] = (string)$row['session_name'];
                if ($ctx['semester'] === '' && !empty($row['semester'])) $ctx['semester'] = (string)$row['semester'];
                if ($ctx['level'] === '' && !empty($row['level'])) $ctx['level'] = (string)$row['level'];
                if ($ctx['faculty_id'] <= 0 && !empty($row['faculty_id'])) $ctx['faculty_id'] = (int)$row['faculty_id'];
                if ($ctx['department_id'] <= 0 && !empty($row['department_id'])) $ctx['department_id'] = (int)$row['department_id'];
                if ($ctx['programme_id'] <= 0 && !empty($row['programme_id'])) $ctx['programme_id'] = (int)$row['programme_id'];
                if ($ctx['faculty_name'] === '' && !empty($row['faculty_name'])) $ctx['faculty_name'] = (string)$row['faculty_name'];
                if ($ctx['department_name'] === '' && !empty($row['department_name'])) $ctx['department_name'] = (string)$row['department_name'];
                if ($ctx['programme_name'] === '' && !empty($row['programme_name'])) $ctx['programme_name'] = (string)$row['programme_name'];
                if ($ctx['faculty_code'] === '' && !empty($row['faculty_code'])) $ctx['faculty_code'] = strtoupper(trim((string)$row['faculty_code']));
                if ($ctx['mode_of_study'] === '' && !empty($row['mode_of_study'])) $ctx['mode_of_study'] = trim((string)$row['mode_of_study']);
            }
        } catch (Throwable $e) {}

        if ($ctx['session_name'] === '') {
            try {
                $stmt = $pdo->query("SELECT name FROM academic_sessions WHERE is_current = 1 LIMIT 1");
                $current = $stmt ? $stmt->fetchColumn() : '';
                if (!empty($current)) $ctx['session_name'] = (string)$current;
            } catch (Throwable $e) {}
        }
        if ($ctx['session_name'] === '') {
            $ctx['session_name'] = portalSettingValueSafe($pdo, 'academic_session', '');
        }
        if ($ctx['semester'] === '') $ctx['semester'] = 'First';
        if ($ctx['faculty_code'] === '') {
            $ctx['faculty_code'] = portalAcronym($ctx['faculty_name'] !== '' ? $ctx['faculty_name'] : 'Student');
        }

        return $ctx;
    }
}

if (!function_exists('portalStudentRegistrationRule')) {
    function portalStudentRegistrationRule(PDO $pdo, array $student): array {
        $ctx = portalStudentContext($pdo, $student);
        $current = ['is_open' => 'Yes', 'min_units' => 0, 'max_units' => 30];

        try {
            $stmt = $pdo->prepare("
                SELECT is_open, min_units, max_units
                FROM registration_settings
                WHERE COALESCE(session_name,'') = ? AND semester = ?
                  AND (
                        (department_id = ? AND level = ?)
                     OR (department_id = ? AND level IS NULL)
                     OR (department_id IS NULL AND level IS NULL)
                  )
                ORDER BY
                    (department_id = ? AND level = ?) DESC,
                    (department_id = ? AND level IS NULL) DESC,
                    (department_id IS NULL AND level IS NULL) DESC
                LIMIT 1
            ");
            $dept = ((int)$ctx['department_id'] > 0) ? (int)$ctx['department_id'] : -1;
            $lvl  = (string)($ctx['level'] ?? '');
            $stmt->execute([
                (string)($ctx['session_name'] ?? ''),
                (string)($ctx['semester'] ?? 'First'),
                $dept,
                $lvl,
                $dept,
                $dept,
                $lvl,
                $dept
            ]);
            if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $current['is_open'] = (string)($row['is_open'] ?? 'Yes');
                $current['min_units'] = (int)($row['min_units'] ?? 0);
                $current['max_units'] = (int)($row['max_units'] ?? 30);
            }
        } catch (Throwable $e) {}

        return $current;
    }
}

if (!function_exists('portalStudentCreditLoadText')) {
    function portalStudentCreditLoadText(PDO $pdo, array $student): string {
        $rule = portalStudentRegistrationRule($pdo, $student);
        $min = (int)($rule['min_units'] ?? 0);
        $max = (int)($rule['max_units'] ?? 0);
        if ($min > 0 || $max > 0) {
            if ($min > 0 && $max > 0 && $min !== $max) return $min . ' - ' . $max;
            return (string)max($min, $max);
        }

        try {
            $ctx = portalStudentContext($pdo, $student);
            $stmt = $pdo->prepare("
                SELECT total_units
                FROM course_registrations
                WHERE student_id = ? AND COALESCE(session_name,'') = ? AND semester = ?
                ORDER BY COALESCE(updated_at, created_at) DESC, id DESC
                LIMIT 1
            ");
            $stmt->execute([(int)($student['id'] ?? 0), (string)($ctx['session_name'] ?? ''), (string)($ctx['semester'] ?? 'First')]);
            $val = $stmt->fetchColumn();
            if ($val !== false && $val !== null && (int)$val > 0) return (string)((int)$val);
        } catch (Throwable $e) {}

        return '0 - 30';
    }
}

if (!function_exists('portalDashboardSummary')) {
    function portalDashboardSummary(PDO $pdo, array $student): array {
        $ctx = portalStudentContext($pdo, $student);
        $rule = portalStudentRegistrationRule($pdo, $student);
        return [
            'session_name' => (string)($ctx['session_name'] ?? ''),
            'semester' => (string)($ctx['semester'] ?? 'First'),
            'registration_status' => portalRegistrationOpenLabel((string)($rule['is_open'] ?? 'Yes')),
            'faculty_name' => (string)($ctx['faculty_name'] !== '' ? $ctx['faculty_name'] : 'Not Set'),
            'department_name' => (string)($ctx['department_name'] !== '' ? $ctx['department_name'] : 'Not Set'),
            'programme_name' => (string)($ctx['programme_name'] !== '' ? $ctx['programme_name'] : 'Not Set'),
            'level' => (string)($ctx['level'] !== '' ? $ctx['level'] : 'Not Set'),
            'credit_load' => portalStudentCreditLoadText($pdo, $student),
        ];
    }
}


if (!function_exists('portalResolveNumberingContext')) {
    function portalResolveNumberingContext(PDO $pdo, array $source): array {
        $ctx = [
            'faculty_id' => (int)($source['faculty_id'] ?? 0),
            'department_id' => (int)($source['department_id'] ?? 0),
            'programme_id' => (int)($source['programme_id'] ?? 0),
            'faculty_name' => trim((string)($source['faculty_name'] ?? $source['faculty'] ?? '')),
            'department_name' => trim((string)($source['department_name'] ?? $source['department'] ?? '')),
            'programme_name' => trim((string)($source['programme_name'] ?? $source['programme'] ?? '')),
            'faculty_code' => trim((string)($source['faculty_code'] ?? '')),
            'department_code' => trim((string)($source['department_code'] ?? '')),
            'programme_code' => trim((string)($source['programme_code'] ?? '')),
            'level' => trim((string)($source['level'] ?? '')),
            'session_name' => trim((string)($source['session_name'] ?? $source['session'] ?? '')),
            'semester' => trim((string)($source['semester'] ?? '')),
        ];

        try {
            if (!empty($source['id'])) {
                $ctx = array_merge($ctx, portalStudentContext($pdo, $source));
                $ctx['department_code'] = trim((string)($ctx['department_code'] ?? ''));
                $ctx['programme_code'] = trim((string)($ctx['programme_code'] ?? ''));
            }
        } catch (Throwable $e) {}

        try {
            if ($ctx['faculty_id'] > 0) {
                $stmt = $pdo->prepare("SELECT name, code FROM faculties WHERE id = ? LIMIT 1");
                $stmt->execute([$ctx['faculty_id']]);
                if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    if ($ctx['faculty_name'] === '' && !empty($row['name'])) $ctx['faculty_name'] = (string)$row['name'];
                    if ($ctx['faculty_code'] === '' && !empty($row['code'])) $ctx['faculty_code'] = strtoupper(trim((string)$row['code']));
                }
            }
        } catch (Throwable $e) {}

        try {
            if ($ctx['department_id'] > 0) {
                $stmt = $pdo->prepare("SELECT name, code FROM departments WHERE id = ? LIMIT 1");
                $stmt->execute([$ctx['department_id']]);
                if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    if ($ctx['department_name'] === '' && !empty($row['name'])) $ctx['department_name'] = (string)$row['name'];
                    if ($ctx['department_code'] === '' && !empty($row['code'])) $ctx['department_code'] = strtoupper(trim((string)$row['code']));
                }
            }
        } catch (Throwable $e) {}

        try {
            if ($ctx['programme_id'] > 0) {
                $stmt = $pdo->prepare("SELECT name, code FROM programmes WHERE id = ? LIMIT 1");
                $stmt->execute([$ctx['programme_id']]);
                if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    if ($ctx['programme_name'] === '' && !empty($row['name'])) $ctx['programme_name'] = (string)$row['name'];
                    if ($ctx['programme_code'] === '' && !empty($row['code'])) $ctx['programme_code'] = strtoupper(trim((string)$row['code']));
                }
            }
        } catch (Throwable $e) {}

        if ($ctx['session_name'] === '') {
            try {
                $stmt = $pdo->query("SELECT name FROM academic_sessions WHERE is_current = 1 LIMIT 1");
                $current = $stmt ? $stmt->fetchColumn() : '';
                if (!empty($current)) $ctx['session_name'] = (string)$current;
            } catch (Throwable $e) {}
        }
        if ($ctx['session_name'] === '') $ctx['session_name'] = portalSettingValueSafe($pdo, 'academic_session', '');
        if ($ctx['semester'] === '') $ctx['semester'] = 'First';
        if ($ctx['faculty_code'] === '') $ctx['faculty_code'] = portalAcronym($ctx['faculty_name'] !== '' ? $ctx['faculty_name'] : 'Student');
        if ($ctx['department_code'] === '') $ctx['department_code'] = portalAcronym($ctx['department_name'] !== '' ? $ctx['department_name'] : 'Department', 3);
        if ($ctx['programme_code'] === '') $ctx['programme_code'] = portalAcronym($ctx['programme_name'] !== '' ? $ctx['programme_name'] : 'Programme', 3);
        $ctx['year'] = portalAcademicYearNumber($pdo, (string)($ctx['session_name'] ?? ''));
        return $ctx;
    }
}

if (!function_exists('portalAutoNumberTokenMap')) {
    function portalAutoNumberTokenMap(array $ctx): array {
        $year = (string)($ctx['year'] ?? date('Y'));
        return [
            '{YEAR}' => $year,
            '{CURRENT_YEAR}' => $year,
            '{SESSION_YEAR}' => $year,
            '{FACULTY_CODE}' => strtoupper(trim((string)($ctx['faculty_code'] ?? 'STD'))),
            '{DEPARTMENT_CODE}' => strtoupper(trim((string)($ctx['department_code'] ?? 'DPT'))),
            '{PROGRAMME_CODE}' => strtoupper(trim((string)($ctx['programme_code'] ?? 'PRG'))),
            '{FACULTY}' => strtoupper(trim((string)($ctx['faculty_name'] ?? ''))),
            '{DEPARTMENT}' => strtoupper(trim((string)($ctx['department_name'] ?? ''))),
            '{PROGRAMME}' => strtoupper(trim((string)($ctx['programme_name'] ?? ''))),
            '{LEVEL}' => strtoupper(trim((string)($ctx['level'] ?? ''))),
        ];
    }
}

if (!function_exists('portalApplyNumberTokens')) {
    function portalApplyNumberTokens(string $template, array $ctx): string {
        return strtr((string)$template, portalAutoNumberTokenMap($ctx));
    }
}

if (!function_exists('portalAllowedNumberingPlaceholders')) {
    function portalAllowedNumberingPlaceholders(): array {
        return ['{YEAR}','{CURRENT_YEAR}','{SESSION_YEAR}','{FACULTY_CODE}','{DEPARTMENT_CODE}','{PROGRAMME_CODE}','{FACULTY}','{DEPARTMENT}','{PROGRAMME}','{LEVEL}'];
    }
}

if (!function_exists('portalNumberingAllowedPlaceholders')) {
    function portalNumberingAllowedPlaceholders(): array {
        return portalAllowedNumberingPlaceholders();
    }
}

if (!function_exists('portalNumberingScopeLabel')) {
    function portalNumberingScopeLabel(array $row): string {
        $scopeType = trim((string)($row['scope_type'] ?? 'global'));
        if ($scopeType === 'session') {
            $label = trim((string)($row['scope_session'] ?? ''));
            return $label !== '' ? $label : 'Session-specific';
        }
        $scopeName = trim((string)($row['scope_name'] ?? ''));
        if ($scopeName !== '') return $scopeName;
        return $scopeType === 'global' ? 'Global Default' : ucfirst($scopeType);
    }
}

if (!function_exists('portalValidateNumberingTemplate')) {
    function portalValidateNumberingTemplate(string $template): string {
        $template = (string)$template;
        if ($template === '') {
            return '';
        }
        if (substr_count($template, '{') !== substr_count($template, '}')) {
            return 'Broken placeholder usage detected.';
        }
        preg_match_all('/\{[A-Z_]+\}/', $template, $matches);
        $allowed = portalAllowedNumberingPlaceholders();
        foreach (($matches[0] ?? []) as $token) {
            if (!in_array($token, $allowed, true)) {
                return 'Invalid placeholder used in prefix.';
            }
        }
        $remaining = preg_replace('/\{[A-Z_]+\}/', '', $template);
        if ($remaining === null) {
            return 'Unable to validate placeholder usage right now.';
        }
        if (strpos($remaining, '{') !== false || strpos($remaining, '}') !== false) {
            return 'Broken placeholder usage detected.';
        }
        return '';
    }
}

if (!function_exists('portalEnsureNumberingSchema')) {
    function portalEnsureNumberingSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS auto_number_formats (
                id INT AUTO_INCREMENT PRIMARY KEY,
                number_type VARCHAR(20) NOT NULL,
                scope_type VARCHAR(20) NOT NULL DEFAULT 'global',
                scope_id INT NOT NULL DEFAULT 0,
                scope_session VARCHAR(80) NOT NULL DEFAULT '',
                prefix VARCHAR(120) NOT NULL DEFAULT '',
                affix VARCHAR(120) NOT NULL DEFAULT '',
                serial_digits INT NOT NULL DEFAULT 4,
                start_from INT NOT NULL DEFAULT 1,
                is_active TINYINT(1) NOT NULL DEFAULT 1,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                KEY idx_number_scope (number_type, scope_type, scope_id, scope_session),
                KEY idx_number_status (is_active)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        $colSql = [
            'scope_type' => "ALTER TABLE auto_number_formats ADD COLUMN scope_type VARCHAR(20) NOT NULL DEFAULT 'global' AFTER number_type",
            'scope_id' => "ALTER TABLE auto_number_formats ADD COLUMN scope_id INT NOT NULL DEFAULT 0 AFTER scope_type",
            'scope_session' => "ALTER TABLE auto_number_formats ADD COLUMN scope_session VARCHAR(80) NOT NULL DEFAULT '' AFTER scope_id",
            'prefix' => "ALTER TABLE auto_number_formats ADD COLUMN prefix VARCHAR(120) NOT NULL DEFAULT '' AFTER scope_session",
            'affix' => "ALTER TABLE auto_number_formats ADD COLUMN affix VARCHAR(120) NOT NULL DEFAULT '' AFTER prefix",
            'serial_digits' => "ALTER TABLE auto_number_formats ADD COLUMN serial_digits INT NOT NULL DEFAULT 4 AFTER affix",
            'start_from' => "ALTER TABLE auto_number_formats ADD COLUMN start_from INT NOT NULL DEFAULT 1 AFTER serial_digits",
            'is_active' => "ALTER TABLE auto_number_formats ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER start_from",
        ];
        foreach ($colSql as $col => $sql) {
            try { if (!portalColumnExistsSafe($pdo, 'auto_number_formats', $col)) $pdo->exec($sql); } catch (Throwable $e) {}
        }
        try { $pdo->exec("UPDATE auto_number_formats SET scope_session = '' WHERE scope_session IS NULL"); } catch (Throwable $e) {}
        try { if (portalIndexExistsSafe($pdo, 'auto_number_formats', 'uniq_number_scope')) $pdo->exec("ALTER TABLE auto_number_formats DROP INDEX uniq_number_scope"); } catch (Throwable $e) {}
        try { if (!portalIndexExistsSafe($pdo, 'auto_number_formats', 'uniq_number_scope_full')) $pdo->exec("ALTER TABLE auto_number_formats ADD UNIQUE KEY uniq_number_scope_full (number_type, scope_type, scope_id, scope_session)"); } catch (Throwable $e) {}
        try { if (!portalIndexExistsSafe($pdo, 'auto_number_formats', 'idx_number_scope')) $pdo->exec("ALTER TABLE auto_number_formats ADD INDEX idx_number_scope (number_type, scope_type, scope_id, scope_session)"); } catch (Throwable $e) {}
        try { if (!portalIndexExistsSafe($pdo, 'auto_number_formats', 'idx_number_status')) $pdo->exec("ALTER TABLE auto_number_formats ADD INDEX idx_number_status (is_active)"); } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS auto_number_sequences (
                id INT AUTO_INCREMENT PRIMARY KEY,
                sequence_key VARCHAR(190) NOT NULL UNIQUE,
                last_seq INT NOT NULL DEFAULT 0,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
        try { if (!portalIndexExistsSafe($pdo, 'auto_number_sequences', 'idx_auto_number_sequence_key')) $pdo->exec("ALTER TABLE auto_number_sequences ADD INDEX idx_auto_number_sequence_key (sequence_key)"); } catch (Throwable $e) {}

        $studentCols = [
            'application_no' => "ALTER TABLE students ADD COLUMN application_no VARCHAR(120) NULL AFTER registration_no",
            'registration_no' => "ALTER TABLE students ADD COLUMN registration_no VARCHAR(120) NULL AFTER application_no",
            'admission_no' => "ALTER TABLE students ADD COLUMN admission_no VARCHAR(120) NULL AFTER registration_no",
            'matric_no' => "ALTER TABLE students ADD COLUMN matric_no VARCHAR(120) NULL AFTER admission_no",
        ];
        foreach ($studentCols as $col => $sql) {
            try { if (!portalColumnExistsSafe($pdo, 'students', $col)) $pdo->exec($sql); } catch (Throwable $e) {}
        }
        foreach (['application_no','registration_no','admission_no','matric_no'] as $col) {
            try { if (!portalIndexExistsSafe($pdo, 'students', 'idx_students_' . $col)) $pdo->exec("ALTER TABLE students ADD INDEX idx_students_{$col} ({$col})"); } catch (Throwable $e) {}
        }
        try { $pdo->exec("UPDATE students SET application_no = registration_no WHERE COALESCE(application_no,'') = '' AND COALESCE(registration_no,'') <> ''"); } catch (Throwable $e) {}
        try { $pdo->exec("UPDATE students SET registration_no = application_no WHERE COALESCE(registration_no,'') = '' AND COALESCE(application_no,'') <> ''"); } catch (Throwable $e) {}
    }
}

if (!function_exists('portalHasExplicitNumberFormat')) {
    function portalHasExplicitNumberFormat(PDO $pdo, string $numberType, array $source = []): bool {
        portalEnsureNumberingSchema($pdo);
        $ctx = portalResolveNumberingContext($pdo, $source);
        $sessionName = trim((string)($ctx['session_name'] ?? ''));
        $candidates = [];
        if ((int)($ctx['programme_id'] ?? 0) > 0) $candidates[] = ['programme', (int)$ctx['programme_id']];
        if ((int)($ctx['department_id'] ?? 0) > 0) $candidates[] = ['department', (int)$ctx['department_id']];
        if ((int)($ctx['faculty_id'] ?? 0) > 0) $candidates[] = ['faculty', (int)$ctx['faculty_id']];
        $candidates[] = ['session', 0];
        $candidates[] = ['global', 0];
        foreach ($candidates as [$scopeType, $scopeId]) {
            foreach ([$sessionName, ''] as $scopeSession) {
                try {
                    $stmt = $pdo->prepare("SELECT id FROM auto_number_formats WHERE number_type = ? AND scope_type = ? AND scope_id = ? AND scope_session = ? AND is_active = 1 LIMIT 1");
                    $stmt->execute([$numberType, $scopeType, $scopeId, (string)$scopeSession]);
                    if ((int)($stmt->fetchColumn() ?: 0) > 0) return true;
                } catch (Throwable $e) {}
            }
        }
        return false;
    }
}

if (!function_exists('portalDefaultNumberFormat')) {
    function portalDefaultNumberFormat(PDO $pdo, string $numberType, array $source): array {
        $ctx = portalResolveNumberingContext($pdo, $source);
        if ($numberType === 'application') {
            return [
                'number_type' => 'application',
                'scope_type' => 'global',
                'scope_id' => 0,
                'scope_session' => '',
                'prefix' => 'APP/{YEAR}/',
                'affix' => '',
                'serial_digits' => 6,
                'start_from' => 1,
                'is_active' => 1,
                'sequence_key' => 'application|global|' . trim((string)($ctx['session_name'] ?? '')),
            ];
        }
        if ($numberType === 'registration') {
            return [
                'number_type' => 'registration',
                'scope_type' => 'global',
                'scope_id' => 0,
                'scope_session' => '',
                'prefix' => 'REG/{YEAR}/',
                'affix' => '',
                'serial_digits' => 6,
                'start_from' => 1,
                'is_active' => 1,
                'sequence_key' => 'registration|global|' . trim((string)($ctx['session_name'] ?? '')),
            ];
        }
        if ($numberType === 'admission') {
            return [
                'number_type' => 'admission',
                'scope_type' => 'global',
                'scope_id' => 0,
                'scope_session' => '',
                'prefix' => 'ADM/{FACULTY_CODE}/{YEAR}/',
                'affix' => '',
                'serial_digits' => 4,
                'start_from' => 1,
                'is_active' => 1,
                'sequence_key' => 'admission|global|' . trim((string)($ctx['session_name'] ?? '')),
            ];
        }
        return [
            'number_type' => 'matric',
            'scope_type' => 'global',
            'scope_id' => 0,
            'scope_session' => '',
            'prefix' => 'S/{YEAR}/',
            'affix' => '',
            'serial_digits' => 4,
            'start_from' => 1,
            'is_active' => 1,
            'sequence_key' => 'matric|global|' . trim((string)($ctx['session_name'] ?? '')),
        ];
    }
}

if (!function_exists('portalAutoNumberFormat')) {
    function portalAutoNumberFormat(PDO $pdo, string $numberType, array $source): array {
        portalEnsureNumberingSchema($pdo);
        $ctx = portalResolveNumberingContext($pdo, $source);
        $default = portalDefaultNumberFormat($pdo, $numberType, $source);
        $sessionName = trim((string)($ctx['session_name'] ?? ''));
        $candidates = [];
        if ((int)($ctx['programme_id'] ?? 0) > 0) $candidates[] = ['programme', (int)$ctx['programme_id']];
        if ((int)($ctx['department_id'] ?? 0) > 0) $candidates[] = ['department', (int)$ctx['department_id']];
        if ((int)($ctx['faculty_id'] ?? 0) > 0) $candidates[] = ['faculty', (int)$ctx['faculty_id']];
        $candidates[] = ['global', 0];

        foreach ($candidates as [$scopeType, $scopeId]) {
            foreach ([$sessionName, ''] as $scopeSession) {
                try {
                    $stmt = $pdo->prepare("SELECT * FROM auto_number_formats WHERE number_type = ? AND scope_type = ? AND scope_id = ? AND scope_session = ? AND is_active = 1 LIMIT 1");
                    $stmt->execute([$numberType, $scopeType, $scopeId, (string)$scopeSession]);
                    $row = $stmt->fetch(PDO::FETCH_ASSOC);
                    if ($row) {
                        $row['serial_digits'] = max(1, min(12, (int)($row['serial_digits'] ?? 4)));
                        $row['start_from'] = max(1, (int)($row['start_from'] ?? 1));
                        $sessionPart = trim((string)($row['scope_session'] ?? '')) !== '' ? trim((string)$row['scope_session']) : $sessionName;
                        $row['sequence_key'] = $numberType . '|' . $scopeType . '|' . (int)$scopeId . '|' . $sessionPart;
                        return array_merge($default, $row);
                    }
                } catch (Throwable $e) {}
            }
        }

        if ($numberType === 'registration' && !portalHasExplicitNumberFormat($pdo, 'registration', $source)) {
            $applicationFormat = portalAutoNumberFormat($pdo, 'application', $source);
            $applicationFormat['number_type'] = 'registration';
            return $applicationFormat;
        }

        return $default;
    }
}

if (!function_exists('portalGenerateAutoNumber')) {
    function portalGenerateAutoNumber(PDO $pdo, string $numberType, array $source, string $existing = ''): string {
        $existing = trim((string)$existing);
        if ($existing !== '') return $existing;
        portalEnsureNumberingSchema($pdo);
        if ($numberType === 'registration' && !portalHasExplicitNumberFormat($pdo, 'registration', $source)) {
            $applicationExisting = trim((string)($source['application_no'] ?? $source['registration_no'] ?? ''));
            return portalGenerateAutoNumber($pdo, 'application', $source, $applicationExisting);
        }

        $ctx = portalResolveNumberingContext($pdo, $source);
        $format = portalAutoNumberFormat($pdo, $numberType, $source);
        $prefix = portalApplyNumberTokens((string)($format['prefix'] ?? ''), $ctx);
        $affix = portalApplyNumberTokens((string)($format['affix'] ?? ''), $ctx);
        $serialDigits = max(1, min(12, (int)($format['serial_digits'] ?? 4)));
        $startFrom = max(1, (int)($format['start_from'] ?? 1));
        $sequenceKey = trim((string)($format['sequence_key'] ?? ($numberType . '|global')));
        if ($sequenceKey === '') $sequenceKey = $numberType . '|global';

        $column = 'matric_no';
        if ($numberType === 'application') $column = 'application_no';
        elseif ($numberType === 'registration') $column = 'registration_no';
        elseif ($numberType === 'admission') $column = 'admission_no';

        try {
            $pdo->beginTransaction();
            $stmt = $pdo->prepare("SELECT id, last_seq FROM auto_number_sequences WHERE sequence_key = ? LIMIT 1 FOR UPDATE");
            $stmt->execute([$sequenceKey]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$row) {
                $pdo->prepare("INSERT INTO auto_number_sequences (sequence_key, last_seq) VALUES (?, ?)")->execute([$sequenceKey, $startFrom - 1]);
                $seqId = (int)$pdo->lastInsertId();
                $lastSeq = $startFrom - 1;
            } else {
                $seqId = (int)($row['id'] ?? 0);
                $lastSeq = max($startFrom - 1, (int)($row['last_seq'] ?? 0));
            }

            do {
                $lastSeq++;
                $candidate = $prefix . str_pad((string)$lastSeq, $serialDigits, '0', STR_PAD_LEFT) . $affix;
                if ($numberType === 'application') {
                    $check = $pdo->prepare("SELECT COUNT(*) FROM students WHERE application_no = ? OR registration_no = ?");
                    $check->execute([$candidate, $candidate]);
                } elseif ($numberType === 'registration') {
                    $check = $pdo->prepare("SELECT COUNT(*) FROM students WHERE registration_no = ?");
                    $check->execute([$candidate]);
                } else {
                    $check = $pdo->prepare("SELECT COUNT(*) FROM students WHERE {$column} = ?");
                    $check->execute([$candidate]);
                }
                $exists = (int)$check->fetchColumn() > 0;
            } while ($exists);

            $pdo->prepare("UPDATE auto_number_sequences SET last_seq = ? WHERE id = ?")->execute([$lastSeq, $seqId]);
            $pdo->commit();
            return $candidate;
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            $fallbackSeed = str_pad((string)random_int(1, 999999), $serialDigits, '0', STR_PAD_LEFT);
            return $prefix . $fallbackSeed . $affix;
        }
    }
}

if (!function_exists('portalNumberingPreview')) {
    function portalNumberingPreview(PDO $pdo, string $numberType, array $source = [], array $override = []): string {
        $format = portalAutoNumberFormat($pdo, $numberType, $source);
        if ($override) {
            $format = array_merge($format, $override);
        }
        if ($numberType === 'registration' && !portalHasExplicitNumberFormat($pdo, 'registration', $source) && !$override) {
            return portalNumberingPreview($pdo, 'application', $source);
        }
        $ctx = portalResolveNumberingContext($pdo, $source);
        $prefix = portalApplyNumberTokens((string)($format['prefix'] ?? ''), $ctx);
        $affix = portalApplyNumberTokens((string)($format['affix'] ?? ''), $ctx);
        $serialDigits = max(1, min(12, (int)($format['serial_digits'] ?? 4)));
        $startFrom = max(1, (int)($format['start_from'] ?? 1));
        $serial = str_pad((string)$startFrom, $serialDigits, '0', STR_PAD_LEFT);
        return $prefix . $serial . $affix;
    }
}


if (!function_exists('portalStudentMatricPlan')) {
    function portalStudentMatricPlan(PDO $pdo, array $student): array {
        $ctx = portalStudentContext($pdo, $student);
        $programme = strtoupper(trim((string)($ctx['programme_name'] ?? '')));
        $facultyCode = strtoupper(trim((string)($ctx['faculty_code'] ?? '')));
        $year = portalAcademicYearNumber($pdo, (string)($ctx['session_name'] ?? ''));

        if ($facultyCode === '') $facultyCode = 'STD';

        if ($programme !== '' && strpos($programme, 'PDE') !== false) {
            return [
                'root_key' => 'PDE',
                'pattern' => 'PDE/' . $year . '/%04d',
            ];
        }

        if ($programme !== '' && strpos($programme, 'NCE') !== false && (strpos($programme, 'PART') !== false || strpos($programme, 'PT') !== false)) {
            $root = $facultyCode . '/PT';
            return [
                'root_key' => $root,
                'pattern' => $root . '/' . $year . '/%04d',
            ];
        }

        if ($programme !== '' && strpos($programme, 'DEGREE') !== false) {
            return [
                'root_key' => $facultyCode,
                'pattern' => $facultyCode . $year . '%04d',
            ];
        }

        if ($programme !== '' && strpos($programme, 'NCE') !== false) {
            return [
                'root_key' => $facultyCode,
                'pattern' => $facultyCode . '/' . $year . '/%04d',
            ];
        }

        return [
            'root_key' => $facultyCode,
            'pattern' => $facultyCode . '/' . $year . '/%04d',
        ];
    }
}


if (!function_exists('portalSyncStudentMatricValue')) {
    function portalSyncStudentMatricValue(PDO $pdo, int $studentId, string $matricNo): void {
        $matricNo = trim((string)$matricNo);
        if ($studentId <= 0 || $matricNo === '') return;
        try {
            $pdo->prepare("UPDATE students SET matric_no = CASE WHEN COALESCE(TRIM(matric_no),'') = '' THEN ? ELSE matric_no END WHERE id = ?")
                ->execute([$matricNo, $studentId]);
        } catch (Throwable $e) {}
        try {
            if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'students', 'matriculation')) {
                $pdo->prepare("UPDATE students SET matriculation = ? WHERE id = ?")
                    ->execute([$matricNo, $studentId]);
            }
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalRunAcceptanceFeeSuccessHooks')) {
    function portalRunAcceptanceFeeSuccessHooks(PDO $pdo, int $studentId, $paymentType = ''): array {
        $isAcceptance = portalPaymentTypeIsAcceptanceFee($paymentType);
        return ensureStudentNumberProgression($pdo, $studentId, [
            'allow_matric_generation' => $isAcceptance,
            'matric_trigger' => $isAcceptance ? 'acceptance_fee_payment' : '',
        ]);
    }
}

if (!function_exists('portalStudentAdmissionOfferGranted')) {
    function portalStudentAdmissionOfferGranted(array $student): bool {
        $status = strtolower(trim((string)($student['application_status'] ?? '')));
        return in_array($status, ['approved', 'admitted', 'given', 'offered'], true);
    }
}

if (!function_exists('portalStudentAdmissionOfferAccepted')) {
    function portalStudentAdmissionOfferAccepted(array $student): bool {
        return strtolower(trim((string)($student['offer_status'] ?? 'pending'))) === 'accepted';
    }
}

if (!function_exists('portalStudentQualifiesForAdmissionNumber')) {
    function portalStudentQualifiesForAdmissionNumber(array $student): bool {
        return portalStudentAdmissionOfferGranted($student) && portalStudentAdmissionOfferAccepted($student);
    }
}

if (!function_exists('portalStudentAdmissionFormLocked')) {
    function portalStudentAdmissionFormLocked(array $student): bool {
        if (portalStudentAdmissionOfferGranted($student)) return true;
        if (trim((string)($student['admission_no'] ?? '')) !== '') return true;
        $offerStatus = strtolower(trim((string)($student['offer_status'] ?? 'pending')));
        return in_array($offerStatus, ['accepted','rejected'], true);
    }
}

if (!function_exists('portalStudentAdmissionFormLockMessage')) {
    function portalStudentAdmissionFormLockMessage(array $student): string {
        if (portalStudentAdmissionOfferGranted($student)) {
            return 'Editing is no longer allowed because admission has already been given for your application.';
        }
        return 'Editing is no longer allowed for this application.';
    }
}

if (!function_exists('assignStudentAdmissionNumber')) {
    function assignStudentAdmissionNumber(PDO $pdo, int $studentId): string {
        if ($studentId <= 0) return '';
        portalEnsureNumberingSchema($pdo);

        try {
            $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $stmt->execute([$studentId]);
            $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$student) return '';

            $existing = trim((string)($student['admission_no'] ?? ''));
            if ($existing !== '') return $existing;
            if (!portalStudentQualifiesForAdmissionNumber($student)) return '';

            $admissionNo = portalGenerateAutoNumber($pdo, 'admission', $student, '');
            if ($admissionNo !== '') {
                try {
                    $pdo->prepare("UPDATE students SET admission_no = CASE WHEN COALESCE(TRIM(admission_no),'') = '' THEN ? ELSE admission_no END WHERE id = ?")
                        ->execute([$admissionNo, $studentId]);
                } catch (Throwable $e) {}

                $stmt = $pdo->prepare("SELECT admission_no FROM students WHERE id = ? LIMIT 1");
                $stmt->execute([$studentId]);
                $saved = trim((string)($stmt->fetchColumn() ?: ''));
                if ($saved !== '') return $saved;
            }
            return '';
        } catch (Throwable $e) {
            return '';
        }
    }
}

if (!function_exists('assignStudentMatricNumber')) {
    function assignStudentMatricNumber(PDO $pdo, int $studentId): string {
        if ($studentId <= 0) return '';

        try {
            $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $stmt->execute([$studentId]);
            $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$student) return '';

            $existing = trim((string)($student['matric_no'] ?? ''));
            if ($existing !== '') {
                portalSyncStudentMatricValue($pdo, $studentId, $existing);
                return $existing;
            }
            if (!portalStudentQualifiesForMatric($pdo, $student)) return '';

            $matricNo = portalGenerateAutoNumber($pdo, 'matric', $student, '');
            if ($matricNo !== '') {
                $pdo->prepare("UPDATE students SET matric_no = CASE WHEN COALESCE(TRIM(matric_no),'') = '' THEN ? ELSE matric_no END WHERE id = ?")
                    ->execute([$matricNo, $studentId]);
                $stmt = $pdo->prepare("SELECT matric_no FROM students WHERE id = ? LIMIT 1");
                $stmt->execute([$studentId]);
                $matricNo = trim((string)($stmt->fetchColumn() ?: $matricNo));
                if ($matricNo !== '') {
                    portalSyncStudentMatricValue($pdo, $studentId, $matricNo);
                }
            }
            return $matricNo;
        } catch (Throwable $e) {
            return '';
        }
    }
}

if (!function_exists('assignStudentRegistrationNumber')) {
    function assignStudentRegistrationNumber(PDO $pdo, int $studentId): string {
        if ($studentId <= 0) return '';
        portalEnsureNumberingSchema($pdo);
        try {
            $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $stmt->execute([$studentId]);
            $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$student) return '';

            $existing = trim((string)($student['registration_no'] ?? ''));
            if ($existing !== '') return $existing;

            if (!portalHasExplicitNumberFormat($pdo, 'registration', $student)) {
                $applicationExisting = trim((string)($student['application_no'] ?? ''));
                if ($applicationExisting === '') {
                    $applicationExisting = portalGenerateAutoNumber($pdo, 'application', $student, '');
                    if ($applicationExisting !== '') {
                        try { $pdo->prepare("UPDATE students SET application_no = ? WHERE id = ?")->execute([$applicationExisting, $studentId]); } catch (Throwable $e) {}
                    }
                }
                if ($applicationExisting !== '') {
                    try { $pdo->prepare("UPDATE students SET registration_no = ? WHERE id = ?")->execute([$applicationExisting, $studentId]); } catch (Throwable $e) {}
                }
                return $applicationExisting;
            }

            $registrationNo = portalGenerateAutoNumber($pdo, 'registration', $student, $existing);
            if ($registrationNo !== '') {
                try { $pdo->prepare("UPDATE students SET registration_no = ? WHERE id = ?")->execute([$registrationNo, $studentId]); } catch (Throwable $e) {}
            }
            return $registrationNo;
        } catch (Throwable $e) {
            return '';
        }
    }
}

if (!function_exists('ensureStudentNumberProgression')) {
    function ensureStudentNumberProgression(PDO $pdo, int $studentId, array $options = []): array {
        $out = ['application_no' => '', 'registration_no' => '', 'admission_no' => '', 'matric_no' => ''];
        if ($studentId <= 0) return $out;
        portalEnsureNumberingSchema($pdo);

        try {
            $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $stmt->execute([$studentId]);
            $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$student) return $out;

            $applicationNo = trim((string)($student['application_no'] ?? ''));
            $legacyNo = trim((string)($student['registration_no'] ?? ''));
            if ($applicationNo === '' && $legacyNo !== '' && !portalHasExplicitNumberFormat($pdo, 'registration', $student)) {
                $applicationNo = $legacyNo;
                try { $pdo->prepare("UPDATE students SET application_no = ? WHERE id = ?")->execute([$applicationNo, $studentId]); } catch (Throwable $e) {}
            }
            if ($applicationNo === '') {
                $applicationNo = portalGenerateAutoNumber($pdo, 'application', $student, '');
                if ($applicationNo !== '') {
                    try { $pdo->prepare("UPDATE students SET application_no = ? WHERE id = ?")->execute([$applicationNo, $studentId]); } catch (Throwable $e) {}
                }
            }
            $out['application_no'] = $applicationNo;

            $registrationNo = trim((string)($student['registration_no'] ?? ''));
            if ($registrationNo === '') {
                $registrationNo = assignStudentRegistrationNumber($pdo, $studentId);
            }
            $out['registration_no'] = $registrationNo;

            $allowAdmissionGeneration = !empty($options['allow_admission_generation']);
            $admissionTrigger = strtolower(trim((string)($options['admission_trigger'] ?? '')));
            if ($allowAdmissionGeneration && $admissionTrigger === 'admission_offer_accepted' && portalStudentQualifiesForAdmissionNumber($student)) {
                $out['admission_no'] = assignStudentAdmissionNumber($pdo, $studentId);
            } else {
                $out['admission_no'] = trim((string)($student['admission_no'] ?? ''));
            }

            $allowMatricGeneration = !empty($options['allow_matric_generation']);
            $matricTrigger = strtolower(trim((string)($options['matric_trigger'] ?? '')));
            if ($allowMatricGeneration && $matricTrigger === 'acceptance_fee_payment' && portalStudentQualifiesForMatric($pdo, $student)) {
                $out['matric_no'] = assignStudentMatricNumber($pdo, $studentId);
            } else {
                $out['matric_no'] = trim((string)($student['matric_no'] ?? ''));
                if ($out['matric_no'] !== '') {
                    portalSyncStudentMatricValue($pdo, $studentId, $out['matric_no']);
                }
            }
        } catch (Throwable $e) {}

        return $out;
    }
}


// -------------------------
// PAYMENT STATUS NORMALIZER
// -------------------------
// Older DBs used "Not Required" for items that were simply unpaid.
// The UI now uses "Not Paid" everywhere, while still accepting older values.
if (!function_exists('payment_status_label')) {
    function payment_status_label(?string $status): string {
        $s = trim((string)$status);
        if ($s === '' || strcasecmp($s, 'Pending') === 0 || strcasecmp($s, 'Not Required') === 0 || strcasecmp($s, 'Not Paid') === 0) {
            return 'Not Paid';
        }
        if (strcasecmp($s, 'Paid') === 0) return 'Paid';
        if (strcasecmp($s, 'Failed') === 0) return 'Failed';
        return $s;
    }
}
if (!function_exists('payment_status_is_paid')) {
    function payment_status_is_paid(?string $status): bool {
        return strcasecmp(trim((string)$status), 'Paid') === 0;
    }
}


if (!function_exists('portalRegistrationIsOpen')) {
    function portalRegistrationIsOpen($value): bool {
        $normalized = strtoupper(trim((string)$value));
        return in_array($normalized, ['YES', 'OPEN', '1', 'TRUE'], true);
    }
}
if (!function_exists('portalRegistrationOpenLabel')) {
    function portalRegistrationOpenLabel($value): string {
        return portalRegistrationIsOpen($value) ? 'OPEN' : 'CLOSED';
    }
}

if (!function_exists('portalRegistrationSettingsSupport')) {
    function portalRegistrationSettingsSupport(PDO $pdo): void {
        try { portalSelfHealAcademicSessions($pdo); } catch (Throwable $e) {}
        try { portalEnsureAcademicLevelsSchema($pdo); } catch (Throwable $e) {}
        try {
            if (!portalTableExistsSafe($pdo, 'registration_settings')) {
                $pdo->exec("CREATE TABLE IF NOT EXISTS registration_settings (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    session_name VARCHAR(30) NULL,
                    semester VARCHAR(30) NOT NULL DEFAULT 'First',
                    is_open VARCHAR(10) NOT NULL DEFAULT 'Yes',
                    faculty_id INT NULL,
                    department_id INT NULL,
                    programme_id INT NULL,
                    level VARCHAR(30) NULL,
                    min_units INT NOT NULL DEFAULT 0,
                    max_units INT NOT NULL DEFAULT 30,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
            }
        } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'registration_settings', 'session_name')) { $pdo->exec("ALTER TABLE registration_settings ADD COLUMN session_name VARCHAR(30) NULL"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'registration_settings', 'semester')) { $pdo->exec("ALTER TABLE registration_settings ADD COLUMN semester VARCHAR(30) NOT NULL DEFAULT 'First'"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'registration_settings', 'is_open')) { $pdo->exec("ALTER TABLE registration_settings ADD COLUMN is_open VARCHAR(10) NOT NULL DEFAULT 'Yes'"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'registration_settings', 'programme_id')) { $pdo->exec("ALTER TABLE registration_settings ADD COLUMN programme_id INT NULL AFTER department_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'registration_settings', 'min_units')) { $pdo->exec("ALTER TABLE registration_settings ADD COLUMN min_units INT NOT NULL DEFAULT 0"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'registration_settings', 'max_units')) { $pdo->exec("ALTER TABLE registration_settings ADD COLUMN max_units INT NOT NULL DEFAULT 30"); } } catch (Throwable $e) {}
        try { $pdo->exec("CREATE INDEX idx_regset_scope_match ON registration_settings (session_name, semester, faculty_id, department_id, programme_id, level)"); } catch (Throwable $e) {}
        try {
            if (portalTableExistsSafe($pdo, 'registration_settings') && portalColumnExistsSafe($pdo, 'registration_settings', 'session_name')) {
                $rows = $pdo->query("SELECT DISTINCT session_name FROM registration_settings WHERE COALESCE(TRIM(session_name),'')<>''")->fetchAll(PDO::FETCH_COLUMN) ?: [];
                foreach ($rows as $row) {
                    $name = portalNormalizeSessionNameSafe((string)$row);
                    if ($name !== '') { try { portalEnsureAcademicSessionRecord($pdo, $name); } catch (Throwable $e) {} }
                }
            }
        } catch (Throwable $e) {}
        try {
            if (portalTableExistsSafe($pdo, 'registration_settings') && portalColumnExistsSafe($pdo, 'registration_settings', 'level')) {
                $rows = $pdo->query("SELECT DISTINCT level FROM registration_settings WHERE COALESCE(TRIM(level),'')<>'' ORDER BY level ASC")->fetchAll(PDO::FETCH_COLUMN) ?: [];
                $sort = 100;
                foreach ($rows as $row) {
                    $level = trim((string)$row);
                    if ($level === '') continue;
                    try {
                        $st = $pdo->prepare("INSERT IGNORE INTO academic_levels (level_name, sort_order, is_active) VALUES (?, ?, 1)");
                        $st->execute([$level, $sort++]);
                    } catch (Throwable $e) {}
                }
            }
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalRegistrationRuleForContext')) {
    function portalRegistrationRuleForContext(PDO $pdo, array $context): array {
        portalRegistrationSettingsSupport($pdo);

        $sessionName = portalNormalizeSessionNameSafe((string)($context['session_name'] ?? ''));
        if ($sessionName === '') $sessionName = portalNormalizeSessionNameSafe((string)portalCurrentAcademicSessionName($pdo));
        $semester = (string)($context['semester'] ?? 'First');
        if (!in_array($semester, ['First', 'Second'], true)) $semester = 'First';

        $facultyId = (int)($context['faculty_id'] ?? 0);
        $departmentId = (int)($context['department_id'] ?? 0);
        $programmeId = (int)($context['programme_id'] ?? 0);
        $level = trim((string)($context['level'] ?? ''));

        $best = ['is_open' => 'Yes', 'min_units' => 0, 'max_units' => 30];
        $hasProgramme = portalColumnExistsSafe($pdo, 'registration_settings', 'programme_id');
        $useSessionId = portalColumnExistsSafe($pdo, 'registration_settings', 'session_id');
        $sessionId = null;

        if ($useSessionId && $sessionName !== '') {
            try {
                $sessionId = portalEnsureAcademicSessionRecord($pdo, $sessionName);
            } catch (Throwable $e) {
                $sessionId = null;
            }
        }

        $sql = "SELECT * FROM registration_settings WHERE ";
        $params = [];
        if ($useSessionId && (int)$sessionId > 0) {
            $sql .= "session_id = ? AND ";
            $params[] = (int)$sessionId;
        } else {
            $sql .= "COALESCE(session_name,'') = ? AND ";
            $params[] = (string)$sessionName;
        }
        $sql .= "semester = ?";
        $params[] = $semester;

        $orderParts = [
            "CASE WHEN COALESCE(faculty_id,0) = 0 THEN 0 WHEN faculty_id = ? THEN 2 ELSE -50 END DESC",
            "CASE WHEN COALESCE(department_id,0) = 0 THEN 0 WHEN department_id = ? THEN 4 ELSE -100 END DESC",
        ];
        $params[] = $facultyId;
        $params[] = $departmentId;

        if ($hasProgramme) {
            $orderParts[] = "CASE WHEN COALESCE(programme_id,0) = 0 THEN 0 WHEN programme_id = ? THEN 8 ELSE -200 END DESC";
            $params[] = $programmeId;
        }

        $orderParts[] = "CASE WHEN COALESCE(level,'') = '' THEN 0 WHEN level = ? THEN 16 ELSE -400 END DESC";
        $params[] = $level;

        if ($facultyId > 0) $sql .= " AND (faculty_id IS NULL OR faculty_id = 0 OR faculty_id = ?)";
        if ($facultyId > 0) $params[] = $facultyId;
        if ($departmentId > 0) $sql .= " AND (department_id IS NULL OR department_id = 0 OR department_id = ?)";
        if ($departmentId > 0) $params[] = $departmentId;
        if ($hasProgramme && $programmeId > 0) $sql .= " AND (programme_id IS NULL OR programme_id = 0 OR programme_id = ?)";
        if ($hasProgramme && $programmeId > 0) $params[] = $programmeId;
        if ($level !== '') $sql .= " AND (level IS NULL OR level = '' OR level = ?)";
        if ($level !== '') $params[] = $level;

        $sql .= " ORDER BY " . implode(', ', $orderParts) . ", id DESC LIMIT 1";

        try {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($row) {
                $best['is_open'] = portalNormalizeRegistrationOpenValue($row['is_open'] ?? 'Yes');
                $best['min_units'] = (int)($row['min_units'] ?? 0);
                $best['max_units'] = (int)($row['max_units'] ?? 30);
                $best['rule_id'] = (int)($row['id'] ?? 0);
            }
        } catch (Throwable $e) {}

        return $best;
    }
}
if (!function_exists('portalNormalizeRegistrationOpenValue')) {
    function portalNormalizeRegistrationOpenValue($value): string {
        return portalRegistrationIsOpen($value) ? 'Yes' : 'No';
    }
}
if (!function_exists('portalNormalizeStudentPaymentPlanPermission')) {
    function portalNormalizeStudentPaymentPlanPermission($value): string {
        $normalized = strtolower(trim((string)$value));
        if (in_array($normalized, ['one-time only', 'one time only', 'one_time_only', 'one-time', 'one time'], true)) {
            return 'One-time Only';
        }
        if (in_array($normalized, ['installment only', 'installments only', 'installment_only', 'installment'], true)) {
            return 'Installment Only';
        }
        return 'Both';
    }
}
if (!function_exists('portalStudentPaymentPlanPermission')) {
    function portalStudentPaymentPlanPermission(array $student): string {
        return portalNormalizeStudentPaymentPlanPermission($student['payment_plan_permission'] ?? 'Both');
    }
}
if (!function_exists('portalStudentPaymentPlanAllowed')) {
    function portalStudentPaymentPlanAllowed(array $student, int $totalInstallments): bool {
        $permission = portalStudentPaymentPlanPermission($student);
        if ($permission === 'One-time Only') {
            return $totalInstallments <= 1;
        }
        if ($permission === 'Installment Only') {
            return $totalInstallments > 1;
        }
        return true;
    }
}
if (!function_exists('portalAssertStudentSchoolPaymentPlanAllowed')) {
    function portalAssertStudentSchoolPaymentPlanAllowed(PDO $pdo, int $studentId, int $totalInstallments): void {
        if ($studentId <= 0) {
            throw new RuntimeException('Invalid student session.');
        }

        $student = [];
        try {
            if (portalColumnExistsSafe($pdo, 'students', 'payment_plan_permission')) {
                $stmt = $pdo->prepare("SELECT payment_plan_permission FROM students WHERE id = ? LIMIT 1");
            } else {
                $stmt = $pdo->prepare("SELECT id FROM students WHERE id = ? LIMIT 1");
            }
            $stmt->execute([$studentId]);
            $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $student = [];
        }

        $permission = portalStudentPaymentPlanPermission($student);
        if (!portalStudentPaymentPlanAllowed($student, $totalInstallments)) {
            if ($permission === 'One-time Only') {
                throw new RuntimeException('This student is restricted to one-time school fee payment only. Installment payment is not allowed.');
            }
            if ($permission === 'Installment Only') {
                throw new RuntimeException('This student is restricted to installment school fee payment only. One-time payment is not allowed.');
            }
        }
    }
}
if (!function_exists('portalVerifySuperadminCredential')) {
    function portalVerifySuperadminCredential(PDO $pdo, string $usernameOrEmail, string $password): array {
        $usernameOrEmail = trim($usernameOrEmail);
        $password = (string)$password;
        if ($usernameOrEmail === '' || $password === '') {
            return ['ok' => false, 'message' => 'Superadmin username/email and password are required.'];
        }
        try {
            $stmt = $pdo->prepare("SELECT * FROM admins WHERE username = ? OR email = ? LIMIT 1");
            $stmt->execute([$usernameOrEmail, $usernameOrEmail]);
            $admin = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$admin) {
                return ['ok' => false, 'message' => 'Superadmin verification failed.'];
            }
            $role = strtolower(trim((string)($admin['role'] ?? '')));
            $username = strtolower(trim((string)($admin['username'] ?? '')));
            $fullName = strtolower(trim((string)($admin['full_name'] ?? '')));
            $isSuper = !empty($admin['is_superadmin']) || $role === 'superadmin' || $role === 'super admin' || $role === 'super-admin' || $role === 'super administrator' || $role === 'superadministrator' || $username === 'superadmin' || str_contains($fullName, 'superadmin');
            if (!$isSuper) {
                return ['ok' => false, 'message' => 'The supplied account is not a Superadmin account.'];
            }
            if (!password_verify($password, (string)($admin['password'] ?? ''))) {
                return ['ok' => false, 'message' => 'Superadmin password verification failed.'];
            }
            return ['ok' => true, 'admin' => $admin];
        } catch (Throwable $e) {
            return ['ok' => false, 'message' => 'Unable to verify Superadmin credentials right now.'];
        }
    }
}


if (!function_exists('portalVerifyLoggedInSuperadminPassword')) {
    function portalVerifyLoggedInSuperadminPassword(PDO $pdo, string $password): array {
        $password = (string)$password;
        if (!isAdmin()) {
            return ['ok' => false, 'message' => 'Admin session is required for this confirmation.'];
        }
        if (trim($password) === '') {
            return ['ok' => false, 'message' => 'Superadmin password is required to confirm this academic mapping change.'];
        }
        $adminId = portalCurrentAdminId();
        if ($adminId <= 0) {
            return ['ok' => false, 'message' => 'Unable to determine the current admin account for confirmation.'];
        }
        try {
            $st = $pdo->prepare("SELECT * FROM admins WHERE id = ? LIMIT 1");
            $st->execute([$adminId]);
            $admin = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$admin) {
                return ['ok' => false, 'message' => 'Unable to load the current admin account for confirmation.'];
            }

            $role = strtolower(trim((string)($admin['role'] ?? '')));
            $username = strtolower(trim((string)($admin['username'] ?? '')));
            $fullName = strtolower(trim((string)($admin['full_name'] ?? '')));
            $email = strtolower(trim((string)($admin['email'] ?? '')));
            $looksSuper = !empty($admin['is_superadmin'])
                || portalIsSuperadminRoleName((string)($admin['role'] ?? ''))
                || $username === 'superadmin'
                || str_contains($fullName, 'superadmin')
                || str_contains($email, 'superadmin');

            if (!$looksSuper || !isSuperadmin($pdo)) {
                return ['ok' => false, 'message' => 'Only a Superadmin can confirm changes to the academic mapping section.'];
            }
            if (!password_verify($password, (string)($admin['password'] ?? ''))) {
                return ['ok' => false, 'message' => 'Incorrect Superadmin password. Academic mapping was not changed.'];
            }
            return ['ok' => true, 'admin' => $admin];
        } catch (Throwable $e) {
            return ['ok' => false, 'message' => 'Unable to verify the Superadmin password right now.'];
        }
    }
}

if (!function_exists('ensureStudentFinanceEnhancements')) {
    function ensureStudentFinanceEnhancements(PDO $pdo): void {
        try { if (!portalColumnExistsSafe($pdo, 'students', 'accommodation_status')) { $pdo->exec("ALTER TABLE students ADD COLUMN accommodation_status ENUM('Campus','Off-Campus') NOT NULL DEFAULT 'Campus' AFTER school_fee_reference"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'hostel_fee_paid_session')) { $pdo->exec("ALTER TABLE students ADD COLUMN hostel_fee_paid_session VARCHAR(80) NULL AFTER hostel_fee_reference"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'school_fee_paid_session')) { $pdo->exec("ALTER TABLE students ADD COLUMN school_fee_paid_session VARCHAR(80) NULL AFTER school_fee_reference"); } } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS email_templates (
                id INT AUTO_INCREMENT PRIMARY KEY,
                template_key VARCHAR(80) NOT NULL UNIQUE,
                template_name VARCHAR(120) NOT NULL,
                subject VARCHAR(255) NOT NULL,
                body_html MEDIUMTEXT NULL,
                is_active TINYINT(1) NOT NULL DEFAULT 1,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        $defaults = [
            'registration_payment' => ['Registration Fee Payment', 'Registration Fee Payment Confirmation'],
            'acceptance_payment' => ['Acceptance Fee Payment', 'Acceptance Fee Payment Confirmation'],
            'hostel_payment' => ['Hostel Fee Payment', 'Hostel Fee Payment Confirmation'],
            'school_payment' => ['School Fee Payment', 'School Fee Payment Confirmation'],
            'payment_approved' => ['Payment Approved', 'Payment Approved - HontTech Registration'],
            'admission_offer_accepted' => ['Admission Offer Accepted', 'Admission Offer Accepted - Hontech Portal'],
            'login_notification' => ['Login Notification', 'New Login Notification - Hontech Portal'],
            'payment_receipt' => ['Payment Receipt', 'Payment Receipt - Hontech Portal'],
            'application_status_update' => ['Application Status Update', 'Application Status Updated - Hontech Portal'],
        ];
        foreach ($defaults as $k => $meta) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO email_templates (template_key, template_name, subject, body_html, is_active) VALUES (?, ?, ?, ?, 1)");
                $st->execute([$k, $meta[0], $meta[1], '']);
            } catch (Throwable $e) {}
        }
    }
}
if (isset($pdo) && ($pdo instanceof PDO)) { try { ensureStudentFinanceEnhancements($pdo); } catch (Throwable $e) {} }

if (!function_exists('portalTableExistsSafe')) {
    function portalTableExistsSafe(PDO $pdo, string $table): bool {
        try {
            $stmt = $pdo->prepare("SHOW TABLES LIKE ?");
            $stmt->execute([$table]);
            return (bool)$stmt->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}
if (!function_exists('portalIndexExistsSafe')) {
    function portalIndexExistsSafe(PDO $pdo, string $table, string $indexName): bool {
        try {
            $stmt = $pdo->prepare("SHOW INDEX FROM `" . $table . "` WHERE Key_name = ?");
            $stmt->execute([$indexName]);
            return (bool)$stmt->fetch(PDO::FETCH_ASSOC);
        } catch (Throwable $e) {
            return false;
        }
    }
}
if (!function_exists('portalEnsureSchoolFeeRegistrationControlSupport')) {
    function portalEnsureSchoolFeeRegistrationControlSupport(PDO $pdo): void {
        try {
            if (portalTableExistsSafe($pdo, 'students')) {
                if (!portalColumnExistsSafe($pdo, 'students', 'payment_plan_permission')) {
                    $pdo->exec("ALTER TABLE students ADD COLUMN payment_plan_permission VARCHAR(50) NOT NULL DEFAULT 'Both' AFTER school_fee_status");
                }
                if (!portalColumnExistsSafe($pdo, 'students', 'school_fee_paid_session')) {
                    $pdo->exec("ALTER TABLE students ADD COLUMN school_fee_paid_session VARCHAR(80) NULL AFTER school_fee_reference");
                }
            }
        } catch (Throwable $e) {}
        try {
            if (portalTableExistsSafe($pdo, 'payment_transactions')) {
                if (!portalColumnExistsSafe($pdo, 'payment_transactions', 'academic_session')) {
                    $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN academic_session VARCHAR(80) NULL AFTER paystack_reference");
                }
                if (!portalColumnExistsSafe($pdo, 'payment_transactions', 'semester')) {
                    $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN semester VARCHAR(40) NULL AFTER academic_session");
                }
                if (!portalColumnExistsSafe($pdo, 'payment_transactions', 'installment_number')) {
                    $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN installment_number INT NOT NULL DEFAULT 1 AFTER qr_code_data");
                }
                if (!portalColumnExistsSafe($pdo, 'payment_transactions', 'total_installments')) {
                    $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN total_installments INT NOT NULL DEFAULT 1 AFTER installment_number");
                }
                if (!portalIndexExistsSafe($pdo, 'payment_transactions', 'idx_pt_school_fee_lookup')) {
                    try { $pdo->exec("CREATE INDEX idx_pt_school_fee_lookup ON payment_transactions(student_id, transaction_type, academic_session, semester, status)"); } catch (Throwable $e) {}
                }
            }
        } catch (Throwable $e) {}
        try {
            if (portalTableExistsSafe($pdo, 'course_registrations') && !portalIndexExistsSafe($pdo, 'course_registrations', 'idx_cr_student_session_sem')) {
                try { $pdo->exec("CREATE INDEX idx_cr_student_session_sem ON course_registrations(student_id, session_name, semester)"); } catch (Throwable $e) {}
            }
        } catch (Throwable $e) {}
        try {
            setSystemSettingValue($pdo, 'school_fee_registration_strict', portalSettingValueSafe($pdo, 'school_fee_registration_strict', '1'), 'Require school-fee clearance before course registration');
            $currentSemester = portalSettingValueSafe($pdo, 'academic_semester', portalSettingValueSafe($pdo, 'current_semester', ''));
            if (trim($currentSemester) === '') {
                setSystemSettingValue($pdo, 'academic_semester', 'First Semester', 'Current academic semester');
            }
            $currentSession = portalNormalizeSessionNameSafe(portalCurrentAcademicSessionName($pdo));
            if ($currentSession !== '' && portalTableExistsSafe($pdo, 'academic_sessions')) {
                $stmt = $pdo->prepare("SELECT id FROM academic_sessions WHERE name = ? LIMIT 1");
                $stmt->execute([$currentSession]);
                $id = (int)($stmt->fetchColumn() ?: 0);
                if ($id > 0) {
                    try { $pdo->prepare("UPDATE academic_sessions SET is_current = CASE WHEN id = ? THEN 1 ELSE is_current END WHERE id = ?")->execute([$id, $id]); } catch (Throwable $e) {}
                } else {
                    try { $pdo->prepare("INSERT INTO academic_sessions (name, is_current) VALUES (?, 1)")->execute([$currentSession]); } catch (Throwable $e) {}
                }
            }
            if ($currentSession !== '' && portalTableExistsSafe($pdo, 'registration_settings')) {
                $currentSemesterNorm = portalNormalizeSemesterName(portalCurrentAcademicSemesterName($pdo));
                $stmt = $pdo->prepare("SELECT COUNT(*) FROM registration_settings WHERE COALESCE(session_name,'') = ? AND semester = ?");
                $stmt->execute([$currentSession, $currentSemesterNorm]);
                if ((int)$stmt->fetchColumn() === 0) {
                    try {
                        $ins = $pdo->prepare("INSERT INTO registration_settings (session_name, semester, is_open, faculty_id, department_id, programme_id, level, min_units, max_units) VALUES (?,?,?,?,?,?,?,?,?)");
                        $ins->execute([$currentSession, $currentSemesterNorm, 'Yes', null, null, null, null, 0, 30]);
                    } catch (Throwable $e) {}
                }
            }
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalSessionSortValue')) {
    function portalSessionSortValue(string $sessionName): int {
        $sessionName = portalNormalizeSessionNameSafe($sessionName);
        if (preg_match('/^(\d{4})\/(\d{4})$/', $sessionName, $m)) {
            return ((int)$m[1] * 10000) + (int)$m[2];
        }
        if (preg_match('/\d{4}/', $sessionName, $m)) {
            return (int)$m[0] * 10000;
        }
        return 0;
    }
}
if (!function_exists('portalCollectStudentKnownSessions')) {
    function portalCollectStudentKnownSessions(PDO $pdo, array $student): array {
        $studentId = (int)($student['id'] ?? 0);
        $out = [];
        foreach (['session', 'session_name', 'school_fee_paid_session'] as $field) {
            $value = portalNormalizeSessionNameSafe((string)($student[$field] ?? ''));
            if ($value !== '') $out[$value] = true;
        }
        if ($studentId > 0) {
            try {
                $stmt = $pdo->prepare("SELECT DISTINCT academic_session FROM payment_transactions WHERE student_id = ? AND COALESCE(academic_session,'') <> ''");
                $stmt->execute([$studentId]);
                foreach (($stmt->fetchAll(PDO::FETCH_COLUMN) ?: []) as $value) {
                    $value = portalNormalizeSessionNameSafe((string)$value);
                    if ($value !== '') $out[$value] = true;
                }
            } catch (Throwable $e) {}
            try {
                $stmt = $pdo->prepare("SELECT DISTINCT session_name FROM course_registrations WHERE student_id = ? AND COALESCE(session_name,'') <> ''");
                $stmt->execute([$studentId]);
                foreach (($stmt->fetchAll(PDO::FETCH_COLUMN) ?: []) as $value) {
                    $value = portalNormalizeSessionNameSafe((string)$value);
                    if ($value !== '') $out[$value] = true;
                }
            } catch (Throwable $e) {}
        }
        $current = portalNormalizeSessionNameSafe(portalCurrentAcademicSessionName($pdo));
        if ($current !== '') $out[$current] = true;
        $sessions = array_keys($out);
        usort($sessions, fn($a, $b) => portalSessionSortValue($a) <=> portalSessionSortValue($b));
        return $sessions;
    }
}

if (!function_exists('portalSchoolFeeSettingsSupport')) {
    function portalSchoolFeeSettingsSupport(PDO $pdo): void {
        try { if (function_exists('portalEnsureApplicationAcademicFieldSupport')) { portalEnsureApplicationAcademicFieldSupport($pdo); } } catch (Throwable $e) {}
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS school_fee_settings (
                id INT AUTO_INCREMENT PRIMARY KEY,
                fee_type VARCHAR(40) NOT NULL DEFAULT 'school',
                custom_title VARCHAR(150) NULL,
                session_name VARCHAR(50) NULL,
                semester VARCHAR(40) NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                level VARCHAR(50) NULL,
                amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
                is_active TINYINT(1) NOT NULL DEFAULT 1,
                scope_hash VARCHAR(64) NULL,
                component_json LONGTEXT NULL,
                notes VARCHAR(255) NULL,
                created_by INT NULL,
                updated_by INT NULL,
                created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_sfs_fee_type (fee_type, is_active),
                INDEX idx_sfs_scope_lookup (fee_type, session_name, semester, faculty_id, department_id, programme_id, level, is_active),
                INDEX idx_sfs_scope_hash (scope_hash),
                INDEX idx_sfs_amount (amount),
                INDEX idx_sfs_active (is_active)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        $columns = [
            'fee_type' => "ALTER TABLE school_fee_settings ADD COLUMN fee_type VARCHAR(40) NOT NULL DEFAULT 'school' AFTER id",
            'custom_title' => "ALTER TABLE school_fee_settings ADD COLUMN custom_title VARCHAR(150) NULL AFTER fee_type",
            'session_name' => "ALTER TABLE school_fee_settings ADD COLUMN session_name VARCHAR(50) NULL AFTER custom_title",
            'semester' => "ALTER TABLE school_fee_settings ADD COLUMN semester VARCHAR(40) NULL AFTER session_name",
            'faculty_id' => "ALTER TABLE school_fee_settings ADD COLUMN faculty_id INT NULL AFTER semester",
            'department_id' => "ALTER TABLE school_fee_settings ADD COLUMN department_id INT NULL AFTER faculty_id",
            'programme_id' => "ALTER TABLE school_fee_settings ADD COLUMN programme_id INT NULL AFTER department_id",
            'level' => "ALTER TABLE school_fee_settings ADD COLUMN level VARCHAR(50) NULL AFTER programme_id",
            'amount' => "ALTER TABLE school_fee_settings ADD COLUMN amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER level",
            'is_active' => "ALTER TABLE school_fee_settings ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER amount",
            'scope_hash' => "ALTER TABLE school_fee_settings ADD COLUMN scope_hash VARCHAR(64) NULL AFTER is_active",
            'component_json' => "ALTER TABLE school_fee_settings ADD COLUMN component_json LONGTEXT NULL AFTER scope_hash",
            'notes' => "ALTER TABLE school_fee_settings ADD COLUMN notes VARCHAR(255) NULL AFTER component_json",
            'created_by' => "ALTER TABLE school_fee_settings ADD COLUMN created_by INT NULL AFTER notes",
            'updated_by' => "ALTER TABLE school_fee_settings ADD COLUMN updated_by INT NULL AFTER created_by",
            'created_at' => "ALTER TABLE school_fee_settings ADD COLUMN created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP AFTER updated_by",
            'updated_at' => "ALTER TABLE school_fee_settings ADD COLUMN updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at",
        ];
        foreach ($columns as $column => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, 'school_fee_settings', $column)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) {}
        }

        try { if (!portalIndexExistsSafe($pdo, 'school_fee_settings', 'idx_sfs_fee_type')) { $pdo->exec("CREATE INDEX idx_sfs_fee_type ON school_fee_settings(fee_type, is_active)"); } } catch (Throwable $e) {}
        try { if (!portalIndexExistsSafe($pdo, 'school_fee_settings', 'idx_sfs_scope_lookup')) { $pdo->exec("CREATE INDEX idx_sfs_scope_lookup ON school_fee_settings(fee_type, session_name, semester, faculty_id, department_id, programme_id, level, is_active)"); } } catch (Throwable $e) {}
        try { if (!portalIndexExistsSafe($pdo, 'school_fee_settings', 'idx_sfs_scope_hash')) { $pdo->exec("CREATE INDEX idx_sfs_scope_hash ON school_fee_settings(scope_hash)"); } } catch (Throwable $e) {}
        try { if (!portalIndexExistsSafe($pdo, 'school_fee_settings', 'idx_sfs_amount')) { $pdo->exec("CREATE INDEX idx_sfs_amount ON school_fee_settings(amount)"); } } catch (Throwable $e) {}
        try { if (!portalIndexExistsSafe($pdo, 'school_fee_settings', 'idx_sfs_active')) { $pdo->exec("CREATE INDEX idx_sfs_active ON school_fee_settings(is_active)"); } } catch (Throwable $e) {}

        try {
            if (portalTableExistsSafe($pdo, 'payment_transactions')) {
                if (!portalColumnExistsSafe($pdo, 'payment_transactions', 'school_fee_setting_id')) {
                    $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN school_fee_setting_id INT NULL AFTER semester");
                }
                if (!portalColumnExistsSafe($pdo, 'payment_transactions', 'school_fee_scope_hash')) {
                    $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN school_fee_scope_hash VARCHAR(64) NULL AFTER school_fee_setting_id");
                }
                if (!portalIndexExistsSafe($pdo, 'payment_transactions', 'idx_pt_school_fee_setting')) {
                    $pdo->exec("CREATE INDEX idx_pt_school_fee_setting ON payment_transactions(school_fee_setting_id, school_fee_scope_hash)");
                }
            }
        } catch (Throwable $e) {}

        try { $pdo->exec("UPDATE school_fee_settings SET fee_type = 'school' WHERE COALESCE(fee_type,'') = ''"); } catch (Throwable $e) {}
        try { if (function_exists('portalSeedManagedFeesIntoManager')) portalSeedManagedFeesIntoManager($pdo); } catch (Throwable $e) {}
    }
}
if (!function_exists('portalNormalizeManagedFeeType')) {
    function portalNormalizeManagedFeeType(string $feeType): string {
        $feeType = strtolower(trim($feeType));
        return in_array($feeType, ['registration','acceptance','hostel','school','custom'], true) ? $feeType : 'school';
    }
}
if (!function_exists('portalManagedFeeTypeOptions')) {
    function portalManagedFeeTypeOptions(): array {
        return [
            'registration' => 'Registration Fee',
            'acceptance' => 'Acceptance Fee',
            'hostel' => 'Hostel Fee',
            'school' => 'School Fee',
            'custom' => 'Custom Fee',
        ];
    }
}
if (!function_exists('portalManagedFeeSettingKey')) {
    function portalManagedFeeSettingKey(string $feeType): string {
        switch (portalNormalizeManagedFeeType($feeType)) {
            case 'registration': return 'registration_fee';
            case 'acceptance': return 'acceptance_fee';
            case 'hostel': return 'hostel_fee';
            case 'custom': return 'custom_fee';
            default: return 'school_fee';
        }
    }
}
if (!function_exists('portalManagedFeeLabel')) {
    function portalManagedFeeLabel(string $feeType): string {
        switch (portalNormalizeManagedFeeType($feeType)) {
            case 'registration': return 'Registration Fee';
            case 'acceptance': return 'Acceptance Fee';
            case 'hostel': return 'Hostel Fee';
            case 'custom': return 'Custom Fee';
            default: return 'School Fee';
        }
    }
}
if (!function_exists('portalManagedFeeDisplayLabel')) {
    function portalManagedFeeDisplayLabel(array $row): string {
        $base = portalManagedFeeLabel((string)($row['fee_type'] ?? 'school'));
        if (portalNormalizeManagedFeeType((string)($row['fee_type'] ?? 'school')) === 'custom') {
            $title = trim((string)($row['custom_title'] ?? $row['notes'] ?? ''));
            if ($title !== '') {
                return $base . ' - ' . $title;
            }
        }
        return $base;
    }
}

if (!function_exists('portalFeesDisplayOrFallback')) {
    function portalFeesDisplayOrFallback($value, string $fallback): string {
        $value = is_string($value) ? trim($value) : (string)$value;
        return $value !== '' ? $value : $fallback;
    }
}
if (!function_exists('portalNormalizeInlineWhitespace')) {
    function portalNormalizeInlineWhitespace(string $value): string {
        $value = trim($value);
        if ($value === '') return '';
        return preg_replace('/\s+/u', ' ', $value) ?: $value;
    }
}
if (!function_exists('portalManagedFeeScopeHash')) {
    function portalManagedFeeScopeHash(array $scope, string $feeType = 'school'): string {
        $feeType = portalNormalizeManagedFeeType($feeType);
        $sessionName = portalNormalizeSessionNameSafe((string)($scope['session_name'] ?? ''));
        $semester = trim((string)($scope['semester'] ?? ''));
        $semester = $semester !== '' ? portalNormalizeSemesterName($semester) : '';
        $facultyId = max(0, (int)($scope['faculty_id'] ?? 0));
        $departmentId = max(0, (int)($scope['department_id'] ?? 0));
        $programmeId = max(0, (int)($scope['programme_id'] ?? 0));
        $level = strtoupper(trim((string)($scope['level'] ?? '')));
        $customTitle = trim((string)($scope['custom_title'] ?? ''));
        if ($feeType === 'school') {
            return sha1(json_encode([$sessionName, $semester, $facultyId, $departmentId, $programmeId, $level], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        }
        if ($feeType === 'custom') {
            return sha1(json_encode([$feeType, $customTitle, $sessionName, $semester, $facultyId, $departmentId, $programmeId, $level], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        }
        return sha1(json_encode([$feeType, $sessionName, $semester, $facultyId, $departmentId, $programmeId, $level], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    }
}
if (!function_exists('portalManagedFeeRuleSpecificity')) {
    function portalManagedFeeRuleSpecificity(array $row): int {
        $score = 0;
        if (trim((string)($row['session_name'] ?? '')) !== '') $score += 2;
        if (trim((string)($row['semester'] ?? '')) !== '') $score += 4;
        if ((int)($row['faculty_id'] ?? 0) > 0) $score += 8;
        if ((int)($row['department_id'] ?? 0) > 0) $score += 16;
        if ((int)($row['programme_id'] ?? 0) > 0) $score += 32;
        if (trim((string)($row['level'] ?? '')) !== '') $score += 64;
        return $score;
    }
}
if (!function_exists('portalManagedFeeRuleMatchesContext')) {
    function portalManagedFeeRuleMatchesContext(array $row, array $ctx, string $feeType = 'school'): bool {
        $feeType = portalNormalizeManagedFeeType($feeType);
        $rowType = portalNormalizeManagedFeeType((string)($row['fee_type'] ?? 'school'));
        if ($rowType !== $feeType) return false;
        return portalSchoolFeeRuleMatchesContext($row, $ctx);
    }
}
if (!function_exists('portalManagedFeeSyncLegacySetting')) {
    function portalManagedFeeSyncLegacySetting(PDO $pdo, string $feeType): void {
        $feeType = portalNormalizeManagedFeeType($feeType);
        if ($feeType === 'custom') return;
        $key = portalManagedFeeSettingKey($feeType);
        $label = portalManagedFeeLabel($feeType);
        try {
            $st = $pdo->prepare("SELECT amount FROM school_fee_settings WHERE COALESCE(fee_type,'school') = ? AND COALESCE(is_active,1) = 1 AND COALESCE(session_name,'') = '' AND COALESCE(semester,'') = '' AND COALESCE(faculty_id,0) = 0 AND COALESCE(department_id,0) = 0 AND COALESCE(programme_id,0) = 0 AND COALESCE(level,'') = '' ORDER BY id DESC LIMIT 1");
            $st->execute([$feeType]);
            $amount = (float)($st->fetchColumn() ?: 0);
            setSystemSettingValue($pdo, $key, number_format(max(0, $amount), 2, '.', ''), $label . ' managed by Fees Manager');
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalManagedFeeCleanReturnUrl')) {
    function portalManagedFeeCleanReturnUrl(string $url, string $default = '?page=admin_fees_manager'): string {
        $url = trim($url);
        if ($url === '') return $default;
        if (strpos($url, '?page=admin_fees_manager') !== 0 && strpos($url, '?page=admin_school_fee_settings') !== 0) {
            return $default;
        }
        $parts = parse_url($url);
        $path = (string)($parts['path'] ?? '');
        $query = [];
        if (!empty($parts['query'])) {
            parse_str((string)$parts['query'], $query);
        } elseif (strpos($url, '?') === 0) {
            parse_str(ltrim($url, '?'), $query);
        }
        unset($query['edit_fee_id'], $query['action'], $query['_token']);
        if (empty($query['page'])) {
            $query['page'] = (strpos($url, 'admin_school_fee_settings') !== false) ? 'admin_school_fee_settings' : 'admin_fees_manager';
        }
        $built = '?' . http_build_query($query);
        return $built !== '?' ? $built : $default;
    }
}
if (!function_exists('portalManagedFeeSyncAfterMutation')) {
    function portalManagedFeeSyncAfterMutation(PDO $pdo, string $feeType): void {
        $feeType = portalNormalizeManagedFeeType($feeType);
        try { portalManagedFeeSyncLegacySetting($pdo, $feeType); } catch (Throwable $e) {}
        try { if (function_exists('portalSeedManagedFeesIntoManager')) portalSeedManagedFeesIntoManager($pdo); } catch (Throwable $e) {}
    }
}
if (!function_exists('portalSeedManagedFeesIntoManager')) {
    function portalSeedManagedFeesIntoManager(PDO $pdo): void {
        $types = ['registration','acceptance','hostel','school'];
        $adminId = (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0);
        foreach ($types as $feeType) {
            $settingKey = portalManagedFeeSettingKey($feeType);
            $amount = (float)portalSettingValueSafe($pdo, $settingKey, '0');
            if ($amount <= 0) continue;
            try {
                $st = $pdo->prepare("SELECT id FROM school_fee_settings WHERE COALESCE(fee_type,'school') = ? AND COALESCE(session_name,'') = '' AND COALESCE(semester,'') = '' AND COALESCE(faculty_id,0) = 0 AND COALESCE(department_id,0) = 0 AND COALESCE(programme_id,0) = 0 AND COALESCE(level,'') = '' ORDER BY id ASC LIMIT 1");
                $st->execute([$feeType]);
                $existingId = (int)($st->fetchColumn() ?: 0);
                $scopeHash = portalManagedFeeScopeHash([], $feeType);
                if ($existingId > 0) {
                    $pdo->prepare("UPDATE school_fee_settings SET fee_type = ?, amount = CASE WHEN COALESCE(amount,0) <= 0 THEN ? ELSE amount END, scope_hash = CASE WHEN COALESCE(scope_hash,'') = '' THEN ? ELSE scope_hash END, updated_by = ?, updated_at = NOW() WHERE id = ?")
                        ->execute([$feeType, $amount, $scopeHash, $adminId > 0 ? $adminId : null, $existingId]);
                } else {
                    $note = portalManagedFeeLabel($feeType) . ' imported from existing portal fee settings';
                    $pdo->prepare("INSERT INTO school_fee_settings (fee_type, session_name, semester, faculty_id, department_id, programme_id, level, amount, is_active, scope_hash, notes, created_by, updated_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)")
                        ->execute([$feeType, null, null, null, null, null, null, $amount, 1, $scopeHash, $note, $adminId > 0 ? $adminId : null, $adminId > 0 ? $adminId : null]);
                }
                portalManagedFeeSyncLegacySetting($pdo, $feeType);
            } catch (Throwable $e) {}
        }
    }
}
if (!function_exists('portalSchoolFeeScopeHash')) {
    function portalSchoolFeeScopeHash(array $scope): string {
        return portalManagedFeeScopeHash($scope, 'school');
    }
}
if (!function_exists('portalSchoolFeeRuleSpecificity')) {
    function portalSchoolFeeRuleSpecificity(array $row): int {
        return portalManagedFeeRuleSpecificity($row);
    }
}
if (!function_exists('portalSchoolFeeRuleMatchesContext')) {
    function portalSchoolFeeRuleMatchesContext(array $row, array $ctx): bool {
        $ctxSession = portalNormalizeSessionNameSafe((string)($ctx['session_name'] ?? ''));
        $ctxSemester = trim((string)($ctx['semester'] ?? ''));
        $ctxSemester = $ctxSemester !== '' ? portalNormalizeSemesterName($ctxSemester) : '';
        $ctxFaculty = max(0, (int)($ctx['faculty_id'] ?? 0));
        $ctxDepartment = max(0, (int)($ctx['department_id'] ?? 0));
        $ctxProgramme = max(0, (int)($ctx['programme_id'] ?? 0));
        $ctxLevel = strtoupper(trim((string)($ctx['level'] ?? '')));

        $ruleSession = portalNormalizeSessionNameSafe((string)($row['session_name'] ?? ''));
        if ($ruleSession !== '' && ($ctxSession === '' || strcasecmp($ruleSession, $ctxSession) !== 0)) return false;
        $ruleSemester = trim((string)($row['semester'] ?? ''));
        $ruleSemester = $ruleSemester !== '' ? portalNormalizeSemesterName($ruleSemester) : '';
        if ($ruleSemester !== '' && ($ctxSemester === '' || strcasecmp($ruleSemester, $ctxSemester) !== 0)) return false;
        $ruleFaculty = max(0, (int)($row['faculty_id'] ?? 0));
        if ($ruleFaculty > 0 && $ruleFaculty !== $ctxFaculty) return false;
        $ruleDepartment = max(0, (int)($row['department_id'] ?? 0));
        if ($ruleDepartment > 0 && $ruleDepartment !== $ctxDepartment) return false;
        $ruleProgramme = max(0, (int)($row['programme_id'] ?? 0));
        if ($ruleProgramme > 0 && $ruleProgramme !== $ctxProgramme) return false;
        $ruleLevel = strtoupper(trim((string)($row['level'] ?? '')));
        if ($ruleLevel !== '' && ($ctxLevel === '' || strcasecmp($ruleLevel, $ctxLevel) !== 0)) return false;
        return true;
    }
}
if (!function_exists('portalManagedFeeRuleForContext')) {
    function portalManagedFeeRuleForContext(PDO $pdo, string $feeType, array $context): array {
        portalSchoolFeeSettingsSupport($pdo);
        $feeType = portalNormalizeManagedFeeType($feeType);
        $ctx = [
            'session_name' => portalNormalizeSessionNameSafe((string)($context['session_name'] ?? portalCurrentAcademicSessionName($pdo))),
            'semester' => portalNormalizeSemesterName((string)($context['semester'] ?? portalCurrentAcademicSemesterName($pdo))),
            'faculty_id' => max(0, (int)($context['faculty_id'] ?? 0)),
            'department_id' => max(0, (int)($context['department_id'] ?? 0)),
            'programme_id' => max(0, (int)($context['programme_id'] ?? 0)),
            'level' => trim((string)($context['level'] ?? '')),
        ];
        if ($ctx['session_name'] === '') $ctx['session_name'] = portalNormalizeSessionNameSafe((string)portalSettingValueSafe($pdo, 'academic_session', ''));
        if ($ctx['semester'] === '') $ctx['semester'] = portalNormalizeSemesterName((string)portalSettingValueSafe($pdo, 'academic_semester', 'First Semester'));

        $defaultAmount = (float)portalSettingValueSafe($pdo, portalManagedFeeSettingKey($feeType), '0');
        $best = [
            'id' => 0,
            'fee_type' => $feeType,
            'amount' => $defaultAmount,
            'is_active' => 1,
            'matched' => false,
            'specificity' => -1,
            'scope_hash' => portalManagedFeeScopeHash($ctx, $feeType),
            'session_name' => (string)$ctx['session_name'],
            'semester' => (string)$ctx['semester'],
            'faculty_id' => (int)$ctx['faculty_id'],
            'department_id' => (int)$ctx['department_id'],
            'programme_id' => (int)$ctx['programme_id'],
            'level' => (string)$ctx['level'],
            'custom_title' => trim((string)($context['custom_title'] ?? '')),
        ];
        try {
            $st = $pdo->prepare("SELECT * FROM school_fee_settings WHERE COALESCE(is_active,1) = 1 AND COALESCE(fee_type,'school') = ? ORDER BY id DESC");
            $st->execute([$feeType]);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($rows as $row) {
                if (!portalManagedFeeRuleMatchesContext((array)$row, $ctx, $feeType)) continue;
                $score = portalManagedFeeRuleSpecificity((array)$row);
                if ($score > (int)$best['specificity']) {
                    $best = array_merge($best, (array)$row, [
                        'id' => (int)($row['id'] ?? 0),
                        'fee_type' => $feeType,
                        'amount' => (float)($row['amount'] ?? $defaultAmount),
                        'matched' => true,
                        'specificity' => $score,
                        'scope_hash' => (string)($row['scope_hash'] ?? portalManagedFeeScopeHash([
                            'session_name' => $row['session_name'] ?? '',
                            'semester' => $row['semester'] ?? '',
                            'faculty_id' => $row['faculty_id'] ?? 0,
                            'department_id' => $row['department_id'] ?? 0,
                            'programme_id' => $row['programme_id'] ?? 0,
                            'level' => $row['level'] ?? '',
                            'custom_title' => $row['custom_title'] ?? '',
                        ], $feeType)),
                    ]);
                }
            }
        } catch (Throwable $e) {}
        return $best;
    }
}
if (!function_exists('portalSchoolFeeRuleForContext')) {
    function portalSchoolFeeRuleForContext(PDO $pdo, array $context): array {
        return portalManagedFeeRuleForContext($pdo, 'school', $context);
    }
}
if (!function_exists('portalStudentSchoolFeeRule')) {
    function portalStudentSchoolFeeRule(PDO $pdo, array $student, string $sessionName = '', string $semester = ''): array {
        $ctx = portalStudentContext($pdo, $student);
        if ($sessionName !== '') $ctx['session_name'] = portalNormalizeSessionNameSafe($sessionName);
        if ($semester !== '') $ctx['semester'] = portalNormalizeSemesterName($semester);
        return portalManagedFeeRuleForContext($pdo, 'school', $ctx);
    }
}
if (!function_exists('portalStudentManagedFeeAmount')) {
    function portalStudentManagedFeeAmount(PDO $pdo, string $feeType, array $student, string $sessionName = '', string $semester = ''): float {
        $ctx = portalStudentContext($pdo, $student);
        if ($sessionName !== '') $ctx['session_name'] = portalNormalizeSessionNameSafe($sessionName);
        if ($semester !== '') $ctx['semester'] = portalNormalizeSemesterName($semester);
        $rule = portalManagedFeeRuleForContext($pdo, $feeType, $ctx);
        $amount = (float)($rule['amount'] ?? 0);
        if ($amount > 0) return $amount;
        return (float)portalSettingValueSafe($pdo, portalManagedFeeSettingKey($feeType), '0');
    }
}
if (!function_exists('portalStudentSchoolFeeAmount')) {
    function portalStudentSchoolFeeAmount(PDO $pdo, array $student, string $sessionName = '', string $semester = ''): float {
        return portalStudentManagedFeeAmount($pdo, 'school', $student, $sessionName, $semester);
    }
}

if (!function_exists('portalSchoolFeeConfiguredAmount')) {
    function portalSchoolFeeConfiguredAmount(PDO $pdo, int $studentId = 0): float {
        if ($studentId > 0) {
            try {
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
                if ($student) {
                    $configured = portalStudentSchoolFeeAmount($pdo, $student, (string)($student['session'] ?? ''), (string)($student['semester'] ?? ''));
                    if ($configured > 0) return $configured;
                }
            } catch (Throwable $e) {}
        }
        $configured = (float)portalSettingValueSafe($pdo, 'school_fee', '0');
        if ($configured > 0) return $configured;
        if ($studentId > 0) {
            try {
                $stmt = $pdo->prepare("SELECT MAX(CASE WHEN COALESCE(total_installments,1) > 1 THEN amount * total_installments ELSE amount END) FROM payment_transactions WHERE student_id = ? AND transaction_type = 'School Fee'");
                $stmt->execute([$studentId]);
                $guess = (float)($stmt->fetchColumn() ?: 0);
                if ($guess > 0) return $guess;
            } catch (Throwable $e) {}
        }
        return 0.0;
    }
}
if (!function_exists('portalSchoolFeeSessionSummary')) {
    function portalSchoolFeeSessionSummary(PDO $pdo, array $student, string $sessionName, string $semester = ''): array {
        $studentId = (int)($student['id'] ?? 0);
        $sessionName = portalNormalizeSessionNameSafe($sessionName);
        $semester = trim((string)$semester);
        $requiredAmount = portalStudentSchoolFeeAmount($pdo, $student, $sessionName, $semester !== '' ? $semester : (string)($student['semester'] ?? ''));
        $approved = [];
        try {
            $sql = "SELECT * FROM payment_transactions WHERE student_id = ? AND transaction_type = 'School Fee' AND COALESCE(academic_session,'') = ?";
            $params = [$studentId, $sessionName];
            if ($semester !== '') {
                $sql .= " AND COALESCE(semester,'') = ?";
                $params[] = portalNormalizeSemesterName($semester);
            }
            $sql .= " ORDER BY COALESCE(payment_date, created_at) ASC, id ASC";
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($rows as $tx) {
                if (portalPaymentIsApproved((array)$tx)) $approved[] = (array)$tx;
            }
        } catch (Throwable $e) {}
        portalEnsureLateFeeSupport($pdo);
        $totalPaid = 0.0;
        $lateFeePaid = 0.0;
        $approvedInstallments = 0;
        $hasOneTimeApproval = false;
        foreach ($approved as $tx) {
            $totalPaid += (float)((isset($tx['school_fee_base_amount']) && (float)$tx['school_fee_base_amount'] > 0) ? $tx['school_fee_base_amount'] : ($tx['amount'] ?? 0));
            $lateFeePaid += (float)($tx['school_fee_late_fee_amount'] ?? 0);
            $installments = max(1, (int)($tx['total_installments'] ?? 1));
            if ($installments <= 1) {
                $hasOneTimeApproval = true;
            } else {
                $approvedInstallments = max($approvedInstallments, (int)($tx['installment_number'] ?? 1));
            }
        }
        $paidInFull = $hasOneTimeApproval || $approvedInstallments >= 2 || ($requiredAmount > 0 && $totalPaid >= $requiredAmount);
        $firstInstallmentPaid = !$paidInFull && ($approvedInstallments >= 1 || $totalPaid > 0.0);
        $outstandingAmount = $requiredAmount > 0 ? max(0.0, $requiredAmount - $totalPaid) : 0.0;
        $lateFeeBalance = (float)(portalActiveLateFeeBalanceForPeriod($pdo, $studentId, $sessionName, $semester !== '' ? portalNormalizeSemesterName($semester) : portalNormalizeSemesterName((string)($student['semester'] ?? 'First Semester')))['amount'] ?? 0);
        $statusLabel = $paidInFull ? 'Paid' : ($firstInstallmentPaid ? '1st Installment Paid' : 'Not Paid');
        return [
            'session_name' => $sessionName,
            'semester' => $semester !== '' ? portalNormalizeSemesterName($semester) : '',
            'required_amount' => $requiredAmount,
            'total_paid' => $totalPaid,
            'late_fee_paid' => $lateFeePaid,
            'approved_installments' => $approvedInstallments,
            'paid_in_full' => $paidInFull,
            'first_installment_paid' => $firstInstallmentPaid,
            'outstanding_amount' => $outstandingAmount,
            'late_fee_balance' => $lateFeeBalance,
            'total_due' => $outstandingAmount + $lateFeeBalance,
            'status_label' => $statusLabel,
            'approved_transactions' => $approved,
        ];
    }
}
if (!function_exists('portalStudentOutstandingPreviousSchoolFee')) {
    function portalStudentOutstandingPreviousSchoolFee(PDO $pdo, array $student, string $currentSession, string $semester = ''): array {
        $currentSession = portalNormalizeSessionNameSafe($currentSession);
        $currentSort = portalSessionSortValue($currentSession);
        foreach (portalCollectStudentKnownSessions($pdo, $student) as $sessionName) {
            if ($sessionName === '' || strcasecmp($sessionName, $currentSession) === 0) continue;
            if ($currentSort > 0 && portalSessionSortValue($sessionName) >= $currentSort) continue;
            $summary = portalSchoolFeeSessionSummary($pdo, $student, $sessionName, $semester);
            if (!$summary['paid_in_full'] && ($summary['total_paid'] > 0 || $summary['required_amount'] > 0)) {
                return ['has_outstanding' => true, 'session_name' => $sessionName, 'summary' => $summary];
            }
        }
        return ['has_outstanding' => false, 'session_name' => '', 'summary' => []];
    }
}
if (!function_exists('portalCourseRegistrationSchoolFeeGuard')) {
    function portalCourseRegistrationSchoolFeeGuard(PDO $pdo, array $student, string $sessionName, string $selectedSemester): array {
        portalEnsureSchoolFeeRegistrationControlSupport($pdo);
        $sessionName = portalNormalizeSessionNameSafe($sessionName !== '' ? $sessionName : portalCurrentAcademicSessionName($pdo));
        $selectedSemester = portalNormalizeSemesterName($selectedSemester !== '' ? $selectedSemester : 'First Semester');
        $systemSemester = portalNormalizeSemesterName(portalCurrentAcademicSemesterName($pdo));
        $previousDebt = portalStudentOutstandingPreviousSchoolFee($pdo, $student, $sessionName, $selectedSemester);
        if (portalFeeWorkflowSettingEnabled($pdo, 'previous_debt_blocking', '1') && !empty($previousDebt['has_outstanding'])) {
            $prevSession = (string)($previousDebt['session_name'] ?? 'previous session');
            $summary = (array)($previousDebt['summary'] ?? []);
            $suffix = ((float)($summary['outstanding_amount'] ?? 0) > 0) ? (' Outstanding balance: ₦' . number_format((float)$summary['outstanding_amount'], 2) . '.') : '';
            return [
                'allowed' => false,
                'reason' => 'previous_session_outstanding',
                'message' => 'You still have unpaid school fees from ' . $prevSession . '. Please pay the outstanding previous-session balance before registering courses for ' . $sessionName . '.' . $suffix,
                'previous_debt' => $previousDebt,
                'current_summary' => [],
                'system_semester' => $systemSemester,
                'selected_semester' => $selectedSemester,
            ];
        }
        $currentSummary = portalSchoolFeeSessionSummary($pdo, $student, $sessionName, $selectedSemester);
        if (!empty($currentSummary['paid_in_full'])) {
            return ['allowed' => true, 'reason' => 'fully_paid', 'message' => '', 'previous_debt' => $previousDebt, 'current_summary' => $currentSummary, 'system_semester' => $systemSemester, 'selected_semester' => $selectedSemester];
        }
        if (strcasecmp($sessionName, portalNormalizeSessionNameSafe(portalCurrentAcademicSessionName($pdo))) === 0 && $systemSemester === 'Second' && empty($currentSummary['paid_in_full'])) {
            $suffix = ((float)($currentSummary['outstanding_amount'] ?? 0) > 0) ? (' Outstanding balance: ₦' . number_format((float)$currentSummary['outstanding_amount'], 2) . '.') : '';
            return [
                'allowed' => false,
                'reason' => 'second_semester_requires_second_installment',
                'message' => 'Second semester course registration is blocked because your 2nd school-fee installment for ' . $sessionName . ' has not been fully paid. Please pay the outstanding balance before continuing.' . $suffix,
                'previous_debt' => $previousDebt,
                'current_summary' => $currentSummary,
                'system_semester' => $systemSemester,
                'selected_semester' => $selectedSemester,
            ];
        }
        if (!empty($currentSummary['first_installment_paid'])) {
            if ($selectedSemester === 'First') {
                return ['allowed' => true, 'reason' => 'first_installment_first_semester_only', 'message' => '', 'previous_debt' => $previousDebt, 'current_summary' => $currentSummary, 'system_semester' => $systemSemester, 'selected_semester' => $selectedSemester];
            }
            $suffix = ((float)($currentSummary['outstanding_amount'] ?? 0) > 0) ? (' Outstanding balance: ₦' . number_format((float)$currentSummary['outstanding_amount'], 2) . '.') : '';
            return [
                'allowed' => false,
                'reason' => 'second_semester_requires_second_installment',
                'message' => 'You have only paid the 1st installment of your school fees for ' . $sessionName . '. You may register first semester courses only. Pay the 2nd installment before registering second semester courses.' . $suffix,
                'previous_debt' => $previousDebt,
                'current_summary' => $currentSummary,
                'system_semester' => $systemSemester,
                'selected_semester' => $selectedSemester,
            ];
        }
        return [
            'allowed' => false,
            'reason' => 'school_fee_not_paid',
            'message' => 'Course registration is blocked because you have not paid the required school fees for ' . $sessionName . '. Please pay your school fees before continuing with course registration.',
            'previous_debt' => $previousDebt,
            'current_summary' => $currentSummary,
            'system_semester' => $systemSemester,
            'selected_semester' => $selectedSemester,
        ];
    }
}
if (isset($pdo) && ($pdo instanceof PDO)) { try { portalEnsureCourseRegistrationProfileSupport($pdo); } catch (Throwable $e) {} }
if (isset($pdo) && ($pdo instanceof PDO)) { try { portalEnsureStudentAcademicMappingSupport($pdo); } catch (Throwable $e) {} }
if (isset($pdo) && ($pdo instanceof PDO)) { try { portalEnsureStudentFullNameSupport($pdo); } catch (Throwable $e) {} }
if (isset($pdo) && ($pdo instanceof PDO)) { try { portalEnsureApplicationAcademicFieldSupport($pdo); } catch (Throwable $e) {} }
if (isset($pdo) && ($pdo instanceof PDO)) { try { portalEnsureAcademicSessionRegistrationControl($pdo); } catch (Throwable $e) {} }
if (isset($pdo) && ($pdo instanceof PDO)) { try { portalEnsureLateFeeSupport($pdo); } catch (Throwable $e) {} }
if (isset($pdo) && ($pdo instanceof PDO)) { try { portalEnsureAccommodationVerificationSupport($pdo); } catch (Throwable $e) {} }
if (isset($pdo) && ($pdo instanceof PDO)) { try { ensurePortalExtensionSupport($pdo); portalSyncGraduationStatuses($pdo); } catch (Throwable $e) {} }

if (!function_exists('portalCurrentAcademicSessionName')) {
    function portalCurrentAcademicSessionName(PDO $pdo): string {
        static $cache = null;
        if ($cache !== null && $cache !== '') {
            return (string)$cache;
        }

        $normalize = static function ($value): string {
            $value = trim((string)$value);
            if ($value === '') return '';
            $value = preg_replace('/\s+/', ' ', $value) ?: $value;
            if (preg_match('/^(\d{4})\s*[\/-]\s*(\d{4})$/', $value, $m)) {
                return $m[1] . '/' . $m[2];
            }
            if (preg_match('/^(\d{4})$/', $value, $m)) {
                $start = (int)$m[1];
                return $start . '/' . ($start + 1);
            }
            return mb_substr($value, 0, 30);
        };

        $candidates = [];
        $add = static function ($value) use (&$candidates, $normalize): void {
            $session = $normalize($value);
            if ($session === '') return;
            $key = strtoupper($session);
            if ($key === 'NOT SET' || $key === 'SESSION NOT SET') return;
            $candidates[$session] = $session;
        };

        try {
            if (portalTableExistsSafe($pdo, 'academic_sessions')) {
                $stmt = $pdo->query("SELECT name FROM academic_sessions WHERE is_current = 1 ORDER BY id DESC LIMIT 1");
                $add($stmt ? $stmt->fetchColumn() : '');
                if (!$candidates) {
                    $stmt = $pdo->query("SELECT name FROM academic_sessions WHERE COALESCE(is_active,1)=1 ORDER BY is_current DESC, end_date DESC, start_date DESC, id DESC LIMIT 1");
                    $add($stmt ? $stmt->fetchColumn() : '');
                }
            }
        } catch (Throwable $e) {}

        $add(portalSettingValueSafe($pdo, 'academic_session', ''));

        foreach ([
            ['students', 'session'],
            ['course_registrations', 'session_name'],
            ['registration_settings', 'session_name'],
            ['payment_transactions', 'academic_session'],
            ['payment_transactions', 'session_name'],
            ['registrable_courses', 'session_name'],
            ['results', 'session'],
            ['result_submissions', 'session'],
        ] as $pair) {
            [$table, $column] = $pair;
            try {
                if (!portalTableExistsSafe($pdo, $table) || !portalColumnExistsSafe($pdo, $table, $column)) continue;
                $stmt = $pdo->query("SELECT {$column} FROM {$table} WHERE COALESCE(TRIM({$column}), '') <> '' ORDER BY {$column} DESC LIMIT 1");
                $add($stmt ? $stmt->fetchColumn() : '');
            } catch (Throwable $e) {}
            if ($candidates) break;
        }

        if (!$candidates) {
            $fallback = date('Y') . '/' . ((int)date('Y') + 1);
            $add($fallback);
        }

        $resolved = (string)(array_values($candidates)[0] ?? '');
        if ($resolved === '') {
            $resolved = date('Y') . '/' . ((int)date('Y') + 1);
        }
        $resolved = $normalize($resolved);
        $cache = $resolved;

        try {
            if ($resolved !== '') {
                setSystemSettingValue($pdo, 'academic_session', $resolved, 'Current academic session');
            }
        } catch (Throwable $e) {}

        try {
            if ($resolved !== '' && portalTableExistsSafe($pdo, 'academic_sessions')) {
                $stmt = $pdo->prepare("SELECT id FROM academic_sessions WHERE name = ? ORDER BY id DESC LIMIT 1");
                $stmt->execute([$resolved]);
                $currentId = (int)($stmt->fetchColumn() ?: 0);
                if ($currentId <= 0) {
                    try {
                        $ins = $pdo->prepare("INSERT INTO academic_sessions (name, is_current, is_active) VALUES (?, 1, 1)");
                        $ins->execute([$resolved]);
                        $currentId = (int)($pdo->lastInsertId() ?: 0);
                    } catch (Throwable $e) {
                        $currentId = 0;
                    }
                }
                if ($currentId > 0) {
                    try { $pdo->exec("UPDATE academic_sessions SET is_current = 0 WHERE COALESCE(is_current,0) <> 0"); } catch (Throwable $e) {}
                    try {
                        $up = $pdo->prepare("UPDATE academic_sessions SET is_current = 1, is_active = 1 WHERE id = ?");
                        $up->execute([$currentId]);
                    } catch (Throwable $e) {}
                }
            }
        } catch (Throwable $e) {}

        return $resolved;
    }
}

if (!function_exists('portalSessionDisplayValue')) {
    function portalSessionDisplayValue(PDO $pdo, $value = '', ?string $academicYear = null): string {
        $session = portalNormalizeSessionNameSafe((string)$value);
        if ($session !== '' && !in_array(strtolower($session), ['not set', 'session not set'], true)) {
            return $session;
        }
        $year = trim((string)$academicYear);
        if ($year !== '' && preg_match('/^(20\d{2})$/', $year, $m)) {
            return $m[1] . '/' . ((int)$m[1] + 1);
        }
        $current = portalNormalizeSessionNameSafe((string)portalCurrentAcademicSessionName($pdo));
        if ($current !== '') return $current;
        try {
            $options = portalAvailableSessionOptions($pdo);
            $first = portalNormalizeSessionNameSafe((string)($options[0] ?? ''));
            if ($first !== '') return $first;
        } catch (Throwable $e) {}
        return date('Y') . '/' . ((int)date('Y') + 1);
    }
}

if (!function_exists('portalSynchronizeCurrentSessionAcrossPortal')) {
    function portalSynchronizeCurrentSessionAcrossPortal(PDO $pdo): void {
        $currentSession = portalSessionDisplayValue($pdo, portalCurrentAcademicSessionName($pdo));
        if ($currentSession === '') return;

        try { setSystemSettingValue($pdo, 'academic_session', $currentSession, 'Current academic session'); } catch (Throwable $e) {}

        $targets = [
            ['students', 'session'],
            ['course_registrations', 'session_name'],
            ['registration_settings', 'session_name'],
            ['registrable_courses', 'session_name'],
            ['payment_transactions', 'academic_session'],
            ['payment_transactions', 'session_name'],
            ['results', 'session'],
            ['result_submissions', 'session'],
            ['applications', 'session'],
            ['school_fee_late_fee_records', 'academic_session'],
            ['late_fee_rules', 'session_name'],
            ['student_attendance', 'session_name'],
            ['attendance_classes', 'session_name'],
        ];

        foreach ($targets as $pair) {
            [$table, $column] = $pair;
            try {
                if (!portalTableExistsSafe($pdo, $table) || !portalColumnExistsSafe($pdo, $table, $column)) continue;
                $sql = "UPDATE `{$table}` SET `{$column}` = ? WHERE `{$column}` IS NULL OR TRIM(`{$column}`) = '' OR LOWER(TRIM(`{$column}`)) IN ('not set','session not set')";
                $stmt = $pdo->prepare($sql);
                $stmt->execute([$currentSession]);
            } catch (Throwable $e) {}
        }

        try {
            if (portalTableExistsSafe($pdo, 'academic_sessions')) {
                $stmt = $pdo->prepare("SELECT id FROM academic_sessions WHERE name = ? ORDER BY id DESC LIMIT 1");
                $stmt->execute([$currentSession]);
                $sessionId = (int)($stmt->fetchColumn() ?: 0);
                if ($sessionId <= 0) {
                    try {
                        $ins = $pdo->prepare("INSERT INTO academic_sessions (name, is_current, is_active) VALUES (?, 1, 1)");
                        $ins->execute([$currentSession]);
                        $sessionId = (int)($pdo->lastInsertId() ?: 0);
                    } catch (Throwable $e) {
                        $sessionId = 0;
                    }
                }
                if ($sessionId > 0) {
                    try { $pdo->exec("UPDATE academic_sessions SET is_current = 0 WHERE COALESCE(is_current,0) <> 0"); } catch (Throwable $e) {}
                    try {
                        $up = $pdo->prepare("UPDATE academic_sessions SET is_current = 1, is_active = 1 WHERE id = ?");
                        $up->execute([$sessionId]);
                    } catch (Throwable $e) {}
                }
            }
        } catch (Throwable $e) {}
    }
}

if (isset($pdo) && ($pdo instanceof PDO)) { try { portalSynchronizeCurrentSessionAcrossPortal($pdo); } catch (Throwable $e) {} }


if (!function_exists('portalNormalizeSessionNameSafe')) {
    function portalNormalizeSessionNameSafe(?string $raw): string {
        $value = trim((string)$raw);
        if ($value === '') return '';
        $value = preg_replace('/\s+/', ' ', $value) ?: $value;
        if (preg_match('/^(\d{4})\s*[\/\-]\s*(\d{4})$/', $value, $m)) {
            return $m[1] . '/' . $m[2];
        }
        if (preg_match('/^(\d{4})$/', $value, $m)) {
            $start = (int)$m[1];
            return $start . '/' . ($start + 1);
        }
        return mb_substr($value, 0, 30);
    }
}

if (!function_exists('portalPostedSessionNameSafe')) {
    function portalPostedSessionNameSafe(array $src): string {
        foreach (['name', 'session_name', 'session', 'academic_session', 'sessionLabel'] as $key) {
            if (array_key_exists($key, $src)) {
                $name = portalNormalizeSessionNameSafe((string)$src[$key]);
                if ($name !== '') {
                    return $name;
                }
            }
        }
        return '';
    }
}

if (!function_exists('portalSelfHealAcademicSessions')) {
    function portalSelfHealAcademicSessions(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS academic_sessions (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(30) NOT NULL UNIQUE,
                start_date DATE NULL,
                end_date DATE NULL,
                is_current TINYINT(1) NOT NULL DEFAULT 0,
                is_active TINYINT(1) NOT NULL DEFAULT 1,
                registration_semester_access VARCHAR(20) NOT NULL DEFAULT 'both',
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $rowsStmt = $pdo->query("SELECT id, name, is_current, is_active FROM academic_sessions ORDER BY is_current DESC, id ASC");
            $rows = $rowsStmt ? ($rowsStmt->fetchAll(PDO::FETCH_ASSOC) ?: []) : [];
        } catch (Throwable $e) {
            $rows = [];
        }

        $preferredCurrent = portalNormalizeSessionNameSafe((string)portalSettingValueSafe($pdo, 'academic_session', ''));
        $used = [];
        $currentId = 0;
        $hasCurrent = false;

        $check = $pdo->prepare("SELECT id FROM academic_sessions WHERE name = ? LIMIT 1");
        $update = $pdo->prepare("UPDATE academic_sessions SET name = ?, is_active = COALESCE(is_active,1) WHERE id = ?");

        foreach ($rows as $index => $row) {
            $id = (int)($row['id'] ?? 0);
            if ($id <= 0) continue;
            $normalized = portalNormalizeSessionNameSafe((string)($row['name'] ?? ''));
            if ($normalized === '') {
                if ($preferredCurrent !== '' && !isset($used[strtolower($preferredCurrent)])) {
                    $normalized = $preferredCurrent;
                } else {
                    $normalized = 'Session-' . $id;
                }
            }

            $base = $normalized;
            $suffix = 1;
            while (isset($used[strtolower($normalized)])) {
                $suffix++;
                $normalized = $base . '-' . $suffix;
            }

            try {
                $check->execute([$normalized]);
                $existingId = (int)($check->fetchColumn() ?? 0);
            } catch (Throwable $e) {
                $existingId = 0;
            }
            if ($existingId > 0 && $existingId !== $id) {
                $base = $normalized;
                $suffix = 1;
                do {
                    $suffix++;
                    $normalized = $base . '-' . $suffix;
                    try {
                        $check->execute([$normalized]);
                        $existingId = (int)($check->fetchColumn() ?? 0);
                    } catch (Throwable $e) {
                        $existingId = 0;
                    }
                } while ($existingId > 0 && $existingId !== $id);
            }

            if ((string)($row['name'] ?? '') !== $normalized) {
                try { $update->execute([$normalized, $id]); } catch (Throwable $e) {}
            }
            $used[strtolower($normalized)] = $id;
            if ((int)($row['is_current'] ?? 0) === 1 && !$hasCurrent) {
                $hasCurrent = true;
                $currentId = $id;
                $preferredCurrent = $normalized;
            }
        }

        if (!$hasCurrent) {
            if (!empty($rows)) {
                $currentId = (int)($rows[0]['id'] ?? 0);
                if ($currentId > 0) {
                    try {
                        $pdo->exec("UPDATE academic_sessions SET is_current = 0");
                        $st = $pdo->prepare("UPDATE academic_sessions SET is_current = 1 WHERE id = ?");
                        $st->execute([$currentId]);
                    } catch (Throwable $e) {}
                }
            } elseif ($preferredCurrent !== '') {
                try {
                    $st = $pdo->prepare("INSERT INTO academic_sessions (name, is_current, is_active) VALUES (?, 1, 1)");
                    $st->execute([$preferredCurrent]);
                    $currentId = (int)$pdo->lastInsertId();
                    $used[strtolower($preferredCurrent)] = $currentId;
                } catch (Throwable $e) {}
            }
        }

        if ($currentId > 0) {
            try {
                $st = $pdo->prepare("SELECT name FROM academic_sessions WHERE id = ? LIMIT 1");
                $st->execute([$currentId]);
                $currentName = portalNormalizeSessionNameSafe((string)($st->fetchColumn() ?? ''));
                if ($currentName !== '') {
                    $up = $pdo->prepare("INSERT INTO system_settings (setting_key, setting_value, description)
                                         VALUES ('academic_session', ?, 'Current academic session')
                                         ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
                    $up->execute([$currentName]);
                }
            } catch (Throwable $e) {}
        }
    }
}

if (!function_exists('portalEnsureAcademicSessionRecord')) {
    function portalEnsureAcademicSessionRecord(PDO $pdo, string $sessionName, bool $markCurrent = false): int {
        $sessionName = portalNormalizeSessionNameSafe($sessionName);
        if ($sessionName === '') return 0;
        portalSelfHealAcademicSessions($pdo);
        try {
            $st = $pdo->prepare("SELECT id FROM academic_sessions WHERE name = ? LIMIT 1");
            $st->execute([$sessionName]);
            $id = (int)($st->fetchColumn() ?? 0);
            if ($id <= 0) {
                $ins = $pdo->prepare("INSERT INTO academic_sessions (name, is_current, is_active) VALUES (?, ?, 1)");
                $ins->execute([$sessionName, $markCurrent ? 1 : 0]);
                $id = (int)$pdo->lastInsertId();
            }
            if ($markCurrent && $id > 0) {
                $pdo->exec("UPDATE academic_sessions SET is_current = 0");
                $up = $pdo->prepare("UPDATE academic_sessions SET is_current = 1 WHERE id = ?");
                $up->execute([$id]);
            }
            return $id;
        } catch (Throwable $e) {
            try {
                $st = $pdo->prepare("SELECT id FROM academic_sessions WHERE name = ? LIMIT 1");
                $st->execute([$sessionName]);
                return (int)($st->fetchColumn() ?? 0);
            } catch (Throwable $ee) {
                return 0;
            }
        }
    }
}

if (!function_exists('portalStudentHostelStatusLabel')) {
    function portalStudentHostelStatusLabel(array $student): string {
        global $pdo;
        if (isset($pdo) && ($pdo instanceof PDO) && function_exists('portalStudentHostelRequirementSummary')) {
            $summary = portalStudentHostelRequirementSummary($pdo, $student);
            if (!empty($summary['label'])) return (string)$summary['label'];
        }
        $acc = strtolower(trim((string)($student['accommodation_status'] ?? 'Campus')));
        if ($acc === 'off-campus') return 'Off-Campus';
        return payment_status_label((string)($student['hostel_fee_status'] ?? 'Not Paid'));
    }
}

if (!function_exists('portalStudentAccommodationLockedForSession')) {
    function portalStudentAccommodationLockedForSession(PDO $pdo, array $student): bool {
        $currentSession = trim((string)portalCurrentAcademicSessionName($pdo));
        $paidSession = trim((string)($student['hostel_fee_paid_session'] ?? ''));
        $isPaid = payment_status_is_paid((string)($student['hostel_fee_status'] ?? ''));
        if (!$isPaid) return false;
        if ($paidSession === '' && $currentSession !== '') $paidSession = $currentSession;
        if ($currentSession === '' || $paidSession === '') return $isPaid;
        return strcasecmp($paidSession, $currentSession) === 0;
    }
}

if (!function_exists('portalStudentCanShowFeeMenu')) {
    function portalStudentCanShowFeeMenu(PDO $pdo, array $student, string $feeKey): bool {
        $currentSession = trim((string)portalCurrentAcademicSessionName($pdo));
        if ($feeKey === 'registration') return !payment_status_is_paid((string)($student['registration_fee_status'] ?? ''));
        if ($feeKey === 'acceptance') return !payment_status_is_paid((string)($student['acceptance_fee_status'] ?? ''));
        if ($feeKey === 'hostel') {
            if (function_exists('portalShouldRequireHostelFeeForStudent') && !portalShouldRequireHostelFeeForStudent($pdo, $student, $currentSession, portalCurrentAcademicSemesterName($pdo))) return false;
            $hostelStatus = function_exists('portalHostelStatusForPeriod') ? portalHostelStatusForPeriod($pdo, $student, $currentSession, portalCurrentAcademicSemesterName($pdo)) : payment_status_label((string)($student['hostel_fee_status'] ?? 'Not Paid'));
            if (strcasecmp($hostelStatus, 'Paid') === 0 || stripos($hostelStatus, 'Approved') !== false || stripos($hostelStatus, 'Off-Campus') !== false) return false;
            return true;
        }
        if ($feeKey === 'school') {
            if (function_exists('portalSchoolFeePeriodSummary')) {
                $summary = portalSchoolFeePeriodSummary($pdo, (int)($student['id'] ?? 0), $currentSession, portalCurrentAcademicSemesterName($pdo), $student);
                return (float)($summary['total_due'] ?? 0) > 0.0001;
            }
            $paidSession = trim((string)($student['school_fee_paid_session'] ?? ''));
            if (payment_status_is_paid((string)($student['school_fee_status'] ?? '')) && $paidSession !== '' && $currentSession !== '' && strcasecmp($paidSession, $currentSession) === 0) return false;
            return true;
        }
        return true;
    }
}

if (!function_exists('portalStudentApplicationNumberValue')) {
    function portalStudentApplicationNumberValue(array $row): string {
        foreach (['application_no', 'application_number', 'applicant_no'] as $field) {
            $value = trim((string)($row[$field] ?? ''));
            if ($value !== '') return $value;
        }
        return '';
    }
}

if (!function_exists('portalStudentRegistrationNumberValue')) {
    function portalStudentRegistrationNumberValue(array $row): string {
        foreach (['registration_no', 'reg_no', 'registration_number'] as $field) {
            $value = trim((string)($row[$field] ?? ''));
            if ($value !== '') return $value;
        }
        return '';
    }
}

if (!function_exists('portalStudentAdmissionNumberValue')) {
    function portalStudentAdmissionNumberValue(array $row): string {
        foreach (['admission_no', 'admission_number'] as $field) {
            $value = trim((string)($row[$field] ?? ''));
            if ($value !== '') return $value;
        }
        return '';
    }
}

if (!function_exists('portalStudentMatricNumberValue')) {
    function portalStudentMatricNumberValue(array $row): string {
        foreach (['matric_no', 'matric_number', 'matriculation'] as $field) {
            $value = trim((string)($row[$field] ?? ''));
            if ($value !== '') return $value;
        }
        return '';
    }
}

if (!function_exists('portalStudentNumberDisplay')) {
    function portalStudentNumberDisplay(array $row, string $numberType, string $fallback = ''): string {
        $numberType = strtolower(trim($numberType));
        if ($numberType === 'application') {
            $value = portalStudentApplicationNumberValue($row);
            return $value !== '' ? $value : ($fallback !== '' ? $fallback : 'Not Generated');
        }
        if ($numberType === 'registration') {
            $value = portalStudentRegistrationNumberValue($row);
            return $value !== '' ? $value : ($fallback !== '' ? $fallback : 'Not Generated');
        }
        if ($numberType === 'admission') {
            $value = portalStudentAdmissionNumberValue($row);
            if ($value !== '') return $value;
            if ($fallback !== '') return $fallback;
            if (portalStudentAdmissionOfferGranted($row) && !portalStudentAdmissionOfferAccepted($row)) return 'Awaiting Acceptance';
            if (portalStudentAdmissionOfferGranted($row) && portalStudentAdmissionOfferAccepted($row)) return 'Processing';
            return 'Pending';
        }
        if ($numberType === 'matric') {
            $value = portalStudentMatricNumberValue($row);
            return $value !== '' ? $value : ($fallback !== '' ? $fallback : 'Processing');
        }
        return $fallback;
    }
}


if (!function_exists('portalApplicationAdmissionReferenceValue')) {
    function portalApplicationAdmissionReferenceValue(array $row): string {
        $candidates = [
            portalStudentAdmissionNumberValue($row),
            trim((string)($row['admission_reference_no'] ?? '')),
            trim((string)($row['admission_reference_number'] ?? '')),
            portalStudentApplicationNumberValue($row),
            portalStudentRegistrationNumberValue($row),
        ];
        foreach ($candidates as $candidate) {
            $candidate = trim((string)$candidate);
            if ($candidate !== '') return $candidate;
        }
        return '';
    }
}

if (!function_exists('portalApplicationFormPdfFilename')) {
    function portalApplicationFormPdfFilename(array $row): string {
        $fullName = trim((string)($row['full_name'] ?? ''));
        if ($fullName === '') {
            $fullName = trim((string)($row['surname'] ?? '') . ' ' . (string)($row['first_name'] ?? '') . ' ' . (string)($row['other_name'] ?? ''));
        }
        $reference = portalApplicationAdmissionReferenceValue($row);
        $year = trim((string)portalStudentAcademicYear($row));
        if ($year === '') $year = date('Y');
        return implode('_', [
            portalSanitizeFilenamePart($fullName !== '' ? $fullName : 'Student', 'Student'),
            portalSanitizeFilenamePart($reference !== '' ? $reference : 'Reference', 'Reference'),
            portalSanitizeFilenamePart($year, 'Year'),
        ]) . '.pdf';
    }
}

if (!function_exists('portalReceiptAdmissionNumber')) {
    function portalReceiptAdmissionNumber(array $row): string {
        return portalStudentNumberDisplay($row, 'admission');
    }
}

if (!function_exists('portalReceiptApplicationNumber')) {
    function portalReceiptApplicationNumber(array $row): string {
        return portalStudentNumberDisplay($row, 'application');
    }
}

if (!function_exists('portalReceiptMatricNumber')) {
    function portalReceiptMatricNumber(array $row): string {
        return portalStudentNumberDisplay($row, 'matric');
    }
}

if (!function_exists('portalStudentQualifiesForMatric')) {
    function portalStudentQualifiesForMatric(PDO $pdo, array $student): bool {
        $studentId = (int)($student['id'] ?? 0);
        if ($studentId <= 0) return false;

        $offerAccepted = ((string)($student['offer_status'] ?? 'Pending') === 'Accepted');
        $applicationApproved = ((string)($student['application_status'] ?? 'Pending') === 'Approved');
        if (!$offerAccepted && !$applicationApproved) return false;

        if (payment_status_is_paid((string)($student['acceptance_fee_status'] ?? 'Not Paid'))) {
            return true;
        }

        try {
            $stmt = $pdo->prepare("SELECT COUNT(*) FROM payment_transactions WHERE student_id = ? AND transaction_type = 'Acceptance Fee' AND status IN ('Approved','Successful')");
            $stmt->execute([$studentId]);
            return ((int)$stmt->fetchColumn() > 0);
        } catch (Throwable $e) {
            return false;
        }
    }
}


if (!function_exists('portalAcademicYearFromValue')) {
    function portalAcademicYearFromValue($value): string {
        $text = trim((string)$value);
        if ($text !== '' && preg_match('/(20\d{2})/', $text, $m)) {
            return (string)$m[1];
        }
        return '';
    }
}

if (!function_exists('portalStudentAcademicYear')) {
    function portalStudentAcademicYear(array $row): string {
        foreach (['session', 'session_name', 'year', 'application_no', 'admission_no', 'matric_no'] as $field) {
            $year = portalAcademicYearFromValue($row[$field] ?? '');
            if ($year !== '') {
                return $year;
            }
        }

        if (!empty($row['created_at'])) {
            try {
                return date('Y', strtotime((string)$row['created_at']));
            } catch (Throwable $e) {}
        }

        return date('Y');
    }
}

if (!function_exists('portalNormalizeSemesterName')) {
    function portalNormalizeSemesterName($value): string {
        $s = trim((string)$value);
        if ($s === '') return 'First Semester';
        $norm = strtolower(preg_replace('/\s+/', ' ', str_replace(['-', '_'], ' ', $s)));
        if (strpos($norm, 'second') !== false || preg_match('/\b2(nd)?\b/', $norm)) return 'Second Semester';
        if (strpos($norm, 'first') !== false || preg_match('/\b1(st)?\b/', $norm)) return 'First Semester';
        return ucwords($s);
    }
}

if (!function_exists('portalAvailableSemesterOptions')) {
    function portalAvailableSemesterOptions(): array {
        return ['First Semester', 'Second Semester'];
    }
}

if (!function_exists('portalAvailableSessionOptions')) {
    function portalAvailableSessionOptions(PDO $pdo, array $student = []): array {
        try { portalSelfHealAcademicSessions($pdo); } catch (Throwable $e) {}
        $out = [];
        $add = static function ($value) use (&$out): void {
            $name = portalNormalizeSessionNameSafe((string)$value);
            if ($name !== '') $out[$name] = $name;
        };
        try {
            $stmt = $pdo->query("SELECT name FROM academic_sessions WHERE COALESCE(is_active,1)=1 ORDER BY is_current DESC, name DESC");
            while ($stmt && ($row = $stmt->fetch(PDO::FETCH_ASSOC))) {
                $add($row['name'] ?? '');
            }
        } catch (Throwable $e) {}
        foreach (['session', 'session_name', 'academic_session'] as $field) {
            $add($student[$field] ?? '');
        }
        try {
            foreach (['students' => 'session', 'course_registrations' => 'session_name', 'registration_settings' => 'session_name', 'payment_transactions' => 'academic_session'] as $table => $col) {
                if (!portalTableExistsSafe($pdo, $table) || !portalColumnExistsSafe($pdo, $table, $col)) continue;
                $stmt = $pdo->query("SELECT DISTINCT {$col} FROM {$table} WHERE COALESCE(TRIM({$col}),'')<>'' ORDER BY {$col} DESC");
                while ($stmt && ($val = $stmt->fetchColumn())) {
                    $add($val);
                }
            }
        } catch (Throwable $e) {}
        $add(portalCurrentAcademicSessionName($pdo));
        $fallback = date('Y') . '/' . (date('Y') + 1);
        if (!$out) $out[$fallback] = $fallback;
        uasort($out, static function($a, $b): int {
            return portalSessionSortValue((string)$b) <=> portalSessionSortValue((string)$a);
        });
        return array_values($out);
    }
}

if (!function_exists('portalSelectedSessionSemester')) {
    function portalSelectedSessionSemester(PDO $pdo, array $student = [], string $sessionKey = 'session', string $semesterKey = 'semester'): array {
        $session = trim((string)($_REQUEST[$sessionKey] ?? ($student['session'] ?? ($student['session_name'] ?? portalCurrentAcademicSessionName($pdo)))));
        $semester = portalNormalizeSemesterName($_REQUEST[$semesterKey] ?? ($student['semester'] ?? 'First Semester'));
        return [$session, $semester];
    }
}

if (!function_exists('portalPaymentAcademicSession')) {
    function portalPaymentAcademicSession(array $tx, array $fallbackStudent = []): string {
        foreach (['academic_session', 'session', 'session_name', 'student_session'] as $field) {
            $value = trim((string)($tx[$field] ?? ''));
            if ($value !== '') return $value;
        }
        foreach (['session', 'session_name'] as $field) {
            $value = trim((string)($fallbackStudent[$field] ?? ''));
            if ($value !== '') return $value;
        }
        return '';
    }
}

if (!function_exists('portalPaymentSemester')) {
    function portalPaymentSemester(array $tx, array $fallbackStudent = []): string {
        foreach (['semester', 'student_semester'] as $field) {
            $value = trim((string)($tx[$field] ?? ''));
            if ($value !== '') return portalNormalizeSemesterName($value);
        }
        $fallback = trim((string)($fallbackStudent['semester'] ?? ''));
        return portalNormalizeSemesterName($fallback !== '' ? $fallback : 'First Semester');
    }
}

if (!function_exists('portalPaymentIsApproved')) {
    function portalPaymentIsApproved(array $tx): bool {
        $status = (string)($tx['status'] ?? '');
        if (!in_array($status, ['Approved', 'Successful'], true)) {
            return false;
        }
        $recon = strtolower(trim((string)($tx['reconciliation_status'] ?? '')));
        if (in_array($recon, ['underpaidwallet', 'underpaidrejected', 'nooutstandingwalletcredited'], true)) {
            return false;
        }
        $settled = (float)($tx['settled_amount'] ?? 0);
        if (array_key_exists('settled_amount', $tx) && $settled <= 0) {
            return false;
        }
        return true;
    }
}


if (!function_exists('portalPaymentTypeIsAcceptanceFee')) {
    function portalPaymentTypeIsAcceptanceFee($value): bool {
        $type = strtolower(trim((string)$value));
        return in_array($type, ['acceptance', 'acceptance fee'], true);
    }
}

if (!function_exists('portalTransactionMatchesPeriod')) {
    function portalTransactionMatchesPeriod(array $tx, string $session = '', string $semester = '', array $fallbackStudent = []): bool {
        $txSession = portalPaymentAcademicSession($tx, $fallbackStudent);
        $txSemester = portalPaymentSemester($tx, $fallbackStudent);
        $sessionMatch = ($session === '' || $txSession === '' || strcasecmp($txSession, $session) === 0);
        $semesterMatch = ($semester === '' || $txSemester === '' || strcasecmp(portalNormalizeSemesterName($txSemester), portalNormalizeSemesterName($semester)) === 0);
        return $sessionMatch && $semesterMatch;
    }
}

if (!function_exists('portalFilterTransactionsForPeriod')) {
    function portalFilterTransactionsForPeriod(array $transactions, string $session = '', string $semester = '', array $fallbackStudent = []): array {
        $out = [];
        foreach ($transactions as $tx) {
            if (portalTransactionMatchesPeriod((array)$tx, $session, $semester, $fallbackStudent)) {
                $out[] = $tx;
            }
        }
        return $out;
    }
}

if (!function_exists('portalOrdinalLabel')) {
    function portalOrdinalLabel(int $n): string {
        if ($n === 1) return '1st';
        if ($n === 2) return '2nd';
        if ($n === 3) return '3rd';
        return $n . 'th';
    }
}

if (!function_exists('portalReceiptNumber')) {
    function portalReceiptNumber(array $transaction): string {
        $receipt = trim((string)($transaction['receipt_number'] ?? ''));
        if ($receipt !== '') return $receipt;
        $id = (int)($transaction['id'] ?? 0);
        $year = '';
        foreach (['payment_date', 'created_at'] as $field) {
            if (!empty($transaction[$field])) {
                try { $year = date('Y', strtotime((string)$transaction[$field])); } catch (Throwable $e) { $year = ''; }
                if ($year !== '') break;
            }
        }
        if ($year === '') $year = date('Y');
        return 'RCT-' . $year . '-' . str_pad((string)max(1, $id), 6, '0', STR_PAD_LEFT);
    }
}


if (!function_exists('portalSystemSettingsMap')) {
    function portalSystemSettingsMap(PDO $pdo): array {
        $settings = [];
        try {
            $stmt = $pdo->query("SELECT setting_key, setting_value FROM system_settings");
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $settings[(string)($row['setting_key'] ?? '')] = (string)($row['setting_value'] ?? '');
            }
        } catch (Throwable $e) {}
        return $settings;
    }
}



if (!function_exists('portalPaymentGatewayRegistry')) {
    function portalPaymentGatewayRegistry(): array {
        static $registry = null;
        if ($registry !== null) return $registry;
        $modeOptions = ['live' => 'Live', 'sandbox' => 'Sandbox'];
        $registry = [
            'paystack' => [
                'label' => 'Paystack',
                'region' => 'Nigeria / Africa',
                'wallet_funding' => true,
                'native_checkout' => true,
                'settings_help' => 'Native checkout already exists in this portal.',
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'live'],
                    'public_key' => ['label' => 'Public Key', 'type' => 'text', 'default' => defined('PAYSTACK_PUBLIC_KEY') ? PAYSTACK_PUBLIC_KEY : ''],
                    'secret_key' => ['label' => 'Secret Key', 'type' => 'password', 'default' => defined('PAYSTACK_SECRET_KEY') ? PAYSTACK_SECRET_KEY : '', 'required' => true],
                    'webhook_secret' => ['label' => 'Webhook Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => 'https://api.paystack.co'],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => 'https://api.paystack.co'],
                ],
            ],
            'flutterwave' => [
                'label' => 'Flutterwave',
                'region' => 'Nigeria / Africa',
                'wallet_funding' => true,
                'native_checkout' => true,
                'settings_help' => 'Native checkout already exists in this portal.',
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'live'],
                    'public_key' => ['label' => 'Public Key', 'type' => 'text', 'default' => defined('FLUTTERWAVE_PUBLIC_KEY') ? FLUTTERWAVE_PUBLIC_KEY : ''],
                    'secret_key' => ['label' => 'Secret Key', 'type' => 'password', 'default' => defined('FLUTTERWAVE_SECRET_KEY') ? FLUTTERWAVE_SECRET_KEY : '', 'required' => true],
                    'currency' => ['label' => 'Currency', 'type' => 'text', 'default' => 'NGN'],
                    'webhook_secret' => ['label' => 'Webhook Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => 'https://api.flutterwave.com'],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => 'https://api.flutterwave.com'],
                ],
            ],
            'monnify' => [
                'label' => 'Monnify',
                'region' => 'Nigeria / Africa',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'settings_help' => 'Use Hosted Checkout URL if your Monnify flow is handled by a middleware or provider-hosted page.',
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'api_key' => ['label' => 'API Key', 'type' => 'text', 'default' => defined('MONNIFY_API_KEY') ? MONNIFY_API_KEY : '', 'required' => true],
                    'secret_key' => ['label' => 'Secret Key', 'type' => 'password', 'default' => defined('MONNIFY_SECRET_KEY') ? MONNIFY_SECRET_KEY : '', 'required' => true],
                    'contract_code' => ['label' => 'Contract Code', 'type' => 'text', 'default' => defined('MONNIFY_CONTRACT_CODE') ? MONNIFY_CONTRACT_CODE : '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook / Return Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Callback URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'remita' => [
                'label' => 'Remita',
                'region' => 'Nigeria / Africa',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'settings_help' => 'Use Hosted Checkout URL when Remita order/RRR generation is handled by your middleware or Remita-hosted flow.',
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_id' => ['label' => 'Merchant ID', 'type' => 'text', 'default' => defined('REMITA_MERCHANT_ID') ? REMITA_MERCHANT_ID : '', 'required' => true],
                    'service_type_id' => ['label' => 'Service Type ID', 'type' => 'text', 'default' => defined('REMITA_SERVICE_TYPE_ID') ? REMITA_SERVICE_TYPE_ID : '', 'required' => true],
                    'api_key' => ['label' => 'API Key', 'type' => 'password', 'default' => defined('REMITA_API_KEY') ? REMITA_API_KEY : '', 'required' => true],
                    'api_secret' => ['label' => 'API Secret', 'type' => 'password', 'default' => ''],
                    'checkout_url' => ['label' => 'Hosted Checkout URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook / Return Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Callback URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'papss' => [
                'label' => 'PAPSS',
                'region' => 'Nigeria / Africa',
                'wallet_funding' => false,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'settings_help' => 'PAPSS usually involves institution/onboarding connectivity. Use this block to store credentials and optional middleware checkout URL.',
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_id' => ['label' => 'Merchant / Participant ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'api_key' => ['label' => 'API Key', 'type' => 'text', 'default' => '', 'required' => true],
                    'api_secret' => ['label' => 'API Secret', 'type' => 'password', 'default' => '', 'required' => true],
                    'public_key' => ['label' => 'Public Key', 'type' => 'text', 'default' => ''],
                    'checkout_url' => ['label' => 'Hosted Checkout / Middleware URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook / Return Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Callback URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'interswitch' => [
                'label' => 'Interswitch',
                'region' => 'Nigeria / Africa',
                'wallet_funding' => true,
                'native_checkout' => true,
                'settings_help' => 'Native redirect checkout already exists in this portal.',
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'live'],
                    'merchant_code' => ['label' => 'Merchant Code', 'type' => 'text', 'default' => '', 'required' => true],
                    'pay_item_id' => ['label' => 'Pay Item ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'currency' => ['label' => 'Currency (Numeric ISO)', 'type' => 'text', 'default' => '566'],
                    'api_key' => ['label' => 'API Key', 'type' => 'text', 'default' => ''],
                    'api_secret' => ['label' => 'API Secret', 'type' => 'password', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => 'https://newwebpay.interswitchng.com'],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => 'https://newwebpay.qa.interswitchng.com'],
                ],
            ],
            'etranzact' => [
                'label' => 'eTranzact',
                'region' => 'Nigeria / Africa',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_id' => ['label' => 'Merchant ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'terminal_id' => ['label' => 'Terminal ID', 'type' => 'text', 'default' => ''],
                    'api_key' => ['label' => 'API Key', 'type' => 'text', 'default' => '', 'required' => true],
                    'api_secret' => ['label' => 'API Secret', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook / Return Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Callback URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'paga' => [
                'label' => 'Paga',
                'region' => 'Nigeria / Africa',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_id' => ['label' => 'Merchant / Business ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'public_key' => ['label' => 'Public Key / Principal', 'type' => 'text', 'default' => ''],
                    'api_key' => ['label' => 'API Key / Credential', 'type' => 'text', 'default' => '', 'required' => true],
                    'api_secret' => ['label' => 'API Secret', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Checkout / Payment Request URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Callback Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Callback URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'opay' => [
                'label' => 'OPay',
                'region' => 'Nigeria / Africa',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'live'],
                    'merchant_id' => ['label' => 'Merchant ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'public_key' => ['label' => 'Public Key', 'type' => 'text', 'default' => ''],
                    'secret_key' => ['label' => 'Secret Key', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook / Return Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => 'https://payapi.opayweb.com'],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => 'https://payapi.opayweb.com'],
                    'callback_url' => ['label' => 'Callback URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'dusupay' => [
                'label' => 'DusuPay',
                'region' => 'Africa',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_id' => ['label' => 'Merchant ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'api_key' => ['label' => 'API Key', 'type' => 'text', 'default' => '', 'required' => true],
                    'api_secret' => ['label' => 'API Secret', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook / Return Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Callback URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'stripe' => [
                'label' => 'Stripe',
                'region' => 'Global',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'public_key' => ['label' => 'Publishable Key', 'type' => 'text', 'default' => ''],
                    'secret_key' => ['label' => 'Secret Key', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout / Middleware URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => 'https://api.stripe.com'],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => 'https://api.stripe.com'],
                    'callback_url' => ['label' => 'Success URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'paypal' => [
                'label' => 'PayPal',
                'region' => 'Global',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'client_id' => ['label' => 'Client ID', 'type' => 'text', 'default' => defined('PAYPAL_CLIENT_ID') ? PAYPAL_CLIENT_ID : '', 'required' => true],
                    'client_secret' => ['label' => 'Client Secret', 'type' => 'password', 'default' => defined('PAYPAL_CLIENT_SECRET') ? PAYPAL_CLIENT_SECRET : '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout / Middleware URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook ID / Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => 'https://api-m.paypal.com'],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => 'https://api-m.sandbox.paypal.com'],
                    'callback_url' => ['label' => 'Return URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'adyen' => [
                'label' => 'Adyen',
                'region' => 'Global',
                'wallet_funding' => false,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_account' => ['label' => 'Merchant Account', 'type' => 'text', 'default' => '', 'required' => true],
                    'client_key' => ['label' => 'Client Key', 'type' => 'text', 'default' => ''],
                    'api_key' => ['label' => 'API Key', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout / Middleware URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook HMAC Key', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Return URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'checkoutcom' => [
                'label' => 'Checkout.com',
                'region' => 'Global',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'public_key' => ['label' => 'Public Key', 'type' => 'text', 'default' => ''],
                    'secret_key' => ['label' => 'Secret Key', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout / Middleware URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Return URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'worldpay' => [
                'label' => 'Worldpay',
                'region' => 'Global',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_id' => ['label' => 'Merchant ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'api_key' => ['label' => 'API Key', 'type' => 'text', 'default' => ''],
                    'api_secret' => ['label' => 'API Secret', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout / Payment Page URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Return URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'authorizenet' => [
                'label' => 'Authorize.net',
                'region' => 'Global',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'api_login_id' => ['label' => 'API Login ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'transaction_key' => ['label' => 'Transaction Key', 'type' => 'password', 'default' => '', 'required' => true],
                    'signature_key' => ['label' => 'Signature / Webhook Key', 'type' => 'password', 'default' => ''],
                    'checkout_url' => ['label' => 'Hosted Checkout / Middleware URL', 'type' => 'text', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Return URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'braintree' => [
                'label' => 'Braintree',
                'region' => 'Global',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_id' => ['label' => 'Merchant ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'public_key' => ['label' => 'Public Key', 'type' => 'text', 'default' => '', 'required' => true],
                    'private_key' => ['label' => 'Private Key', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout / Middleware URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Return URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'square' => [
                'label' => 'Square',
                'region' => 'Global',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'application_id' => ['label' => 'Application ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'access_token' => ['label' => 'Access Token', 'type' => 'password', 'default' => '', 'required' => true],
                    'location_id' => ['label' => 'Location ID', 'type' => 'text', 'default' => ''],
                    'checkout_url' => ['label' => 'Hosted Checkout / Middleware URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook Signature Key', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => 'https://connect.squareup.com'],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => 'https://connect.squareupsandbox.com'],
                    'callback_url' => ['label' => 'Return URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'amazonpay' => [
                'label' => 'Amazon Pay',
                'region' => 'Global',
                'wallet_funding' => false,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_id' => ['label' => 'Merchant ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'public_key_id' => ['label' => 'Public Key ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'private_key' => ['label' => 'Private Key', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout / Middleware URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook / Return Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Return URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
            'twocheckout' => [
                'label' => 'Verifone / 2Checkout',
                'region' => 'Global',
                'wallet_funding' => true,
                'native_checkout' => false,
                'requires_checkout_url' => true,
                'fields' => [
                    'mode' => ['label' => 'Environment Mode', 'type' => 'select', 'options' => $modeOptions, 'default' => 'sandbox'],
                    'merchant_id' => ['label' => 'Merchant / Account ID', 'type' => 'text', 'default' => '', 'required' => true],
                    'api_key' => ['label' => 'API Key', 'type' => 'text', 'default' => ''],
                    'api_secret' => ['label' => 'API Secret / Buy Link Secret', 'type' => 'password', 'default' => '', 'required' => true],
                    'checkout_url' => ['label' => 'Hosted Checkout / Buy Link URL', 'type' => 'text', 'default' => ''],
                    'webhook_secret' => ['label' => 'Webhook Secret', 'type' => 'password', 'default' => ''],
                    'base_url' => ['label' => 'Base URL', 'type' => 'text', 'default' => ''],
                    'sandbox_url' => ['label' => 'Sandbox URL', 'type' => 'text', 'default' => ''],
                    'callback_url' => ['label' => 'Return URL Override', 'type' => 'text', 'default' => ''],
                ],
            ],
        ];

foreach (array_keys($registry) as $providerKey) {
    if (!isset($registry[$providerKey]['fields']['verify_url'])) {
        $registry[$providerKey]['fields']['verify_url'] = ['label' => 'Server Verify URL', 'type' => 'text', 'default' => ''];
    }
    if (!isset($registry[$providerKey]['fields']['live_url'])) {
        $registry[$providerKey]['fields']['live_url'] = ['label' => 'Live URL', 'type' => 'text', 'default' => (string)($registry[$providerKey]['fields']['base_url']['default'] ?? '')];
    }
}

if (isset($registry['monnify'])) {
    $registry['monnify']['native_checkout'] = true;
    unset($registry['monnify']['requires_checkout_url']);
    $registry['monnify']['settings_help'] = 'Native server-to-server transaction initialization with redirect checkout is supported in this portal. Hosted Checkout URL remains optional.';
    if (!isset($registry['monnify']['fields']['currency'])) {
        $registry['monnify']['fields']['currency'] = ['label' => 'Currency', 'type' => 'text', 'default' => 'NGN'];
    }
    $registry['monnify']['fields']['base_url']['default'] = 'https://api.monnify.com';
    $registry['monnify']['fields']['sandbox_url']['default'] = 'https://sandbox.monnify.com';
    $registry['monnify']['fields']['live_url']['default'] = 'https://api.monnify.com';
}
if (isset($registry['stripe'])) {
    $registry['stripe']['native_checkout'] = true;
    unset($registry['stripe']['requires_checkout_url']);
    $registry['stripe']['settings_help'] = 'Native Stripe Checkout Session initialization and return verification are supported in this portal.';
    if (!isset($registry['stripe']['fields']['currency'])) {
        $registry['stripe']['fields']['currency'] = ['label' => 'Currency', 'type' => 'text', 'default' => 'USD'];
    }
    $registry['stripe']['fields']['base_url']['default'] = 'https://api.stripe.com';
    $registry['stripe']['fields']['sandbox_url']['default'] = 'https://api.stripe.com';
    $registry['stripe']['fields']['live_url']['default'] = 'https://api.stripe.com';
}
if (isset($registry['paypal'])) {
    $registry['paypal']['native_checkout'] = true;
    unset($registry['paypal']['requires_checkout_url']);
    $registry['paypal']['settings_help'] = 'Native PayPal OAuth + Orders checkout and capture are supported in this portal.';
    if (!isset($registry['paypal']['fields']['currency'])) {
        $registry['paypal']['fields']['currency'] = ['label' => 'Currency', 'type' => 'text', 'default' => 'USD'];
    }
    $registry['paypal']['fields']['base_url']['default'] = 'https://api-m.paypal.com';
    $registry['paypal']['fields']['sandbox_url']['default'] = 'https://api-m.sandbox.paypal.com';
    $registry['paypal']['fields']['live_url']['default'] = 'https://api-m.paypal.com';
}
        return $registry;
    }
}

if (!function_exists('portalGatewayConfigKeys')) {
    function portalGatewayConfigKeys(): array {
        $scalar = [];
        $checkbox = [];
        foreach (portalPaymentGatewayRegistry() as $providerKey => $providerDef) {
            $checkbox[] = $providerKey . '_enabled';
            foreach ((array)($providerDef['fields'] ?? []) as $fieldKey => $fieldMeta) {
                $scalar[] = $providerKey . '_' . $fieldKey;
            }
            foreach (['charge_percent','charge_flat','charge_cap','charge_flat_waive_below'] as $chargeKey) {
                $scalar[] = $providerKey . '_' . $chargeKey;
            }
        }
        return ['scalar' => array_values(array_unique($scalar)), 'checkbox' => array_values(array_unique($checkbox))];
    }
}

if (!function_exists('portalGatewayLabel')) {
    function portalGatewayLabel(string $provider): string {
        $registry = portalPaymentGatewayRegistry();
        return (string)($registry[$provider]['label'] ?? ucwords(str_replace(['_','-'], ' ', $provider)));
    }
}

if (!function_exists('portalGatewayEnabled')) {
    function portalGatewayEnabled(PDO $pdo, string $provider): bool {
        return getSystemSettingValue($pdo, $provider . '_enabled', 'disabled') === 'enabled';
    }
}

if (!function_exists('portalGatewayModeValue')) {
    function portalGatewayModeValue(PDO $pdo, string $provider): string {
        $registry = portalPaymentGatewayRegistry();
        $default = (string)($registry[$provider]['fields']['mode']['default'] ?? 'live');
        return strtolower(trim((string)getSystemSettingValue($pdo, $provider . '_mode', $default)));
    }
}

if (!function_exists('portalGatewaySupportsWalletFunding')) {
    function portalGatewaySupportsWalletFunding(string $provider): bool {
        $registry = portalPaymentGatewayRegistry();
        return !empty($registry[$provider]['wallet_funding']);
    }
}

if (!function_exists('portalGatewayHasNativeCheckout')) {
    function portalGatewayHasNativeCheckout(string $provider): bool {
        $registry = portalPaymentGatewayRegistry();
        return !empty($registry[$provider]['native_checkout']);
    }
}

if (!function_exists('portalGatewayCheckoutUrl')) {
    function portalGatewayCheckoutUrl(PDO $pdo, string $provider): string {
        return trim((string)getSystemSettingValue($pdo, $provider . '_checkout_url', ''));
    }
}


if (!function_exists('portalGatewayVerifyUrl')) {
    function portalGatewayVerifyUrl(PDO $pdo, string $provider): string {
        return trim((string)getSystemSettingValue($pdo, $provider . '_verify_url', ''));
    }
}

if (!function_exists('portalGatewayConfigValue')) {
    function portalGatewayConfigValue(PDO $pdo, string $provider, string $field, string $default = ''): string {
        return trim((string)getSystemSettingValue($pdo, $provider . '_' . $field, $default));
    }
}

if (!function_exists('portalGatewayApiBaseUrl')) {
    function portalGatewayApiBaseUrl(PDO $pdo, string $provider): string {
        $mode = portalGatewayModeValue($pdo, $provider);
        $sandbox = portalGatewayConfigValue($pdo, $provider, 'sandbox_url', portalGatewayConfigValue($pdo, $provider, 'base_url', ''));
        $live = portalGatewayConfigValue($pdo, $provider, 'live_url', portalGatewayConfigValue($pdo, $provider, 'base_url', ''));
        $base = ($mode === 'sandbox') ? $sandbox : $live;
        if ($base === '') $base = portalGatewayConfigValue($pdo, $provider, 'base_url', '');
        return rtrim($base, '/');
    }
}

if (!function_exists('portalGatewayHttpRequest')) {
    function portalGatewayHttpRequest(string $method, string $url, array $headers = [], $body = null, bool $formEncoded = false): array {
        $method = strtoupper(trim($method));
        $ch = curl_init();
        $resolvedBody = $body;
        if ($formEncoded && is_array($body)) {
            $resolvedBody = http_build_query($body);
        } elseif (!$formEncoded && is_array($body)) {
            $resolvedBody = json_encode($body);
            $hasContentType = false;
            foreach ($headers as $headerLine) {
                if (stripos((string)$headerLine, 'Content-Type:') === 0) { $hasContentType = true; break; }
            }
            if (!$hasContentType) $headers[] = 'Content-Type: application/json';
        }
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_TIMEOUT => 60,
            CURLOPT_HEADER => true,
        ]);
        if ($method !== 'GET' && $resolvedBody !== null && $resolvedBody !== '') {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $resolvedBody);
        }
        $raw = curl_exec($ch);
        $err = curl_error($ch);
        $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $headerSize = (int)curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        curl_close($ch);
        if ($raw === false) {
            return ['ok' => false, 'status' => $status, 'headers' => '', 'body' => '', 'json' => null, 'error' => $err !== '' ? $err : 'Unknown cURL error'];
        }
        $rawHeaders = substr((string)$raw, 0, $headerSize);
        $rawBody = substr((string)$raw, $headerSize);
        $json = json_decode((string)$rawBody, true);
        return ['ok' => $status >= 200 && $status < 300, 'status' => $status, 'headers' => $rawHeaders, 'body' => $rawBody, 'json' => is_array($json) ? $json : null, 'error' => $err];
    }
}

if (!function_exists('portalGatewayRemoteVerify')) {
    function portalGatewayRemoteVerify(PDO $pdo, string $provider, string $reference, array $request): array|false {
        $verifyUrl = portalGatewayVerifyUrl($pdo, $provider);
        if ($verifyUrl === '') return false;
        $query = array_merge($request, ['reference' => $reference, 'provider' => $provider]);
        $sep = str_contains($verifyUrl, '?') ? '&' : '?';
        $headers = ['Accept: application/json'];
        $secret = portalGatewayReturnTokenSecret($pdo, $provider);
        if ($secret !== '') {
            $headers[] = 'Authorization: Bearer ' . $secret;
            $headers[] = 'X-Gateway-Secret: ' . $secret;
        }
        foreach (['public_key','api_key','client_id','merchant_id','api_login_id'] as $candidateField) {
            $val = portalGatewayConfigValue($pdo, $provider, $candidateField, '');
            if ($val !== '') { $headers[] = 'X-Gateway-Key: ' . $val; break; }
        }
        $response = portalGatewayHttpRequest('GET', $verifyUrl . $sep . http_build_query($query), $headers, null, false);
        portalLogGatewayCallback($pdo, $provider, 'remote-verify', $reference, ['request' => $query, 'response_status' => $response['status'], 'response' => $response['json'] ?: $response['body']], '', $response['ok'] ? 'verify_response' : 'verify_failed');
        if (!$response['ok'] || !is_array($response['json'])) return false;
        $payload = $response['json'];
        $statusCandidates = [
            strtolower(trim((string)($payload['status'] ?? ''))),
            strtolower(trim((string)($payload['payment_status'] ?? ''))),
            strtolower(trim((string)($payload['gateway_status'] ?? ''))),
            strtolower(trim((string)($payload['transactionStatus'] ?? ''))),
            strtolower(trim((string)($payload['paymentStatus'] ?? ''))),
            strtolower(trim((string)($payload['responseBody']['paymentStatus'] ?? ''))),
            strtolower(trim((string)($payload['responseBody']['status'] ?? ''))),
        ];
        $responseCode = strtoupper(trim((string)($payload['responseCode'] ?? $payload['responseBody']['responseCode'] ?? '')));
        $statusOk = in_array(true, array_map(static fn($v) => in_array($v, ['success','successful','approved','paid','completed','complete','succeeded','captured','overpaid','partially_paid'], true), $statusCandidates), true) || $responseCode === '00';
        if (!$statusOk) return false;
        $amount = 0.0;
        foreach ([
            $payload['amount'] ?? null,
            $payload['charged_amount'] ?? null,
            $payload['amount_paid'] ?? null,
            $payload['data']['amount'] ?? null,
            $payload['data']['charged_amount'] ?? null,
            $payload['data']['amount_paid'] ?? null,
            $payload['responseBody']['amountPaid'] ?? null,
            $payload['responseBody']['totalPayable'] ?? null,
            $payload['responseBody']['amount'] ?? null,

        ] as $candidateAmount) {
            if (is_numeric($candidateAmount) && (float)$candidateAmount > 0) { $amount = (float)$candidateAmount; break; }
        }
        if ($amount <= 0) {
            $pending = $_SESSION['pending_payments'][$reference] ?? [];
            $amount = (float)($pending['charged_amount'] ?? $pending['amount'] ?? 0);
        }
        $pending = $_SESSION['pending_payments'][$reference] ?? [];
        return [
            'data' => [
                'status' => 'success',
                'reference' => $reference,
                'amount' => $amount * 100,
                'paid_at' => date('c'),
                'metadata' => [
                    'student_id' => (int)($request['student_id'] ?? ($pending['student_id'] ?? ($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0))),
                    'transaction_id' => (string)($request['transaction_id'] ?? ($pending['transaction_id'] ?? '')),
                    'base_amount' => (float)($request['base_amount'] ?? ($pending['base_amount'] ?? $amount)),
                    'gateway_fee' => (float)($request['gateway_fee'] ?? ($pending['gateway_fee'] ?? 0)),
                    'charged_amount' => (float)($request['charged_amount'] ?? ($pending['charged_amount'] ?? $amount)),
                    'installment_number' => (int)($pending['installment_number'] ?? 1),
                    'total_installments' => (int)($pending['total_installments'] ?? 1),
                    'academic_session' => (string)($request['academic_session'] ?? ($pending['academic_session'] ?? '')),
                    'semester' => (string)($request['semester'] ?? ($pending['semester'] ?? '')),
                ],
            ],
        ];
    }
}

if (!function_exists('portalGatewayExposedToStudents')) {
    function portalGatewayExposedToStudents(PDO $pdo, string $provider): bool {
        if (!portalGatewayEnabled($pdo, $provider)) return false;
        if (portalGatewayHasNativeCheckout($provider)) return true;
        return portalGatewayCheckoutUrl($pdo, $provider) !== '';
    }
}

if (!function_exists('portalGatewayConfigurationState')) {
    function portalGatewayConfigurationState(PDO $pdo, string $provider): array {
        $registry = portalPaymentGatewayRegistry();
        if (!isset($registry[$provider])) {
            return ['ok' => false, 'message' => 'Unknown gateway.', 'missing' => []];
        }
        $providerDef = $registry[$provider];
        $missing = [];
        foreach ((array)($providerDef['fields'] ?? []) as $fieldKey => $fieldMeta) {
            if (empty($fieldMeta['required'])) continue;
            $value = trim((string)getSystemSettingValue($pdo, $provider . '_' . $fieldKey, (string)($fieldMeta['default'] ?? '')));
            if ($value === '') $missing[] = (string)($fieldMeta['label'] ?? $fieldKey);
        }
        if (empty($providerDef['native_checkout']) && !empty($providerDef['requires_checkout_url'])) {
            $checkoutUrl = portalGatewayCheckoutUrl($pdo, $provider);
            if ($checkoutUrl === '') {
                $missing[] = 'Hosted Checkout URL / Middleware URL';
            }
        }
        if ($missing) {
            return ['ok' => false, 'message' => 'Missing: ' . implode(', ', array_unique($missing)), 'missing' => array_values(array_unique($missing))];
        }
        return ['ok' => true, 'message' => !empty($providerDef['native_checkout']) ? 'Native checkout ready.' : 'Configured for hosted/middleware checkout.', 'missing' => []];
    }
}

if (!function_exists('portalGatewaySupportsReturnVerification')) {
    function portalGatewaySupportsReturnVerification(string $provider): bool {
        return !portalGatewayHasNativeCheckout($provider);
    }
}

if (!function_exists('portalGatewayReturnTokenSecret')) {
    function portalGatewayReturnTokenSecret(PDO $pdo, string $provider): string {
        $secret = trim((string)getSystemSettingValue($pdo, $provider . '_webhook_secret', ''));
        if ($secret !== '') return $secret;
        foreach (['secret_key','api_secret','client_secret','private_key','transaction_key','access_token'] as $fallbackField) {
            $v = trim((string)getSystemSettingValue($pdo, $provider . '_' . $fallbackField, ''));
            if ($v !== '') return $v;
        }
        return '';
    }
}


if (!function_exists('portalBuildManagedGatewayReturnResult')) {
    function portalBuildManagedGatewayReturnResult(PDO $pdo, string $provider, string $reference, array $request): array|false {
        $provider = strtolower(trim($provider));
        if ($provider === '' || !portalGatewaySupportsReturnVerification($provider)) return false;

        $pending = $_SESSION['pending_payments'][$reference] ?? [];
        $status = strtolower(trim((string)($request['status'] ?? $request['payment_status'] ?? $request['gateway_status'] ?? '')));
        $amount = (float)($request['amount'] ?? $request['charged_amount'] ?? ($pending['charged_amount'] ?? $pending['amount'] ?? 0));
        $token = trim((string)($request['token'] ?? $request['signature'] ?? $request['hash'] ?? ''));
        $secret = portalGatewayReturnTokenSecret($pdo, $provider);

        if ($status !== '' && in_array($status, ['success','successful','approved','paid','completed'], true) && $reference !== '' && $amount > 0 && $secret !== '' && $token !== '') {
            $expected = hash_hmac('sha256', $reference . '|' . $status . '|' . number_format($amount, 2, '.', ''), $secret);
            if (hash_equals($expected, $token)) {
                return [
                    'data' => [
                        'status' => 'success',
                        'reference' => $reference,
                        'amount' => $amount * 100,
                        'paid_at' => date('c'),
                        'metadata' => [
                            'student_id' => (int)($request['student_id'] ?? ($pending['student_id'] ?? ($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0))),
                            'transaction_id' => (string)($request['transaction_id'] ?? ($pending['transaction_id'] ?? '')),
                            'base_amount' => (float)($request['base_amount'] ?? ($pending['base_amount'] ?? $amount)),
                            'gateway_fee' => (float)($request['gateway_fee'] ?? ($pending['gateway_fee'] ?? 0)),
                            'charged_amount' => (float)($request['charged_amount'] ?? ($pending['charged_amount'] ?? $amount)),
                            'installment_number' => (int)($pending['installment_number'] ?? 1),
                            'total_installments' => (int)($pending['total_installments'] ?? 1),
                            'academic_session' => (string)($request['academic_session'] ?? ($pending['academic_session'] ?? '')),
                            'semester' => (string)($request['semester'] ?? ($pending['semester'] ?? '')),
                        ],
                    ],
                ];
            }
        }
        return portalGatewayRemoteVerify($pdo, $provider, $reference, $request);
    }
}

if (!function_exists('portalEnabledGatewayOptions')) {
    function portalEnabledGatewayOptions(PDO $pdo, bool $walletOnly = false): array {
        $registry = portalPaymentGatewayRegistry();
        $options = [];
        foreach ($registry as $providerKey => $providerDef) {
            if ($walletOnly && empty($providerDef['wallet_funding'])) continue;
            if (!portalGatewayExposedToStudents($pdo, $providerKey)) continue;
            $options[$providerKey] = (string)($providerDef['label'] ?? ucwords($providerKey));
        }
        if (!$options && !$walletOnly) {
            $options['paystack'] = 'Paystack';
        }
        return $options;
    }
}

if (!function_exists('portalSortGatewayOptionsByDefault')) {
    function portalSortGatewayOptionsByDefault(array $options, string $defaultCsv): array {
        $ordered = [];
        $seen = [];
        $defaults = array_filter(array_map(static fn($v) => strtolower(trim((string)$v)), explode(',', $defaultCsv)));
        foreach ($defaults as $providerKey) {
            if (isset($options[$providerKey])) {
                $ordered[$providerKey] = $options[$providerKey];
                $seen[$providerKey] = true;
            }
        }
        foreach ($options as $providerKey => $label) {
            if (!isset($seen[$providerKey])) {
                $ordered[$providerKey] = $label;
            }
        }
        return $ordered;
    }
}



if (!function_exists('portalLogGatewayCallback')) {
    function portalLogGatewayCallback(PDO $pdo, string $provider, string $eventId, string $reference, array $payload, string $signatureHash = '', string $status = 'received'): void {
        try {
            $stmt = $pdo->prepare("INSERT INTO payment_gateway_callbacks (provider, event_id, reference, payload, signature_hash, status) VALUES (?,?,?,?,?,?)
                                   ON DUPLICATE KEY UPDATE reference = VALUES(reference), payload = VALUES(payload), signature_hash = VALUES(signature_hash), status = VALUES(status)");
            $stmt->execute([
                $provider,
                $eventId !== '' ? $eventId : null,
                $reference !== '' ? $reference : null,
                json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                $signatureHash !== '' ? $signatureHash : null,
                $status !== '' ? $status : 'received',
            ]);
        } catch (Throwable $e) {}
    }
}


if (!function_exists('portalGatewayLastUpdated')) {
    function portalGatewayLastUpdated(PDO $pdo, string $provider): string {
        $registry = portalPaymentGatewayRegistry();
        if (!isset($registry[$provider])) return '';
        $keys = [$provider . '_enabled'];
        foreach ((array)($registry[$provider]['fields'] ?? []) as $fieldKey => $_meta) {
            $keys[] = $provider . '_' . $fieldKey;
        }
        foreach (['charge_percent','charge_flat','charge_cap','charge_flat_waive_below'] as $chargeKey) {
            $keys[] = $provider . '_' . $chargeKey;
        }
        try {
            $ph = implode(',', array_fill(0, count($keys), '?'));
            $stmt = $pdo->prepare("SELECT MAX(updated_at) FROM system_settings WHERE setting_key IN ($ph)");
            $stmt->execute($keys);
            $val = (string)$stmt->fetchColumn();
            if ($val !== '' && $val !== '0000-00-00 00:00:00') {
                $ts = strtotime($val);
                if ($ts) return date('Y-m-d H:i', $ts);
            }
        } catch (Throwable $e) {}
        return '';
    }
}

if (!function_exists('portalGatewaySettingKeysForProvider')) {
    function portalGatewaySettingKeysForProvider(string $provider): array {
        $provider = strtolower(trim($provider));
        $registry = portalPaymentGatewayRegistry();
        if (!isset($registry[$provider])) return [];
        $keys = [$provider . '_enabled'];
        foreach ((array)($registry[$provider]['fields'] ?? []) as $fieldKey => $_meta) {
            $keys[] = $provider . '_' . $fieldKey;
        }
        foreach (['charge_percent','charge_flat','charge_cap','charge_flat_waive_below'] as $chargeKey) {
            $keys[] = $provider . '_' . $chargeKey;
        }
        return array_values(array_unique($keys));
    }
}

if (!function_exists('portalDefaultPaymentMethod')) {
    function portalDefaultPaymentMethod(PDO $pdo): string {
        $method = strtolower(trim((string)getSystemSettingValue($pdo, 'default_payment_method', 'auto')));
        return in_array($method, ['auto', 'manual'], true) ? $method : 'auto';
    }
}

if (!function_exists('portalDefaultGatewayProvider')) {
    function portalDefaultGatewayProvider(PDO $pdo, string $fallback = 'paystack'): string {
        $registry = portalPaymentGatewayRegistry();
        $stored = strtolower(trim((string)getSystemSettingValue($pdo, 'default_gateway', '')));
        if ($stored !== '' && isset($registry[$stored])) return $stored;
        $csv = (string)getSystemSettingValue($pdo, 'default_gateways', $fallback);
        foreach (explode(',', $csv) as $candidate) {
            $candidate = strtolower(trim((string)$candidate));
            if ($candidate !== '' && isset($registry[$candidate])) return $candidate;
        }
        return isset($registry[$fallback]) ? $fallback : (string)array_key_first($registry);
    }
}

if (!function_exists('portalGatewayFallbackProvider')) {
    function portalGatewayFallbackProvider(PDO $pdo, string $excludeProvider = ''): string {
        $excludeProvider = strtolower(trim($excludeProvider));
        $registry = portalPaymentGatewayRegistry();
        foreach ($registry as $providerKey => $_def) {
            if ($providerKey === $excludeProvider) continue;
            if (portalGatewayEnabled($pdo, $providerKey)) return $providerKey;
        }
        foreach ($registry as $providerKey => $_def) {
            if ($providerKey !== $excludeProvider) return $providerKey;
        }
        return 'paystack';
    }
}

if (!function_exists('portalRenderUpdateSettingsHiddenInputs')) {
    function portalRenderUpdateSettingsHiddenInputs(array $settings, array $excludeKeys = []): void {
        $excludeMap = array_fill_keys($excludeKeys, true);
        $allScalarKeys = [
            'registration_fee','acceptance_fee','hostel_fee','school_fee','payment_description','academic_session','application_deadline',
            'late_fee_type','late_fee_value','late_fee_grace_days','late_fee_apply_mode','late_fee_apply_to','late_fee_reason',
            'late_fee_first_semester_end_date','late_fee_second_semester_end_date','portal_title',
            'default_gateways','manual_payment_instructions',
            'smtp_host','smtp_port','smtp_username','smtp_password','smtp_from_email','smtp_from_name','smtp_reply_to',
            'idcard_signatory_name','admission_signatory_name','application_signatory_name','account_officer_signatory_name'
        ];
        $allCheckboxKeys = [
            'email_notifications','installment_enabled','late_fee_enabled','late_fee_auto_generate','online_payment_enabled',
            'manual_payment_enabled','lazy_loader_enabled','resume_user_progress_enabled','theme_mode_enabled'
        ];
        $gatewayKeys = portalGatewayConfigKeys();
        $allScalarKeys = array_values(array_unique(array_merge($allScalarKeys, $gatewayKeys['scalar'] ?? [])));
        $allCheckboxKeys = array_values(array_unique(array_merge($allCheckboxKeys, $gatewayKeys['checkbox'] ?? [])));

        foreach ($allScalarKeys as $key) {
            if (isset($excludeMap[$key])) continue;
            echo '<input type="hidden" name="' . htmlspecialchars($key, ENT_QUOTES, 'UTF-8') . '" value="' . htmlspecialchars((string)($settings[$key] ?? ''), ENT_QUOTES, 'UTF-8') . '">';
        }
        foreach ($allCheckboxKeys as $key) {
            if (isset($excludeMap[$key])) continue;
            if (($settings[$key] ?? 'disabled') === 'enabled') {
                echo '<input type="hidden" name="' . htmlspecialchars($key, ENT_QUOTES, 'UTF-8') . '" value="1">';
            }
        }
    }
}

if (!function_exists('portalManagedFeeScopeLabel')) {
    function portalManagedFeeScopeLabel(array $row): string {
        $parts = [];
        $session = trim((string)($row['session_name'] ?? ''));
        $semester = trim((string)($row['semester'] ?? ''));
        $faculty = trim((string)($row['faculty_name'] ?? ''));
        $department = trim((string)($row['department_name'] ?? ''));
        $programme = trim((string)($row['programme_name'] ?? ''));
        $level = trim((string)($row['level'] ?? ''));
        if ($faculty !== '') $parts[] = $faculty;
        if ($department !== '') $parts[] = $department;
        if ($programme !== '') $parts[] = $programme;
        if ($session !== '') $parts[] = $session;
        if ($semester !== '') $parts[] = $semester;
        if ($level !== '') $parts[] = $level;
        if (!$parts) return 'All Students';
        return implode(' / ', $parts);
    }
}

if (!function_exists('portalEnsurePublicReceiptSupport')) {
    function portalEnsurePublicReceiptSupport(PDO $pdo): void {
        try {
            if (portalTableExistsSafe($pdo, 'payment_transactions')) {
                if (!portalColumnExistsSafe($pdo, 'payment_transactions', 'receipt_public_token')) {
                    $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN receipt_public_token VARCHAR(96) NULL AFTER receipt_number");
                }
                if (!portalColumnExistsSafe($pdo, 'payment_transactions', 'receipt_public_expires_at')) {
                    $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN receipt_public_expires_at DATETIME NULL AFTER receipt_public_token");
                }
                if (!portalIndexExistsSafe($pdo, 'payment_transactions', 'idx_pt_receipt_public_token')) {
                    $pdo->exec("CREATE INDEX idx_pt_receipt_public_token ON payment_transactions(receipt_public_token)");
                }
            }
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalReceiptPublicToken')) {
    function portalReceiptPublicToken(PDO $pdo, array $transaction): string {
        portalEnsurePublicReceiptSupport($pdo);
        $id = (int)($transaction['id'] ?? 0);
        if ($id <= 0) return '';
        $existing = trim((string)($transaction['receipt_public_token'] ?? ''));
        if ($existing !== '') return $existing;
        try {
            $stmt = $pdo->prepare("SELECT receipt_public_token FROM payment_transactions WHERE id = ? LIMIT 1");
            $stmt->execute([$id]);
            $dbToken = trim((string)($stmt->fetchColumn() ?: ''));
            if ($dbToken !== '') return $dbToken;
        } catch (Throwable $e) {}
        try {
            $token = bin2hex(random_bytes(32));
        } catch (Throwable $e) {
            $token = hash('sha256', uniqid((string)$id, true) . microtime(true) . mt_rand());
        }
        try {
            $pdo->prepare("UPDATE payment_transactions SET receipt_public_token = ?, receipt_public_expires_at = COALESCE(receipt_public_expires_at, DATE_ADD(NOW(), INTERVAL 3650 DAY)) WHERE id = ? AND COALESCE(receipt_public_token,'') = ''")
                ->execute([$token, $id]);
            $stmt = $pdo->prepare("SELECT receipt_public_token FROM payment_transactions WHERE id = ? LIMIT 1");
            $stmt->execute([$id]);
            $fresh = trim((string)($stmt->fetchColumn() ?: ''));
            if ($fresh !== '') return $fresh;
        } catch (Throwable $e) {}
        return $token;
    }
}

if (!function_exists('portalPublicReceiptUrl')) {
    function portalPublicReceiptUrl(PDO $pdo, array $transaction): string {
        $token = portalReceiptPublicToken($pdo, $transaction);
        if ($token === '') return '';
        return rtrim(appBaseUrl(), '/') . '/index.php?page=payment_receipt&receipt_token=' . rawurlencode($token);
    }
}

if (!function_exists('portalFindReceiptTransactionByPublicToken')) {
    function portalFindReceiptTransactionByPublicToken(PDO $pdo, string $token): ?array {
        $token = trim($token);
        if ($token === '') return null;
        portalEnsurePublicReceiptSupport($pdo);
        try {
            $stmt = $pdo->prepare("SELECT pt.*, s.first_name, s.surname, s.other_name, s.registration_no, s.application_no, s.admission_no, s.matric_no, s.level
                FROM payment_transactions pt
                JOIN students s ON pt.student_id = s.id
                WHERE pt.receipt_public_token = ?
                  AND (pt.receipt_public_expires_at IS NULL OR pt.receipt_public_expires_at >= NOW())
                LIMIT 1");
            $stmt->execute([$token]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
            return $row ?: null;
        } catch (Throwable $e) {
            return null;
        }
    }
}

if (!function_exists('portalSchoolLogoPath')) {
    function portalSchoolLogoPath(): ?string {
        $logo = getSystemAsset('school_logo');
        if (!$logo) $logo = getSystemAsset('left_logo');
        if (!$logo) $logo = getSystemAsset('right_logo');
        return $logo ?: null;
    }
}


if (!function_exists('portalEnsureCourseRegistrationProfileSupport')) {
    function portalEnsureCourseRegistrationProfileSupport(PDO $pdo): void {
        try { if (!portalColumnExistsSafe($pdo, 'students', 'title')) { $pdo->exec("ALTER TABLE students ADD COLUMN title VARCHAR(20) NULL AFTER matric_no"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'marital_status')) { $pdo->exec("ALTER TABLE students ADD COLUMN marital_status VARCHAR(40) NULL AFTER date_of_birth"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'mode_of_study')) { $pdo->exec("ALTER TABLE students ADD COLUMN mode_of_study VARCHAR(40) NULL AFTER email"); } } catch (Throwable $e) {}
        portalEnsureProgrammeModeOfStudySupport($pdo);
        try { if (!portalColumnExistsSafe($pdo, 'students', 'school_name')) { $pdo->exec("ALTER TABLE students ADD COLUMN school_name VARCHAR(180) NULL AFTER faculty"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'school')) { $pdo->exec("ALTER TABLE students ADD COLUMN school VARCHAR(180) NULL AFTER school_name"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'course_in_view')) { $pdo->exec("ALTER TABLE students ADD COLUMN course_in_view VARCHAR(180) NULL AFTER department"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'course_of_study')) { $pdo->exec("ALTER TABLE students ADD COLUMN course_of_study VARCHAR(180) NULL AFTER course_in_view"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'programme_name_cache')) { $pdo->exec("ALTER TABLE students ADD COLUMN programme_name_cache VARCHAR(180) NULL AFTER programme"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'course_registrations', 'carry_over_status')) { $pdo->exec("ALTER TABLE course_registrations ADD COLUMN carry_over_status ENUM('Yes','No') NOT NULL DEFAULT 'No' AFTER total_units"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'course_registrations', 'carry_over_note')) { $pdo->exec("ALTER TABLE course_registrations ADD COLUMN carry_over_note TEXT NULL AFTER carry_over_status"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'course_registrations', 'public_form_token')) { $pdo->exec("ALTER TABLE course_registrations ADD COLUMN public_form_token VARCHAR(96) NULL AFTER carry_over_note"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'course_registrations', 'public_form_token_hash')) { $pdo->exec("ALTER TABLE course_registrations ADD COLUMN public_form_token_hash CHAR(64) NULL AFTER public_form_token"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'course_registrations', 'public_form_token_created_at')) { $pdo->exec("ALTER TABLE course_registrations ADD COLUMN public_form_token_created_at DATETIME NULL AFTER public_form_token_hash"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'course_registrations') && !portalIndexExistsSafe($pdo, 'course_registrations', 'idx_cr_public_form_token_hash')) { $pdo->exec("CREATE INDEX idx_cr_public_form_token_hash ON course_registrations(public_form_token_hash)"); } } catch (Throwable $e) {}
        portalEnsureCourseRegistrationCarryOverItemSupport($pdo);
        try {
            $pdo->exec("UPDATE students
                SET mode_of_study = CASE
                    WHEN LOWER(COALESCE(programme, '')) LIKE '%part%' OR LOWER(COALESCE(programme, '')) LIKE '%pde%' THEN 'Part-Time'
                    WHEN LOWER(COALESCE(programme, '')) LIKE '%sandwich%' THEN 'Sandwich'
                    WHEN LOWER(COALESCE(programme, '')) LIKE '%distance%' THEN 'Distance Learning'
                    ELSE 'Full-Time'
                END
                WHERE mode_of_study IS NULL OR TRIM(mode_of_study) = ''");
        } catch (Throwable $e) {}
        try {
            $pdo->exec("UPDATE students
                SET school_name = COALESCE(NULLIF(TRIM(school_name),''), NULLIF(TRIM(faculty),'')),
                    course_in_view = COALESCE(NULLIF(TRIM(course_in_view),''), NULLIF(TRIM(department),''), NULLIF(TRIM(programme),'')),
                    course_of_study = COALESCE(NULLIF(TRIM(course_of_study),''), NULLIF(TRIM(department),''), NULLIF(TRIM(programme),'')),
                    programme_name_cache = COALESCE(NULLIF(TRIM(programme_name_cache),''), NULLIF(TRIM(programme),''))");
        } catch (Throwable $e) {}
    }

if (!function_exists('portalCourseRegistrationStatusExpr')) {
    function portalCourseRegistrationStatusExpr(PDO $pdo, string $alias = 'cr'): string {
        $prefix = trim($alias) !== '' ? trim($alias) . '.' : '';
        return portalColumnExistsSafe($pdo, 'course_registrations', 'status')
            ? "COALESCE({$prefix}status, 'submitted')"
            : "'submitted'";
    }
}

if (!function_exists('portalEnsureCourseRegistrationSyncSupport')) {
    function portalEnsureCourseRegistrationSyncSupport(PDO $pdo): void {
        portalEnsureCourseRegistrationProfileSupport($pdo);

        try {
            if (!portalColumnExistsSafe($pdo, 'course_registrations', 'status')) {
                $pdo->exec("ALTER TABLE course_registrations ADD COLUMN status ENUM('draft','submitted','cancelled') NOT NULL DEFAULT 'submitted' AFTER level");
            }
        } catch (Throwable $e) {}

        try {
            if (portalTableExistsSafe($pdo, 'course_registrations') && !portalIndexExistsSafe($pdo, 'course_registrations', 'idx_cr_sync_status')) {
                $pdo->exec("CREATE INDEX idx_cr_sync_status ON course_registrations(status)");
            }
        } catch (Throwable $e) {}

        try {
            $pdo->exec("UPDATE course_registrations SET status = 'submitted' WHERE status IS NULL OR TRIM(status) = ''");
        } catch (Throwable $e) {}

        $crSessionCol = portalCourseRegistrationSessionColumn($pdo);
        $statusExpr = portalCourseRegistrationStatusExpr($pdo, 'cr');

        try {
            $pdo->exec("
                UPDATE course_registrations cr
                INNER JOIN students s ON s.id = cr.student_id
                SET
                    cr.faculty_id = CASE
                        WHEN COALESCE(cr.faculty_id, 0) = 0 AND COALESCE(s.faculty_id, 0) > 0 THEN s.faculty_id
                        ELSE cr.faculty_id
                    END,
                    cr.department_id = CASE
                        WHEN COALESCE(cr.department_id, 0) = 0 AND COALESCE(s.department_id, 0) > 0 THEN s.department_id
                        ELSE cr.department_id
                    END,
                    cr.programme_id = CASE
                        WHEN COALESCE(cr.programme_id, 0) = 0 AND COALESCE(s.programme_id, 0) > 0 THEN s.programme_id
                        ELSE cr.programme_id
                    END,
                    cr.level = CASE
                        WHEN TRIM(COALESCE(cr.level, '')) = '' AND TRIM(COALESCE(s.level, '')) <> '' THEN s.level
                        ELSE cr.level
                    END,
                    cr.{$crSessionCol} = CASE
                        WHEN TRIM(COALESCE(cr.{$crSessionCol}, '')) = '' AND TRIM(COALESCE(s.session, '')) <> '' THEN s.session
                        ELSE cr.{$crSessionCol}
                    END,
                    cr.status = CASE
                        WHEN {$statusExpr} = 'cancelled' THEN 'cancelled'
                        ELSE 'submitted'
                    END
                WHERE cr.student_id > 0
            ");
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalSynchronizeCourseRegistrationForScoresheet')) {
    function portalSynchronizeCourseRegistrationForScoresheet(PDO $pdo, int $registrationId, int $studentId = 0): void {
        portalEnsureCourseRegistrationSyncSupport($pdo);
        if ($registrationId <= 0) return;

        $crSessionCol = portalCourseRegistrationSessionColumn($pdo);

        if ($studentId <= 0) {
            try {
                $st = $pdo->prepare("SELECT student_id FROM course_registrations WHERE id = ? LIMIT 1");
                $st->execute([$registrationId]);
                $studentId = (int)($st->fetchColumn() ?: 0);
            } catch (Throwable $e) {
                $studentId = 0;
            }
        }

        try {
            $st = $pdo->prepare("
                UPDATE course_registrations cr
                LEFT JOIN students s ON s.id = cr.student_id
                SET
                    cr.faculty_id = CASE
                        WHEN COALESCE(cr.faculty_id, 0) = 0 AND COALESCE(s.faculty_id, 0) > 0 THEN s.faculty_id
                        ELSE cr.faculty_id
                    END,
                    cr.department_id = CASE
                        WHEN COALESCE(cr.department_id, 0) = 0 AND COALESCE(s.department_id, 0) > 0 THEN s.department_id
                        ELSE cr.department_id
                    END,
                    cr.programme_id = CASE
                        WHEN COALESCE(cr.programme_id, 0) = 0 AND COALESCE(s.programme_id, 0) > 0 THEN s.programme_id
                        ELSE cr.programme_id
                    END,
                    cr.level = CASE
                        WHEN TRIM(COALESCE(cr.level, '')) = '' AND TRIM(COALESCE(s.level, '')) <> '' THEN s.level
                        ELSE cr.level
                    END,
                    cr.{$crSessionCol} = CASE
                        WHEN TRIM(COALESCE(cr.{$crSessionCol}, '')) = '' AND TRIM(COALESCE(s.session, '')) <> '' THEN s.session
                        ELSE cr.{$crSessionCol}
                    END,
                    cr.status = CASE
                        WHEN COALESCE(cr.status, '') = 'cancelled' THEN 'cancelled'
                        WHEN COALESCE(cr.status, '') = '' THEN 'submitted'
                        ELSE cr.status
                    END
                WHERE cr.id = ?
            ");
            $st->execute([$registrationId]);
        } catch (Throwable $e) {}

        try {
            $st = $pdo->prepare("SELECT faculty_id, department_id, programme_id, level, {$crSessionCol} AS session_name, semester FROM course_registrations WHERE id = ? LIMIT 1");
            $st->execute([$registrationId]);
            $reg = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            portalSyncCourseRegistrationItemLinks($pdo, [
                'faculty_id' => (int)($reg['faculty_id'] ?? 0),
                'department_id' => (int)($reg['department_id'] ?? 0),
                'programme_id' => (int)($reg['programme_id'] ?? 0),
                'level' => (string)($reg['level'] ?? ''),
                'session' => (string)($reg['session_name'] ?? ''),
                'semester' => (string)($reg['semester'] ?? ''),
            ]);
        } catch (Throwable $e) {}
    }
}

}

if (!function_exists('portalCourseRegistrationOtherNames')) {
    function portalCourseRegistrationOtherNames(array $row): string {
        $first = trim((string)($row['first_name'] ?? ''));
        $other = trim((string)($row['other_name'] ?? ''));
        return trim($first . ' ' . $other);
    }
}

if (!function_exists('portalSplitCourseRegistrationOtherNames')) {
    function portalSplitCourseRegistrationOtherNames(string $value): array {
        $value = trim((string)$value);
        $value = preg_replace('/\s+/', ' ', $value) ?: $value;
        if ($value === '') return ['', ''];
        $parts = explode(' ', $value, 2);
        return [trim((string)($parts[0] ?? '')), trim((string)($parts[1] ?? ''))];
    }
}

if (!function_exists('portalInferModeOfStudy')) {
    function portalInferModeOfStudy(array $row): string {
        $stored = trim((string)($row['mode_of_study'] ?? ''));
        if ($stored !== '') return $stored;
        $programmeMode = portalProgrammeModeOfStudyDisplay($row, '');
        if ($programmeMode !== '') return $programmeMode;
        $hay = strtolower(trim((string)($row['programme_name_cache'] ?? $row['programme_name'] ?? $row['programme_text'] ?? $row['programme'] ?? '')));
        if (str_contains($hay, 'part') || str_contains($hay, 'pde')) return 'Part-Time';
        if (str_contains($hay, 'sandwich')) return 'Sandwich';
        if (str_contains($hay, 'distance')) return 'Distance Learning';
        return 'Full-Time';
    }
}

if (!function_exists('portalResolveStudentModeOfStudy')) {
    function portalResolveStudentModeOfStudy(PDO $pdo, array $student, array $context = [], int $programmeId = 0): string {
        $candidates = [
            trim((string)($student['mode_of_study'] ?? '')),
            trim((string)($context['mode_of_study'] ?? '')),
            trim((string)($student['programme_mode_of_study'] ?? '')),
            trim((string)($context['programme_mode_of_study'] ?? '')),
        ];
        foreach ($candidates as $candidate) {
            $candidate = portalNormalizeRegistrableCourseModeOfStudy($candidate, '');
            if ($candidate !== '') {
                return $candidate;
            }
        }
        if ($programmeId <= 0) {
            $programmeId = (int)($context['programme_id'] ?? ($student['programme_id'] ?? 0));
        }
        if ($programmeId > 0) {
            $programmeMode = portalNormalizeRegistrableCourseModeOfStudy(portalProgrammeModeOfStudyValueById($pdo, $programmeId, ''), '');
            if ($programmeMode !== '') {
                return $programmeMode;
            }
        }
        return portalNormalizeRegistrableCourseModeOfStudy(portalInferModeOfStudy(array_merge($student, $context, ['programme_id' => $programmeId])), 'Full-Time');
    }
}


if (!function_exists('portalResolveCourseFormProfile')) {
    function portalResolveCourseFormProfile(PDO $pdo, array $row): array {
        $studentId = (int)($row['student_id'] ?? $row['id'] ?? 0);
        $facultyId = (int)($row['faculty_id'] ?? 0);
        $departmentId = (int)($row['department_id'] ?? 0);
        $programmeId = (int)($row['programme_id'] ?? 0);

        $school = trim((string)($row['school_name'] ?? $row['school'] ?? $row['faculty_name'] ?? $row['faculty_text'] ?? $row['faculty'] ?? ''));
        $department = trim((string)($row['course_in_view'] ?? $row['course_of_study'] ?? $row['department_name'] ?? $row['department_text'] ?? $row['department'] ?? ''));
        $programme = trim((string)($row['programme_name_cache'] ?? $row['programme_name'] ?? $row['programme_text'] ?? $row['programme'] ?? ''));

        try {
            if ($facultyId > 0 && $school === '') {
                $st = $pdo->prepare("SELECT name FROM faculties WHERE id = ? LIMIT 1");
                $st->execute([$facultyId]);
                $school = trim((string)($st->fetchColumn() ?: ''));
            }
            if ($departmentId > 0 && $department === '') {
                $st = $pdo->prepare("SELECT name FROM departments WHERE id = ? LIMIT 1");
                $st->execute([$departmentId]);
                $department = trim((string)($st->fetchColumn() ?: ''));
            }
            if ($programmeId > 0 && $programme === '') {
                $st = $pdo->prepare("SELECT name FROM programmes WHERE id = ? LIMIT 1");
                $st->execute([$programmeId]);
                $programme = trim((string)($st->fetchColumn() ?: ''));
            }
        } catch (Throwable $e) {}

        $courseInView = $department !== '' ? $department : ($programme !== '' ? $programme : trim((string)($row['course_in_view'] ?? $row['course_of_study'] ?? '')));
        $courseOfStudy = trim((string)($row['course_of_study'] ?? ''));
        if ($courseOfStudy === '') $courseOfStudy = $courseInView !== '' ? $courseInView : ($programme !== '' ? $programme : 'Not Set');
        if ($school === '') $school = 'Not Set';
        if ($programme === '') $programme = ($courseOfStudy !== '' && $courseOfStudy !== 'Not Set') ? $courseOfStudy : 'Not Set';
        if ($courseInView === '') $courseInView = $courseOfStudy !== '' ? $courseOfStudy : 'Not Set';
        $school = portalNormalizeCourseFormSchoolName($school);

        if ($studentId > 0) {
            try {
                $st = $pdo->prepare("UPDATE students SET school_name = ?, course_in_view = ?, course_of_study = ?, programme_name_cache = ? WHERE id = ?");
                $st->execute([
                    $school !== 'Not Set' ? $school : null,
                    $courseInView !== 'Not Set' ? $courseInView : null,
                    $courseOfStudy !== 'Not Set' ? $courseOfStudy : null,
                    $programme !== 'Not Set' ? $programme : null,
                    $studentId
                ]);
            } catch (Throwable $e) {}
        }

        return [
            'school' => $school,
            'course_in_view' => $courseInView,
            'course_of_study' => $courseOfStudy,
            'programme' => $programme,
        ];
    }
}


if (!function_exists('portalEnsureStudentAcademicMappingSupport')) {
    function portalEnsureStudentAcademicMappingSupport(PDO $pdo): void {
        portalEnsureCourseRegistrationProfileSupport($pdo);
        try { if (!portalColumnExistsSafe($pdo, 'students', 'school')) { $pdo->exec("ALTER TABLE students ADD COLUMN school VARCHAR(180) NULL AFTER school_name"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'students') && !portalIndexExistsSafe($pdo, 'students', 'idx_students_academic_scope')) { $pdo->exec("CREATE INDEX idx_students_academic_scope ON students(faculty_id, department_id, programme_id)"); } } catch (Throwable $e) {}
        try {
            $pdo->exec("UPDATE students s
                LEFT JOIN faculties f ON f.id = s.faculty_id
                LEFT JOIN departments d ON d.id = s.department_id
                LEFT JOIN programmes p ON p.id = s.programme_id
                LEFT JOIN (
                    SELECT cr1.student_id, cr1.faculty_id, cr1.department_id, cr1.programme_id
                    FROM course_registrations cr1
                    INNER JOIN (
                        SELECT student_id, MAX(id) AS latest_id
                        FROM course_registrations
                        GROUP BY student_id
                    ) latest ON latest.latest_id = cr1.id
                ) reg ON reg.student_id = s.id
                LEFT JOIN faculties rf ON rf.id = reg.faculty_id
                LEFT JOIN departments rd ON rd.id = reg.department_id
                LEFT JOIN programmes rp ON rp.id = reg.programme_id
                SET
                    s.school_name = COALESCE(NULLIF(TRIM(s.school_name),''), NULLIF(TRIM(s.school),''), NULLIF(TRIM(f.name),''), NULLIF(TRIM(rf.name),''), NULLIF(TRIM(s.faculty),'')),
                    s.school = COALESCE(NULLIF(TRIM(s.school),''), NULLIF(TRIM(s.school_name),''), NULLIF(TRIM(f.name),''), NULLIF(TRIM(rf.name),''), NULLIF(TRIM(s.faculty),'')),
                    s.course_in_view = COALESCE(NULLIF(TRIM(s.course_in_view),''), NULLIF(TRIM(s.course_of_study),''), NULLIF(TRIM(d.name),''), NULLIF(TRIM(rd.name),''), NULLIF(TRIM(s.department),''), NULLIF(TRIM(p.name),''), NULLIF(TRIM(rp.name),''), NULLIF(TRIM(s.programme),'')),
                    s.course_of_study = COALESCE(NULLIF(TRIM(s.course_of_study),''), NULLIF(TRIM(s.course_in_view),''), NULLIF(TRIM(d.name),''), NULLIF(TRIM(rd.name),''), NULLIF(TRIM(s.department),''), NULLIF(TRIM(p.name),''), NULLIF(TRIM(rp.name),''), NULLIF(TRIM(s.programme),'')),
                    s.programme_name_cache = COALESCE(NULLIF(TRIM(s.programme_name_cache),''), NULLIF(TRIM(p.name),''), NULLIF(TRIM(rp.name),''), NULLIF(TRIM(s.programme),'')),
                    s.faculty = COALESCE(NULLIF(TRIM(s.faculty),''), NULLIF(TRIM(f.name),''), NULLIF(TRIM(rf.name),''), NULLIF(TRIM(s.school_name),''), NULLIF(TRIM(s.school),'')),
                    s.department = COALESCE(NULLIF(TRIM(s.department),''), NULLIF(TRIM(d.name),''), NULLIF(TRIM(rd.name),''), NULLIF(TRIM(s.course_of_study),''), NULLIF(TRIM(s.course_in_view),'')),
                    s.programme = COALESCE(NULLIF(TRIM(s.programme),''), NULLIF(TRIM(p.name),''), NULLIF(TRIM(rp.name),''), NULLIF(TRIM(s.programme_name_cache),'')),
                    s.faculty_id = COALESCE(s.faculty_id, reg.faculty_id),
                    s.department_id = COALESCE(s.department_id, reg.department_id),
                    s.programme_id = COALESCE(s.programme_id, reg.programme_id)");
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalResolveStudentAcademicProfile')) {
    function portalResolveStudentAcademicProfile(PDO $pdo, array $row): array {
        $profile = portalResolveCourseFormProfile($pdo, $row);
        $school = trim((string)($row['school'] ?? $row['school_name'] ?? $profile['school'] ?? $row['faculty_name'] ?? $row['faculty'] ?? ''));
        $courseOfStudy = trim((string)($row['course_of_study'] ?? $profile['course_of_study'] ?? $profile['course_in_view'] ?? $row['department_name'] ?? $row['department'] ?? ''));
        $programme = trim((string)($row['programme_name_cache'] ?? $profile['programme'] ?? $row['programme_name'] ?? $row['programme'] ?? ''));
        if ($school === '' || strcasecmp($school, 'Not Set') === 0) $school = trim((string)($profile['school'] ?? $row['faculty_name'] ?? $row['faculty'] ?? ''));
        if ($courseOfStudy === '' || strcasecmp($courseOfStudy, 'Not Set') === 0) $courseOfStudy = trim((string)($profile['course_of_study'] ?? $profile['course_in_view'] ?? $row['department_name'] ?? $row['department'] ?? $programme));
        if ($programme === '' || strcasecmp($programme, 'Not Set') === 0) $programme = trim((string)($profile['programme'] ?? $row['programme_name'] ?? $row['programme'] ?? ''));
        $modeOfStudy = portalInferModeOfStudy(array_merge($profile, $row, ['programme' => $programme]));
        $school = $school !== '' && strcasecmp($school, 'Not Set') !== 0 ? $school : '—';
        $courseOfStudy = $courseOfStudy !== '' && strcasecmp($courseOfStudy, 'Not Set') !== 0 ? $courseOfStudy : '—';
        $programme = $programme !== '' && strcasecmp($programme, 'Not Set') !== 0 ? $programme : '—';
        $modeOfStudy = $modeOfStudy !== '' ? $modeOfStudy : 'Full-Time';
        $studentId = (int)($row['id'] ?? $row['student_id'] ?? 0);
        if ($studentId > 0) {
            try {
                $st = $pdo->prepare("UPDATE students SET school_name = COALESCE(?, school_name), school = COALESCE(?, school), course_of_study = COALESCE(?, course_of_study), course_in_view = COALESCE(?, course_in_view), programme_name_cache = COALESCE(?, programme_name_cache), mode_of_study = COALESCE(?, mode_of_study) WHERE id = ?");
                $st->execute([$school !== '—' ? $school : null, $school !== '—' ? $school : null, $courseOfStudy !== '—' ? $courseOfStudy : null, $courseOfStudy !== '—' ? $courseOfStudy : null, $programme !== '—' ? $programme : null, $modeOfStudy !== '' ? $modeOfStudy : null, $studentId]);
            } catch (Throwable $e) {}
        }
        return ['school' => $school, 'course_of_study' => $courseOfStudy, 'programme' => $programme, 'mode_of_study' => $modeOfStudy];
    }
}

if (!function_exists('portalRenderAdminStudentListRow')) {
    function portalRenderAdminStudentListRow(PDO $pdo, array $student): string {
        $academic = portalResolveStudentAcademicProfile($pdo, $student);
        $applicationStatus = (string)($student['application_status'] ?? 'Pending');
        $accountStatus = portalNormalizeStudentAccountStatus($student['account_status'] ?? 'Active');
        $statusBadgeClass = strtolower(str_replace(' ', '-', $accountStatus));
        $canUpdateStatus = portalAdminStudentStatusUpdateAllowed($pdo);
        $statusToken = $canUpdateStatus ? portalStudentStatusUpdateToken() : '';
        $createdAt = trim((string)($student['created_at'] ?? ''));
        $dateLabel = '—';
        if ($createdAt !== '') { try { $dateLabel = date('d M Y', strtotime($createdAt)); } catch (Throwable $e) { $dateLabel = $createdAt; } }
        ob_start();
        ?>
        <tr data-student-id="<?php echo (int)($student['id'] ?? 0); ?>" data-account-status="<?php echo e($accountStatus); ?>">
            <td><?php echo e(portalReceiptMatricNumber($student)); ?></td>
            <td><?php echo e(studentFullName($student)); ?></td>
            <td><?php echo e($academic['school']); ?></td>
            <td><?php echo e($academic['course_of_study']); ?></td>
            <td><?php echo e($academic['programme']); ?></td>
            <td><?php echo e((string)($academic['mode_of_study'] ?? portalInferModeOfStudy($student))); ?></td>
            <td><?php echo e((string)($student['email'] ?? '')); ?></td>
            <td><?php echo e((string)($student['mobile_phone'] ?? '')); ?></td>
            <td><span class="status-badge status-<?php echo e(strtolower(str_replace(' ', '-', $applicationStatus))); ?>"><?php echo e($applicationStatus); ?></span></td>
            <td><span class="status-badge account-status-badge status-<?php echo e($statusBadgeClass); ?>" data-status-value="<?php echo e($accountStatus); ?>"><?php echo e($accountStatus); ?></span></td>
            <td><?php echo !empty($student['id_card_generated']) ? 'Generated' : 'Not Generated'; ?></td>
            <td><?php echo e($dateLabel); ?></td>
            <td class="student-actions-cell text-nowrap">
                <a href="?page=admin_students&action=view&id=<?php echo (int)($student['id'] ?? 0); ?>" class="btn btn-sm btn-primary">View</a>
                <?php if (isSuperadmin($pdo)): ?>
                    <a href="?page=admin_students&action=login&id=<?php echo (int)($student['id'] ?? 0); ?>" class="btn btn-sm btn-dark ms-1">Access Account</a>
                <?php endif; ?>
                <?php if ($canUpdateStatus): ?>
                    <div class="btn-group dropstart ms-1 student-status-dropdown" data-student-status-dropdown="1" data-student-id="<?php echo (int)($student['id'] ?? 0); ?>" data-status-token="<?php echo e($statusToken); ?>">
                        <button class="btn btn-sm btn-warning dropdown-toggle student-status-toggle" type="button" data-bs-toggle="dropdown" data-bs-auto-close="true" aria-expanded="false" aria-label="Change student account status">Status</button>
                        <ul class="dropdown-menu dropdown-menu-end shadow-sm student-status-menu" data-student-status-menu="1" role="menu">
                            <?php foreach (portalStudentAccountStatusAllowedValues() as $rowStatus): ?>
                                <li>
                                    <button
                                        type="button"
                                        class="dropdown-item student-status-action <?php echo $accountStatus === $rowStatus ? 'active' : ''; ?>"
                                        data-student-id="<?php echo (int)($student['id'] ?? 0); ?>"
                                        data-status="<?php echo e($rowStatus); ?>"
                                        data-current-status="<?php echo e($accountStatus); ?>"
                                        aria-pressed="<?php echo $accountStatus === $rowStatus ? 'true' : 'false'; ?>"
                                        role="menuitem"
                                    ><?php echo e($rowStatus); ?></button>
                                </li>
                            <?php endforeach; ?>
                        </ul>
                    </div>
                <?php else: ?>
                    <button type="button" class="btn btn-sm btn-warning ms-1" disabled title="You do not have permission to change student status.">Status</button>
                <?php endif; ?>
                <?php if ($applicationStatus === 'Approved' && empty($student['id_card_generated'])): ?>
                    <button class="btn btn-sm btn-info ms-1" onclick="generateIdCard(<?php echo (int)($student['id'] ?? 0); ?>)">ID Card</button>
                <?php endif; ?>
            </td>
        </tr>
        <?php
        return (string)ob_get_clean();
    }
}

if (!function_exists('portalEnsureCourseRegistrationPublicToken')) {
    function portalEnsureCourseRegistrationPublicToken(PDO $pdo, int $registrationId): string {
        portalEnsureCourseRegistrationProfileSupport($pdo);
        if ($registrationId <= 0) return '';
        try {
            $st = $pdo->prepare("SELECT public_form_token FROM course_registrations WHERE id = ? LIMIT 1");
            $st->execute([$registrationId]);
            $existing = trim((string)($st->fetchColumn() ?: ''));
            if ($existing !== '') return $existing;
            $token = rtrim(strtr(base64_encode(random_bytes(33)), '+/', '-_'), '=');
            $hash = hash('sha256', $token);
            $up = $pdo->prepare("UPDATE course_registrations SET public_form_token = ?, public_form_token_hash = ?, public_form_token_created_at = NOW() WHERE id = ?");
            $up->execute([$token, $hash, $registrationId]);
            return $token;
        } catch (Throwable $e) {
            return '';
        }
    }
}

if (!function_exists('portalCourseFormPublicUrl')) {
    function portalCourseFormPublicUrl(PDO $pdo, int $registrationId): string {
        $token = portalEnsureCourseRegistrationPublicToken($pdo, $registrationId);
        if ($token === '') return '';
        return appBaseUrl() . '/index.php?page=public_course_form&token=' . urlencode($token);
    }
}

if (!function_exists('portalFindCourseRegistrationByPublicToken')) {
    function portalFindCourseRegistrationByPublicToken(PDO $pdo, string $token): ?array {
        $token = trim((string)$token);
        if ($token === '') return null;
        portalEnsureCourseRegistrationProfileSupport($pdo);
        try {
            $hash = hash('sha256', $token);
            $st = $pdo->prepare("SELECT cr.*, " . portalCourseRegistrationStudentSelectList($pdo, 's') . ", f.name AS faculty_name, d.name AS department_name, p.name AS programme_name FROM course_registrations cr LEFT JOIN students s ON s.id = cr.student_id LEFT JOIN faculties f ON f.id = cr.faculty_id LEFT JOIN departments d ON d.id = cr.department_id LEFT JOIN programmes p ON p.id = cr.programme_id WHERE cr.public_form_token_hash = ? LIMIT 1");
            $st->execute([$hash]);
            $reg = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$reg) return null;
            $reg['__items'] = portalFetchSynchronizedCourseRegistrationItems($pdo, (int)$reg['id']);
            return $reg;
        } catch (Throwable $e) {
            return null;
        }
    }
}


if (!function_exists('portalFormatHumanDate')) {
    function portalFormatHumanDate(?string $value): string {
        $value = trim((string)$value);
        if ($value === '') return '';
        try { $ts = strtotime($value); return $ts ? date('j M Y', $ts) : $value; } catch (Throwable $e) { return $value; }
    }
}


if (!function_exists('portalCourseFormVariantMeta')) {
    function portalCourseFormVariantMeta(string $programmeName = '', string $modeOfStudy = ''): array {
        $programmeName = trim((string)$programmeName);
        $modeOfStudy = trim((string)$modeOfStudy);
        $programmeNeedle = function_exists('mb_strtolower') ? mb_strtolower($programmeName, 'UTF-8') : strtolower($programmeName);
        $modeNormalized = portalNormalizeRegistrableCourseModeOfStudy($modeOfStudy, $modeOfStudy !== '' ? $modeOfStudy : 'Full-Time');
        $modeNeedle = function_exists('mb_strtolower') ? mb_strtolower($modeNormalized, 'UTF-8') : strtolower($modeNormalized);
        $haystack = trim($programmeNeedle . ' ' . $modeNeedle);

        $variant = [
            'key' => 'nce',
            'ribbon' => 'NCE FULL-TIME',
            'copy_label' => 'Regular Academic Copy',
            'title' => 'COURSE REGISTRATION FORM',
            'season_band' => '',
            'official_note' => 'Academic registry validation copy for regular NCE students.',
            'theme' => [
                'name' => 'Indigo Slate',
                'primary' => '#4338ca',
                'primary_dark' => '#312e81',
                'soft' => '#eef2ff',
                'soft_alt' => '#e0e7ff',
                'accent' => '#c2410c',
            ],
        ];

        if (preg_match('/\b(pde|certificate|diploma)\b/iu', $haystack)) {
            $variant = [
                'key' => 'certificate',
                'ribbon' => 'PDE / CERTIFICATE / DIPLOMA',
                'copy_label' => 'Academic Registry Copy',
                'title' => 'COURSE REGISTRATION FORM – PROFESSIONAL / CERTIFICATE PROGRAMME',
                'season_band' => '',
                'official_note' => 'Compact professional programme registration copy.',
                'theme' => [
                    'name' => 'Registry Slate',
                    'primary' => '#475467',
                    'primary_dark' => '#1f2937',
                    'soft' => '#f8fafc',
                    'soft_alt' => '#e5e7eb',
                    'accent' => '#9a3412',
                ],
            ];
        } elseif (str_contains($haystack, 'distance') || str_contains($haystack, 'open') || str_contains($haystack, 'online')) {
            $variant = [
                'key' => 'distance',
                'ribbon' => 'DISTANCE / OPEN / ONLINE LEARNING',
                'copy_label' => 'Identity & Verification Copy',
                'title' => 'COURSE REGISTRATION FORM – DISTANCE / OPEN LEARNING',
                'season_band' => 'Verified student identity • QR-enabled academic record',
                'official_note' => 'Remote learning copy with stronger verification emphasis.',
                'theme' => [
                    'name' => 'Digital Teal',
                    'primary' => '#0f766e',
                    'primary_dark' => '#134e4a',
                    'soft' => '#f0fdfa',
                    'soft_alt' => '#ccfbf1',
                    'accent' => '#0369a1',
                ],
            ];
        } elseif (str_contains($haystack, 'sandwich') || str_contains($haystack, 'long vacation')) {
            $variant = [
                'key' => 'sandwich',
                'ribbon' => 'SANDWICH / LONG VACATION',
                'copy_label' => 'Seasonal Academic Copy',
                'title' => 'COURSE REGISTRATION FORM – SANDWICH / LONG VACATION PROGRAMME',
                'season_band' => 'Seasonal study block • Session-specific academic registration',
                'official_note' => 'Seasonal programme copy with session emphasis.',
                'theme' => [
                    'name' => 'Vacation Emerald',
                    'primary' => '#0f766e',
                    'primary_dark' => '#115e59',
                    'soft' => '#ecfdf5',
                    'soft_alt' => '#d1fae5',
                    'accent' => '#b45309',
                ],
            ];
        } elseif (str_contains($haystack, 'part-time') || str_contains($haystack, 'part time') || str_contains($haystack, 'weekend')) {
            $variant = [
                'key' => 'part-time',
                'ribbon' => 'PART-TIME / WEEKEND',
                'copy_label' => 'Flexible Study Copy',
                'title' => 'COURSE REGISTRATION FORM – PART-TIME / WEEKEND PROGRAMME',
                'season_band' => 'Flexible schedule • Print-friendly weekend / part-time layout',
                'official_note' => 'Flexible study mode copy for reduced-campus-visit students.',
                'theme' => [
                    'name' => 'Amber Navy',
                    'primary' => '#1d4ed8',
                    'primary_dark' => '#172554',
                    'soft' => '#eff6ff',
                    'soft_alt' => '#dbeafe',
                    'accent' => '#b45309',
                ],
            ];
        } elseif (preg_match('/\b(degree|b\.?\s*ed|b\.?\s*sc|b\.?\s*a|ba\.?ed|bed|btech|b\.?\s*tech)\b/iu', $programmeName)) {
            $variant = [
                'key' => 'degree',
                'ribbon' => 'DEGREE FULL-TIME',
                'copy_label' => 'Faculty / Department Validation Copy',
                'title' => 'COURSE REGISTRATION FORM – DEGREE PROGRAMME',
                'season_band' => '',
                'official_note' => 'Formal university-style registration copy.',
                'theme' => [
                    'name' => 'HontTech Blue',
                    'primary' => '#0b4a8b',
                    'primary_dark' => '#0b2447',
                    'soft' => '#eef5ff',
                    'soft_alt' => '#dbeafe',
                    'accent' => '#dc6803',
                ],
            ];
        } elseif (preg_match('/\bnce\b/iu', $programmeName)) {
            $variant = [
                'key' => 'nce',
                'ribbon' => 'NCE FULL-TIME',
                'copy_label' => 'Regular Academic Copy',
                'title' => 'COURSE REGISTRATION FORM – NCE PROGRAMME',
                'season_band' => '',
                'official_note' => 'Clean academic copy for regular NCE students.',
                'theme' => [
                    'name' => 'Indigo Slate',
                    'primary' => '#4338ca',
                    'primary_dark' => '#312e81',
                    'soft' => '#eef2ff',
                    'soft_alt' => '#e0e7ff',
                    'accent' => '#c2410c',
                ],
            ];
        }

        $variant['mode_label'] = $modeNormalized !== '' ? $modeNormalized : 'Full-Time';
        $variant['programme_label'] = $programmeName !== '' ? $programmeName : 'General Programme';
        return $variant;
    }
}

if (!function_exists('portalCourseFormTheme')) {
    function portalCourseFormTheme(string $programmeName = ''): array {
        $programmeName = trim($programmeName);
        $palettes = [
            ['name' => 'HontTech Blue', 'primary' => '#0b4a8b', 'primary_dark' => '#0b2447', 'soft' => '#eef5ff', 'soft_alt' => '#dbeafe', 'accent' => '#dc6803'],
            ['name' => 'Emerald', 'primary' => '#0f766e', 'primary_dark' => '#134e4a', 'soft' => '#ecfdf5', 'soft_alt' => '#ccfbf1', 'accent' => '#b54708'],
            ['name' => 'Royal Purple', 'primary' => '#6d28d9', 'primary_dark' => '#4c1d95', 'soft' => '#f5f3ff', 'soft_alt' => '#ede9fe', 'accent' => '#c2410c'],
            ['name' => 'Crimson', 'primary' => '#be123c', 'primary_dark' => '#881337', 'soft' => '#fff1f2', 'soft_alt' => '#ffe4e6', 'accent' => '#9a3412'],
            ['name' => 'Amber Navy', 'primary' => '#1d4ed8', 'primary_dark' => '#172554', 'soft' => '#eff6ff', 'soft_alt' => '#dbeafe', 'accent' => '#b45309'],
            ['name' => 'Indigo Slate', 'primary' => '#4338ca', 'primary_dark' => '#312e81', 'soft' => '#eef2ff', 'soft_alt' => '#e0e7ff', 'accent' => '#c2410c'],
        ];
        $index = 0;
        if ($programmeName !== '') {
            $hash = abs(crc32(function_exists('mb_strtolower') ? mb_strtolower($programmeName, 'UTF-8') : strtolower($programmeName)));
            $index = $hash % count($palettes);
        }
        $theme = $palettes[$index];
        $theme['programme'] = $programmeName !== '' ? $programmeName : 'General Programme';
        return $theme;
    }
}

if (!function_exists('portalCourseFormThemeCssVars')) {
    function portalCourseFormThemeCssVars(array $theme): string {
        $pairs = [
            '--cf-primary' => (string)($theme['primary'] ?? '#0b4a8b'),
            '--cf-primary-dark' => (string)($theme['primary_dark'] ?? '#0b2447'),
            '--cf-soft' => (string)($theme['soft'] ?? '#eef5ff'),
            '--cf-soft-alt' => (string)($theme['soft_alt'] ?? '#dbeafe'),
            '--cf-accent' => (string)($theme['accent'] ?? '#dc6803'),
            '--cf-border' => '#cfd8e3',
            '--cf-ink' => '#101828',
            '--cf-muted' => '#475467',
        ];
        $parts = [];
        foreach ($pairs as $key => $value) {
            $parts[] = $key . ':' . preg_replace('/[^#(),.% a-zA-Z0-9_-]/', '', (string)$value);
        }
        return implode(';', $parts) . ';';
    }
}

if (!function_exists('portalCourseRegistrationCss')) {
    function portalCourseRegistrationCss(): string {
        return <<<'CSS'
.course-reg-pdf-shell,.course-form-paper{--cf-primary:#0b4a8b;--cf-primary-dark:#0b2447;--cf-soft:#eef5ff;--cf-soft-alt:#dbeafe;--cf-accent:#dc6803;--cf-border:#cfd8e3;--cf-ink:#101828;--cf-muted:#475467;}\n.course-reg-pdf-shell{max-width:1120px;margin:0 auto;background:linear-gradient(180deg,var(--cf-soft) 0, #fff 180px);border:1px solid var(--cf-border);border-radius:22px;box-shadow:0 10px 30px rgba(15,23,42,.08);padding:24px 28px 30px;}
.course-reg-toolbar{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:16px;}
.course-form-download-status{display:none;margin:0 0 14px;padding:10px 12px;border-radius:12px;border:1px solid #b8d4ff;background:#eff6ff;color:#12314f;font-weight:600;}
.course-form-download-status.show{display:block;}
.course-form-download-status.error{border-color:#fecdca;background:#fef3f2;color:#b42318;}
.course-reg-head{display:flex;justify-content:space-between;align-items:flex-start;gap:18px;border-bottom:1px dashed #bcc6d3;padding-bottom:14px;margin-bottom:12px;}
.course-reg-brand{display:flex;align-items:center;gap:14px;min-width:0;}
.course-reg-brand img{width:72px;height:72px;object-fit:contain;}
.course-reg-brand-fallback{width:72px;height:72px;border-radius:14px;background:var(--cf-soft);border:1px dashed var(--cf-border);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:var(--cf-primary);}
.course-reg-school{font-size:1.25rem;font-weight:800;color:var(--cf-primary-dark);text-transform:uppercase;}
.course-reg-title{font-size:1.05rem;font-weight:700;color:var(--cf-primary-dark);margin-top:4px;}
.course-reg-session{min-width:220px;text-align:right;font-weight:800;color:#111827;font-size:1.1rem;}
.course-reg-note-title{font-weight:800;color:var(--cf-accent);text-transform:uppercase;margin-top:2px;}
.course-reg-note{font-size:.93rem;color:#374151;margin-bottom:14px;}
.course-reg-note ol{margin:6px 0 0 18px;padding:0;}
.course-reg-summary-table,.course-reg-courses-table{width:100%;border-collapse:collapse;}
.course-reg-summary-table th,.course-reg-summary-table td,.course-reg-courses-table th,.course-reg-courses-table td{border:1px solid #444;padding:8px 9px;vertical-align:top;}
.course-reg-summary-table th,.course-reg-courses-table thead th{background:var(--cf-soft);font-size:.88rem;text-transform:uppercase;white-space:nowrap;}
.course-reg-summary-table td{font-weight:700;color:var(--cf-primary-dark);font-size:.95rem;}
.course-reg-alert-line{margin:10px 0 12px;font-size:1rem;font-weight:800;color:var(--cf-accent);}
.course-reg-section-title{font-size:1rem;font-weight:800;color:var(--cf-primary-dark);text-transform:uppercase;border-bottom:2px solid var(--cf-soft-alt);margin:12px 0 10px;padding-bottom:3px;}
.course-reg-personal-grid{border:1px solid #bfc9d8;padding:16px;background:#fcfdff;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 18px;}
.course-reg-field{display:flex;flex-direction:column;gap:6px;}
.course-reg-field label{font-size:.89rem;font-weight:700;color:#1f2937;}
.course-reg-inline-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 18px;grid-column:1/-1;}
.course-reg-inline-three{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px 18px;grid-column:1/-1;}
.course-reg-carry{margin:16px 0 18px;}
.course-reg-carry-card{border:1px solid var(--cf-border);border-radius:18px;background:linear-gradient(180deg,#f8fbff 0%,#fff 100%);padding:16px 18px;box-shadow:0 4px 12px rgba(15,23,42,.04);}
.course-reg-carry-head{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:10px;}
.course-reg-carry-title{font-size:1rem;font-weight:800;color:var(--cf-primary-dark);}
.course-reg-carry-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:#eef5ff;color:var(--cf-primary);font-size:.82rem;font-weight:800;border:1px solid #cfe0ff;}
.course-reg-carry-controls{display:grid;grid-template-columns:minmax(280px,380px) minmax(0,1fr);gap:14px 18px;align-items:start;}
.course-reg-carry-help{margin:0;font-size:.88rem;color:#475467;}
.course-reg-carry-panel{margin-top:14px;border-top:1px dashed #c8d2e2;padding-top:14px;}
.course-reg-carry-empty{margin-bottom:0;}
.course-reg-carry-note-row{margin-top:12px;}
.course-reg-carry-table thead th{background:#eef5ff;font-size:.86rem;}
.course-reg-carry-table td,.course-reg-carry-table th{vertical-align:middle;}
.course-reg-carry-status{font-weight:700;color:#b54708;}
.course-reg-subtitle{font-weight:800;color:var(--cf-primary-dark);}
.course-reg-red-note{color:var(--cf-accent);font-weight:700;margin:4px 0 10px;}
.course-reg-total{display:flex;justify-content:flex-end;align-items:center;gap:12px;padding-top:12px;font-weight:800;color:#111827;}
.course-reg-total strong{min-width:170px;text-align:right;}
.course-reg-submit-row{display:flex;justify-content:flex-start;gap:12px;flex-wrap:wrap;margin-top:20px;}
.course-reg-submit-row .btn{min-width:210px;font-weight:700;}
.course-reg-mini-meta{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:16px;}
.course-reg-mini-card{background:rgba(255,255,255,.85);border:1px solid var(--cf-border);border-radius:12px;padding:12px 14px;backdrop-filter:blur(3px);}
.course-reg-mini-card .label{font-size:.8rem;text-transform:uppercase;color:#667085;font-weight:700;margin-bottom:4px;}
.course-reg-mini-card .value{font-size:1rem;font-weight:700;color:var(--cf-primary-dark);}

.course-form-paper{width:210mm;min-height:297mm;max-width:210mm;margin:0 auto;background:linear-gradient(180deg,var(--cf-soft) 0, #fff 24mm);color:var(--cf-ink);border:1px solid var(--cf-border);border-top:2.2mm solid var(--cf-primary);box-shadow:0 8px 24px rgba(15,23,42,.10);padding:8mm 8mm 8.5mm;font-family:'Segoe UI',Tahoma,Verdana,sans-serif;font-size:12px;line-height:1.16;overflow:hidden;}
.course-form-paper.course-form-fit-page{zoom:var(--course-form-fit-scale,1);}
@supports not (zoom:1){.course-form-paper.course-form-fit-page{transform:scale(var(--course-form-fit-scale,1));transform-origin:top center;width:calc(210mm / var(--course-form-fit-scale,1));max-width:none;}}
.course-form-header{display:grid;grid-template-columns:24mm 1fr 28mm;align-items:start;gap:5mm;margin-bottom:2.2mm;}
.course-form-logo{width:21mm;height:21mm;object-fit:contain;justify-self:start;}
.course-form-header-center{text-align:center;line-height:1.05;padding-top:1mm;}
.course-form-school-line1{font-size:7.2mm;font-weight:900;letter-spacing:.01em;color:var(--cf-primary-dark);}
.course-form-school-line2{font-size:4.1mm;font-style:italic;font-weight:700;color:#222;margin-top:.6mm;}
.course-form-programme-badge{display:inline-flex;align-items:center;gap:1.6mm;margin-top:1.4mm;padding:1.1mm 3.2mm;border-radius:999px;background:linear-gradient(90deg,var(--cf-primary),var(--cf-primary-dark));color:#fff;font-size:2.55mm;font-weight:900;letter-spacing:.04em;text-transform:uppercase;box-shadow:0 1mm 3mm rgba(15,23,42,.10);}
.course-form-programme-badge .tone{display:inline-block;width:2.2mm;height:2.2mm;border-radius:50%;background:rgba(255,255,255,.95);}
.course-form-variant-strip{display:flex;justify-content:center;align-items:center;gap:1.8mm;flex-wrap:wrap;margin-top:1.35mm;}
.course-form-variant-ribbon,.course-form-variant-copy,.course-form-chip{display:inline-flex;align-items:center;justify-content:center;border-radius:999px;font-weight:900;letter-spacing:.03em;}
.course-form-variant-ribbon{padding:1.1mm 3.3mm;background:linear-gradient(90deg,var(--cf-primary),var(--cf-primary-dark));color:#fff;font-size:2.5mm;text-transform:uppercase;box-shadow:0 1mm 3mm rgba(15,23,42,.10);}
.course-form-variant-copy{padding:.95mm 2.8mm;background:rgba(255,255,255,.92);color:var(--cf-primary-dark);border:1px solid rgba(15,23,42,.10);font-size:2.35mm;text-transform:uppercase;}
.course-form-header-chips{display:flex;justify-content:center;align-items:center;gap:1.4mm;flex-wrap:wrap;margin-top:1.35mm;}
.course-form-chip{padding:.9mm 2.4mm;background:rgba(255,255,255,.9);border:1px solid rgba(15,23,42,.10);color:var(--cf-primary-dark);font-size:2.45mm;line-height:1.1;max-width:100%;}
.course-form-chip .tone{display:inline-block;width:2mm;height:2mm;border-radius:50%;margin-right:1.15mm;background:var(--cf-primary);}
.course-form-chip--programme{max-width:72mm;white-space:normal;text-transform:uppercase;}
.course-form-chip--mode{background:rgba(255,255,255,.96);}
.course-form-chip--level{background:var(--cf-soft-alt);}
.course-form-main-title{margin-top:1.35mm;font-size:5.35mm;font-weight:900;line-height:1.08;text-transform:uppercase;color:var(--cf-primary-dark);}
.course-form-main-subtitle{margin-top:1mm;font-size:3.12mm;font-weight:800;letter-spacing:.03em;text-transform:uppercase;color:#334155;}
.course-form-season-band{display:inline-flex;align-items:center;justify-content:center;gap:1mm;margin-top:1.45mm;padding:1.1mm 3.2mm;border-radius:999px;background:linear-gradient(90deg,var(--cf-accent),var(--cf-primary));color:#fff;font-size:2.4mm;font-weight:800;letter-spacing:.03em;}
.course-form-header-right{display:flex;justify-content:flex-end;align-items:flex-start;}
.course-form-photo{width:23mm;height:28mm;border:1px solid var(--cf-border);background:#fafafa;display:flex;align-items:center;justify-content:center;overflow:hidden;border-radius:3mm;box-shadow:0 2mm 5mm rgba(15,23,42,.08);}
.course-form-photo img{width:100%;height:100%;object-fit:cover;display:block;}
.course-form-photo span{font-size:2.6mm;color:#777;}
.course-form-toprule{border-top:1px solid var(--cf-border);margin:2.2mm 0 3mm;}
.course-form-meta-table,.course-form-courses-table{width:100%;border-collapse:collapse;}
.course-form-meta-table{table-layout:fixed;}
.course-form-meta-table th,.course-form-meta-table td,.course-form-courses-table th,.course-form-courses-table td{border:1px solid #6b7280;padding:1.7mm 2mm;font-size:3.1mm;vertical-align:middle;}
.course-form-meta-table th,.course-form-courses-table th{font-weight:800;background:var(--cf-soft);text-align:left;color:var(--cf-primary-dark);}
.course-form-meta-table td{font-weight:500;color:#111;}
.course-form-meta-table--compact{margin-top:2.4mm;}
.course-form-meta-table--compact th,.course-form-meta-table--compact td{text-align:center;}
.course-form-meta-table .course-form-qr-cell{padding:1.2mm;vertical-align:middle;text-align:center;background:#fff;}
.course-form-meta-table .course-form-qr-box{width:100%;height:100%;min-height:33mm;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:1mm;background:#fff;overflow:hidden;}
.course-form-meta-table .course-form-qr-box img{width:100%;max-width:26mm;max-height:26mm;height:auto;aspect-ratio:1 / 1;object-fit:contain;display:block;margin:0 auto;}
.course-form-meta-table .course-form-qr-note{font-size:2.25mm;line-height:1.15;color:#344054;text-align:center;}
.course-form-meta-table--identity{margin:0;}
.course-form-meta-table--identity th,.course-form-meta-table--identity td{padding:1.45mm 1.95mm;vertical-align:middle;}
.course-form-meta-table--identity th{line-height:1.08;white-space:normal;font-size:3.02mm;}
.course-form-meta-table--identity td{line-height:1.16;overflow-wrap:anywhere;word-break:break-word;}
.course-form-meta-table--identity .course-form-meta-value{display:block;min-height:5.3mm;font-weight:700;color:#101828;}
.course-form-meta-table--identity .course-form-meta-value--student{font-size:3.16mm;}
.course-form-meta-table--identity .course-form-meta-value--admission,.course-form-meta-table--identity .course-form-meta-value--application{font-size:2.94mm;}
.course-form-meta-table--identity .course-form-meta-value--programme,.course-form-meta-table--identity .course-form-meta-value--school,.course-form-meta-table--identity .course-form-meta-value--department{font-size:2.98mm;}
.course-form-meta-table--identity .course-form-qr-cell{padding:0;vertical-align:top;background:#fff;}
.course-form-meta-table--identity .course-form-qr-box{min-height:0;height:100%;display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;gap:0;background:#fff;}
.course-form-meta-table--identity .course-form-qr-media{display:flex;align-items:center;justify-content:center;min-height:24mm;padding:1.6mm 1.6mm .55mm;}
.course-form-meta-table--identity .course-form-qr-box img{max-width:27mm;max-height:27mm;}
.course-form-meta-table--identity .course-form-qr-note{padding:0 1.4mm 1.1mm;font-size:2.05mm;line-height:1.18;color:#344054;text-align:center;}
.course-form-meta-table--identity .course-form-qr-verify-badge{margin:0 1.4mm 1.35mm;padding:.8mm 1.1mm;border-radius:999px;background:var(--cf-soft);border:1px solid rgba(15,23,42,.08);font-size:2.08mm;font-weight:800;line-height:1.12;color:var(--cf-primary-dark);text-transform:uppercase;}
.course-form-meta-table--identity .course-form-qr-missing{padding:2.6mm 1.4mm 1.6mm;font-size:2.45mm;font-weight:700;color:#667085;text-align:center;}
.course-form-section{margin-top:3mm;}
.course-form-section-title{font-size:4.7mm;font-weight:900;color:var(--cf-primary);margin:0 0 1.6mm;border-bottom:1px solid rgba(26,63,122,.22);padding-bottom:.8mm;}
.course-form-personal{display:grid;grid-template-columns:1fr 1fr;gap:2mm 10mm;align-items:start;}
.course-form-personal--two-col{grid-template-columns:repeat(2,minmax(0,1fr)) !important;column-gap:10mm;row-gap:2mm;}
.course-form-section--personal{page-break-inside:avoid;break-inside:avoid-page;}
.course-form-personal-col{min-width:0;}
.course-form-detail-row{display:grid;grid-template-columns:31mm 1fr;gap:2mm 3mm;align-items:start;margin-bottom:1mm;font-size:3.25mm;}
.course-form-detail-row .label{font-weight:700;color:#111;line-height:1.18;}
.course-form-detail-row .value{color:#111;line-height:1.18;word-break:break-word;}
.course-form-courses-heading{margin:3mm 0 2mm;text-align:center;font-size:4.45mm;font-weight:900;color:var(--cf-primary-dark);text-transform:uppercase;line-height:1.1;}
.course-form-courses-table th,.course-form-courses-table td{font-size:3.05mm;padding:1.5mm 1.7mm;}
.course-form-courses-table td:nth-child(3),.course-form-courses-table th:nth-child(3),.course-form-courses-table td:nth-child(4),.course-form-courses-table th:nth-child(4),.course-form-courses-table td:nth-child(5),.course-form-courses-table th:nth-child(5){text-align:center;}
.course-form-total{margin-top:2.2mm;text-align:right;font-size:4mm;font-weight:900;color:var(--cf-primary-dark);}
.course-form-signature{margin-top:3mm;font-size:3.45mm;}
.course-form-signature-line{display:inline-block;width:72%;border-bottom:1px solid #555;height:1.1em;vertical-align:bottom;}
.course-form-official-title{margin-top:4.5mm;text-align:center;font-size:4.2mm;font-weight:900;color:var(--cf-primary-dark);text-transform:uppercase;}
.course-form-official-note{margin-top:1.6mm;text-align:center;font-size:3.1mm;line-height:1.25;}
.course-form-official-copy-label{margin-top:1.25mm;text-align:center;font-size:2.8mm;line-height:1.22;font-weight:800;color:var(--cf-muted);text-transform:uppercase;letter-spacing:.03em;}
.course-form-official-grid{margin-top:4.5mm;display:grid;grid-template-columns:1fr 1fr;gap:18mm;}
.course-form-official-box{text-align:center;}
.course-form-official-box .line{display:block;border-bottom:1px solid #555;height:7mm;margin-bottom:1.4mm;}
.course-form-official-box .label{font-style:italic;font-weight:800;}
.course-form-qr-wrap{margin-top:3.4mm;text-align:center;}
.course-form-qr-wrap img{width:24mm;height:24mm;object-fit:contain;display:block;background:#fff;border:1px solid #d7d7d7;padding:1mm;margin:0 auto;}
.course-form-qr-label{font-size:2.5mm;line-height:1.15;margin-top:1mm;color:#344054;}
.course-form--degree .course-form-main-title{font-size:5.15mm;letter-spacing:.02em;}
.course-form--degree .course-form-official-title,.course-form--degree .course-form-official-copy-label{letter-spacing:.05em;}
.course-form--part-time .course-form-variant-ribbon,.course-form--part-time .course-form-season-band{background:linear-gradient(90deg,#b45309,#1d4ed8);}
.course-form--part-time .course-form-chip--mode{background:#fff7ed;border-color:#fed7aa;color:#9a3412;}
.course-form--sandwich .course-form-variant-ribbon,.course-form--sandwich .course-form-season-band{background:linear-gradient(90deg,#0f766e,#b45309);}
.course-form--sandwich .course-form-chip--mode{background:#ecfdf5;border-color:#a7f3d0;color:#065f46;}
.course-form--distance{background:linear-gradient(180deg,#f8fbff 0,#fff 26mm);}
.course-form--distance .course-form-variant-ribbon,.course-form--distance .course-form-season-band{background:linear-gradient(90deg,#0f766e,#0369a1);}
.course-form--distance .course-form-meta-table--identity .course-form-qr-box{background:linear-gradient(180deg,var(--cf-soft),#fff 70%);}
.course-form--distance .course-form-chip--mode{background:#ecfeff;border-color:#a5f3fc;color:#155e75;}
.course-form--certificate{background:#fff;border-top-width:1.6mm;}
.course-form--certificate .course-form-variant-ribbon{background:#fff;color:var(--cf-primary-dark);border:1px solid var(--cf-border);box-shadow:none;}
.course-form--certificate .course-form-section-title{font-size:4.15mm;border-bottom-style:dashed;}
.course-form--certificate .course-form-chip,.course-form--certificate .course-form-variant-copy{background:#fff;}
.course-form--nce .course-form-chip--mode{background:#eef2ff;border-color:#c7d2fe;color:#3730a3;}
@media (max-width: 991px){
  .course-form-variant-strip,.course-form-header-chips{gap:6px;}
  .course-form-main-subtitle{font-size:.82rem;}
  .course-form-chip--programme{max-width:none;}
  .course-form-season-band{font-size:.74rem;}
}
@page{size:A4 portrait;margin:7mm;}
@media (max-width: 767.98px){
  .course-reg-head{flex-direction:column;align-items:flex-start;}
  .course-reg-session{text-align:left;min-width:0;}
  .course-reg-personal-grid{grid-template-columns:1fr;}
  .course-reg-inline-two,.course-reg-inline-three,.course-reg-carry-controls{grid-template-columns:1fr;}
  .course-reg-carry-card{padding:14px 14px 16px;}
  .course-reg-carry-head{align-items:flex-start;}
  .course-reg-mini-meta{grid-template-columns:1fr;}
  .course-reg-pdf-shell{padding:18px 16px 24px;border-radius:16px;}
  .course-form-paper{width:auto;max-width:100%;min-height:auto;padding:16px 14px 20px;overflow:visible;}
  .course-form-header{grid-template-columns:72px 1fr 90px;}
  .course-form-school-line1{font-size:1.65rem;}
  .course-form-school-line2{font-size:1rem;}
  .course-form-main-title{font-size:1.25rem;}
  .course-form-personal{grid-template-columns:1fr;}
  .course-form-qr-wrap img{width:100px;height:100px;}
  .course-form-meta-table .course-form-qr-box{min-height:124px;}
  .course-form-meta-table .course-form-qr-box img{max-width:96px;max-height:96px;}
  .course-form-meta-table--identity .course-form-qr-media{min-height:104px;}
  .course-form-meta-table--identity .course-form-qr-box img{max-width:98px;max-height:98px;}
  .course-form-meta-table--identity .course-form-qr-note{font-size:.68rem;}
  .course-form-meta-table--identity .course-form-qr-level-label{font-size:.76rem;}
  .course-form-meta-table--identity .course-form-qr-level-value{font-size:1.08rem;}
}
@media print{
  body{background:#fff !important;}
  .course-reg-toolbar,.course-form-download-status,.d-print-none,.navbar,.sidebar,.main-header,.footer{display:none !important;}
  .course-reg-pdf-shell,.course-form-paper{box-shadow:none;border:none;border-radius:0;}
  .course-reg-pdf-shell{padding:0;margin:0;max-width:100%;}
  .course-form-paper{width:100%;max-width:none;min-height:auto;margin:0;padding:0;page-break-inside:avoid;break-inside:avoid;overflow:visible;}
  .course-form-paper.course-form-fit-page{zoom:var(--course-form-fit-scale,1);}
  .course-reg-submit-row{display:none !important;}
}
CSS;
    }
}

if (!function_exists('portalRenderCourseRegistrationPrintable')) {
    function portalRenderCourseRegistrationPrintable(PDO $pdo, array $reg, array $items, bool $includeActions = true, bool $autoDownloadPdf = false, bool $autoPrint = false): void {
        portalEnsureCourseRegistrationProfileSupport($pdo);
        $profile = portalResolveCourseFormProfile($pdo, $reg);
        $displaySchool = portalNormalizeCourseFormSchoolName(trim((string)($profile['school'] ?? $profile['school_name'] ?? '')));
        $displayDepartment = trim((string)($profile['course_in_view'] ?? $profile['department'] ?? ''));
        $displayProgramme = trim((string)($profile['programme'] ?? ''));
        $displayModeOfStudy = portalNormalizeRegistrableCourseModeOfStudy(trim((string)($reg['mode_of_study'] ?? portalInferModeOfStudy($reg))), 'Full-Time');
        $studentName = trim((string)($reg['full_name'] ?? ''));
        if ($studentName === '') {
            $studentName = trim((string)($reg['surname'] ?? '') . ' ' . portalCourseRegistrationOtherNames($reg));
        }
        $matricNo = portalStudentNumberDisplay($reg, 'matric');
        $admissionNo = portalStudentNumberDisplay($reg, 'admission');
        $applicationNo = portalStudentNumberDisplay($reg, 'application');
        if ($displaySchool === '') $displaySchool = 'Not Set';
        if ($displayDepartment === '') $displayDepartment = 'Not Set';
        if ($displayProgramme === '') $displayProgramme = 'Not Set';
        if ($displayModeOfStudy === '') $displayModeOfStudy = portalNormalizeRegistrableCourseModeOfStudy(portalInferModeOfStudy($reg), 'Full-Time');
        $courseFormVariant = portalCourseFormVariantMeta($displayProgramme, $displayModeOfStudy);
        $courseFormTheme = array_merge(portalCourseFormTheme($displayProgramme), (array)($courseFormVariant['theme'] ?? []));
        $courseFormThemeCss = portalCourseFormThemeCssVars($courseFormTheme);
        $courseItemPartitions = portalPartitionCourseRegistrationItems($items);
        $regularItems = (array)($courseItemPartitions['regular'] ?? []);
        $carryItems = (array)($courseItemPartitions['carry'] ?? []);
        $regularUnits = 0; foreach ($regularItems as $it) { $regularUnits += (int)($it['credit_unit'] ?? 0); }
        $carryUnits = 0; foreach ($carryItems as $it) { $carryUnits += (int)($it['credit_unit'] ?? 0); }
        $totalUnits = $regularUnits + $carryUnits;
        $registeredCourseCount = count($items);
        $logoPath = portalSchoolLogoPath();
        $schoolName = trim((string)setting('portal_title', 'HontTech Registration System'));
        $otherNames = portalCourseRegistrationOtherNames($reg);
        $semesterTitle = portalNormalizeSemesterName((string)($reg['semester'] ?? 'First'));
        $semesterHeading = preg_replace('/\s*semester\s*$/i', '', $semesterTitle) ?: $semesterTitle;
        $semesterHeading = strtoupper(trim($semesterHeading));
        $sessionName = trim((string)($reg['session_name'] ?? ''));
        $photo = trim((string)($reg['passport_photo'] ?? '')) ?: trim((string)($reg['profile_image'] ?? ''));
        $level = trim((string)($reg['level'] ?? ''));
        $carryStatus = trim((string)($reg['carry_over_status'] ?? 'No'));
        $carryNote = trim((string)($reg['carry_over_note'] ?? ''));
        $carrySummaryText = !empty($carryItems) ? ('Yes (' . count($carryItems) . ' selected)') : (($carryStatus !== '' && strcasecmp($carryStatus, 'Yes') === 0) ? 'Yes' : 'No');
        $publicUrl = portalCourseFormPublicUrl($pdo, (int)($reg['id'] ?? 0));
        $qrSrc = $publicUrl !== '' ? generateQRCode($publicUrl) : '';
        $downloadFilename = portalCourseFormDownloadFilename($pdo, $reg);
        $documentTitle = preg_replace('/\.pdf$/i', '', $downloadFilename) ?: 'course-form';
        $variantKey = preg_replace('/[^a-z0-9-]+/i', '', (string)($courseFormVariant['key'] ?? 'nce')) ?: 'nce';
        $courseFormShellClass = 'course-form-shell course-form-shell--' . $variantKey;
        $courseFormPaperClass = 'course-form-paper course-form--' . $variantKey;
        $sourcePage = isAdmin() ? 'admin_registrations_manager' : 'student_course_registration';
        $printHref = '?page=course_form&reg_id=' . (int)($reg['id'] ?? 0) . '&source=' . rawurlencode($sourcePage);
        $downloadHref = '?page=course_form&reg_id=' . (int)($reg['id'] ?? 0) . '&download=1&source=' . rawurlencode($sourcePage);
        ?>
        <?php if ($documentTitle !== ''): ?><script>try{document.title=<?php echo json_encode($documentTitle, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;}catch(e){}</script><?php endif; ?>
        <style><?php echo portalCourseRegistrationCss(); ?></style>
        <div class="container-fluid px-0 <?php echo e($courseFormShellClass); ?>" data-prev-course-form-result="1" data-course-form-variant="<?php echo e($variantKey); ?>" style="<?php echo e($courseFormThemeCss); ?>">
            <?php if ($includeActions): ?>
                <div class="course-reg-toolbar d-print-none">
                    <button class="btn btn-primary" type="button" onclick="window.print()"><i class="bi bi-printer"></i> Print</button>
                    <?php if ((int)($reg['id'] ?? 0) > 0): ?><a class="btn btn-outline-secondary" id="downloadCourseFormBtn" href="<?php echo e($downloadHref); ?>" data-download-filename="<?php echo e($downloadFilename); ?>"><i class="bi bi-download"></i> Download</a><?php endif; ?>
                    <?php if (isAdmin()): ?><a class="btn btn-outline-dark" href="?page=admin_registrations_manager">Back</a><?php elseif (isStudent()): ?><a class="btn btn-outline-dark" href="?page=student_course_registration&tab=print">Back</a><?php endif; ?>
                </div>
                <div id="courseFormDownloadStatus" class="course-form-download-status d-print-none" aria-live="polite"></div>
            <?php elseif ($autoDownloadPdf): ?>
                <div id="courseFormDownloadStatus" class="course-form-download-status show d-print-none" aria-live="polite">Preparing PDF download...</div>
            <?php endif; ?>
            <div class="<?php echo e($courseFormPaperClass); ?>" id="courseFormPaper">
                <div class="course-form-header">
                    <div><?php if ($logoPath): ?><img src="<?php echo e($logoPath); ?>" alt="School Logo" class="course-form-logo"><?php else: ?><div class="course-form-logo"></div><?php endif; ?></div>
                    <div class="course-form-header-center">
                        <div class="course-form-school-line1"><?php echo e($schoolName !== '' ? $schoolName : 'HontTech Registration System'); ?></div>
                        <div class="course-form-school-line2">Asaba, Delta State</div>
                        <div class="course-form-variant-strip">
                            <span class="course-form-variant-ribbon"><?php echo e((string)($courseFormVariant['ribbon'] ?? 'PROGRAMME')); ?></span>
                            <span class="course-form-variant-copy"><?php echo e((string)($courseFormVariant['copy_label'] ?? 'Academic Copy')); ?></span>
                        </div>
                        <div class="course-form-header-chips">
                            <span class="course-form-chip course-form-chip--palette"><span class="tone"></span><?php echo e((string)($courseFormTheme['name'] ?? 'Theme')); ?></span>
                            <span class="course-form-chip course-form-chip--programme"><?php echo e($displayProgramme); ?></span>
                            <span class="course-form-chip course-form-chip--mode"><?php echo e($displayModeOfStudy !== '' ? $displayModeOfStudy : 'Full-Time'); ?></span>
                            <span class="course-form-chip course-form-chip--level">Level <?php echo e($level !== '' ? $level : 'Not Set'); ?></span>
                        </div>
                        <div class="course-form-main-title"><?php echo e((string)($courseFormVariant['title'] ?? 'COURSE REGISTRATION FORM')); ?></div>
                        <div class="course-form-main-subtitle">FOR <?php echo e($semesterHeading); ?> SEMESTER, <?php echo e($sessionName !== '' ? $sessionName : 'CURRENT'); ?> ACADEMIC SESSION</div>
                        <?php if (!empty($courseFormVariant['season_band'])): ?><div class="course-form-season-band"><?php echo e((string)$courseFormVariant['season_band']); ?></div><?php endif; ?>
                    </div>
                    <div class="course-form-header-right">
                        <div class="course-form-photo"><?php if ($photo !== ''): ?><img src="<?php echo e($photo); ?>" alt="Passport Photo"><?php else: ?><span>No Photo</span><?php endif; ?></div>
                    </div>
                </div>

                <div class="course-form-toprule"></div>

                <table class="course-form-meta-table course-form-meta-table--identity">
                    <colgroup>
                        <col style="width:25%;">
                        <col style="width:15%;">
                        <col style="width:20%;">
                        <col style="width:18%;">
                        <col style="width:22%;">
                    </colgroup>
                    <tbody>
                        <tr>
                            <th>Student Name</th>
                            <th>Matric Number</th>
                            <th>Admission No</th>
                            <th>Application No</th>
                            <td class="course-form-qr-cell" rowspan="4">
                                <div class="course-form-qr-box">
                                    <?php if ($qrSrc !== ''): ?>
                                        <div class="course-form-qr-media">
                                            <img src="<?php echo e($qrSrc); ?>" alt="Verification QR Code">
                                        </div>
                                        <div class="course-form-qr-note">Scan to verify this course form online.</div>
                                        <div class="course-form-qr-verify-badge">QR verification enabled</div>
                                    <?php else: ?>
                                        <div class="course-form-qr-missing">QR unavailable</div>
                                    <?php endif; ?>
                                </div>
                            </td>
                        </tr>
                        <tr>
                            <td><span class="course-form-meta-value course-form-meta-value--student"><?php echo e($studentName !== '' ? $studentName : 'Not Set'); ?></span></td>
                            <td><span class="course-form-meta-value"><?php echo e($matricNo); ?></span></td>
                            <td><span class="course-form-meta-value course-form-meta-value--admission"><?php echo e($admissionNo !== '' ? $admissionNo : 'Not Set'); ?></span></td>
                            <td><span class="course-form-meta-value course-form-meta-value--application"><?php echo e($applicationNo !== '' ? $applicationNo : 'Not Set'); ?></span></td>
                        </tr>
                        <tr>
                            <th>School</th>
                            <th>Department</th>
                            <th>Programme</th>
                            <th>Mode of Study</th>
                        </tr>
                        <tr>
                            <td><span class="course-form-meta-value course-form-meta-value--school"><?php echo e($displaySchool); ?></span></td>
                            <td><span class="course-form-meta-value course-form-meta-value--department"><?php echo e($displayDepartment); ?></span></td>
                            <td><span class="course-form-meta-value course-form-meta-value--programme"><?php echo e($displayProgramme); ?></span></td>
                            <td><span class="course-form-meta-value"><?php echo e($displayModeOfStudy !== '' ? $displayModeOfStudy : 'Full-Time'); ?></span></td>
                        </tr>
                    </tbody>
                </table>

                <div class="course-form-section course-form-section--personal">
                    <div class="course-form-section-title">Personal Details</div>
                    <div class="course-form-personal course-form-personal--two-col">
                        <div class="course-form-personal-col">
                            <div class="course-form-detail-row"><div class="label">Title</div><div class="value"><?php echo e((string)($reg['title'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Surname</div><div class="value"><?php echo e((string)($reg['surname'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Other Names</div><div class="value"><?php echo e($otherNames); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Maiden name (If married Female)</div><div class="value"><?php echo e((string)($reg['maiden_name'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Date of Birth</div><div class="value"><?php echo e((string)($reg['date_of_birth'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Marital Status</div><div class="value"><?php echo e((string)($reg['marital_status'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Sex</div><div class="value"><?php echo e((string)($reg['sex'] ?? '')); ?></div></div>
                        </div>
                        <div class="course-form-personal-col">
                            <div class="course-form-detail-row"><div class="label">Nationality</div><div class="value"><?php echo e((string)($reg['nationality'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">State</div><div class="value"><?php echo e((string)($reg['state_of_origin'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Local Govt. Area</div><div class="value"><?php echo e((string)($reg['lga'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Permanent Home Address</div><div class="value"><?php echo e((string)($reg['permanent_address'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Email Address</div><div class="value"><?php echo e((string)($reg['email'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Phone Number</div><div class="value"><?php echo e((string)($reg['mobile_phone'] ?? '')); ?></div></div>
                            <div class="course-form-detail-row"><div class="label">Carry-over courses</div><div class="value"><?php echo e($carrySummaryText . (($carryNote !== '') ? ' - ' . $carryNote : '')); ?></div></div>
                        </div>
                    </div>
                </div>

                <div class="course-form-courses-heading">Regular Courses Registered for <?php echo e($semesterHeading); ?> Semester, <?php echo e($sessionName !== '' ? $sessionName : 'CURRENT'); ?> Academic Session</div>
                <table class="course-form-courses-table">
                    <thead><tr><th style="width:23mm;">Course Code</th><th>Course Title</th><th style="width:16mm;">Credit Load</th><th style="width:15mm;">Status</th><th style="width:20mm;">Semester</th></tr></thead>
                    <tbody>
                        <?php foreach ($regularItems as $it): ?>
                            <tr><td><?php echo e((string)($it['course_code'] ?? '')); ?></td><td><?php echo e((string)($it['course_title'] ?? '')); ?></td><td><?php echo (int)($it['credit_unit'] ?? 0); ?></td><td><?php echo e((string)($it['course_type'] ?? 'C')); ?></td><td><?php echo e((string)($reg['semester'] ?? '')); ?></td></tr>
                        <?php endforeach; ?>
                        <?php if (!$regularItems): ?><tr><td colspan="5" class="text-center text-muted">No regular courses saved for this form.</td></tr><?php endif; ?>
                    </tbody>
                </table>
                <?php if ($carryItems): ?>
                    <div class="course-form-courses-heading">Carry-Over Courses</div>
                    <table class="course-form-courses-table">
                        <thead><tr><th style="width:23mm;">Course Code</th><th>Course Title</th><th style="width:16mm;">Credit Load</th><th style="width:15mm;">Status</th><th style="width:20mm;">Semester</th><th style="width:28mm;">Session</th></tr></thead>
                        <tbody>
                            <?php foreach ($carryItems as $it): ?>
                                <tr><td><?php echo e((string)($it['course_code'] ?? '')); ?></td><td><?php echo e((string)($it['course_title'] ?? '')); ?></td><td><?php echo (int)($it['credit_unit'] ?? 0); ?></td><td>CO</td><td><?php echo e((string)($it['carry_over_source_semester'] ?? ($reg['semester'] ?? ''))); ?></td><td><?php echo e((string)($it['carry_over_source_session'] ?? '')); ?></td></tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                <?php endif; ?>
                <div class="course-form-total"><?php if ($carryItems): ?>Regular Credit Units: <span><?php echo (int)$regularUnits; ?></span> &nbsp; | &nbsp; Carry-over Credit Units: <span><?php echo (int)$carryUnits; ?></span> &nbsp; | &nbsp; <?php endif; ?>Total Credit Units: <span><?php echo (int)$totalUnits; ?></span></div>
                <div class="course-form-signature">Student's Signature : <span class="course-form-signature-line"></span></div>
                <div class="course-form-official-title">For Official Use</div>
                <div class="course-form-official-note">This student has satisfied all requirements for the courses listed above. He/She is hereby registered for <?php echo e(portalNormalizeSemesterName((string)($reg['semester'] ?? 'First'))); ?> <?php echo e($sessionName !== '' ? $sessionName : ''); ?> Session</div>
                <div class="course-form-official-copy-label"><?php echo e((string)($courseFormVariant['official_note'] ?? 'Official academic registration copy.')); ?></div>
                <div class="course-form-official-grid"><div class="course-form-official-box"><span class="line"></span><div class="label">HOD</div></div><div class="course-form-official-box"><span class="line"></span><div class="label">Dean of School</div></div></div>


            </div>
        </div>
        <?php if ($includeActions || $autoDownloadPdf): ?>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" referrerpolicy="no-referrer"></script>
            <script>
            (function(){
                const autoDownload = <?php echo $autoDownloadPdf ? 'true' : 'false'; ?>;
                const autoPrint = <?php echo $autoPrint ? 'true' : 'false'; ?>;
                const fileName = <?php echo json_encode($downloadFilename, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); ?>;
                try{ if(fileName){ document.title = String(fileName).replace(/\.pdf$/i, ''); } }catch(e){}
                const paper = document.getElementById('courseFormPaper');
                const statusEl = document.getElementById('courseFormDownloadStatus');
                const downloadBtn = document.getElementById('downloadCourseFormBtn');
                let busy = false;
                let previousFitScale = '';
                let previousFitClass = false;

                function applySinglePageFit(){
                    if(!paper) return 1;
                    paper.classList.remove('course-form-fit-page');
                    paper.style.removeProperty('--course-form-fit-scale');
                    const pageHeightPx = ((paper.offsetWidth || 794) * 297) / 210;
                    const actualHeightPx = Math.max(paper.scrollHeight || 0, paper.offsetHeight || 0);
                    if(!pageHeightPx || !actualHeightPx || actualHeightPx <= (pageHeightPx + 10)){
                        return 1;
                    }
                    const fitScale = Math.max(0.84, Math.min(0.985, (pageHeightPx - 6) / actualHeightPx));
                    if(fitScale < 0.995){
                        paper.style.setProperty('--course-form-fit-scale', String(fitScale));
                        paper.classList.add('course-form-fit-page');
                    }
                    return fitScale;
                }

                function preserveFitState(){
                    if(!paper) return;
                    previousFitScale = paper.style.getPropertyValue('--course-form-fit-scale') || '';
                    previousFitClass = paper.classList.contains('course-form-fit-page');
                }

                function restoreFitState(){
                    if(!paper) return;
                    paper.classList.toggle('course-form-fit-page', !!previousFitClass);
                    if(previousFitScale){
                        paper.style.setProperty('--course-form-fit-scale', previousFitScale);
                    } else {
                        paper.style.removeProperty('--course-form-fit-scale');
                    }
                }

                function setStatus(message, isError){
                    if(!statusEl) return;
                    const text = String(message || '').trim();
                    statusEl.textContent = text;
                    statusEl.classList.toggle('show', text !== '');
                    statusEl.classList.toggle('error', !!isError);
                }

                function triggerBrowserPrint(delay){
                    if(!paper) return;
                    preserveFitState();
                    applySinglePageFit();
                    window.setTimeout(function(){ window.print(); }, typeof delay === 'number' ? delay : 120);
                }

                function waitForImages(root){
                    if(!root) return Promise.resolve();
                    const images = Array.from(root.querySelectorAll('img'));
                    if(!images.length) return Promise.resolve();
                    return Promise.all(images.map(function(img){
                        if(img.complete && img.naturalWidth > 0) return Promise.resolve();
                        return new Promise(function(resolve){
                            const done = function(){ resolve(); };
                            img.addEventListener('load', done, {once:true});
                            img.addEventListener('error', done, {once:true});
                        });
                    }));
                }

                function ensurePdfLibrary(){
                    if(window.html2pdf) return Promise.resolve(window.html2pdf);
                    return new Promise(function(resolve, reject){
                        const existing = document.querySelector('script[data-course-form-pdf-lib="1"]');
                        if(existing){
                            existing.addEventListener('load', function(){ window.html2pdf ? resolve(window.html2pdf) : reject(new Error('PDF library failed to load.')); }, {once:true});
                            existing.addEventListener('error', function(){ reject(new Error('PDF library failed to load.')); }, {once:true});
                            return;
                        }
                        const script = document.createElement('script');
                        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js';
                        script.async = true;
                        script.setAttribute('data-course-form-pdf-lib', '1');
                        script.onload = function(){ window.html2pdf ? resolve(window.html2pdf) : reject(new Error('PDF library failed to load.')); };
                        script.onerror = function(){ reject(new Error('PDF library failed to load.')); };
                        document.head.appendChild(script);
                    });
                }

                async function downloadCourseForm(){
                    if(busy) return false;
                    if(!paper){
                        setStatus('Course form content could not be found for PDF download.', true);
                        return false;
                    }
                    busy = true;
                    if(downloadBtn){
                        downloadBtn.classList.add('disabled');
                        downloadBtn.setAttribute('aria-disabled', 'true');
                    }
                    setStatus('Preparing PDF download...', false);
                    try{
                        await ensurePdfLibrary();
                        await waitForImages(paper);
                        preserveFitState();
                        applySinglePageFit();
                        document.body.classList.add('pdf-export-active');
                        await window.html2pdf().set({
                            filename: fileName || 'course-form.pdf',
                            margin: 0,
                            image: { type: 'jpeg', quality: 0.98 },
                            html2canvas: {
                                scale: 2,
                                useCORS: true,
                                allowTaint: true,
                                backgroundColor: '#ffffff',
                                logging: false,
                                scrollX: 0,
                                scrollY: -window.scrollY
                            },
                            jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
                            pagebreak: { mode: ['css', 'legacy'] }
                        }).from(paper).save();
                        setStatus('PDF downloaded successfully.', false);
                        return true;
                    }catch(err){
                        const message = (err && err.message) ? String(err.message) : 'Unable to generate the PDF right now.';
                        if(paper){
                            setStatus(message + ' Opening browser print so you can save the form as PDF.', false);
                            triggerBrowserPrint(220);
                        } else {
                            setStatus(message + ' Please try again.', true);
                        }
                        return false;
                    }finally{
                        document.body.classList.remove('pdf-export-active');
                        restoreFitState();
                        busy = false;
                        if(downloadBtn){
                            downloadBtn.classList.remove('disabled');
                            downloadBtn.removeAttribute('aria-disabled');
                        }
                    }
                }

                window.addEventListener('load', function(){
                    window.setTimeout(function(){ applySinglePageFit(); }, 120);
                    if(autoPrint && !autoDownload){
                        window.setTimeout(function(){ triggerBrowserPrint(120); }, 360);
                    }
                });

                window.addEventListener('beforeprint', function(){
                    preserveFitState();
                    applySinglePageFit();
                });

                window.addEventListener('afterprint', function(){
                    restoreFitState();
                });

                if(downloadBtn){
                    downloadBtn.addEventListener('click', function(e){
                        if(window.html2pdf){
                            e.preventDefault();
                            downloadCourseForm();
                        }
                    });
                }

                if(autoDownload){
                    window.addEventListener('load', function(){
                        window.setTimeout(function(){ downloadCourseForm(); }, 450);
                    }, {once:true});
                }
            })();
            </script>
        <?php endif; ?>
        <?php
    }
}

if (!function_exists('portalAccountOfficerSignaturePath')) {
}

if (!function_exists('portalAccountOfficerSignaturePath')) {
    function portalAccountOfficerSignaturePath(): ?string {
        $sig = getSystemAsset('account_officer_signature');
        if (!$sig) $sig = getSystemAsset('admission_signature');
        if (!$sig) $sig = getSystemAsset('idcard_signature');
        return $sig ?: null;
    }
}

if (!function_exists('portalSchoolFeePeriodSummary')) {
    function portalSchoolFeePeriodSummary(PDO $pdo, int $studentId, string $session = '', string $semester = '', array $fallbackStudent = []): array {
        portalEnsureLateFeeSupport($pdo);
        $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE student_id = ? AND transaction_type = 'School Fee' ORDER BY COALESCE(payment_date, created_at) ASC, id ASC");
        $stmt->execute([$studentId]);
        $transactions = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        $transactions = portalFilterTransactionsForPeriod($transactions, $session, $semester, $fallbackStudent);

        $approved = [];
        $installmentApproved = 0;
        $installmentStarted = false;
        $latestReference = '';
        $principalPaid = 0.0;
        $lateFeePaid = 0.0;

        foreach ($transactions as $tx) {
            if (((int)($tx['total_installments'] ?? 1)) > 1) $installmentStarted = true;
            if (!portalPaymentIsApproved($tx)) continue;
            $approved[] = $tx;
            $latestReference = (string)($tx['reference'] ?? $latestReference);
            $principalPaid += (float)((isset($tx['school_fee_base_amount']) && (float)$tx['school_fee_base_amount'] > 0) ? $tx['school_fee_base_amount'] : ($tx['amount'] ?? 0));
            $lateFeePaid += (float)($tx['school_fee_late_fee_amount'] ?? 0);
            if (((int)($tx['total_installments'] ?? 1)) > 1) $installmentApproved++;
        }

        $student = $fallbackStudent;
        if (!$student && $studentId > 0) {
            try {
                $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $st->execute([$studentId]);
                $student = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) { $student = []; }
        }

        $sessionName = portalNormalizeSessionNameSafe($session !== '' ? $session : (string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)));
        $semesterName = portalNormalizeSemesterName($semester !== '' ? $semester : (string)($student['semester'] ?? 'First Semester'));
        $requiredAmount = $student ? (float)portalStudentSchoolFeeAmount($pdo, (array)$student, $sessionName, $semesterName) : 0.0;
        $carryForward = $student ? portalStudentPreviousSchoolFeeCredit($pdo, (array)$student, $sessionName, $semesterName) : ['credit_amount' => 0.0, 'source_session' => '', 'message' => ''];
        $carryCredit = (float)($carryForward['credit_amount'] ?? 0);
        $principalOutstanding = max(0.0, $requiredAmount - ($principalPaid + $carryCredit));

        $lateAssessment = $student ? portalLateFeeAssessmentForPeriod($pdo, (array)$student, $sessionName, $semesterName, true) : ['late_fee_balance' => 0.0, 'late_fee_records' => [], 'reason' => ''];
        $lateFeeBalance = (float)($lateAssessment['late_fee_balance'] ?? 0);
        $paidInFull = ($principalOutstanding <= 0.0001 && $lateFeeBalance <= 0.0001);
        $firstInstallmentPaid = !$paidInFull && ($installmentApproved >= 1 || $principalPaid > 0.0001);
        if ($paidInFull) {
            $label = ($principalPaid <= 0.0001 && $carryCredit > 0.0001) ? 'Covered' : 'Paid';
        } elseif ($installmentApproved >= 1) {
            $label = '1st Installment Paid';
        } elseif ($principalPaid > 0.0001) {
            $label = 'Partially Paid';
        } elseif ($carryCredit > 0.0001) {
            $label = 'Difference Pending';
        } else {
            $label = 'Not Paid';
        }
        $totalDue = $principalOutstanding + $lateFeeBalance;

        return [
            'status_label' => $label,
            'approved_transactions' => $approved,
            'approved_installments' => $installmentApproved,
            'installment_started' => $installmentStarted,
            'next_installment' => min(2, max(1, $installmentApproved + 1)),
            'total_paid' => $principalPaid,
            'principal_paid' => $principalPaid,
            'late_fee_paid' => $lateFeePaid,
            'late_fee_balance' => $lateFeeBalance,
            'late_fee_records' => (array)($lateAssessment['late_fee_records'] ?? []),
            'configured_amount' => $requiredAmount,
            'required_amount' => $requiredAmount,
            'carry_forward_credit' => round($carryCredit, 2),
            'carry_forward_source_session' => (string)($carryForward['source_session'] ?? ''),
            'carry_forward_message' => (string)($carryForward['message'] ?? ''),
            'principal_outstanding' => $principalOutstanding,
            'outstanding_amount' => $principalOutstanding,
            'total_due' => $totalDue,
            'late_fee_notice' => (string)($lateAssessment['reason'] ?? ''),
            'latest_reference' => $latestReference,
            'paid_in_full' => $paidInFull,
            'first_installment_paid' => $firstInstallmentPaid,
        ];
    }
}


if (!function_exists('portalEnsureLateFeeSupport')) {
    function portalEnsureLateFeeSupport(PDO $pdo): void {
        try {
            if (portalTableExistsSafe($pdo, 'payment_transactions')) {
                $txnCols = [
                    'school_fee_base_amount' => [
                        'preferred' => "ALTER TABLE payment_transactions ADD COLUMN school_fee_base_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER approved_amount",
                        'fallback' => "ALTER TABLE payment_transactions ADD COLUMN school_fee_base_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00"
                    ],
                    'school_fee_late_fee_amount' => [
                        'preferred' => "ALTER TABLE payment_transactions ADD COLUMN school_fee_late_fee_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER school_fee_base_amount",
                        'fallback' => "ALTER TABLE payment_transactions ADD COLUMN school_fee_late_fee_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00"
                    ],
                    'late_fee_cycle_key' => [
                        'preferred' => "ALTER TABLE payment_transactions ADD COLUMN late_fee_cycle_key VARCHAR(120) NULL AFTER school_fee_late_fee_amount",
                        'fallback' => "ALTER TABLE payment_transactions ADD COLUMN late_fee_cycle_key VARCHAR(120) NULL"
                    ],
                    'late_fee_reason' => [
                        'preferred' => "ALTER TABLE payment_transactions ADD COLUMN late_fee_reason VARCHAR(255) NULL AFTER late_fee_cycle_key",
                        'fallback' => "ALTER TABLE payment_transactions ADD COLUMN late_fee_reason VARCHAR(255) NULL"
                    ]
                ];
                foreach ($txnCols as $col => $sqlSet) {
                    try {
                        if (!portalColumnExistsSafe($pdo, 'payment_transactions', $col)) {
                            try {
                                $pdo->exec($sqlSet['preferred']);
                            } catch (Throwable $e) {
                                if (!portalColumnExistsSafe($pdo, 'payment_transactions', $col)) {
                                    try { $pdo->exec($sqlSet['fallback']); } catch (Throwable $e2) {}
                                }
                            }
                        }
                    } catch (Throwable $e) {}
                }
                try { if (!portalIndexExistsSafe($pdo, 'payment_transactions', 'idx_pt_school_late_fee_cycle')) { $pdo->exec("CREATE INDEX idx_pt_school_late_fee_cycle ON payment_transactions(student_id, academic_session, semester, late_fee_cycle_key(100), status)"); } } catch (Throwable $e) {}
                try { $pdo->exec("UPDATE payment_transactions SET school_fee_base_amount = amount WHERE transaction_type = 'School Fee' AND COALESCE(school_fee_base_amount,0) <= 0"); } catch (Throwable $e) {}
            }

            $pdo->exec("CREATE TABLE IF NOT EXISTS school_fee_late_fee_records (
                id INT AUTO_INCREMENT PRIMARY KEY,
                student_id INT NOT NULL,
                academic_session VARCHAR(80) NOT NULL,
                semester VARCHAR(40) NOT NULL DEFAULT '',
                cycle_type VARCHAR(30) NOT NULL DEFAULT 'session_closure',
                cycle_key VARCHAR(120) NOT NULL,
                status VARCHAR(20) NOT NULL DEFAULT 'Active',
                base_outstanding_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
                late_fee_type VARCHAR(20) NOT NULL DEFAULT 'fixed',
                late_fee_value DECIMAL(12,2) NOT NULL DEFAULT 0.00,
                late_fee_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
                paid_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
                generated_at DATETIME NULL,
                waived_at DATETIME NULL,
                waived_by INT NULL,
                paid_at DATETIME NULL,
                remarks VARCHAR(255) NULL,
                created_by INT NULL,
                updated_by INT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_student_late_cycle (student_id, academic_session, semester, cycle_key),
                INDEX idx_late_fee_status (status, academic_session, semester),
                INDEX idx_late_fee_student (student_id, academic_session, semester)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        $defaults = [
            'late_fee_enabled' => ['disabled', 'Enable or disable school-fee late fee system'],
            'late_fee_type' => ['fixed', 'Late fee mode: fixed or percentage'],
            'late_fee_value' => ['0', 'Late fee fixed amount or percentage value'],
            'late_fee_grace_days' => ['0', 'Grace period in days after closure/end'],
            'late_fee_apply_mode' => ['session_closure', 'Apply late fee by session_closure, semester_end or both'],
            'late_fee_apply_to' => ['all_unpaid', 'Apply late fee to all_unpaid, approved_only or active_only'],
            'late_fee_auto_generate' => ['disabled', 'Automatically generate late fee when eligible'],
            'late_fee_reason' => ['Late school fee charged after deadline.', 'Late fee default reason'],
            'late_fee_first_semester_end_date' => ['', 'Configured first semester end date for late fee'],
            'late_fee_second_semester_end_date' => ['', 'Configured second semester end date for late fee']
        ];
        foreach ($defaults as $key => $meta) {
            try { setSystemSettingValue($pdo, $key, portalSettingValueSafe($pdo, $key, (string)$meta[0]), (string)$meta[1]); } catch (Throwable $e) {}
        }
    }
}

if (!function_exists('portalLateFeeSettings')) {
    function portalLateFeeSettings(PDO $pdo): array {
        portalEnsureLateFeeSupport($pdo);
        portalEnsureLateFeeRuleSchema($pdo);
        return [
            'enabled' => portalSettingValueSafe($pdo, 'late_fee_enabled', 'disabled') === 'enabled',
            'type' => in_array(portalSettingValueSafe($pdo, 'late_fee_type', 'fixed'), ['fixed','percentage'], true) ? portalSettingValueSafe($pdo, 'late_fee_type', 'fixed') : 'fixed',
            'value' => (float)portalSettingValueSafe($pdo, 'late_fee_value', '0'),
            'grace_days' => max(0, (int)portalSettingValueSafe($pdo, 'late_fee_grace_days', '0')),
            'apply_mode' => portalSettingValueSafe($pdo, 'late_fee_apply_mode', 'session_closure'),
            'apply_to' => portalSettingValueSafe($pdo, 'late_fee_apply_to', 'all_unpaid'),
            'auto_generate' => portalSettingValueSafe($pdo, 'late_fee_auto_generate', 'disabled') === 'enabled',
            'reason' => trim((string)portalSettingValueSafe($pdo, 'late_fee_reason', 'Late school fee charged after deadline.')),
            'first_semester_end_date' => trim((string)portalSettingValueSafe($pdo, 'late_fee_first_semester_end_date', '')),
            'second_semester_end_date' => trim((string)portalSettingValueSafe($pdo, 'late_fee_second_semester_end_date', '')),
        ];
    }
}



if (!function_exists('portalLateFeeCourseOfStudyOptions')) {
    function portalLateFeeCourseOfStudyOptions(PDO $pdo, int $facultyId = 0, int $departmentId = 0, int $programmeId = 0): array {
        $resolvedFacultyId = max(0, $facultyId);
        $resolvedDepartmentId = max(0, $departmentId);

        if ($programmeId > 0) {
            try {
                $st = $pdo->prepare("SELECT p.department_id, COALESCE(d.faculty_id, 0) AS faculty_id
                    FROM programmes p
                    LEFT JOIN departments d ON d.id = p.department_id
                    WHERE p.id = ?
                    LIMIT 1");
                $st->execute([$programmeId]);
                $prog = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                if ($prog) {
                    $resolvedDepartmentId = (int)($prog['department_id'] ?? 0);
                    $resolvedFacultyId = (int)($prog['faculty_id'] ?? 0);
                }
            } catch (Throwable $e) {}
        } elseif ($resolvedDepartmentId > 0 && $resolvedFacultyId <= 0) {
            try {
                $st = $pdo->prepare("SELECT faculty_id FROM departments WHERE id = ? LIMIT 1");
                $st->execute([$resolvedDepartmentId]);
                $resolvedFacultyId = (int)($st->fetchColumn() ?: 0);
            } catch (Throwable $e) {}
        }

        try {
            $sql = "SELECT d.id, d.faculty_id, d.name
                    FROM departments d
                    WHERE COALESCE(d.is_active, 1) = 1";
            $params = [];
            if ($resolvedDepartmentId > 0) {
                $sql .= " AND d.id = ?";
                $params[] = $resolvedDepartmentId;
            } elseif ($resolvedFacultyId > 0) {
                $sql .= " AND d.faculty_id = ?";
                $params[] = $resolvedFacultyId;
            }
            $sql .= " ORDER BY d.name ASC";
            $st = $pdo->prepare($sql);
            $st->execute($params);
            return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalLateFeeCourseOfStudyLabel')) {
    function portalLateFeeCourseOfStudyLabel(PDO $pdo, int $courseOfStudyId, string $fallback = ''): string {
        if ($courseOfStudyId <= 0) return $fallback;
        static $cache = [];
        if (array_key_exists($courseOfStudyId, $cache)) {
            return (string)$cache[$courseOfStudyId];
        }
        try {
            $st = $pdo->prepare("SELECT name FROM departments WHERE id = ? LIMIT 1");
            $st->execute([$courseOfStudyId]);
            $cache[$courseOfStudyId] = trim((string)($st->fetchColumn() ?: ''));
        } catch (Throwable $e) {
            $cache[$courseOfStudyId] = $fallback;
        }
        return (string)($cache[$courseOfStudyId] !== '' ? $cache[$courseOfStudyId] : $fallback);
    }
}


if (!function_exists('portalEnsureLateFeeRuleSchema')) {
    function portalEnsureLateFeeRuleSchema(PDO $pdo): void {
        portalEnsureLateFeeSupport($pdo);
        portalEnsureProgrammeModeOfStudySupport($pdo);
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS late_fee_rules (
                id INT AUTO_INCREMENT PRIMARY KEY,
                session_name VARCHAR(80) NULL,
                semester VARCHAR(40) NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                mode_of_study VARCHAR(30) NULL,
                mode_of_study_custom VARCHAR(180) NULL,
                course_of_study_id INT NULL,
                course_of_study VARCHAR(180) NULL,
                level VARCHAR(20) NULL,
                late_fee_type VARCHAR(20) NOT NULL DEFAULT 'fixed',
                late_fee_value DECIMAL(12,2) NOT NULL DEFAULT 0.00,
                trigger_condition VARCHAR(30) NOT NULL DEFAULT 'session_closure',
                due_date DATE NULL,
                is_enabled TINYINT(1) NOT NULL DEFAULT 1,
                reason VARCHAR(255) NULL,
                created_by INT NULL,
                updated_by INT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_lfr_scope (session_name, semester, faculty_id, department_id, programme_id, mode_of_study, course_of_study_id, level),
                INDEX idx_lfr_status (is_enabled, trigger_condition)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        $columns = [
            'mode_of_study' => "ALTER TABLE late_fee_rules ADD COLUMN mode_of_study VARCHAR(30) NULL AFTER programme_id",
            'mode_of_study_custom' => "ALTER TABLE late_fee_rules ADD COLUMN mode_of_study_custom VARCHAR(180) NULL AFTER mode_of_study",
            'course_of_study_id' => "ALTER TABLE late_fee_rules ADD COLUMN course_of_study_id INT NULL AFTER mode_of_study_custom",
            'course_of_study' => "ALTER TABLE late_fee_rules ADD COLUMN course_of_study VARCHAR(180) NULL AFTER course_of_study_id",
            'level' => "ALTER TABLE late_fee_rules ADD COLUMN level VARCHAR(20) NULL AFTER course_of_study",
            'late_fee_type' => "ALTER TABLE late_fee_rules ADD COLUMN late_fee_type VARCHAR(20) NOT NULL DEFAULT 'fixed' AFTER level",
            'late_fee_value' => "ALTER TABLE late_fee_rules ADD COLUMN late_fee_value DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER late_fee_type",
            'trigger_condition' => "ALTER TABLE late_fee_rules ADD COLUMN trigger_condition VARCHAR(30) NOT NULL DEFAULT 'session_closure' AFTER late_fee_value",
            'due_date' => "ALTER TABLE late_fee_rules ADD COLUMN due_date DATE NULL AFTER trigger_condition",
            'is_enabled' => "ALTER TABLE late_fee_rules ADD COLUMN is_enabled TINYINT(1) NOT NULL DEFAULT 1 AFTER due_date",
            'reason' => "ALTER TABLE late_fee_rules ADD COLUMN reason VARCHAR(255) NULL AFTER is_enabled",
            'created_by' => "ALTER TABLE late_fee_rules ADD COLUMN created_by INT NULL AFTER reason",
            'updated_by' => "ALTER TABLE late_fee_rules ADD COLUMN updated_by INT NULL AFTER created_by",
            'created_at' => "ALTER TABLE late_fee_rules ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP AFTER updated_by",
            'updated_at' => "ALTER TABLE late_fee_rules ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at",
        ];
        foreach ($columns as $col => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, 'late_fee_rules', $col)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) {}
        }

        try {
            if (!portalIndexExistsSafe($pdo, 'late_fee_rules', 'idx_lfr_scope')) {
                $pdo->exec("CREATE INDEX idx_lfr_scope ON late_fee_rules(session_name, semester, faculty_id, department_id, programme_id, mode_of_study, course_of_study_id, level)");
            }
        } catch (Throwable $e) {}
        try {
            if (!portalIndexExistsSafe($pdo, 'late_fee_rules', 'idx_lfr_status')) {
                $pdo->exec("CREATE INDEX idx_lfr_status ON late_fee_rules(is_enabled, trigger_condition)");
            }
        } catch (Throwable $e) {}
        try {
            if (!portalIndexExistsSafe($pdo, 'late_fee_rules', 'idx_lfr_course')) {
                $pdo->exec("CREATE INDEX idx_lfr_course ON late_fee_rules(course_of_study_id, department_id)");
            }
        } catch (Throwable $e) {}

        try {
            $pdo->exec("UPDATE late_fee_rules
                SET mode_of_study_custom = CASE
                        WHEN COALESCE(TRIM(mode_of_study_custom), '') <> '' THEN TRIM(mode_of_study_custom)
                        WHEN COALESCE(TRIM(mode_of_study), '') NOT IN ('', 'Full-Time', 'Part-Time', 'Others') THEN TRIM(mode_of_study)
                        ELSE mode_of_study_custom
                    END,
                    mode_of_study = CASE
                        WHEN COALESCE(TRIM(mode_of_study), '') IN ('Full-Time', 'Part-Time', 'Others') THEN TRIM(mode_of_study)
                        WHEN COALESCE(TRIM(mode_of_study), '') = '' THEN NULL
                        ELSE 'Others'
                    END
                WHERE COALESCE(TRIM(mode_of_study), '') <> '' OR COALESCE(TRIM(mode_of_study_custom), '') <> ''");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("UPDATE late_fee_rules lfr
                LEFT JOIN programmes p ON p.id = lfr.programme_id
                LEFT JOIN departments pd ON pd.id = p.department_id
                LEFT JOIN departments d ON d.id = lfr.department_id
                SET lfr.department_id = COALESCE(NULLIF(lfr.department_id, 0), NULLIF(p.department_id, 0), lfr.department_id),
                    lfr.faculty_id = COALESCE(NULLIF(lfr.faculty_id, 0), NULLIF(d.faculty_id, 0), NULLIF(pd.faculty_id, 0), lfr.faculty_id)
                WHERE (COALESCE(lfr.department_id, 0) = 0 AND COALESCE(p.department_id, 0) <> 0)
                   OR (COALESCE(lfr.faculty_id, 0) = 0 AND (COALESCE(d.faculty_id, 0) <> 0 OR COALESCE(pd.faculty_id, 0) <> 0))");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("UPDATE late_fee_rules lfr
                LEFT JOIN departments d ON d.id = lfr.department_id
                SET lfr.course_of_study_id = COALESCE(NULLIF(lfr.course_of_study_id, 0), NULLIF(lfr.department_id, 0), lfr.course_of_study_id),
                    lfr.course_of_study = COALESCE(NULLIF(TRIM(lfr.course_of_study), ''), NULLIF(TRIM(d.name), ''), lfr.course_of_study)
                WHERE (COALESCE(lfr.course_of_study_id, 0) = 0 AND COALESCE(lfr.department_id, 0) <> 0)
                   OR COALESCE(TRIM(lfr.course_of_study), '') = ''");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("UPDATE late_fee_rules lfr
                INNER JOIN departments d ON LOWER(TRIM(d.name)) = LOWER(TRIM(lfr.course_of_study))
                SET lfr.course_of_study_id = COALESCE(NULLIF(lfr.course_of_study_id, 0), d.id),
                    lfr.department_id = COALESCE(NULLIF(lfr.department_id, 0), d.id),
                    lfr.faculty_id = COALESCE(NULLIF(lfr.faculty_id, 0), d.faculty_id)
                WHERE COALESCE(TRIM(lfr.course_of_study), '') <> ''
                  AND COALESCE(lfr.course_of_study_id, 0) = 0");
        } catch (Throwable $e) {}
    }
}


if (!function_exists('portalNormalizeLateFeeRuleRow')) {
    function portalNormalizeLateFeeRuleRow(PDO $pdo, array $row): array {
        portalEnsureLateFeeRuleSchema($pdo);

        $sessionName = portalNormalizeSessionNameSafe((string)($row['session_name'] ?? ''));
        $semesterRaw = trim((string)($row['semester'] ?? ''));
        $semester = $semesterRaw !== '' ? portalNormalizeSemesterName($semesterRaw) : '';
        $facultyId = max(0, (int)($row['faculty_id'] ?? 0));
        $departmentId = max(0, (int)($row['department_id'] ?? 0));
        $programmeId = max(0, (int)($row['programme_id'] ?? 0));
        $modeMeta = function_exists('portalNormalizeRegistrationScopeModeInput')
            ? portalNormalizeRegistrationScopeModeInput($row['mode_of_study'] ?? '', $row['mode_of_study_custom'] ?? '')
            : ['mode_of_study' => '', 'mode_of_study_custom' => '', 'display' => 'All Modes of Study'];
        $courseOfStudyId = max(0, (int)($row['course_of_study_id'] ?? 0));
        $courseOfStudy = portalNormalizeInlineWhitespace((string)($row['course_of_study'] ?? ''));
        $level = strtoupper(trim((string)($row['level'] ?? '')));
        $lateFeeType = in_array((string)($row['late_fee_type'] ?? 'fixed'), ['fixed', 'percentage'], true) ? (string)$row['late_fee_type'] : 'fixed';
        $lateFeeValue = max(0.0, (float)($row['late_fee_value'] ?? 0));
        $triggerCondition = in_array((string)($row['trigger_condition'] ?? 'session_closure'), ['session_closure', 'semester_end', 'due_date'], true) ? (string)$row['trigger_condition'] : 'session_closure';
        $dueDate = trim((string)($row['due_date'] ?? ''));
        if ($triggerCondition === 'due_date' && $dueDate !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $dueDate)) {
            $dueDate = '';
        }
        $isEnabled = !empty($row['is_enabled']) && !in_array(strtolower(trim((string)($row['is_enabled'] ?? ''))), ['0', 'no', 'disabled'], true) ? 1 : 0;
        $reason = portalNormalizeInlineWhitespace((string)($row['reason'] ?? ''));

        if ($programmeId > 0) {
            try {
                $st = $pdo->prepare("SELECT p.id, p.department_id, COALESCE(d.faculty_id, 0) AS faculty_id
                    FROM programmes p
                    LEFT JOIN departments d ON d.id = p.department_id
                    WHERE p.id = ?
                    LIMIT 1");
                $st->execute([$programmeId]);
                $prog = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                if ($prog) {
                    $departmentId = (int)($prog['department_id'] ?? 0);
                    $facultyId = (int)($prog['faculty_id'] ?? 0);
                } else {
                    $programmeId = 0;
                }
            } catch (Throwable $e) {
                $programmeId = 0;
            }
        }

        if ($departmentId > 0) {
            try {
                $st = $pdo->prepare("SELECT id, faculty_id, name FROM departments WHERE id = ? LIMIT 1");
                $st->execute([$departmentId]);
                $dept = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                if ($dept) {
                    $facultyId = (int)($dept['faculty_id'] ?? 0);
                } else {
                    $departmentId = 0;
                }
            } catch (Throwable $e) {
                $departmentId = 0;
            }
        }

        if ($courseOfStudyId > 0) {
            try {
                $st = $pdo->prepare("SELECT id, faculty_id, name FROM departments WHERE id = ? LIMIT 1");
                $st->execute([$courseOfStudyId]);
                $course = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                if ($course) {
                    if ($facultyId <= 0) {
                        $facultyId = (int)($course['faculty_id'] ?? 0);
                    }
                    if ($courseOfStudy === '') {
                        $courseOfStudy = trim((string)($course['name'] ?? ''));
                    }
                } else {
                    $courseOfStudyId = 0;
                }
            } catch (Throwable $e) {
                $courseOfStudyId = 0;
            }
        }

        if ($courseOfStudyId <= 0 && $courseOfStudy !== '') {
            try {
                $resolvedCourseId = portalResolveDepartmentIdByText($pdo, $courseOfStudy, $facultyId);
                if ($resolvedCourseId > 0) {
                    $courseOfStudyId = $resolvedCourseId;
                    $courseOfStudy = portalLateFeeCourseOfStudyLabel($pdo, $courseOfStudyId, $courseOfStudy);
                }
            } catch (Throwable $e) {}
        }

        if ($courseOfStudyId > 0 && $courseOfStudy === '') {
            $courseOfStudy = portalLateFeeCourseOfStudyLabel($pdo, $courseOfStudyId, '');
        }

        if ($facultyId > 0) {
            try {
                $st = $pdo->prepare("SELECT id FROM faculties WHERE id = ? LIMIT 1");
                $st->execute([$facultyId]);
                if (!(int)($st->fetchColumn() ?: 0)) {
                    $facultyId = 0;
                }
            } catch (Throwable $e) {
                $facultyId = 0;
            }
        }

        return [
            'id' => max(0, (int)($row['id'] ?? 0)),
            'session_name' => $sessionName,
            'semester' => $semester,
            'faculty_id' => $facultyId,
            'department_id' => $departmentId,
            'programme_id' => $programmeId,
            'mode_of_study' => (string)($modeMeta['mode_of_study'] ?? ''),
            'mode_of_study_custom' => (string)($modeMeta['mode_of_study_custom'] ?? ''),
            'course_of_study_id' => $courseOfStudyId,
            'course_of_study' => $courseOfStudy,
            'level' => $level,
            'late_fee_type' => $lateFeeType,
            'late_fee_value' => $lateFeeValue,
            'trigger_condition' => $triggerCondition,
            'due_date' => $dueDate,
            'is_enabled' => $isEnabled,
            'reason' => $reason,
        ];
    }
}

if (!function_exists('portalValidateLateFeeRuleScope')) {
    function portalValidateLateFeeRuleScope(PDO $pdo, array $row): array {
        $row = portalNormalizeLateFeeRuleRow($pdo, $row);

        $sessionName = trim((string)($row['session_name'] ?? ''));
        if ($sessionName !== '') {
            $validSessions = array_map('strval', portalAvailableSessionOptions($pdo));
            if ($validSessions && !in_array($sessionName, $validSessions, true)) {
                throw new RuntimeException('Please select a valid session for the Late Fee rule.');
            }
        }

        $semester = trim((string)($row['semester'] ?? ''));
        if ($semester !== '') {
            $validSemesters = array_map('strval', portalAvailableSemesterOptions());
            if ($validSemesters && !in_array($semester, $validSemesters, true)) {
                throw new RuntimeException('Please select a valid semester for the Late Fee rule.');
            }
        }

        $level = trim((string)($row['level'] ?? ''));
        if ($level !== '') {
            $validLevels = array_map(static function($v){ return strtoupper(trim((string)$v)); }, portalAcademicLevelOptions($pdo));
            $validLevels = array_values(array_filter(array_unique($validLevels), static function($v){ return $v !== ''; }));
            if ($validLevels && !in_array(strtoupper($level), $validLevels, true)) {
                throw new RuntimeException('Please select a valid level for the Late Fee rule.');
            }
        }

        $facultyId = (int)($row['faculty_id'] ?? 0);
        $departmentId = (int)($row['department_id'] ?? 0);
        $programmeId = (int)($row['programme_id'] ?? 0);
        $courseOfStudyId = (int)($row['course_of_study_id'] ?? 0);
        $courseOfStudy = portalNormalizeInlineWhitespace((string)($row['course_of_study'] ?? ''));

        if ($facultyId > 0) {
            $st = $pdo->prepare("SELECT id FROM faculties WHERE id = ? LIMIT 1");
            $st->execute([$facultyId]);
            if (!(int)($st->fetchColumn() ?: 0)) {
                throw new RuntimeException('Invalid faculty selected for the Late Fee rule.');
            }
        }

        if ($departmentId > 0) {
            $st = $pdo->prepare("SELECT id, faculty_id FROM departments WHERE id = ? LIMIT 1");
            $st->execute([$departmentId]);
            $dept = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$dept) {
                throw new RuntimeException('Invalid department selected for the Late Fee rule.');
            }
            $deptFacultyId = (int)($dept['faculty_id'] ?? 0);
            if ($facultyId > 0 && $deptFacultyId !== $facultyId) {
                throw new RuntimeException('Do not save Department without a matching Faculty. The selected department does not belong to the chosen faculty.');
            }
            if ($facultyId <= 0) {
                $facultyId = $deptFacultyId;
            }
        }

        if ($programmeId > 0) {
            $st = $pdo->prepare("SELECT id, department_id, COALESCE(faculty_id, 0) AS faculty_id FROM programmes WHERE id = ? LIMIT 1");
            $st->execute([$programmeId]);
            $prog = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$prog) {
                throw new RuntimeException('Invalid programme selected for the Late Fee rule.');
            }
            $progDepartmentId = (int)($prog['department_id'] ?? 0);
            $progFacultyId = (int)($prog['faculty_id'] ?? 0);
            if ($departmentId > 0 && $progDepartmentId !== $departmentId) {
                throw new RuntimeException('Do not save Programme without a matching Department. The selected programme does not belong to the chosen department.');
            }
            if ($facultyId > 0 && $progFacultyId > 0 && $progFacultyId !== $facultyId) {
                throw new RuntimeException('The selected programme does not belong to the chosen faculty.');
            }
            if ($departmentId <= 0) {
                $departmentId = $progDepartmentId;
            }
            if ($facultyId <= 0 && $progFacultyId > 0) {
                $facultyId = $progFacultyId;
            }
        }

        if ($courseOfStudyId > 0) {
            $st = $pdo->prepare("SELECT id, faculty_id, name FROM departments WHERE id = ? LIMIT 1");
            $st->execute([$courseOfStudyId]);
            $course = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$course) {
                throw new RuntimeException('Please select a valid Course of Study from the linked dropdown.');
            }
            if ($facultyId > 0 && (int)($course['faculty_id'] ?? 0) !== $facultyId) {
                throw new RuntimeException('Do not save Course of Study outside the selected academic structure.');
            }
            if ($departmentId > 0 && $courseOfStudyId !== $departmentId) {
                throw new RuntimeException('Do not save Course of Study outside the selected Department.');
            }
            if ($departmentId <= 0) {
                $departmentId = $courseOfStudyId;
            }
            if ($courseOfStudy === '') {
                $courseOfStudy = trim((string)($course['name'] ?? ''));
            }
        }

        if ($programmeId > 0 && $courseOfStudyId > 0 && $departmentId > 0 && $courseOfStudyId !== $departmentId) {
            throw new RuntimeException('The selected Programme and Course of Study do not match.');
        }

        if (!empty($row['is_enabled']) && (float)($row['late_fee_value'] ?? 0) <= 0) {
            throw new RuntimeException('Late Fee Amount must be greater than zero before saving an enabled rule.');
        }

        $row['faculty_id'] = $facultyId;
        $row['department_id'] = $departmentId;
        $row['programme_id'] = $programmeId;
        $row['course_of_study_id'] = $courseOfStudyId;
        $row['course_of_study'] = $courseOfStudy;

        return $row;
    }
}


if (!function_exists('portalLateFeeRuleSpecificity')) {
    function portalLateFeeRuleSpecificity(array $row): int {
        $score = 0;
        if (trim((string)($row['session_name'] ?? '')) !== '') $score += 2;
        if (trim((string)($row['semester'] ?? '')) !== '') $score += 4;
        if ((int)($row['faculty_id'] ?? 0) > 0) $score += 8;
        if ((int)($row['department_id'] ?? 0) > 0) $score += 16;
        if ((int)($row['programme_id'] ?? 0) > 0) $score += 32;
        if (trim((string)($row['mode_of_study'] ?? '')) !== '' || trim((string)($row['mode_of_study_custom'] ?? '')) !== '') $score += 64;
        if (trim((string)($row['level'] ?? '')) !== '') $score += 128;
        if ((int)($row['course_of_study_id'] ?? 0) > 0 || trim((string)($row['course_of_study'] ?? '')) !== '') $score += 256;
        return $score;
    }
}


if (!function_exists('portalLateFeeRuleRows')) {
    function portalLateFeeRuleRows(PDO $pdo): array {
        portalEnsureLateFeeRuleSchema($pdo);
        try {
            $sql = "SELECT lfr.*,
                           f.name AS faculty_name,
                           d.name AS department_name,
                           p.name AS programme_name,
                           cd.name AS course_of_study_name
                    FROM late_fee_rules lfr
                    LEFT JOIN faculties f ON f.id = lfr.faculty_id
                    LEFT JOIN departments d ON d.id = lfr.department_id
                    LEFT JOIN programmes p ON p.id = lfr.programme_id
                    LEFT JOIN departments cd ON cd.id = lfr.course_of_study_id
                    ORDER BY COALESCE(NULLIF(lfr.session_name, ''), 'ZZZZ') DESC,
                             COALESCE(lfr.semester, ''),
                             COALESCE(f.name, ''),
                             COALESCE(d.name, ''),
                             COALESCE(p.name, ''),
                             lfr.id DESC";
            $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($rows as &$row) {
                $row = portalNormalizeLateFeeRuleRow($pdo, (array)$row) + (array)$row;
                $row['programme_name'] = portalProgrammeSelectorLabelById(
                    $pdo,
                    (int)($row['programme_id'] ?? 0),
                    (int)($row['faculty_id'] ?? 0),
                    (int)($row['department_id'] ?? 0),
                    trim((string)($row['programme_name'] ?? ''))
                );
                $row['course_of_study_name'] = portalLateFeeCourseOfStudyLabel(
                    $pdo,
                    (int)($row['course_of_study_id'] ?? 0),
                    trim((string)($row['course_of_study_name'] ?? $row['course_of_study'] ?? ''))
                );
                $row['mode_of_study_display'] = function_exists('portalRegistrationScopeModeDisplay')
                    ? portalRegistrationScopeModeDisplay((array)$row)
                    : trim((string)($row['mode_of_study'] ?? ''));
            }
            unset($row);
            return $rows;
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalLateFeeRuleTriggerLabel')) {
    function portalLateFeeRuleTriggerLabel(array $row): string {
        $trigger = (string)($row['trigger_condition'] ?? 'session_closure');
        if ($trigger === 'semester_end') return 'After Semester Closes';
        if ($trigger === 'due_date') return 'After Due Date' . (!empty($row['due_date']) ? ' (' . (string)$row['due_date'] . ')' : '');
        return 'After Session Closes';
    }
}


if (!function_exists('portalLateFeeRuleScopeSummary')) {
    function portalLateFeeRuleScopeSummary(PDO $pdo, array $row): string {
        $parts = [];
        $sessionName = trim((string)($row['session_name'] ?? ''));
        $semester = trim((string)($row['semester'] ?? ''));
        $facultyName = trim((string)($row['faculty_name'] ?? ''));
        $departmentName = trim((string)($row['department_name'] ?? ''));
        $programmeName = trim((string)($row['programme_name'] ?? ''));
        $modeDisplay = function_exists('portalRegistrationScopeModeDisplay') ? portalRegistrationScopeModeDisplay((array)$row) : trim((string)($row['mode_of_study'] ?? ''));
        $courseName = portalLateFeeCourseOfStudyLabel($pdo, (int)($row['course_of_study_id'] ?? 0), trim((string)($row['course_of_study'] ?? '')));
        $level = trim((string)($row['level'] ?? ''));

        if ($sessionName !== '') $parts[] = $sessionName;
        if ($semester !== '') $parts[] = $semester;
        if ($facultyName !== '') $parts[] = $facultyName;
        if ($departmentName !== '') $parts[] = $departmentName;
        if ($programmeName !== '') $parts[] = $programmeName;
        if ($modeDisplay !== '' && strcasecmp($modeDisplay, 'All Modes of Study') !== 0) $parts[] = $modeDisplay;
        if ($courseName !== '') $parts[] = $courseName;
        if ($level !== '') $parts[] = $level;

        return $parts ? implode(' • ', $parts) : 'Global fallback rule';
    }
}


if (!function_exists('portalLateFeeRuleForStudent')) {
    function portalLateFeeRuleForStudent(PDO $pdo, array $student, string $sessionName = '', string $semester = ''): array {
        portalEnsureLateFeeRuleSchema($pdo);

        $ctxSession = portalNormalizeSessionNameSafe($sessionName !== '' ? $sessionName : (string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)));
        $ctxSemester = portalNormalizeSemesterName($semester !== '' ? $semester : (string)($student['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
        $ctxFaculty = max(0, (int)($student['faculty_id'] ?? 0));
        $ctxDepartment = max(0, (int)($student['department_id'] ?? 0));
        $ctxProgramme = max(0, (int)($student['programme_id'] ?? 0));
        $ctxMode = trim((string)($student['mode_of_study'] ?? portalInferModeOfStudy($student)));
        $ctxLevel = strtoupper(trim((string)($student['level'] ?? '')));
        $ctxCourse = portalNormalizeInlineWhitespace((string)($student['course_of_study'] ?? $student['course_in_view'] ?? $student['department'] ?? ''));
        $ctxCourseId = $ctxDepartment > 0 ? $ctxDepartment : 0;
        if ($ctxCourseId <= 0 && $ctxCourse !== '') {
            try {
                $ctxCourseId = portalResolveDepartmentIdByText($pdo, $ctxCourse, $ctxFaculty);
            } catch (Throwable $e) {}
        }

        $best = [];
        $bestScore = -1;

        foreach (portalLateFeeRuleRows($pdo) as $row) {
            $rowSession = trim((string)($row['session_name'] ?? ''));
            if ($rowSession !== '' && strcasecmp($rowSession, $ctxSession) !== 0) continue;

            $rowSemester = trim((string)($row['semester'] ?? ''));
            if ($rowSemester !== '' && strcasecmp(portalNormalizeSemesterName($rowSemester), $ctxSemester) !== 0) continue;

            $rowFaculty = (int)($row['faculty_id'] ?? 0);
            if ($rowFaculty > 0 && $rowFaculty !== $ctxFaculty) continue;

            $rowDepartment = (int)($row['department_id'] ?? 0);
            if ($rowDepartment > 0 && $rowDepartment !== $ctxDepartment) continue;

            $rowProgramme = (int)($row['programme_id'] ?? 0);
            if ($rowProgramme > 0) {
                if ($ctxProgramme <= 0) continue;
                if ($rowProgramme !== $ctxProgramme && !portalProgrammesShareScopeName($pdo, $rowProgramme, $ctxProgramme)) continue;
            }

            if (function_exists('portalRegistrationScopeModeMatches') && !portalRegistrationScopeModeMatches((array)$row, $ctxMode)) {
                continue;
            }

            $rowLevel = strtoupper(trim((string)($row['level'] ?? '')));
            if ($rowLevel !== '' && strcasecmp($rowLevel, $ctxLevel) !== 0) continue;

            $rowCourseId = (int)($row['course_of_study_id'] ?? 0);
            if ($rowCourseId > 0) {
                if ($ctxCourseId <= 0 || $rowCourseId !== $ctxCourseId) continue;
            } else {
                $rowCourse = portalNormalizeInlineWhitespace((string)($row['course_of_study'] ?? ''));
                if ($rowCourse !== '') {
                    if ($ctxCourse === '' || strcasecmp($rowCourse, $ctxCourse) !== 0) continue;
                }
            }

            $score = portalLateFeeRuleSpecificity($row);
            if ($score > $bestScore) {
                $bestScore = $score;
                $best = $row;
            }
        }

        if (!$best) {
            return ['matched' => false, 'id' => 0, 'is_enabled' => 0, 'session_name' => $ctxSession, 'semester' => $ctxSemester];
        }

        $best['matched'] = true;
        return $best;
    }
}

if (!function_exists('portalLateFeeRuleSettingsForStudent')) {
    function portalLateFeeRuleSettingsForStudent(PDO $pdo, array $student, string $sessionName = '', string $semester = ''): array {
        $global = portalLateFeeSettings($pdo);
        $rule = portalLateFeeRuleForStudent($pdo, $student, $sessionName, $semester);
        if (!empty($rule['matched'])) {
            return [
                'source' => 'rule',
                'rule' => $rule,
                'enabled' => !empty($rule['is_enabled']),
                'type' => (string)($rule['late_fee_type'] ?? 'fixed'),
                'value' => (float)($rule['late_fee_value'] ?? 0),
                'grace_days' => (int)($global['grace_days'] ?? 0),
                'apply_mode' => (string)($rule['trigger_condition'] ?? 'session_closure'),
                'apply_to' => (string)($global['apply_to'] ?? 'all_unpaid'),
                'auto_generate' => (bool)($global['auto_generate'] ?? false),
                'reason' => trim((string)($rule['reason'] ?? ($global['reason'] ?? 'Late school fee charged after deadline.'))),
                'first_semester_end_date' => (string)($global['first_semester_end_date'] ?? ''),
                'second_semester_end_date' => (string)($global['second_semester_end_date'] ?? ''),
                'due_date' => trim((string)($rule['due_date'] ?? '')),
            ];
        }
        $global['source'] = 'global';
        $global['rule'] = [];
        $global['due_date'] = '';
        return $global;
    }
}
if (!function_exists('portalLateFeeCycleReferenceDate')) {
    function portalLateFeeCycleReferenceDate(PDO $pdo, string $sessionName, string $semester, string $cycleType): string {
        $sessionName = portalNormalizeSessionNameSafe($sessionName);
        $semesterKey = (stripos($semester, 'Second') !== false) ? 'Second' : 'First';
        if ($cycleType === 'semester_end') {
            $settings = portalLateFeeSettings($pdo);
            return trim((string)($semesterKey === 'Second' ? ($settings['second_semester_end_date'] ?? '') : ($settings['first_semester_end_date'] ?? '')));
        }
        try {
            $st = $pdo->prepare("SELECT end_date FROM academic_sessions WHERE name = ? ORDER BY is_current DESC, id DESC LIMIT 1");
            $st->execute([$sessionName]);
            return trim((string)($st->fetchColumn() ?: ''));
        } catch (Throwable $e) {
            return '';
        }
    }
}

if (!function_exists('portalLateFeeStudentEligible')) {
    function portalLateFeeStudentEligible(array $student, string $applyTo): bool {
        $applyTo = trim((string)$applyTo);
        if ($applyTo === 'approved_only') {
            return strcasecmp((string)($student['application_status'] ?? ''), 'Approved') === 0;
        }
        if ($applyTo === 'active_only') {
            $account = strtolower(trim((string)($student['account_status'] ?? '')));
            return $account === '' || in_array($account, ['active','enabled'], true);
        }
        return true;
    }
}

if (!function_exists('portalActiveLateFeeRecordsForPeriod')) {
    function portalActiveLateFeeRecordsForPeriod(PDO $pdo, int $studentId, string $sessionName, string $semester): array {
        portalEnsureLateFeeSupport($pdo);
        try {
            $st = $pdo->prepare("SELECT * FROM school_fee_late_fee_records WHERE student_id = ? AND academic_session = ? AND semester = ? AND status IN ('Active','Partially Paid') ORDER BY id ASC");
            $st->execute([$studentId, portalNormalizeSessionNameSafe($sessionName), portalNormalizeSemesterName($semester)]);
            return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalActiveLateFeeBalanceForPeriod')) {
    function portalActiveLateFeeBalanceForPeriod(PDO $pdo, int $studentId, string $sessionName, string $semester): array {
        $records = portalActiveLateFeeRecordsForPeriod($pdo, $studentId, $sessionName, $semester);
        $total = 0.0;
        foreach ($records as $row) {
            $total += max(0.0, (float)($row['late_fee_amount'] ?? 0) - (float)($row['paid_amount'] ?? 0));
        }
        return ['amount' => $total, 'records' => $records];
    }
}

if (!function_exists('portalApplyLateFeePaymentToRecords')) {
    function portalApplyLateFeePaymentToRecords(PDO $pdo, int $studentId, string $sessionName, string $semester, float $amount, string $reference = ''): void {
        portalEnsureLateFeeSupport($pdo);
        if ($amount <= 0 || $studentId <= 0) return;
        try {
            $records = portalActiveLateFeeRecordsForPeriod($pdo, $studentId, $sessionName, $semester);
            foreach ($records as $row) {
                if ($amount <= 0) break;
                $due = max(0.0, (float)($row['late_fee_amount'] ?? 0) - (float)($row['paid_amount'] ?? 0));
                if ($due <= 0) continue;
                $pay = min($due, $amount);
                $newPaid = (float)($row['paid_amount'] ?? 0) + $pay;
                $newStatus = ($newPaid + 0.0001 >= (float)($row['late_fee_amount'] ?? 0)) ? 'Paid' : 'Partially Paid';
                $remarks = trim((string)($row['remarks'] ?? ''));
                if ($reference !== '') {
                    $remarks = trim($remarks . ($remarks !== '' ? ' | ' : '') . 'Paid via ' . $reference);
                }
                $st = $pdo->prepare("UPDATE school_fee_late_fee_records SET paid_amount = ?, status = ?, paid_at = CASE WHEN ? = 'Paid' THEN NOW() ELSE paid_at END, remarks = ?, updated_at = NOW() WHERE id = ?");
                $st->execute([$newPaid, $newStatus, $newStatus, $remarks !== '' ? $remarks : null, (int)$row['id']]);
                $amount -= $pay;
            }
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalLateFeeAssessmentForPeriod')) {
    function portalLateFeeAssessmentForPeriod(PDO $pdo, array $student, string $sessionName, string $semester, bool $allowGenerate = true): array {
        portalEnsureLateFeeSupport($pdo);
        $studentId = (int)($student['id'] ?? 0);
        $sessionName = portalNormalizeSessionNameSafe($sessionName);
        $semester = portalNormalizeSemesterName($semester !== '' ? $semester : (string)($student['semester'] ?? 'First Semester'));
        $settings = portalLateFeeRuleSettingsForStudent($pdo, $student, $sessionName, $semester);
        $summary = portalSchoolFeeSessionSummary($pdo, $student, $sessionName, $semester);
        $principalOutstanding = max(0.0, (float)($summary['outstanding_amount'] ?? 0));
        $result = [
            'eligible' => false,
            'generated' => false,
            'principal_outstanding' => $principalOutstanding,
            'late_fee_amount' => 0.0,
            'late_fee_records' => [],
            'late_fee_balance' => 0.0,
            'notice' => '',
            'reason' => trim((string)($settings['reason'] ?? 'Late school fee charged after deadline.')),
        ];

        if (!$settings['enabled'] || $studentId <= 0 || $principalOutstanding <= 0.0001 || !portalLateFeeStudentEligible($student, (string)($settings['apply_to'] ?? 'all_unpaid'))) {
            $balance = portalActiveLateFeeBalanceForPeriod($pdo, $studentId, $sessionName, $semester);
            $result['late_fee_balance'] = (float)($balance['amount'] ?? 0);
            $result['late_fee_records'] = (array)($balance['records'] ?? []);
            return $result;
        }

        $today = date('Y-m-d');
        $cycleTypes = [];
        $applyMode = (string)($settings['apply_mode'] ?? 'session_closure');
        if ($applyMode === 'both') {
            $cycleTypes = ['session_closure', 'semester_end'];
        } elseif (in_array($applyMode, ['semester_end','due_date'], true)) {
            $cycleTypes = [$applyMode];
        } else {
            $cycleTypes = ['session_closure'];
        }

        $adminId = (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0);
        foreach ($cycleTypes as $cycleType) {
            $refDate = $cycleType === 'due_date'
                ? trim((string)($settings['due_date'] ?? ''))
                : portalLateFeeCycleReferenceDate($pdo, $sessionName, $semester, $cycleType);
            if ($refDate === '') continue;
            $graceDate = date('Y-m-d', strtotime($refDate . ' +' . max(0, (int)($settings['grace_days'] ?? 0)) . ' day'));
            if (strtotime($today) < strtotime($graceDate)) continue;

            $result['eligible'] = true;
            $amount = ((string)($settings['type'] ?? 'fixed') === 'percentage')
                ? round(($principalOutstanding * max(0.0, (float)($settings['value'] ?? 0))) / 100, 2)
                : round(max(0.0, (float)($settings['value'] ?? 0)), 2);
            if ($amount <= 0) continue;

            $cycleKey = $cycleType . '|' . $sessionName . '|' . $semester . '|' . $refDate . '|' . (string)($settings['source'] ?? 'global') . '|' . (string)(int)(($settings['rule']['id'] ?? 0));
            if ($allowGenerate && ($settings['auto_generate'] ?? false)) {
                try {
                    $st = $pdo->prepare("SELECT id FROM school_fee_late_fee_records WHERE student_id = ? AND academic_session = ? AND semester = ? AND cycle_key = ? LIMIT 1");
                    $st->execute([$studentId, $sessionName, $semester, $cycleKey]);
                    $existingId = (int)($st->fetchColumn() ?: 0);
                    if ($existingId <= 0) {
                        $ins = $pdo->prepare("INSERT INTO school_fee_late_fee_records (student_id, academic_session, semester, cycle_type, cycle_key, status, base_outstanding_amount, late_fee_type, late_fee_value, late_fee_amount, generated_at, remarks, created_by, updated_by) VALUES (?,?,?,?,?,'Active',?,?,?,?,NOW(),?,?,?)");
                        $ins->execute([$studentId, $sessionName, $semester, $cycleType, $cycleKey, $principalOutstanding, (string)($settings['type'] ?? 'fixed'), (float)($settings['value'] ?? 0), $amount, (string)($settings['reason'] ?? ''), $adminId > 0 ? $adminId : null, $adminId > 0 ? $adminId : null]);
                        $result['generated'] = true;
                    }
                } catch (Throwable $e) {}
            }
        }

        $balance = portalActiveLateFeeBalanceForPeriod($pdo, $studentId, $sessionName, $semester);
        $result['late_fee_balance'] = (float)($balance['amount'] ?? 0);
        $result['late_fee_records'] = (array)($balance['records'] ?? []);
        if ($result['late_fee_balance'] > 0) {
            $first = $result['late_fee_records'][0] ?? [];
            $result['late_fee_amount'] = $result['late_fee_balance'];
            $result['notice'] = trim((string)($first['remarks'] ?? $result['reason']));
        }
        return $result;
    }
}

if (!function_exists('portalGenerateLateFeesForStudent')) {
    function portalGenerateLateFeesForStudent(PDO $pdo, int $studentId, string $sessionName = '', string $semester = ''): array {
        try {
            $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $st->execute([$studentId]);
            $student = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$student) return ['ok' => false, 'message' => 'Student not found.'];
            $sessionName = portalNormalizeSessionNameSafe($sessionName !== '' ? $sessionName : (string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)));
            $semester = portalNormalizeSemesterName($semester !== '' ? $semester : (string)($student['semester'] ?? 'First Semester'));
            $assessment = portalLateFeeAssessmentForPeriod($pdo, $student, $sessionName, $semester, true);
            return ['ok' => true, 'message' => $assessment['generated'] ? 'Late fee generated.' : (($assessment['late_fee_balance'] ?? 0) > 0 ? 'Late fee already exists for this cycle.' : 'No eligible late fee was generated.'), 'assessment' => $assessment];
        } catch (Throwable $e) {
            return ['ok' => false, 'message' => 'Unable to generate late fee.'];
        }
    }
}

if (!function_exists('portalWaiveLateFeeForStudent')) {
    function portalWaiveLateFeeForStudent(PDO $pdo, int $studentId, string $sessionName, string $semester, string $remarks = ''): void {
        portalEnsureLateFeeSupport($pdo);
        $adminId = (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0);
        try {
            $st = $pdo->prepare("UPDATE school_fee_late_fee_records SET status = 'Waived', waived_at = NOW(), waived_by = ?, remarks = ?, updated_by = ? WHERE student_id = ? AND academic_session = ? AND semester = ? AND status IN ('Active','Partially Paid')");
            $st->execute([$adminId > 0 ? $adminId : null, $remarks !== '' ? $remarks : 'Late fee waived by admin', $adminId > 0 ? $adminId : null, $studentId, portalNormalizeSessionNameSafe($sessionName), portalNormalizeSemesterName($semester)]);
        } catch (Throwable $e) {}
    }
}


if (!function_exists('portalSchoolFeeStatusForPeriod')) {
    function portalSchoolFeeStatusForPeriod(PDO $pdo, array $student, string $session = '', string $semester = ''): string {
        $summary = portalSchoolFeePeriodSummary($pdo, (int)($student['id'] ?? 0), $session, $semester, $student);
        return (string)($summary['status_label'] ?? 'Not Paid');
    }
}

if (!function_exists('portalHostelStatusForPeriod')) {
    function portalHostelStatusForPeriod(PDO $pdo, array $student, string $session = '', string $semester = ''): string {
        $targetSession = trim($session);
        if ($targetSession === '') {
            $targetSession = trim((string)($student['session'] ?? $student['session_name'] ?? portalCurrentAcademicSessionName($pdo)));
        }
        $hostelSummary = portalStudentHostelRequirementSummary($pdo, $student, $targetSession, $semester);
        if (!$hostelSummary['requires_hostel_fee']) {
            return (string)($hostelSummary['label'] ?? 'Off-Campus');
        }
        $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE student_id = ? AND transaction_type = 'Hostel Fee' ORDER BY COALESCE(payment_date, created_at) DESC, id DESC");
        $stmt->execute([(int)($student['id'] ?? 0)]);
        $transactions = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        $hasPending = false;
        foreach ($transactions as $tx) {
            $txSession = trim((string)portalPaymentAcademicSession((array)$tx, $student));
            if ($targetSession !== '' && $txSession !== '' && strcasecmp($txSession, $targetSession) !== 0) continue;
            if (portalPaymentIsApproved($tx)) return 'Paid';
            $status = strtolower(trim((string)($tx['status'] ?? '')));
            if (in_array($status, ['pending', 'processing'], true)) $hasPending = true;
        }
        return $hasPending ? 'Pending' : 'Not Paid';
    }
}

if (!function_exists('portalClearanceReceiptPaymentAmount')) {
    function portalClearanceReceiptPaymentAmount(array $tx): float {
        $candidates = [];
        if (isset($tx['settled_amount'])) $candidates[] = (float)$tx['settled_amount'];
        if (isset($tx['approved_amount'])) $candidates[] = (float)$tx['approved_amount'];
        if (isset($tx['received_fee_amount'])) $candidates[] = (float)$tx['received_fee_amount'];

        $schoolBase = (float)($tx['school_fee_base_amount'] ?? 0);
        $schoolLate = (float)($tx['school_fee_late_fee_amount'] ?? 0);
        if (($schoolBase + $schoolLate) > 0) {
            $candidates[] = $schoolBase + $schoolLate;
        }

        if (function_exists('portalPaymentApprovedAmount')) {
            $candidates[] = (float)portalPaymentApprovedAmount($tx);
        }
        $candidates[] = (float)($tx['amount'] ?? 0);

        foreach ($candidates as $candidate) {
            if ($candidate > 0) return round($candidate, 2);
        }
        return 0.0;
    }
}

if (!function_exists('portalClearanceReceiptStatusClass')) {
    function portalClearanceReceiptStatusClass(string $status): string {
        $value = strtolower(trim($status));
        if (in_array($value, ['paid', 'approved', 'successful'], true)) return 'success';
        if (in_array($value, ['completed', 'complete'], true)) return 'primary';
        if (in_array($value, ['processing', 'in progress'], true)) return 'info';
        if (in_array($value, ['pending', 'partially paid', 'partially settled'], true)) return 'warning';
        if (in_array($value, ['not paid', 'rejected', 'failed', 'declined'], true)) return 'danger';
        if (in_array($value, ['covered', 'waived', 'not created'], true)) return 'secondary';
        return 'secondary';
    }
}

if (!function_exists('portalClearanceReceiptItemStatus')) {
    function portalClearanceReceiptItemStatus(array $tx, ?float $amount = null): string {
        $amount = $amount ?? portalClearanceReceiptPaymentAmount($tx);
        if ($amount > 0) return 'Paid';

        $recon = strtolower(trim((string)($tx['reconciliation_status'] ?? '')));
        $notes = strtolower(trim((string)($tx['approval_notes'] ?? $tx['narration'] ?? '')));

        if ($recon !== '') {
            if (str_contains($recon, 'underpaid') || str_contains($recon, 'rejected')) return 'Not Paid';
            if (str_contains($recon, 'wallet') || str_contains($recon, 'covered') || str_contains($recon, 'nooutstanding')) return 'Covered';
        }
        if ($notes !== '') {
            if (str_contains($notes, 'waiv')) return 'Waived';
            if (str_contains($notes, 'carry') || str_contains($notes, 'migrat') || str_contains($notes, 'covered') || str_contains($notes, 'already paid')) return 'Covered';
        }

        $status = strtolower(trim((string)($tx['status'] ?? '')));
        if (in_array($status, ['pending', 'rejected', 'failed'], true)) return ucfirst($status);
        return 'Covered';
    }
}

if (!function_exists('portalPeriodPaidItems')) {
    function portalPeriodPaidItems(PDO $pdo, array $student, string $session = '', string $semester = ''): array {
        $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE student_id = ? ORDER BY COALESCE(payment_date, created_at) ASC, id ASC");
        $stmt->execute([(int)($student['id'] ?? 0)]);
        $transactions = portalFilterClearanceTransactionsForPeriod($stmt->fetchAll(PDO::FETCH_ASSOC) ?: [], $session, $semester, $student);

        $items = [];
        $seen = [];
        foreach ($transactions as $tx) {
            $type = trim((string)($tx['transaction_type'] ?? ''));
            if ($type === 'Wallet Funding') continue;

            $statusRaw = strtolower(trim((string)($tx['status'] ?? '')));
            if (!portalPaymentIsApproved($tx) && !in_array($statusRaw, ['approved', 'successful'], true)) continue;

            $label = $type;
            if ($type === 'School Fee') {
                $label = ((int)($tx['total_installments'] ?? 1) > 1)
                    ? ('School Fee ' . portalOrdinalLabel((int)($tx['installment_number'] ?? 1)) . ' Installment')
                    : 'School Fee';
            } elseif (stripos($type, 'Custom:') === 0) {
                $label = trim(substr($type, 7));
                if ($label === '') $label = 'Other Approved Charge';
            }

            $amount = portalClearanceReceiptPaymentAmount($tx);
            $status = portalClearanceReceiptItemStatus($tx, $amount);
            $reference = trim((string)($tx['reference'] ?? ''));
            $paymentDate = (string)($tx['payment_date'] ?? $tx['created_at'] ?? '');
            $dedupeKey = strtolower($label . '|' . $reference . '|' . number_format($amount, 2, '.', '') . '|' . $paymentDate);
            if ($dedupeKey !== '|||'
                and isset($seen[$dedupeKey])) {
                continue;
            }
            $seen[$dedupeKey] = true;

            $items[] = [
                'label' => $label !== '' ? $label : 'Payment',
                'status' => $status,
                'status_class' => portalClearanceReceiptStatusClass($status),
                'amount' => $amount,
                'payment_date' => $paymentDate,
                'reference' => $reference,
                'transaction' => $tx,
            ];
        }
        return $items;
    }
}


if (!function_exists('portalFetchStudentMetaRows')) {
    function portalFetchStudentMetaRows(PDO $pdo, array $filters = [], bool $onlyPreviousAdmissions = false): array {
        $where = ['1=1'];
        $params = [];

        if ($onlyPreviousAdmissions) {
            $where[] = "(s.application_status = 'Approved' OR COALESCE(s.offer_status,'Pending') = 'Accepted' OR COALESCE(s.admission_no,'') <> '' OR COALESCE(s.matric_no,'') <> '')";
        }

        if (!empty($filters['faculty_id'])) {
            $where[] = 's.faculty_id = ?';
            $params[] = (int)$filters['faculty_id'];
        }
        if (!empty($filters['department_id'])) {
            $where[] = 's.department_id = ?';
            $params[] = (int)$filters['department_id'];
        }
        if (!empty($filters['programme_id'])) {
            $where[] = 's.programme_id = ?';
            $params[] = (int)$filters['programme_id'];
        }

        $level = trim((string)($filters['level'] ?? ''));
        $modeOfStudy = trim((string)($filters['mode_of_study'] ?? ''));
        if ($level !== '') {
            $where[] = 'COALESCE(s.level,\'\') = ?';
            $params[] = $level;
        }

        $session = trim((string)($filters['session'] ?? ''));
        if ($session !== '') {
            $where[] = 'COALESCE(s.session,\'\') = ?';
            $params[] = $session;
        }

        $semester = trim((string)($filters['semester'] ?? ''));
        if ($semester !== '') {
            $where[] = 'COALESCE(s.semester,\'\') = ?';
            $params[] = $semester;
        }

        $sql = "SELECT s.*,\n                       COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.faculty),''), 'Not Set') AS faculty_name,\n                       COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.department),''), 'Not Set') AS department_name,\n                       COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme),''), 'Not Set') AS programme_name\n                FROM students s\n                LEFT JOIN faculties f ON f.id = s.faculty_id\n                LEFT JOIN departments d ON d.id = s.department_id\n                LEFT JOIN programmes p ON p.id = s.programme_id\n                WHERE " . implode(' AND ', $where) . "\n                ORDER BY COALESCE(s.created_at, NOW()) DESC, s.id DESC";

        try {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $rows = [];
        }

        $yearFilter = trim((string)($filters['year'] ?? ''));
        $out = [];
        foreach ($rows as $row) {
            $row['academic_year'] = portalStudentAcademicYear($row);
            if ($yearFilter !== '' && $row['academic_year'] !== $yearFilter) {
                continue;
            }

            $row['session'] = portalSessionDisplayValue($pdo, (string)($row['session'] ?? ''), (string)($row['academic_year'] ?? ''));
            if (trim((string)($row['semester'] ?? '')) === '') {
                $row['semester'] = 'Not Set';
            }
            if (trim((string)($row['level'] ?? '')) === '') {
                $row['level'] = 'Not Set';
            }
            if (trim((string)($row['faculty_name'] ?? '')) === '') {
                $row['faculty_name'] = 'Not Set';
            }
            if (trim((string)($row['department_name'] ?? '')) === '') {
                $row['department_name'] = 'Not Set';
            }
            if (trim((string)($row['programme_name'] ?? '')) === '') {
                $row['programme_name'] = 'Not Set';
            }
            $row['student_name'] = full_name($row);
            $out[] = $row;
        }

        return $out;
    }
}

if (!function_exists('portalUniqueAcademicYears')) {
    function portalUniqueAcademicYears(array $rows): array {
        $years = [];
        foreach ($rows as $row) {
            $year = trim((string)($row['academic_year'] ?? portalStudentAcademicYear($row)));
            if ($year !== '') {
                $years[$year] = $year;
            }
        }
        $years = array_values($years);
        rsort($years, SORT_NATURAL);
        return $years;
    }
}

if (!function_exists('portalSortMetaRows')) {
    function portalSortMetaRows(array $rows, string $sortBy = 'created_at', string $sortDir = 'desc'): array {
        $allowed = [
            'student_name' => 'student_name',
            'faculty_name' => 'faculty_name',
            'department_name' => 'department_name',
            'programme_name' => 'programme_name',
            'session' => 'session',
            'semester' => 'semester',
            'level' => 'level',
            'academic_year' => 'academic_year',
            'application_no' => 'application_no',
            'admission_no' => 'admission_no',
            'created_at' => 'created_at',
        ];
        $key = $allowed[$sortBy] ?? 'created_at';
        $dir = strtolower($sortDir) === 'asc' ? 'asc' : 'desc';

        usort($rows, function(array $a, array $b) use ($key, $dir): int {
            $va = (string)($a[$key] ?? '');
            $vb = (string)($b[$key] ?? '');
            if ($key === 'created_at') {
                $va = (string)strtotime($va ?: '1970-01-01');
                $vb = (string)strtotime($vb ?: '1970-01-01');
            }
            $cmp = strnatcasecmp($va, $vb);
            return $dir === 'asc' ? $cmp : -$cmp;
        });

        return $rows;
    }
}

if (!function_exists('portalPaymentTotalsMap')) {
    function portalPaymentTotalsMap(PDO $pdo, array $studentIds): array {
        $studentIds = array_values(array_unique(array_filter(array_map('intval', $studentIds))));
        if (!$studentIds) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($studentIds), '?'));
        $sql = "SELECT student_id,\n                       SUM(CASE WHEN status IN ('Approved','Successful') THEN amount ELSE 0 END) AS approved_total,\n                       SUM(CASE WHEN status = 'Pending' THEN amount ELSE 0 END) AS pending_total,\n                       SUM(CASE WHEN status IN ('Approved','Successful') THEN 1 ELSE 0 END) AS approved_count,\n                       SUM(CASE WHEN status = 'Pending' THEN 1 ELSE 0 END) AS pending_count\n                FROM payment_transactions\n                WHERE student_id IN ($placeholders)\n                GROUP BY student_id";

        $map = [];
        try {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($studentIds);
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $map[(int)$row['student_id']] = [
                    'approved_total' => (float)($row['approved_total'] ?? 0),
                    'pending_total' => (float)($row['pending_total'] ?? 0),
                    'approved_count' => (int)($row['approved_count'] ?? 0),
                    'pending_count' => (int)($row['pending_count'] ?? 0),
                ];
            }
        } catch (Throwable $e) {}

        return $map;
    }
}

if (!function_exists('portalSummaryCountMap')) {
    function portalSummaryCountMap(array $rows, string $field): array {
        $summary = [];
        foreach ($rows as $row) {
            $label = trim((string)($row[$field] ?? ''));
            if ($label === '') {
                $label = 'Not Set';
            }
            $summary[$label] = ($summary[$label] ?? 0) + 1;
        }
        arsort($summary, SORT_NUMERIC);
        return $summary;
    }
}

if (!function_exists('portalCombinationSummaryRows')) {
    function portalCombinationSummaryRows(array $rows): array {
        $summary = [];
        foreach ($rows as $row) {
            $faculty = trim((string)($row['faculty_name'] ?? '')) ?: 'Not Set';
            $department = trim((string)($row['department_name'] ?? '')) ?: 'Not Set';
            $programme = trim((string)($row['programme_name'] ?? '')) ?: 'Not Set';
            $session = trim((string)($row['session'] ?? '')) ?: 'Not Set';
            $semester = trim((string)($row['semester'] ?? '')) ?: 'Not Set';
            $level = trim((string)($row['level'] ?? '')) ?: 'Not Set';
            $year = trim((string)($row['academic_year'] ?? '')) ?: 'Not Set';
            $key = implode('|', [$faculty, $department, $programme, $session, $semester, $level, $year]);
            if (!isset($summary[$key])) {
                $summary[$key] = [
                    'faculty_name' => $faculty,
                    'department_name' => $department,
                    'programme_name' => $programme,
                    'session' => $session,
                    'semester' => $semester,
                    'level' => $level,
                    'academic_year' => $year,
                    'total' => 0,
                ];
            }
            $summary[$key]['total']++;
        }

        $summary = array_values($summary);
        usort($summary, function(array $a, array $b): int {
            if ((int)$a['total'] === (int)$b['total']) {
                return strnatcasecmp(
                    implode(' ', [$a['faculty_name'], $a['department_name'], $a['programme_name'], $a['session'], $a['semester'], $a['level'], $a['academic_year']]),
                    implode(' ', [$b['faculty_name'], $b['department_name'], $b['programme_name'], $b['session'], $b['semester'], $b['level'], $b['academic_year']])
                );
            }
            return (int)$b['total'] <=> (int)$a['total'];
        });

        return $summary;
    }
}


if (!function_exists('ensureHrmModule')) {
    function ensureHrmModule(PDO $pdo): void {
        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_departments (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(120) NOT NULL UNIQUE,
            description TEXT NULL,
            is_active TINYINT(1) NOT NULL DEFAULT 1,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_units (
            id INT AUTO_INCREMENT PRIMARY KEY,
            department_id INT NULL,
            name VARCHAR(120) NOT NULL,
            description TEXT NULL,
            is_active TINYINT(1) NOT NULL DEFAULT 1,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_hr_unit (department_id, name)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_units ADD CONSTRAINT fk_hr_units_department FOREIGN KEY (department_id) REFERENCES hr_departments(id) ON DELETE SET NULL"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_designations (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(120) NOT NULL UNIQUE,
            description TEXT NULL,
            is_active TINYINT(1) NOT NULL DEFAULT 1,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_staff (
            id INT AUTO_INCREMENT PRIMARY KEY,
            admin_id INT NULL,
            staff_no VARCHAR(60) NULL UNIQUE,
            first_name VARCHAR(80) NOT NULL,
            last_name VARCHAR(80) NOT NULL,
            other_name VARCHAR(80) NULL,
            email VARCHAR(120) NULL,
            phone VARCHAR(30) NULL,
            gender VARCHAR(20) NULL,
            department_id INT NULL,
            unit_id INT NULL,
            designation_id INT NULL,
            employment_type VARCHAR(50) NOT NULL DEFAULT 'Full-Time',
            employment_status VARCHAR(40) NOT NULL DEFAULT 'Active',
            portal_access TINYINT(1) NOT NULL DEFAULT 1,
            date_hired DATE NULL,
            basic_salary DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            address TEXT NULL,
            notes TEXT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
            KEY idx_hr_staff_dept (department_id),
            KEY idx_hr_staff_unit (unit_id),
            KEY idx_hr_staff_designation (designation_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'hr_staff', 'portal_access')) { $pdo->exec("ALTER TABLE hr_staff ADD COLUMN portal_access TINYINT(1) NOT NULL DEFAULT 1 AFTER employment_status"); } } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_staff ADD CONSTRAINT fk_hr_staff_department FOREIGN KEY (department_id) REFERENCES hr_departments(id) ON DELETE SET NULL"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_staff ADD CONSTRAINT fk_hr_staff_unit FOREIGN KEY (unit_id) REFERENCES hr_units(id) ON DELETE SET NULL"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_staff ADD CONSTRAINT fk_hr_staff_designation FOREIGN KEY (designation_id) REFERENCES hr_designations(id) ON DELETE SET NULL"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_staff ADD CONSTRAINT fk_hr_staff_admin FOREIGN KEY (admin_id) REFERENCES admins(id) ON DELETE SET NULL"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_attendance (
            id INT AUTO_INCREMENT PRIMARY KEY,
            staff_id INT NOT NULL,
            attendance_date DATE NOT NULL,
            status ENUM('Present','Absent','Late','Excused') NOT NULL DEFAULT 'Present',
            check_in_time TIME NULL,
            check_out_time TIME NULL,
            remark TEXT NULL,
            recorded_by INT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_hr_att (staff_id, attendance_date),
            KEY idx_hr_att_date (attendance_date)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_attendance ADD CONSTRAINT fk_hr_att_staff FOREIGN KEY (staff_id) REFERENCES hr_staff(id) ON DELETE CASCADE"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_leave_requests (
            id INT AUTO_INCREMENT PRIMARY KEY,
            staff_id INT NOT NULL,
            leave_type VARCHAR(80) NOT NULL,
            start_date DATE NOT NULL,
            end_date DATE NOT NULL,
            days_requested INT NOT NULL DEFAULT 0,
            reason TEXT NULL,
            status ENUM('Pending','Approved','Rejected') NOT NULL DEFAULT 'Pending',
            approved_by INT NULL,
            approval_note TEXT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_hr_leave_status (status)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_leave_requests ADD CONSTRAINT fk_hr_leave_staff FOREIGN KEY (staff_id) REFERENCES hr_staff(id) ON DELETE CASCADE"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_payroll (
            id INT AUTO_INCREMENT PRIMARY KEY,
            staff_id INT NOT NULL,
            payroll_month TINYINT NOT NULL,
            payroll_year SMALLINT NOT NULL,
            basic_salary DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            allowance DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            deduction DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            net_salary DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            payment_status ENUM('Pending','Paid') NOT NULL DEFAULT 'Pending',
            note TEXT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_hr_payroll (staff_id, payroll_month, payroll_year),
            KEY idx_hr_payroll_period (payroll_year, payroll_month)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_payroll ADD CONSTRAINT fk_hr_payroll_staff FOREIGN KEY (staff_id) REFERENCES hr_staff(id) ON DELETE CASCADE"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_recruitment (
            id INT AUTO_INCREMENT PRIMARY KEY,
            job_title VARCHAR(140) NOT NULL,
            department_id INT NULL,
            employment_type VARCHAR(50) NOT NULL DEFAULT 'Full-Time',
            vacancy_count INT NOT NULL DEFAULT 1,
            applicant_name VARCHAR(140) NULL,
            applicant_email VARCHAR(120) NULL,
            stage VARCHAR(80) NULL,
            status ENUM('Open','Shortlisted','Interview','Offered','Closed','Rejected') NOT NULL DEFAULT 'Open',
            applied_at DATE NULL,
            note TEXT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_hr_recruitment_status (status)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_performance_reviews (
            id INT AUTO_INCREMENT PRIMARY KEY,
            staff_id INT NOT NULL,
            review_period VARCHAR(80) NOT NULL,
            reviewer_name VARCHAR(120) NULL,
            rating DECIMAL(4,2) NOT NULL DEFAULT 0.00,
            strengths TEXT NULL,
            weaknesses TEXT NULL,
            recommendations TEXT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_hr_perf_staff (staff_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_performance_reviews ADD CONSTRAINT fk_hr_perf_staff FOREIGN KEY (staff_id) REFERENCES hr_staff(id) ON DELETE CASCADE"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_documents (
            id INT AUTO_INCREMENT PRIMARY KEY,
            staff_id INT NOT NULL,
            document_title VARCHAR(160) NOT NULL,
            document_type VARCHAR(80) NULL,
            file_path VARCHAR(255) NULL,
            note TEXT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_hr_doc_staff (staff_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE hr_documents ADD CONSTRAINT fk_hr_doc_staff FOREIGN KEY (staff_id) REFERENCES hr_staff(id) ON DELETE CASCADE"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_notifications (
            id INT AUTO_INCREMENT PRIMARY KEY,
            target_role VARCHAR(80) NULL,
            title VARCHAR(160) NOT NULL,
            message TEXT NOT NULL,
            is_read TINYINT(1) NOT NULL DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}

        try { $pdo->exec("CREATE TABLE IF NOT EXISTS hr_settings (
            id INT AUTO_INCREMENT PRIMARY KEY,
            setting_key VARCHAR(120) NOT NULL UNIQUE,
            setting_value TEXT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); } catch (Throwable $e) {}

        foreach (['Administration','Academic Affairs','Finance','Works & Services'] as $name) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO hr_departments (name, description, is_active) VALUES (?, ?, 1)");
                $st->execute([$name, $name . ' Department']);
            } catch (Throwable $e) {}
        }
        foreach (['HR Manager','HR Officer','Accountant','Lecturer','Registrar','Support Staff'] as $name) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO hr_designations (name, description, is_active) VALUES (?, ?, 1)");
                $st->execute([$name, $name]);
            } catch (Throwable $e) {}
        }
        foreach (['company_name' => APP_NAME, 'default_currency' => 'NGN', 'attendance_default_status' => 'Present'] as $k => $v) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO hr_settings (setting_key, setting_value) VALUES (?, ?)");
                $st->execute([$k, $v]);
            } catch (Throwable $e) {}
        }
    }
}
if (!function_exists('hrmMenuPages')) {
    function hrmMenuPages(): array {
        return [
            'hrm_dashboard' => 'Dashboard',
            'hrm_staff_management' => 'Staff Management',
            'hrm_departments' => 'Departments',
            'hrm_units' => 'Units',
            'hrm_designations' => 'Designations',
            'hrm_attendance' => 'Attendance',
            'hrm_leave_management' => 'Leave Management',
            'hrm_payroll' => 'Payroll',
            'hrm_recruitment' => 'Recruitment',
            'hrm_performance_review' => 'Performance Review',
            'hrm_staff_documents' => 'Staff Documents',
            'hrm_reports' => 'HR Reports',
            'hrm_settings' => 'HR Settings',
            'hrm_notifications' => 'Notifications',
        ];
    }
}
if (!function_exists('renderHrmSubmenu')) {
    function renderHrmSubmenu(string $currentPage): void {
        echo '<div class="mb-3 d-flex flex-wrap gap-2">';
        foreach (hrmMenuPages() as $slug => $label) {
            $cls = $currentPage === $slug ? 'btn btn-primary btn-sm' : 'btn btn-outline-primary btn-sm';
            echo '<a class="' . $cls . '" href="?page=' . e($slug) . '">' . e($label) . '</a>';
        }
        echo '</div>';
    }
}
if (!function_exists('hrmSidebarHiddenPages')) {
    function hrmSidebarHiddenPages(): array {
        return [
            'hrm_staff_management',
            'hrm_departments',
            'hrm_units',
            'hrm_designations',
            'hrm_attendance',
            'hrm_leave_management',
            'hrm_payroll',
            'hrm_recruitment',
            'hrm_performance_review',
            'hrm_staff_documents',
            'hrm_reports',
            'hrm_settings',
            'hrm_notifications',
        ];
    }
}
if (!function_exists('hrmSidebarPageVisible')) {
    function hrmSidebarPageVisible(string $slug): bool {
        return !in_array($slug, hrmSidebarHiddenPages(), true);
    }
}
if (!function_exists('hrmCsvDownload')) {
    function hrmCsvDownload(string $filename, array $headers, array $rows): void {
        if (ob_get_length()) { @ob_end_clean(); }
        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        $out = fopen('php://output', 'w');
        fputcsv($out, $headers);
        foreach ($rows as $row) fputcsv($out, $row);
        fclose($out);
        exit;
    }
}

if (!function_exists('ensureStudentAttendanceModule')) {
    function ensureStudentAttendanceModule(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS student_attendance_settings (
                id INT AUTO_INCREMENT PRIMARY KEY,
                setting_key VARCHAR(120) NOT NULL UNIQUE,
                setting_value TEXT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        foreach ([
            'attendance_default_status' => 'Present',
            'attendance_allow_future_date' => '0',
            'attendance_show_student_portal' => '1'
        ] as $k => $v) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO student_attendance_settings (setting_key, setting_value) VALUES (?, ?)");
                $st->execute([$k, $v]);
            } catch (Throwable $e) {}
        }

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS student_attendance (
                id INT AUTO_INCREMENT PRIMARY KEY,
                student_id INT NOT NULL,
                registration_id INT NULL,
                course_id INT NOT NULL,
                attendance_date DATE NOT NULL,
                session_name VARCHAR(80) NOT NULL,
                semester VARCHAR(20) NOT NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                level VARCHAR(20) NULL,
                status ENUM('Present','Absent','Late','Excused') NOT NULL DEFAULT 'Present',
                remark TEXT NULL,
                lecturer_staff_id INT NULL,
                recorded_by INT NULL,
                venue VARCHAR(160) NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_student_attendance (student_id, course_id, attendance_date, session_name, semester),
                KEY idx_att_course_date (course_id, attendance_date),
                KEY idx_att_student (student_id),
                KEY idx_att_filters (faculty_id, department_id, programme_id, level, semester, session_name)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'registration_id')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN registration_id INT NULL AFTER student_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'course_id')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN course_id INT NOT NULL AFTER registration_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'attendance_date')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN attendance_date DATE NOT NULL AFTER course_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'session_name')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN session_name VARCHAR(80) NOT NULL DEFAULT '' AFTER attendance_date"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'semester')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN semester VARCHAR(20) NOT NULL DEFAULT 'First' AFTER session_name"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'faculty_id')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN faculty_id INT NULL AFTER semester"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'department_id')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN department_id INT NULL AFTER faculty_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'programme_id')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN programme_id INT NULL AFTER department_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'level')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN level VARCHAR(20) NULL AFTER programme_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'status')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'Present' AFTER level"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'remark')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN remark TEXT NULL AFTER status"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'lecturer_staff_id')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN lecturer_staff_id INT NULL AFTER remark"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'recorded_by')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN recorded_by INT NULL AFTER lecturer_staff_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance', 'venue')) { $pdo->exec("ALTER TABLE student_attendance ADD COLUMN venue VARCHAR(160) NULL AFTER recorded_by"); } } catch (Throwable $e) {}
        try { $pdo->exec("CREATE INDEX idx_student_attendance_course_date ON student_attendance(course_id, attendance_date)"); } catch (Throwable $e) {}
        try { $pdo->exec("CREATE INDEX idx_student_attendance_student ON student_attendance(student_id)"); } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS student_attendance_classes (
                id INT AUTO_INCREMENT PRIMARY KEY,
                course_id INT NOT NULL,
                attendance_date DATE NOT NULL,
                session_name VARCHAR(80) NOT NULL,
                semester VARCHAR(20) NOT NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                level VARCHAR(20) NULL,
                lecturer_staff_id INT NULL,
                venue VARCHAR(160) NULL,
                note TEXT NULL,
                created_by INT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_att_class (course_id, attendance_date, session_name, semester),
                KEY idx_att_class_filter (faculty_id, department_id, programme_id, level, semester, session_name)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'student_attendance_classes', 'venue')) { $pdo->exec("ALTER TABLE student_attendance_classes ADD COLUMN venue VARCHAR(160) NULL AFTER lecturer_staff_id"); } } catch (Throwable $e) {}

        try { $pdo->prepare("INSERT IGNORE INTO student_menu_toggles (menu_key, enabled) VALUES ('menu_student_attendance', 1)")->execute(); } catch (Throwable $e) {}
        foreach (['Admin','Dean','HOD','Lecturer','Course Adviser'] as $role) {
            try {
                $pdo->prepare("INSERT IGNORE INTO staff_permissions (role_name, perm_key, allowed) VALUES (?, 'attendance_manage', 1)")->execute([$role]);
            } catch (Throwable $e) {}
            try {
                $pdo->prepare("INSERT IGNORE INTO staff_menu_toggles (role_name, menu_key, enabled) VALUES (?, 'menu_student_attendance_admin', 1)")->execute([$role]);
            } catch (Throwable $e) {}
        }
    }
}
if (!function_exists('studentAttendanceSetting')) {
    function studentAttendanceSetting(PDO $pdo, string $key, string $default = ''): string {
        try {
            $st = $pdo->prepare("SELECT setting_value FROM student_attendance_settings WHERE setting_key = ? LIMIT 1");
            $st->execute([$key]);
            $v = $st->fetchColumn();
            return ($v !== false && $v !== null && $v !== '') ? (string)$v : $default;
        } catch (Throwable $e) {
            return $default;
        }
    }
}
if (!function_exists('studentAttendanceSubmenuTabs')) {
    function studentAttendanceSubmenuTabs(): array {
        return [
            'take' => 'Take Attendance',
            'view' => 'View Attendance',
            'edit' => 'Edit Attendance',
            'reports' => 'Attendance Reports',
            'settings' => 'Attendance Settings',
        ];
    }
}
if (!function_exists('renderStudentAttendanceSubmenu')) {
    function renderStudentAttendanceSubmenu(string $activeTab): void {
        echo '<div class="mb-3 d-flex flex-wrap gap-2">';
        foreach (studentAttendanceSubmenuTabs() as $slug => $label) {
            $cls = $activeTab === $slug ? 'btn btn-primary btn-sm' : 'btn btn-outline-primary btn-sm';
            echo '<a class="' . $cls . '" href="?page=admin_student_attendance&tab=' . e($slug) . '">' . e($label) . '</a>';
        }
        echo '</div>';
    }
}
if (!function_exists('studentAttendanceExportRows')) {
    function studentAttendanceExportRows(array $rows): array {
        $out = [];
        foreach ($rows as $r) {
            $out[] = [
                (string)($r['attendance_date'] ?? ''),
                (string)($r['session_name'] ?? ''),
                (string)($r['semester'] ?? ''),
                (string)($r['faculty_name'] ?? ''),
                (string)($r['department_name'] ?? ''),
                (string)($r['programme_name'] ?? ''),
                (string)($r['level'] ?? ''),
                (string)($r['course_code'] ?? ''),
                (string)($r['course_title'] ?? ''),
                (string)($r['matric_no'] ?? $r['registration_no'] ?? ''),
                (string)($r['student_name'] ?? ''),
                (string)($r['status'] ?? ''),
                (string)($r['remark'] ?? ''),
                (string)($r['venue'] ?? ''),
            ];
        }
        return $out;
    }
}
if (!function_exists('studentAttendanceExcelDownload')) {
    function studentAttendanceExcelDownload(string $filename, array $headers, array $rows): void {
        if (ob_get_length()) { @ob_end_clean(); }
        header('Content-Type: application/vnd.ms-excel; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        echo implode("	", $headers) . "
";
        foreach ($rows as $row) {
            $line = [];
            foreach ($row as $cell) {
                $line[] = str_replace(["	", "
", "
"], ' ', (string)$cell);
            }
            echo implode("	", $line) . "
";
        }
        exit;
    }
}
if (!function_exists('studentAttendanceJoinedRows')) {
    function studentAttendanceJoinedRows(PDO $pdo, array $filters = []): array {
        $where = ["1=1"];
        $params = [];
        $map = [
            'faculty_id' => 'sa.faculty_id',
            'department_id' => 'sa.department_id',
            'programme_id' => 'sa.programme_id',
            'level' => 'sa.level',
            'semester' => 'sa.semester',
            'session_name' => 'sa.session_name',
            'course_id' => 'sa.course_id',
            'student_id' => 'sa.student_id',
            'attendance_date' => 'sa.attendance_date',
            'status' => 'sa.status'
        ];
        foreach ($map as $k => $col) {
            if (isset($filters[$k]) && $filters[$k] !== '' && $filters[$k] !== null && $filters[$k] !== 0 && $filters[$k] !== '0') {
                $where[] = "$col = ?";
                $params[] = $filters[$k];
            }
        }
        if (!empty($filters['date_from'])) { $where[] = "sa.attendance_date >= ?"; $params[] = $filters['date_from']; }
        if (!empty($filters['date_to'])) { $where[] = "sa.attendance_date <= ?"; $params[] = $filters['date_to']; }
        if (!empty($filters['search'])) {
            $where[] = "(s.surname LIKE ? OR s.first_name LIKE ? OR s.other_name LIKE ? OR s.matric_no LIKE ? OR s.registration_no LIKE ? OR rc.course_code LIKE ? OR rc.course_title LIKE ?)";
            $like = '%' . $filters['search'] . '%';
            $params += [$like,$like,$like,$like,$like,$like,$like];
        }
        $sql = "SELECT sa.*, 
                    CONCAT(TRIM(COALESCE(s.surname,'')), CASE WHEN COALESCE(s.first_name,'')<>'' THEN CONCAT(', ', TRIM(s.first_name)) ELSE '' END, CASE WHEN COALESCE(s.other_name,'')<>'' THEN CONCAT(' ', TRIM(s.other_name)) ELSE '' END) AS student_name,
                    s.matric_no, s.registration_no,
                    rc.course_code, rc.course_title,
                    f.name AS faculty_name, d.name AS department_name, p.name AS programme_name
                FROM student_attendance sa
                LEFT JOIN students s ON s.id = sa.student_id
                LEFT JOIN registrable_courses rc ON rc.id = sa.course_id
                LEFT JOIN faculties f ON f.id = sa.faculty_id
                LEFT JOIN departments d ON d.id = sa.department_id
                LEFT JOIN programmes p ON p.id = sa.programme_id
                WHERE " . implode(' AND ', $where) . "
                ORDER BY sa.attendance_date DESC, student_name ASC";
        try {
            $st = $pdo->prepare($sql);
            $st->execute($params);
            return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}
if (isset($pdo) && ($pdo instanceof PDO)) { try { ensureStudentAttendanceModule($pdo); } catch (Throwable $e) { /* ignore */ } }



if (!function_exists('portalEnsureWalletTransactionMetaSupport')) {
    function portalEnsureWalletTransactionMetaSupport(PDO $pdo): void {
        try {
            if (portalTableExistsSafe($pdo, 'wallet_transactions')) {
                $cols = [
                    'balance_before' => "ALTER TABLE wallet_transactions ADD COLUMN balance_before DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER narration",
                    'balance_after' => "ALTER TABLE wallet_transactions ADD COLUMN balance_after DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER balance_before",
                    'related_payment_reference' => "ALTER TABLE wallet_transactions ADD COLUMN related_payment_reference VARCHAR(120) NULL AFTER related_transaction_id",
                    'notification_context' => "ALTER TABLE wallet_transactions ADD COLUMN notification_context VARCHAR(120) NULL AFTER related_payment_reference",
                    'notification_sent_at' => "ALTER TABLE wallet_transactions ADD COLUMN notification_sent_at DATETIME NULL AFTER notification_context",
                ];
                foreach ($cols as $column => $sql) {
                    try { if (!portalColumnExistsSafe($pdo, 'wallet_transactions', $column)) { $pdo->exec($sql); } } catch (Throwable $e) {}
                }
                try { if (!portalIndexExistsSafe($pdo, 'wallet_transactions', 'idx_wallet_related_payment_reference')) { $pdo->exec("CREATE INDEX idx_wallet_related_payment_reference ON wallet_transactions(related_payment_reference)"); } } catch (Throwable $e) {}
                try { if (!portalIndexExistsSafe($pdo, 'wallet_transactions', 'idx_wallet_notification_sent_at')) { $pdo->exec("CREATE INDEX idx_wallet_notification_sent_at ON wallet_transactions(notification_sent_at)"); } } catch (Throwable $e) {}
            }
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalInsertWalletTransaction')) {
    function portalInsertWalletTransaction(PDO $pdo, int $studentId, string $type, float $amount, string $narration, ?string $relatedTxId = null, string $relatedPaymentReference = '', ?float $balanceBefore = null, ?float $balanceAfter = null): int {
        portalEnsureWalletTransactionMetaSupport($pdo);
        $stmt = $pdo->prepare("INSERT INTO wallet_transactions (student_id, type, amount, narration, balance_before, balance_after, related_transaction_id, related_payment_reference) VALUES (?,?,?,?,?,?,?,?)");
        $stmt->execute([
            $studentId,
            $type,
            $amount,
            $narration,
            (float)($balanceBefore ?? 0),
            (float)($balanceAfter ?? 0),
            $relatedTxId,
            ($relatedPaymentReference !== '' ? $relatedPaymentReference : null),
        ]);
        return (int)$pdo->lastInsertId();
    }
}

if (!function_exists('portalWalletNotificationSubject')) {
    function portalWalletNotificationSubject(string $type): string {
        return strtolower(trim($type)) === 'debit' ? 'Wallet Debited' : 'Wallet Credited';
    }
}

if (!function_exists('portalWalletNotificationContext')) {
    function portalWalletNotificationContext(array $walletTx): string {
        $type = strtolower(trim((string)($walletTx['type'] ?? 'credit')));
        $id = (int)($walletTx['id'] ?? 0);
        return 'wallet_' . ($type === 'debit' ? 'debit' : 'credit') . ':' . $id;
    }
}

if (!function_exists('portalSendWalletTransactionNotificationById')) {
    function portalSendWalletTransactionNotificationById(PDO $pdo, int $walletTransactionId): void {
        if ($walletTransactionId <= 0) return;
        portalEnsureWalletTransactionMetaSupport($pdo);
        try {
            $stmt = $pdo->prepare("SELECT wt.*, s.email, s.phone, s.surname, s.first_name, s.other_name
                FROM wallet_transactions wt
                JOIN students s ON s.id = wt.student_id
                WHERE wt.id = ?
                LIMIT 1");
            $stmt->execute([$walletTransactionId]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$row) return;
            if (!empty($row['notification_sent_at'])) return;

            $studentId = (int)($row['student_id'] ?? 0);
            $type = strtolower(trim((string)($row['type'] ?? 'credit')));
            $title = portalWalletNotificationSubject($type);
            $studentName = htmlspecialchars(studentFullName($row), ENT_QUOTES, 'UTF-8');
            $amount = (float)($row['amount'] ?? 0);
            $before = (float)($row['balance_before'] ?? 0);
            $after = (float)($row['balance_after'] ?? 0);
            $relatedReference = trim((string)($row['related_payment_reference'] ?? $row['related_transaction_id'] ?? ''));
            $context = portalWalletNotificationContext($row);
            $verb = ($type === 'debit') ? 'debited' : 'credited';
            $narration = trim((string)($row['narration'] ?? ''));
            $dateTime = trim((string)($row['created_at'] ?? date('Y-m-d H:i:s')));
            $bodyText = "Your wallet was {$verb} with ₦" . number_format($amount, 2)
                . ". Previous balance: ₦" . number_format($before, 2)
                . ". New balance: ₦" . number_format($after, 2)
                . ". Total balance remaining: ₦" . number_format($after, 2)
                . ($narration !== '' ? ". Narration: {$narration}" : '')
                . ($relatedReference !== '' ? ". Reference: {$relatedReference}" : '')
                . ". Date/Time: {$dateTime}.";

            createNotification($pdo, 'student', $studentId, $title, $bodyText, '?page=wallet');

            $email = trim((string)($row['email'] ?? ''));
            $emailBody = portalWrapNotificationEmail($pdo, $title,
                '<p>Dear ' . $studentName . ',</p>'
                . '<p>' . htmlspecialchars($bodyText, ENT_QUOTES, 'UTF-8') . '</p>'
                . '<ul>'
                . '<li><strong>Transaction Type:</strong> ' . htmlspecialchars($type === 'debit' ? 'Debited' : 'Credited', ENT_QUOTES, 'UTF-8') . '</li>'
                . '<li><strong>Amount:</strong> ₦' . number_format($amount, 2) . '</li>'
                . '<li><strong>Previous Balance:</strong> ₦' . number_format($before, 2) . '</li>'
                . '<li><strong>New Balance:</strong> ₦' . number_format($after, 2) . '</li>'
                . '<li><strong>Total Balance Remaining:</strong> ₦' . number_format($after, 2) . '</li>'
                . ($narration !== '' ? '<li><strong>Narration:</strong> ' . htmlspecialchars($narration, ENT_QUOTES, 'UTF-8') . '</li>' : '')
                . ($relatedReference !== '' ? '<li><strong>Related Reference:</strong> ' . htmlspecialchars($relatedReference, ENT_QUOTES, 'UTF-8') . '</li>' : '')
                . '<li><strong>Date/Time:</strong> ' . htmlspecialchars($dateTime, ENT_QUOTES, 'UTF-8') . '</li>'
                . '</ul>'
            );
            sendLoggedEmail($pdo, 'student', $studentId, $email, $context, $title . ' - ' . portalNotificationMailSubjectPrefix($pdo), $emailBody);

            $pdo->prepare("UPDATE wallet_transactions SET notification_context = ?, notification_sent_at = NOW() WHERE id = ? AND notification_sent_at IS NULL")
                ->execute([$context, $walletTransactionId]);
        } catch (Throwable $e) {}
    }
}

// -------------------------
// WALLET HELPERS
// -------------------------
if (!function_exists('ensureWallet')) {
    function ensureWallet(PDO $pdo, int $studentId): void {
        if ($studentId <= 0) return;
        try {
            $st = $pdo->prepare("SELECT id FROM wallets WHERE student_id = ? LIMIT 1");
            $st->execute([$studentId]);
            if (!$st->fetchColumn()) {
                $ins = $pdo->prepare("INSERT INTO wallets (student_id, balance) VALUES (?, 0.00)");
                $ins->execute([$studentId]);
            }
        } catch (Throwable $e) { /* ignore */ }
    }
}
if (!function_exists('getWalletBalance')) {
    function getWalletBalance(PDO $pdo, int $studentId): float {
        ensureWallet($pdo, $studentId);
        try {
            $st = $pdo->prepare("SELECT balance FROM wallets WHERE student_id = ? LIMIT 1");
            $st->execute([$studentId]);
            $b = $st->fetchColumn();
            return (float)($b ?? 0);
        } catch (Throwable $e) {
            return 0.0;
        }
    }
}
if (!function_exists('creditWallet')) {
    function creditWallet(PDO $pdo, int $studentId, float $amount, string $narration, ?string $relatedTxId = null): void {
        if ($amount <= 0) return;
        ensureWallet($pdo, $studentId);
        $walletTxId = 0;
        $pdo->beginTransaction();
        try {
            $before = getWalletBalance($pdo, $studentId);
            $after = $before + $amount;
            $pdo->prepare("UPDATE wallets SET balance = balance + ? WHERE student_id = ?")->execute([$amount, $studentId]);
            $walletTxId = portalInsertWalletTransaction($pdo, $studentId, 'credit', $amount, $narration, $relatedTxId, $relatedTxId !== null ? (string)$relatedTxId : '', $before, $after);
            $pdo->commit();
        } catch (Throwable $e) {
            $pdo->rollBack();
            throw $e;
        }
        if ($walletTxId > 0) {
            try { portalSendWalletTransactionNotificationById($pdo, $walletTxId); } catch (Throwable $e) {}
        }
    }
}
if (!function_exists('debitWallet')) {
    function debitWallet(PDO $pdo, int $studentId, float $amount, string $narration, ?string $relatedTxId = null): void {
        if ($amount <= 0) return;
        ensureWallet($pdo, $studentId);
        $walletTxId = 0;
        $pdo->beginTransaction();
        try {
            // Block wallet usage if disabled/suspended/blocked (best-effort; older DBs may not have these columns)
            try {
                $ws = $pdo->prepare("SELECT status, is_enabled FROM wallets WHERE student_id = ? LIMIT 1");
                $ws->execute([$studentId]);
                $meta = $ws->fetch(PDO::FETCH_ASSOC) ?: [];
                $st = (string)($meta['status'] ?? 'Active');
                $en = (int)($meta['is_enabled'] ?? 1);
                if ($en !== 1) { throw new RuntimeException('Wallet is disabled for your account.'); }
                if ($st === 'Suspended') { throw new RuntimeException('Wallet is suspended. Please contact admin.'); }
                if ($st === 'Blocked') { throw new RuntimeException('Wallet is blocked. Please contact admin.'); }
            } catch (PDOException $e) { /* ignore missing columns */ }

            $bal = getWalletBalance($pdo, $studentId);
            if ($bal + 0.00001 < $amount) {
                throw new RuntimeException('Insufficient wallet balance.');
            }
            $after = $bal - $amount;
            $pdo->prepare("UPDATE wallets SET balance = balance - ? WHERE student_id = ?")->execute([$amount, $studentId]);
            $walletTxId = portalInsertWalletTransaction($pdo, $studentId, 'debit', $amount, $narration, $relatedTxId, $relatedTxId !== null ? (string)$relatedTxId : '', $bal, $after);
            $pdo->commit();
        } catch (Throwable $e) {
            $pdo->rollBack();
            throw $e;
        }
        if ($walletTxId > 0) {
            try { portalSendWalletTransactionNotificationById($pdo, $walletTxId); } catch (Throwable $e) {}
        }
    }
}

if (!defined('APP_NAME')) define('APP_NAME', 'Admission Portal');
if (!defined('APP_VERSION')) define('APP_VERSION', '1.0');

// Base URL for production (set to your live domain + folder)
// IMPORTANT: Update this if you move the portal to a different path.
if (!defined('APP_BASE_URL')) define('APP_BASE_URL', 'https://honttech.com.ng/portal');

// =====================================================
// PAYMENT FEES (PAYER BEARS CHARGES)
// =====================================================
// Some gateways deduct transaction fees from the merchant settlement.
// To let the payer bear charges, we add an estimated gateway fee on top
// of the base amount shown for the item being paid. These fee rules are
// configurable in Admin Settings.
//
// NOTE: Defaults are only estimates. Always confirm the current fee rules
// with your payment provider and adjust the settings as needed.
if (!function_exists('calcGatewayFee')) {
    function calcGatewayFee(PDO $pdo, string $gateway, float $baseAmount): float {
        $gateway = strtolower(trim($gateway));
        if ($baseAmount <= 0) return 0.0;

        $payerBears = getSystemSettingValue($pdo, 'payer_bears_charges', 'enabled');
        if ($payerBears !== 'enabled') return 0.0;

        // per-gateway settings
        $pKey = $gateway . '_charge_percent';
        $fKey = $gateway . '_charge_flat';
        $cKey = $gateway . '_charge_cap';
        $wKey = $gateway . '_charge_flat_waive_below';

        $percent = (float)getSystemSettingValue($pdo, $pKey, '0');
        $flat = (float)getSystemSettingValue($pdo, $fKey, '0');
        $cap = (float)getSystemSettingValue($pdo, $cKey, '0');
        $waiveBelow = (float)getSystemSettingValue($pdo, $wKey, '0');

        // Optionally waive the flat fee for small transactions (common on some gateways)
        if ($waiveBelow > 0 && $baseAmount < $waiveBelow) {
            $flat = 0.0;
        }

        $fee = ($percent > 0 ? ($baseAmount * ($percent / 100.0)) : 0.0) + $flat;
        if ($cap > 0) $fee = min($fee, $cap);

        // round to 2dp
        $fee = round(max(0.0, $fee), 2);
        return $fee;
    }
}

// Database configuration
if (!defined('DB_HOST')) define('DB_HOST', (string)honttechAppEnv('DB_HOST', 'localhost'));
if (!defined('DB_USER')) define('DB_USER', (string)honttechAppEnv('DB_USER', 'root'));
if (!defined('DB_PASS')) define('DB_PASS', (string)honttechAppEnv('DB_PASS', ''));
if (!defined('DB_NAME')) define('DB_NAME', (string)honttechAppEnv('DB_NAME', 'admission_portal'));
if (!defined('DB_PORT')) define('DB_PORT', (string)honttechAppEnv('DB_PORT', ''));
if (!defined('DB_SOCKET')) define('DB_SOCKET', (string)honttechAppEnv('DB_SOCKET', ''));

// Paystack configuration. Never hardcode live payment keys in a downloadable PHP file.
if (!defined('PAYSTACK_SECRET_KEY')) define('PAYSTACK_SECRET_KEY', (string)honttechAppEnv('PAYSTACK_SECRET_KEY', ''));
if (!defined('PAYSTACK_PUBLIC_KEY')) define('PAYSTACK_PUBLIC_KEY', (string)honttechAppEnv('PAYSTACK_PUBLIC_KEY', ''));


// Other payment gateways (optional)
// Flutterwave
if (!defined('FLUTTERWAVE_PUBLIC_KEY')) define('FLUTTERWAVE_PUBLIC_KEY', '');  // set in Settings
if (!defined('FLUTTERWAVE_SECRET_KEY')) define('FLUTTERWAVE_SECRET_KEY', '');  // set in Settings
// Monnify
if (!defined('MONNIFY_API_KEY')) define('MONNIFY_API_KEY', '');
if (!defined('MONNIFY_SECRET_KEY')) define('MONNIFY_SECRET_KEY', '');
if (!defined('MONNIFY_CONTRACT_CODE')) define('MONNIFY_CONTRACT_CODE', '');
// Remita
if (!defined('REMITA_MERCHANT_ID')) define('REMITA_MERCHANT_ID', '');
if (!defined('REMITA_SERVICE_TYPE_ID')) define('REMITA_SERVICE_TYPE_ID', '');
if (!defined('REMITA_API_KEY')) define('REMITA_API_KEY', '');
// PayPal
if (!defined('PAYPAL_CLIENT_ID')) define('PAYPAL_CLIENT_ID', '');
if (!defined('PAYPAL_CLIENT_SECRET')) define('PAYPAL_CLIENT_SECRET', '');

// Email configuration. Keep usernames/passwords outside the source code on production servers.
if (!defined('SMTP_HOST')) define('SMTP_HOST', (string)honttechAppEnv('SMTP_HOST', 'smtp.gmail.com'));
if (!defined('SMTP_PORT')) define('SMTP_PORT', (int)honttechAppEnv('SMTP_PORT', 587));
if (!defined('SMTP_USERNAME')) define('SMTP_USERNAME', (string)honttechAppEnv('SMTP_USERNAME', ''));
if (!defined('SMTP_PASSWORD')) define('SMTP_PASSWORD', (string)honttechAppEnv('SMTP_PASSWORD', ''));
if (!defined('FROM_EMAIL')) define('FROM_EMAIL', (string)honttechAppEnv('FROM_EMAIL', SMTP_USERNAME));
if (!defined('FROM_NAME')) define('FROM_NAME', (string)honttechAppEnv('FROM_NAME', 'HontTech Registration - HontTech Technologies'));
if (!defined('REPLY_TO_EMAIL')) define('REPLY_TO_EMAIL', (string)honttechAppEnv('REPLY_TO_EMAIL', ''));
// File upload configuration
define('UPLOAD_DIR', 'uploads/');
define('MAX_FILE_SIZE', 5242880); // 5MB
define('ALLOWED_FILE_TYPES', ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx']);

// Robust database bootstrap helpers. These keep legacy/shared-hosting databases from breaking the whole portal during self-healing upgrades.
if (!function_exists('honttechQuoteDbIdentifier')) {
    function honttechQuoteDbIdentifier(string $identifier): string {
        $identifier = trim($identifier);
        if ($identifier === '') {
            throw new RuntimeException('Database name cannot be empty.');
        }
        return '`' . str_replace('`', '``', $identifier) . '`';
    }
}

if (!function_exists('honttechPdoOptions')) {
    function honttechPdoOptions(): array {
        return [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ];
    }
}

if (!function_exists('honttechMysqlDsn')) {
    function honttechMysqlDsn(bool $withDatabase = true): string {
        $host = trim((string)DB_HOST);
        $port = trim((string)(defined('DB_PORT') ? DB_PORT : honttechAppEnv('DB_PORT', '')));
        $socket = trim((string)(defined('DB_SOCKET') ? DB_SOCKET : honttechAppEnv('DB_SOCKET', '')));
        $dbName = trim((string)DB_NAME);

        if ($socket !== '') {
            $dsn = 'mysql:unix_socket=' . $socket;
        } else {
            // Accept DB_HOST values like "127.0.0.1:3307" while remaining compatible with normal "localhost".
            if ($port === '' && preg_match('/^(.+):(\d+)$/', $host, $m)) {
                $host = $m[1];
                $port = $m[2];
            }
            $dsn = 'mysql:host=' . ($host !== '' ? $host : 'localhost');
            if ($port !== '') {
                $dsn .= ';port=' . $port;
            }
        }
        if ($withDatabase && $dbName !== '') {
            $dsn .= ';dbname=' . $dbName;
        }
        $dsn .= ';charset=utf8mb4';
        return $dsn;
    }
}

if (!function_exists('honttechConnectDatabase')) {
    function honttechConnectDatabase(): PDO {
        try {
            return new PDO(honttechMysqlDsn(true), DB_USER, DB_PASS, honttechPdoOptions());
        } catch (PDOException $e) {
            $message = $e->getMessage();
            $code = (int)($e->errorInfo[1] ?? 0);
            $unknownDatabase = $code === 1049 || stripos($message, 'Unknown database') !== false;
            if (!$unknownDatabase) {
                throw $e;
            }

            // Self-create the configured database on XAMPP/shared hosting when the MySQL user has permission.
            $serverPdo = new PDO(honttechMysqlDsn(false), DB_USER, DB_PASS, honttechPdoOptions());
            $serverPdo->exec('CREATE DATABASE IF NOT EXISTS ' . honttechQuoteDbIdentifier((string)DB_NAME) . ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
            return new PDO(honttechMysqlDsn(true), DB_USER, DB_PASS, honttechPdoOptions());
        }
    }
}

if (!function_exists('honttechEnsureSystemSettingsTable')) {
    function honttechEnsureSystemSettingsTable(PDO $pdo): void {
        // Build this table in small steps so a half-created/legacy table cannot break bootstrapping.
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS system_settings (
                id INT AUTO_INCREMENT PRIMARY KEY,
                setting_key VARCHAR(190) NULL,
                setting_value LONGTEXT NULL,
                description TEXT NULL,
                created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {
            error_log('System settings base table creation failed: ' . $e->getMessage());
            // Retry with MyISAM for older local servers that have InnoDB disabled/misconfigured.
            try {
                $pdo->exec("CREATE TABLE IF NOT EXISTS system_settings (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    setting_key VARCHAR(190) NULL,
                    setting_value LONGTEXT NULL,
                    description TEXT NULL,
                    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
                ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4");
            } catch (Throwable $retry) {
                error_log('System settings fallback table creation failed: ' . $retry->getMessage());
                return;
            }
        }

        if (function_exists('honttechBootstrapColumnExists') && !honttechBootstrapColumnExists($pdo, 'system_settings', 'id')) {
            try {
                $pdo->exec("ALTER TABLE system_settings ADD COLUMN id INT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST");
            } catch (Throwable $e) {
                try { $pdo->exec("ALTER TABLE system_settings ADD COLUMN id INT NULL FIRST"); } catch (Throwable $ignored) {}
            }
        }

        $columns = [
            'setting_key' => ['definition' => "VARCHAR(190) NULL", 'after' => 'id'],
            'setting_value' => ['definition' => "LONGTEXT NULL", 'after' => 'setting_key'],
            'description' => ['definition' => "TEXT NULL", 'after' => 'setting_value'],
            'created_at' => ['definition' => "TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP", 'after' => 'description'],
            'updated_at' => ['definition' => "TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", 'after' => 'created_at'],
        ];
        foreach ($columns as $column => $meta) {
            try {
                $exists = function_exists('honttechBootstrapColumnExists')
                    ? honttechBootstrapColumnExists($pdo, 'system_settings', $column)
                    : (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'system_settings', $column));
                if (!$exists) {
                    if (function_exists('honttechSafeAddColumn')) {
                        honttechSafeAddColumn($pdo, 'system_settings', $column, (string)$meta['definition'], (string)$meta['after']);
                    } else {
                        $pdo->exec('ALTER TABLE system_settings ADD COLUMN `' . str_replace('`', '``', $column) . '` ' . $meta['definition']);
                    }
                }
            } catch (Throwable $e) {
                error_log('System settings column repair failed for ' . $column . ': ' . $e->getMessage());
            }
        }
        try { if (function_exists('honttechSafeAddIndex')) { honttechSafeAddIndex($pdo, 'system_settings', 'idx_system_settings_key', ['setting_key'], false); } } catch (Throwable $e) {}
        try { if (function_exists('honttechSafeAddIndex')) { honttechSafeAddIndex($pdo, 'system_settings', 'uniq_system_settings_key', ['setting_key'], true); } } catch (Throwable $e) {}
    }
}

if (!function_exists('honttechSafeAddColumn')) {
    function honttechSafeAddColumn(PDO $pdo, string $table, string $column, string $definition, string $afterColumn = ''): bool {
        $table = trim($table);
        $column = trim($column);
        $definition = trim($definition);
        $afterColumn = trim($afterColumn);
        if ($table === '' || $column === '' || $definition === '') {
            return false;
        }
        $tableExists = function_exists('honttechBootstrapTableExists') ? honttechBootstrapTableExists($pdo, $table) : (function_exists('portalTableExistsSafe') && portalTableExistsSafe($pdo, $table));
        if (!$tableExists) {
            return false;
        }
        $columnExists = function_exists('honttechBootstrapColumnExists') ? honttechBootstrapColumnExists($pdo, $table, $column) : (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, $table, $column));
        if ($columnExists) {
            return true;
        }
        $tableSql = '`' . str_replace('`', '``', $table) . '`';
        $columnSql = '`' . str_replace('`', '``', $column) . '`';
        $afterSql = '';
        if ($afterColumn !== '') {
            $afterExists = function_exists('honttechBootstrapColumnExists') ? honttechBootstrapColumnExists($pdo, $table, $afterColumn) : (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, $table, $afterColumn));
            if ($afterExists) {
                $afterSql = ' AFTER `' . str_replace('`', '``', $afterColumn) . '`';
            }
        }
        try {
            $pdo->exec("ALTER TABLE {$tableSql} ADD COLUMN {$columnSql} {$definition}{$afterSql}");
            return true;
        } catch (Throwable $e) {
            // Retry without AFTER because older installations may not have the requested anchor column.
            if ($afterSql !== '') {
                try {
                    $pdo->exec("ALTER TABLE {$tableSql} ADD COLUMN {$columnSql} {$definition}");
                    return true;
                } catch (Throwable $retry) {
                    error_log('Safe add column failed for ' . $table . '.' . $column . ': ' . $retry->getMessage());
                    return false;
                }
            }
            error_log('Safe add column failed for ' . $table . '.' . $column . ': ' . $e->getMessage());
            return false;
        }
    }
}

if (!function_exists('honttechSafeAddIndex')) {
    function honttechSafeAddIndex(PDO $pdo, string $table, string $indexName, array $columns, bool $unique = false): bool {
        $table = trim($table);
        $indexName = trim($indexName);
        $columns = array_values(array_filter(array_map('trim', $columns), static fn($v) => $v !== ''));
        if ($table === '' || $indexName === '' || !$columns) return false;
        $tableExists = function_exists('honttechBootstrapTableExists') ? honttechBootstrapTableExists($pdo, $table) : (function_exists('portalTableExistsSafe') && portalTableExistsSafe($pdo, $table));
        if (!$tableExists) return false;
        try {
            if (function_exists('portalIndexExistsSafe') && portalIndexExistsSafe($pdo, $table, $indexName)) return true;
            $check = $pdo->prepare('SHOW INDEX FROM `' . str_replace('`', '``', $table) . '` WHERE Key_name = ?');
            $check->execute([$indexName]);
            if ($check->fetch(PDO::FETCH_ASSOC)) return true;
        } catch (Throwable $e) {}
        foreach ($columns as $column) {
            $columnExists = function_exists('honttechBootstrapColumnExists') ? honttechBootstrapColumnExists($pdo, $table, $column) : (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, $table, $column));
            if (!$columnExists) return false;
        }
        $tableSql = '`' . str_replace('`', '``', $table) . '`';
        $indexSql = '`' . str_replace('`', '``', $indexName) . '`';
        $columnsSql = implode(',', array_map(static fn($col) => '`' . str_replace('`', '``', $col) . '`', $columns));
        try {
            $pdo->exec('CREATE ' . ($unique ? 'UNIQUE ' : '') . "INDEX {$indexSql} ON {$tableSql} ({$columnsSql})");
            return true;
        } catch (Throwable $e) {
            // Duplicate key/index or missing INDEX privilege should never stop boot.
            error_log('Safe add index skipped for ' . $table . '.' . $indexName . ': ' . $e->getMessage());
            return false;
        }
    }
}

if (!function_exists('honttechDatabaseHasCoreTables')) {
    function honttechDatabaseHasCoreTables(PDO $pdo): bool {
        // Keep this check intentionally small. Full column/index repair is handled elsewhere and may be
        // limited by shared-hosting permissions. The portal should not die simply because a legacy table
        // still needs ALTER/INDEX repair; those operations are retried best-effort and logged.
        try {
            foreach (['system_settings', 'admins', 'students'] as $table) {
                $tableExists = function_exists('honttechBootstrapTableExists')
                    ? honttechBootstrapTableExists($pdo, $table)
                    : (function_exists('portalTableExistsSafe') && portalTableExistsSafe($pdo, $table));
                if (!$tableExists) {
                    return false;
                }
                $quotedTable = '`' . str_replace('`', '``', $table) . '`';
                $pdo->query("SELECT 1 FROM {$quotedTable} LIMIT 1");
            }
            return true;
        } catch (Throwable $e) {
            error_log('Core table read verification failed: ' . $e->getMessage());
            return false;
        }
    }
}

if (!function_exists('honttechBootstrapTableExists')) {
    function honttechBootstrapTableExists(PDO $pdo, string $table): bool {
        try {
            $stmt = $pdo->prepare('SHOW TABLES LIKE ?');
            $stmt->execute([$table]);
            return (bool)$stmt->fetchColumn();
        } catch (Throwable $e) {
            error_log('Bootstrap table check failed for ' . $table . ': ' . $e->getMessage());
            return false;
        }
    }
}

if (!function_exists('honttechBootstrapColumnExists')) {
    function honttechBootstrapColumnExists(PDO $pdo, string $table, string $column): bool {
        try {
            $stmt = $pdo->prepare('SHOW COLUMNS FROM `' . str_replace('`', '``', $table) . '` LIKE ?');
            $stmt->execute([$column]);
            return (bool)$stmt->fetch(PDO::FETCH_ASSOC);
        } catch (Throwable $e) {
            error_log('Bootstrap column check failed for ' . $table . '.' . $column . ': ' . $e->getMessage());
            return false;
        }
    }
}

if (!function_exists('honttechEnsureCoreSchema')) {
    function honttechEnsureCoreSchema(PDO $pdo): bool {
        try {
            try { $pdo->exec('SET FOREIGN_KEY_CHECKS = 0'); } catch (Throwable $ignored) {}
            honttechEnsureSystemSettingsTable($pdo);

            // Create bare tables first. Columns/indexes are added in separate best-effort steps so missing INDEX/ALTER support
            // cannot make a fresh XAMPP/shared-hosting install fail before the portal can load.
            try { $pdo->exec("CREATE TABLE IF NOT EXISTS admins (
                id INT AUTO_INCREMENT PRIMARY KEY,
                username VARCHAR(50) NULL,
                password VARCHAR(255) NULL,
                email VARCHAR(100) NULL,
                full_name VARCHAR(100) NULL,
                role VARCHAR(50) NULL DEFAULT 'Super Admin',
                status VARCHAR(30) NULL DEFAULT 'Active',
                last_login DATETIME NULL,
                created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); }
            catch (Throwable $e) {
                error_log('Admins base table creation failed: ' . $e->getMessage());
                try { $pdo->exec("CREATE TABLE IF NOT EXISTS admins (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    username VARCHAR(50) NULL,
                    password VARCHAR(255) NULL,
                    email VARCHAR(100) NULL,
                    full_name VARCHAR(100) NULL,
                    role VARCHAR(50) NULL DEFAULT 'Super Admin',
                    status VARCHAR(30) NULL DEFAULT 'Active',
                    last_login DATETIME NULL,
                    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
                ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4"); } catch (Throwable $ignored) {}
            }
            try { $pdo->exec("CREATE TABLE IF NOT EXISTS students (
                id INT AUTO_INCREMENT PRIMARY KEY,
                registration_no VARCHAR(100) NULL,
                application_no VARCHAR(100) NULL,
                admission_no VARCHAR(100) NULL,
                matric_no VARCHAR(100) NULL,
                surname VARCHAR(80) NULL,
                first_name VARCHAR(80) NULL,
                other_name VARCHAR(80) NULL,
                full_name VARCHAR(180) NULL,
                email VARCHAR(120) NULL,
                mobile_phone VARCHAR(30) NULL,
                password VARCHAR(255) NULL,
                faculty VARCHAR(180) NULL,
                department VARCHAR(180) NULL,
                programme VARCHAR(180) NULL,
                program VARCHAR(180) NULL,
                level VARCHAR(30) NULL,
                session_name VARCHAR(30) NULL,
                mode_of_study VARCHAR(100) NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                application_status VARCHAR(40) NULL DEFAULT 'Pending',
                account_status VARCHAR(30) NULL DEFAULT 'Active',
                created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); }
            catch (Throwable $e) {
                error_log('Students base table creation failed: ' . $e->getMessage());
                try { $pdo->exec("CREATE TABLE IF NOT EXISTS students (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    registration_no VARCHAR(100) NULL,
                    application_no VARCHAR(100) NULL,
                    admission_no VARCHAR(100) NULL,
                    matric_no VARCHAR(100) NULL,
                    surname VARCHAR(80) NULL,
                    first_name VARCHAR(80) NULL,
                    other_name VARCHAR(80) NULL,
                    full_name VARCHAR(180) NULL,
                    email VARCHAR(120) NULL,
                    mobile_phone VARCHAR(30) NULL,
                    password VARCHAR(255) NULL,
                    faculty VARCHAR(180) NULL,
                    department VARCHAR(180) NULL,
                    programme VARCHAR(180) NULL,
                    program VARCHAR(180) NULL,
                    level VARCHAR(30) NULL,
                    session_name VARCHAR(30) NULL,
                    mode_of_study VARCHAR(100) NULL,
                    faculty_id INT NULL,
                    department_id INT NULL,
                    programme_id INT NULL,
                    application_status VARCHAR(40) NULL DEFAULT 'Pending',
                    account_status VARCHAR(30) NULL DEFAULT 'Active',
                    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
                ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4"); } catch (Throwable $ignored) {}
            }

            foreach (['admins', 'students'] as $coreTable) {
                if (!honttechBootstrapColumnExists($pdo, $coreTable, 'id')) {
                    try {
                        $pdo->exec('ALTER TABLE `' . str_replace('`', '``', $coreTable) . '` ADD COLUMN id INT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST');
                    } catch (Throwable $e) {
                        try { $pdo->exec('ALTER TABLE `' . str_replace('`', '``', $coreTable) . '` ADD COLUMN id INT NULL FIRST'); } catch (Throwable $ignored) {}
                    }
                }
            }

            $adminColumns = [
                'username' => ["VARCHAR(50) NULL", 'id'],
                'password' => ["VARCHAR(255) NULL", 'username'],
                'email' => ["VARCHAR(100) NULL", 'password'],
                'full_name' => ["VARCHAR(100) NULL", 'email'],
                'role' => ["VARCHAR(50) NULL DEFAULT 'Super Admin'", 'full_name'],
                'status' => ["VARCHAR(30) NULL DEFAULT 'Active'", 'role'],
                'last_login' => ["DATETIME NULL", 'status'],
                'created_at' => ["TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP", 'last_login'],
                'updated_at' => ["TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", 'created_at'],
            ];
            foreach ($adminColumns as $column => $meta) {
                if (!honttechBootstrapColumnExists($pdo, 'admins', $column)) {
                    honttechSafeAddColumn($pdo, 'admins', $column, $meta[0], $meta[1]);
                }
            }

            $studentColumns = [
                'registration_no' => ["VARCHAR(100) NULL", 'id'],
                'application_no' => ["VARCHAR(100) NULL", 'registration_no'],
                'admission_no' => ["VARCHAR(100) NULL", 'application_no'],
                'matric_no' => ["VARCHAR(100) NULL", 'admission_no'],
                'surname' => ["VARCHAR(80) NULL", 'matric_no'],
                'first_name' => ["VARCHAR(80) NULL", 'surname'],
                'other_name' => ["VARCHAR(80) NULL", 'first_name'],
                'full_name' => ["VARCHAR(180) NULL", 'other_name'],
                'sex' => ["VARCHAR(20) NULL", 'full_name'],
                'date_of_birth' => ["DATE NULL", 'sex'],
                'nationality' => ["VARCHAR(80) NULL", 'date_of_birth'],
                'state_of_origin' => ["VARCHAR(80) NULL", 'nationality'],
                'lga' => ["VARCHAR(80) NULL", 'state_of_origin'],
                'mobile_phone' => ["VARCHAR(30) NULL", 'lga'],
                'residential_phone' => ["VARCHAR(30) NULL", 'mobile_phone'],
                'email' => ["VARCHAR(120) NULL", 'residential_phone'],
                'permanent_address' => ["TEXT NULL", 'email'],
                'extra_curricular' => ["TEXT NULL", 'permanent_address'],
                'nok_name' => ["TEXT NULL", 'extra_curricular'],
                'nok_phone' => ["VARCHAR(30) NULL", 'nok_name'],
                'sponsor_name' => ["TEXT NULL", 'nok_phone'],
                'sponsor_phone' => ["VARCHAR(30) NULL", 'sponsor_name'],
                'sponsor_occupation' => ["VARCHAR(150) NULL", 'sponsor_phone'],
                'subject1' => ["VARCHAR(80) NULL", 'sponsor_occupation'],
                'subject2' => ["VARCHAR(80) NULL", 'subject1'],
                'subject3' => ["VARCHAR(80) NULL", 'subject2'],
                'password' => ["VARCHAR(255) NULL", 'subject3'],
                'faculty' => ["VARCHAR(180) NULL", 'password'],
                'department' => ["VARCHAR(180) NULL", 'faculty'],
                'programme' => ["VARCHAR(180) NULL", 'department'],
                'program' => ["VARCHAR(180) NULL", 'programme'],
                'level' => ["VARCHAR(30) NULL", 'program'],
                'session_name' => ["VARCHAR(30) NULL", 'level'],
                'mode_of_study' => ["VARCHAR(100) NULL", 'session_name'],
                'faculty_id' => ["INT NULL", 'mode_of_study'],
                'department_id' => ["INT NULL", 'faculty_id'],
                'programme_id' => ["INT NULL", 'department_id'],
                'registration_fee_status' => ["VARCHAR(30) NULL DEFAULT 'Pending'", 'programme_id'],
                'registration_fee_reference' => ["VARCHAR(100) NULL", 'registration_fee_status'],
                'acceptance_fee_status' => ["VARCHAR(30) NULL DEFAULT 'Not Paid'", 'registration_fee_reference'],
                'acceptance_fee_reference' => ["VARCHAR(100) NULL", 'acceptance_fee_status'],
                'hostel_fee_status' => ["VARCHAR(30) NULL DEFAULT 'Not Paid'", 'acceptance_fee_reference'],
                'hostel_fee_reference' => ["VARCHAR(100) NULL", 'hostel_fee_status'],
                'school_fee_status' => ["VARCHAR(30) NULL DEFAULT 'Not Paid'", 'hostel_fee_reference'],
                'school_fee_reference' => ["VARCHAR(100) NULL", 'school_fee_status'],
                'payment_plan' => ["VARCHAR(30) NULL DEFAULT 'One-time'", 'school_fee_reference'],
                'payment_status' => ["VARCHAR(30) NULL DEFAULT 'Pending'", 'payment_plan'],
                'payment_reference' => ["VARCHAR(100) NULL", 'payment_status'],
                'application_status' => ["VARCHAR(40) NULL DEFAULT 'Pending'", 'payment_reference'],
                'application_submitted' => ["TINYINT(1) NOT NULL DEFAULT 0", 'application_status'],
                'application_submitted_at' => ["DATETIME NULL", 'application_submitted'],
                'signature_data' => ["LONGTEXT NULL", 'application_submitted_at'],
                'profile_image' => ["VARCHAR(255) NULL", 'signature_data'],
                'passport_photo' => ["VARCHAR(255) NULL", 'profile_image'],
                'account_status' => ["VARCHAR(30) NULL DEFAULT 'Active'", 'passport_photo'],
                'application_edit_count' => ["INT NULL DEFAULT 0", 'account_status'],
                'id_card_generated' => ["TINYINT(1) NULL DEFAULT 0", 'application_edit_count'],
                'id_card_generated_date' => ["TIMESTAMP NULL", 'id_card_generated'],
                'offer_status' => ["VARCHAR(30) NULL DEFAULT 'Pending'", 'id_card_generated_date'],
                'offer_decision_date' => ["DATETIME NULL", 'offer_status'],
                'created_at' => ["TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP", 'offer_decision_date'],
                'updated_at' => ["TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", 'created_at'],
            ];
            foreach ($studentColumns as $column => $meta) {
                if (!honttechBootstrapColumnExists($pdo, 'students', $column)) {
                    honttechSafeAddColumn($pdo, 'students', $column, $meta[0], $meta[1]);
                }
            }

            // Indexes are useful but must remain optional; lack of INDEX privilege must not block the portal.
            honttechSafeAddIndex($pdo, 'students', 'idx_students_registration_no', ['registration_no']);
            honttechSafeAddIndex($pdo, 'students', 'idx_students_application_no', ['application_no']);
            honttechSafeAddIndex($pdo, 'students', 'idx_students_admission_no', ['admission_no']);
            honttechSafeAddIndex($pdo, 'students', 'idx_students_matric_no', ['matric_no']);
            honttechSafeAddIndex($pdo, 'students', 'idx_students_email', ['email']);
            honttechSafeAddIndex($pdo, 'admins', 'idx_admins_username', ['username']);
            honttechSafeAddIndex($pdo, 'admins', 'idx_admins_email', ['email']);

            try { $pdo->exec('SET FOREIGN_KEY_CHECKS = 1'); } catch (Throwable $ignored) {}
            return honttechDatabaseHasCoreTables($pdo);
        } catch (Throwable $e) {
            error_log('Core schema bootstrap failed: ' . $e->getMessage());
            try { $pdo->exec('SET FOREIGN_KEY_CHECKS = 1'); } catch (Throwable $ignored) {}
            return honttechDatabaseHasCoreTables($pdo);
        }
    }
}

// Connect to database
try {
    $pdo = honttechConnectDatabase();
    try { honttechEnsureSystemSettingsTable($pdo); } catch (Throwable $e) { error_log('System settings bootstrap failed: ' . $e->getMessage()); }
    try { if (function_exists('honttechEnsureCoreSchema')) { honttechEnsureCoreSchema($pdo); } } catch (Throwable $e) { error_log('Core schema bootstrap after connection failed: ' . $e->getMessage()); }
    try { ensureStudentFinanceEnhancements($pdo); } catch (Throwable $e) {}
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}
    try { ensureStudentAttendanceModule($pdo); } catch (Throwable $e) {}
    try { portalEnsureLateFeeSupport($pdo); } catch (Throwable $e) {}
} catch (Throwable $e) {
    error_log('Database connection failed: ' . $e->getMessage());
    http_response_code(500);
    die(APP_DEBUG ? ("Database connection failed: " . e($e->getMessage())) : 'Database connection failed. Please check DB_HOST, DB_NAME, DB_USER and DB_PASS, or contact the administrator.');
}

// Create tables if they don't exist
if (!function_exists('initializeDatabase')) {
function initializeDatabase() {
    global $pdo;

    try {
        // Drop tables if they exist with incorrect structure (for development)
        // In production, you would handle migrations differently
        $pdo->exec("SET FOREIGN_KEY_CHECKS = 0");
        honttechEnsureSystemSettingsTable($pdo);

        // Check if students table exists and has the correct structure
        $stmt = $pdo->prepare("SHOW TABLES LIKE 'students'");
        $stmt->execute();
        $tableExists = $stmt->rowCount() > 0;

        if ($tableExists) {
            // Check if profile_image column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'profile_image'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'profile_image', 'VARCHAR(255) NULL', 'signature_data');
            }

            // Check if passport_photo column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'passport_photo'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'passport_photo', 'VARCHAR(255) NULL', 'profile_image');
            }


            // Check if application_submitted column exists (locks application after final submit)
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'application_submitted'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;
            if (!$columnExists) {
                honttechSafeAddColumn($pdo, 'students', 'application_submitted', 'TINYINT(1) NOT NULL DEFAULT 0', 'application_status');
            }

            // Check if application_submitted_at column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'application_submitted_at'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;
            if (!$columnExists) {
                honttechSafeAddColumn($pdo, 'students', 'application_submitted_at', 'DATETIME NULL', 'application_submitted');
            }


            // Check if profile_image column exists (student dashboard photo)
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'profile_image'");
            $stmt->execute();
            if ($stmt->rowCount() == 0) {
                honttechSafeAddColumn($pdo, 'students', 'profile_image', 'VARCHAR(255) NULL', 'passport_photo');
            }

            // Assigned custom payments (admin can assign extra fees)
            $pdo->exec("CREATE TABLE IF NOT EXISTS assigned_payments (
                id INT AUTO_INCREMENT PRIMARY KEY,
                student_id INT NOT NULL,
                title VARCHAR(120) NOT NULL,
                amount DECIMAL(10,2) NOT NULL,
                status ENUM('Pending','Paid','Not Paid','Not Required') NOT NULL DEFAULT 'Pending',
                reference VARCHAR(100) NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_student (student_id),
                FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

            // Theme colors (up to 5)
            $themeDefaults = [
                'theme_color_1' => '#003366',
                'theme_color_2' => '#f0a500',
                'theme_color_3' => '#f4f7f6',
                'theme_color_4' => '#ffffff',
                'theme_color_5' => '#333333'
            ];
            foreach ($themeDefaults as $k => $v) {
                $st = $pdo->prepare("SELECT COUNT(*) FROM system_settings WHERE setting_key = ?");
                $st->execute([$k]);
                if ((int)$st->fetchColumn() === 0) {
                    $ins = $pdo->prepare("INSERT INTO system_settings (setting_key, setting_value) VALUES (?, ?)");
                    $ins->execute([$k, $v]);
                }
            }
// Check if offer_status column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'offer_status'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;
            if (!$columnExists) {
                honttechSafeAddColumn($pdo, 'students', 'offer_status', "ENUM('Pending','Accepted','Rejected') DEFAULT 'Pending'", 'acceptance_fee_status');
            }

            // Check if offer_decision_date column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'offer_decision_date'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;
            if (!$columnExists) {
                honttechSafeAddColumn($pdo, 'students', 'offer_decision_date', 'DATETIME NULL', 'offer_status');
            }

// Check if hostel_fee_status column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'hostel_fee_status'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'hostel_fee_status', "ENUM('Pending', 'Paid', 'Failed', 'Not Paid', 'Not Required') DEFAULT 'Not Paid'", 'acceptance_fee_status');
            }

            // Check if hostel_fee_reference column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'hostel_fee_reference'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'hostel_fee_reference', 'VARCHAR(100) NULL', 'hostel_fee_status');
            }

            // Check if school_fee_status column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'school_fee_status'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'school_fee_status', "ENUM('Pending', 'Paid', 'Failed', 'Not Paid', 'Not Required') DEFAULT 'Not Paid'", 'hostel_fee_status');
            }

            // Check if school_fee_reference column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'school_fee_reference'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'school_fee_reference', 'VARCHAR(100) NULL', 'school_fee_status');

            // Normalize payment status enums / values (best-effort; keeps backward compatibility)
            try {
                // Expand ENUMs to include both 'Not Paid' and older 'Not Required', and set default to 'Not Paid'
                $pdo->exec("ALTER TABLE students MODIFY COLUMN acceptance_fee_status ENUM('Pending','Paid','Failed','Not Paid','Not Required') DEFAULT 'Not Paid'");
                $pdo->exec("ALTER TABLE students MODIFY COLUMN hostel_fee_status ENUM('Pending','Paid','Failed','Not Paid','Not Required') DEFAULT 'Not Paid'");
                $pdo->exec("ALTER TABLE students MODIFY COLUMN school_fee_status ENUM('Pending','Paid','Failed','Not Paid','Not Required') DEFAULT 'Not Paid'");
                // Convert existing values
                $pdo->exec("UPDATE students SET acceptance_fee_status = 'Not Paid' WHERE acceptance_fee_status = 'Not Required'");
                $pdo->exec("UPDATE students SET hostel_fee_status = 'Not Paid' WHERE hostel_fee_status = 'Not Required'");
                $pdo->exec("UPDATE students SET school_fee_status = 'Not Paid' WHERE school_fee_status = 'Not Required'");
            } catch (Throwable $e) { /* ignore */ }

            try {
                $pdo->exec("ALTER TABLE assigned_payments MODIFY COLUMN status ENUM('Pending','Paid','Not Paid','Not Required') NOT NULL DEFAULT 'Pending'");
                $pdo->exec("UPDATE assigned_payments SET status = 'Not Paid' WHERE status = 'Not Required'");
            } catch (Throwable $e) { /* ignore */ }

            // END NORMALIZE PAYMENT STATUS ENUMS

            }

            // Check if payment_plan column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'payment_plan'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'payment_plan', "ENUM('One-time', 'Installment') DEFAULT 'One-time'", 'school_fee_reference');
            }

            // Check if application_edit_count column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'application_edit_count'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'application_edit_count', 'INT DEFAULT 0', 'payment_plan');
            }

            // Check if id_card_generated column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'id_card_generated'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'id_card_generated', 'BOOLEAN DEFAULT FALSE', 'application_edit_count');
            }

            // Check if id_card_generated_date column exists
            $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'id_card_generated_date'");
            $stmt->execute();
            $columnExists = $stmt->rowCount() > 0;

            if (!$columnExists) {
                // Table exists but column doesn't, add it
                honttechSafeAddColumn($pdo, 'students', 'id_card_generated_date', 'TIMESTAMP NULL', 'id_card_generated');
            }
        }

        $pdo->exec("SET FOREIGN_KEY_CHECKS = 1");

        // Admins table
        $pdo->exec("CREATE TABLE IF NOT EXISTS admins (
            id INT AUTO_INCREMENT PRIMARY KEY,
            username VARCHAR(50) UNIQUE NOT NULL,
            password VARCHAR(255) NOT NULL,
            email VARCHAR(100) NOT NULL,
            full_name VARCHAR(100) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        // Students table
        $pdo->exec("CREATE TABLE IF NOT EXISTS students (
            id INT AUTO_INCREMENT PRIMARY KEY,
            registration_no VARCHAR(100) UNIQUE NOT NULL,
            application_no VARCHAR(100) NULL,
            admission_no VARCHAR(100) NULL,
            matric_no VARCHAR(100) NULL,
            surname VARCHAR(50) NOT NULL,
            first_name VARCHAR(50) NOT NULL,
            other_name VARCHAR(50),
            full_name VARCHAR(160) NULL,
            maiden_name VARCHAR(50),
            sex ENUM('Male', 'Female') NOT NULL,
            date_of_birth DATE NOT NULL,
            nationality VARCHAR(50) NOT NULL,
            state_of_origin VARCHAR(50) NOT NULL,
            lga VARCHAR(50) NOT NULL,
            mobile_phone VARCHAR(20) NOT NULL,
            residential_phone VARCHAR(20),
            email VARCHAR(100) NOT NULL,
            permanent_address TEXT NOT NULL,
            extra_curricular TEXT,
            nok_name TEXT NOT NULL,
            nok_phone VARCHAR(20) NOT NULL,
            sponsor_name TEXT NOT NULL,
            sponsor_phone VARCHAR(20) NOT NULL,
            sponsor_occupation VARCHAR(100) NOT NULL,
            subject1 VARCHAR(50) NOT NULL,
            subject2 VARCHAR(50) NOT NULL,
            subject3 VARCHAR(50) NOT NULL,
            password VARCHAR(255) NOT NULL,
            registration_fee_status ENUM('Pending', 'Paid', 'Failed') DEFAULT 'Pending',
            registration_fee_reference VARCHAR(100),
            acceptance_fee_status ENUM('Pending', 'Paid', 'Failed', 'Not Paid', 'Not Required') DEFAULT 'Not Paid',
            acceptance_fee_reference VARCHAR(100),
            hostel_fee_status ENUM('Pending', 'Paid', 'Failed', 'Not Paid', 'Not Required') DEFAULT 'Not Paid',
            hostel_fee_reference VARCHAR(100),
            school_fee_status ENUM('Pending', 'Paid', 'Failed', 'Not Paid', 'Not Required') DEFAULT 'Not Paid',
            school_fee_reference VARCHAR(100),
            payment_plan ENUM('One-time', 'Installment') DEFAULT 'One-time',
            payment_status ENUM('Pending', 'Paid', 'Failed') DEFAULT 'Pending',
            payment_reference VARCHAR(100),
            application_status ENUM('Pending', 'Under Review', 'Approved', 'Rejected') DEFAULT 'Pending',
            application_submitted TINYINT(1) NOT NULL DEFAULT 0,
            application_submitted_at DATETIME NULL,
            signature_data TEXT,
            profile_image VARCHAR(255),
            passport_photo VARCHAR(255),
            account_status ENUM('Active', 'Suspended', 'Blocked') DEFAULT 'Active',
            application_edit_count INT DEFAULT 0,
            id_card_generated BOOLEAN DEFAULT FALSE,
            id_card_generated_date TIMESTAMP NULL,
            offer_status ENUM('Pending','Accepted','Rejected') DEFAULT 'Pending',
            offer_decision_date DATETIME NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        // Ensure unique student email (best effort; wrapped to avoid breaking existing installs)
        try { $pdo->exec("ALTER TABLE students ADD UNIQUE KEY uniq_students_email (email)"); } catch (Throwable $e) {}


        // Password reset tokens (students + admins)
        $pdo->exec("CREATE TABLE IF NOT EXISTS password_resets (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_type ENUM('student','admin') NOT NULL,
            user_id INT NOT NULL,
            email VARCHAR(120) NOT NULL,
            token_hash CHAR(64) NOT NULL,
            expires_at DATETIME NOT NULL,
            used_at DATETIME NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_email (email),
            KEY idx_user (user_type, user_id),
            KEY idx_token (token_hash)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");


        // OTP logins table (for email OTP login)
        $pdo->exec("CREATE TABLE IF NOT EXISTS otp_logins (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_type ENUM('student','admin') NOT NULL,
            user_id INT NOT NULL,
            email VARCHAR(120) NOT NULL,
            otp_hash VARCHAR(255) NOT NULL,
            expires_at DATETIME NOT NULL,
            attempts INT NOT NULL DEFAULT 0,
            consumed TINYINT(1) NOT NULL DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_user (user_type, user_id),
            KEY idx_email (email),
            KEY idx_exp (expires_at),
            KEY idx_consumed (consumed)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        // Email logs (prevent duplicate mail sends)
        $pdo->exec("CREATE TABLE IF NOT EXISTS email_logs (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_type ENUM('student','admin') NOT NULL,
            user_id INT NOT NULL,
            email VARCHAR(120) NOT NULL,
            context VARCHAR(120) NOT NULL,
            subject VARCHAR(255) NOT NULL,
            success TINYINT(1) NOT NULL DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_user (user_type, user_id),
            KEY idx_email (email),
            KEY idx_ctx (context),
            KEY idx_created (created_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");


        // Education history table
        $pdo->exec("CREATE TABLE IF NOT EXISTS education_history (
            id INT AUTO_INCREMENT PRIMARY KEY,
            student_id INT NOT NULL,
            institution_name VARCHAR(100) NOT NULL,
            from_date DATE NOT NULL,
            to_date DATE NOT NULL,
            qualification VARCHAR(100) NOT NULL,
            FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        // Examination results table
        $pdo->exec("CREATE TABLE IF NOT EXISTS examination_results (
            id INT AUTO_INCREMENT PRIMARY KEY,
            student_id INT NOT NULL,
            exam_type ENUM('WAEC', 'NECO', 'NABTEB', 'OTHER') NOT NULL,
            exam_date DATE NOT NULL,
            exam_number VARCHAR(50) NOT NULL,
            subjects_grades TEXT NOT NULL,
            credentials_attached BOOLEAN DEFAULT TRUE,
            FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");


        // Allow flexible exam types (e.g., GCE) and custom labels for OTHER
        try { $pdo->exec("ALTER TABLE examination_results MODIFY exam_type VARCHAR(30) NOT NULL"); } catch (Throwable $e) {}
        try { $pdo->exec("ALTER TABLE examination_results ADD COLUMN exam_type_other VARCHAR(120) NULL"); } catch (Throwable $e) {}
// Referees table
        $pdo->exec("CREATE TABLE IF NOT EXISTS referees (
            id INT AUTO_INCREMENT PRIMARY KEY,
            student_id INT NOT NULL,
            full_name VARCHAR(100) NOT NULL,
            occupation VARCHAR(100) NOT NULL,
            post_held VARCHAR(100) NOT NULL,
            address TEXT NOT NULL,
            date DATE NOT NULL,
            signature_data TEXT,
            FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        // Admin decisions table
        $pdo->exec("CREATE TABLE IF NOT EXISTS admin_decisions (
            id INT AUTO_INCREMENT PRIMARY KEY,
            student_id INT NOT NULL,
            qualified BOOLEAN,
            officer_name VARCHAR(100) NOT NULL,
            officer_signature TEXT,
            decision_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        // Payment transactions table
        $pdo->exec("CREATE TABLE IF NOT EXISTS payment_transactions (
            id INT AUTO_INCREMENT PRIMARY KEY,
            transaction_id VARCHAR(20) UNIQUE NOT NULL,
            student_id INT NOT NULL,
            transaction_type VARCHAR(150) NOT NULL,
            amount DECIMAL(10, 2) NOT NULL,
            reference VARCHAR(100) NOT NULL,
            paystack_reference VARCHAR(100),
            status ENUM('Pending', 'Successful', 'Failed', 'Approved', 'Rejected') DEFAULT 'Pending',
            payment_method VARCHAR(50) DEFAULT 'Paystack',
            payment_date TIMESTAMP NULL,
            approval_date TIMESTAMP NULL,
            approved_by INT,
            approval_notes TEXT,
            receipt_data TEXT,
            qr_code_data TEXT,
            receipt_public_token VARCHAR(96) NULL,
            receipt_public_expires_at DATETIME NULL,
            installment_number INT DEFAULT 1,
            total_installments INT DEFAULT 1,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_pt_receipt_public_token (receipt_public_token),
            FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
            FOREIGN KEY (approved_by) REFERENCES admins(id) ON DELETE SET NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        $pdo->exec("CREATE TABLE IF NOT EXISTS payment_gateway_callbacks (
            id INT AUTO_INCREMENT PRIMARY KEY,
            provider VARCHAR(80) NOT NULL,
            event_id VARCHAR(190) NULL,
            reference VARCHAR(150) NULL,
            payload LONGTEXT NULL,
            signature_hash VARCHAR(190) NULL,
            status VARCHAR(50) NOT NULL DEFAULT 'received',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_gateway_event (provider, event_id),
            INDEX idx_gateway_reference (provider, reference)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        try { if (function_exists('portalEnsurePublicReceiptSupport')) { portalEnsurePublicReceiptSupport($pdo); } } catch (Throwable $e) {}

// Self-heal: allow custom/assigned payment types (change transaction_type from ENUM to VARCHAR if needed)
try {
    $col = $pdo->query("SHOW COLUMNS FROM payment_transactions LIKE 'transaction_type'")->fetch(PDO::FETCH_ASSOC);
    if ($col && isset($col['Type']) && stripos((string)$col['Type'], 'enum(') === 0) {
        try { $pdo->exec("ALTER TABLE payment_transactions MODIFY transaction_type VARCHAR(150) NOT NULL"); } catch (Throwable $e) {}
    }
} catch (Throwable $e) {}

// Self-heal: add manual payment fields to payment_transactions
$cols = [
    'manual_proof_path' => "ALTER TABLE payment_transactions ADD COLUMN manual_proof_path VARCHAR(255) NULL AFTER payment_method",
    'manual_payer_name' => "ALTER TABLE payment_transactions ADD COLUMN manual_payer_name VARCHAR(150) NULL AFTER manual_proof_path",
    'manual_notes'      => "ALTER TABLE payment_transactions ADD COLUMN manual_notes TEXT NULL AFTER manual_payer_name"
];
foreach ($cols as $col => $sql) {
    $stc = $pdo->prepare("SHOW COLUMNS FROM payment_transactions LIKE ?");
    $stc->execute([$col]);
    if ($stc->rowCount() == 0) {
        try { $pdo->exec($sql); } catch (Throwable $e) {}
    }
}

// Self-heal: store payer-bears-charges info (gross amount charged + gateway fee)
try {
    $c = $pdo->prepare("SHOW COLUMNS FROM payment_transactions LIKE 'gateway_fee'");
    $c->execute();
    if ($c->rowCount() == 0) {
        $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN gateway_fee DECIMAL(10,2) NOT NULL DEFAULT 0.00 AFTER amount");
    }
} catch (Throwable $e) {}
try {
    $c = $pdo->prepare("SHOW COLUMNS FROM payment_transactions LIKE 'charged_amount'");
    $c->execute();
    if ($c->rowCount() == 0) {
        $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN charged_amount DECIMAL(10,2) NULL AFTER gateway_fee");
        // Backfill gross amount for older records
        try { $pdo->exec("UPDATE payment_transactions SET charged_amount = amount WHERE charged_amount IS NULL"); } catch (Throwable $e) {}
    }
} catch (Throwable $e) {}


try {
    $c = $pdo->prepare("SHOW COLUMNS FROM payment_transactions LIKE 'academic_session'");
    $c->execute();
    if ($c->rowCount() == 0) {
        $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN academic_session VARCHAR(80) NULL AFTER paystack_reference");
    }
} catch (Throwable $e) {}
try {
    $c = $pdo->prepare("SHOW COLUMNS FROM payment_transactions LIKE 'semester'");
    $c->execute();
    if ($c->rowCount() == 0) {
        $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN semester VARCHAR(40) NULL AFTER academic_session");
    }
} catch (Throwable $e) {}
try {
    $c = $pdo->prepare("SHOW COLUMNS FROM payment_transactions LIKE 'receipt_number'");
    $c->execute();
    if ($c->rowCount() == 0) {
        $pdo->exec("ALTER TABLE payment_transactions ADD COLUMN receipt_number VARCHAR(100) NULL AFTER reference");
    }
} catch (Throwable $e) {}
try {
    $pdo->exec("UPDATE payment_transactions pt JOIN students s ON s.id = pt.student_id SET pt.academic_session = COALESCE(NULLIF(pt.academic_session,''), NULLIF(s.session,''), NULLIF(s.session_name,'')) WHERE pt.academic_session IS NULL OR pt.academic_session = ''");
} catch (Throwable $e) {}
try {
    $pdo->exec("UPDATE payment_transactions pt JOIN students s ON s.id = pt.student_id SET pt.semester = COALESCE(NULLIF(pt.semester,''), NULLIF(s.semester,''), 'First Semester') WHERE pt.semester IS NULL OR pt.semester = ''");
} catch (Throwable $e) {}
try {
    $pdo->exec("UPDATE payment_transactions SET receipt_number = CONCAT('RCT-', YEAR(COALESCE(payment_date, created_at)), '-', LPAD(id, 6, '0')) WHERE (receipt_number IS NULL OR receipt_number = '') AND id IS NOT NULL");
} catch (Throwable $e) {}


// -------------------------
// WALLET (Self-healing)
// -------------------------
try {
    // Wallets table
    $pdo->exec("CREATE TABLE IF NOT EXISTS wallets (
        id INT AUTO_INCREMENT PRIMARY KEY,
        student_id INT UNIQUE NOT NULL,
        balance DECIMAL(12, 2) NOT NULL DEFAULT 0.00,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // Wallet transactions
    $pdo->exec("CREATE TABLE IF NOT EXISTS wallet_transactions (
        id INT AUTO_INCREMENT PRIMARY KEY,
        student_id INT NOT NULL,
        type ENUM('credit','debit') NOT NULL,
        amount DECIMAL(12,2) NOT NULL,
        narration VARCHAR(255) NOT NULL,
        balance_before DECIMAL(12,2) NOT NULL DEFAULT 0.00,
        balance_after DECIMAL(12,2) NOT NULL DEFAULT 0.00,
        related_transaction_id VARCHAR(40) NULL,
        related_payment_reference VARCHAR(120) NULL,
        notification_context VARCHAR(120) NULL,
        notification_sent_at DATETIME NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_student (student_id),
        INDEX idx_wallet_related_payment_reference (related_payment_reference),
        INDEX idx_wallet_notification_sent_at (notification_sent_at),
        FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    try { if (function_exists('portalEnsureWalletTransactionMetaSupport')) { portalEnsureWalletTransactionMetaSupport($pdo); } } catch (Throwable $e) {}

    // Self-heal wallet status/enable flags
    try {
        $c1 = $pdo->prepare("SHOW COLUMNS FROM wallets LIKE 'status'");
        $c1->execute();
        if ($c1->rowCount() == 0) {
            $pdo->exec("ALTER TABLE wallets ADD COLUMN status ENUM('Active','Suspended','Blocked') NOT NULL DEFAULT 'Active' AFTER balance");
        }
    } catch (Throwable $e) {}
    try {
        $c2 = $pdo->prepare("SHOW COLUMNS FROM wallets LIKE 'is_enabled'");
        $c2->execute();
        if ($c2->rowCount() == 0) {
            $pdo->exec("ALTER TABLE wallets ADD COLUMN is_enabled TINYINT(1) NOT NULL DEFAULT 1 AFTER status");
        }
    } catch (Throwable $e) {}

    // Extend payment_transactions.transaction_type enum to include Wallet Funding (best-effort)
    try {
        $col = $pdo->query("SHOW COLUMNS FROM payment_transactions LIKE 'transaction_type'")->fetch(PDO::FETCH_ASSOC);
        if ($col && isset($col['Type']) && stripos($col['Type'], 'Wallet Funding') === false) {
            // Attempt to add enum value
            $pdo->exec("ALTER TABLE payment_transactions MODIFY transaction_type ENUM('Registration Fee','Acceptance Fee','Hostel Fee','School Fee','Wallet Funding') NOT NULL");
        }
    } catch (Throwable $e) { /* ignore */ }
} catch (Throwable $e) {
    // do not block boot
}

        // Student documents table
        $pdo->exec("CREATE TABLE IF NOT EXISTS student_documents (
            id INT AUTO_INCREMENT PRIMARY KEY,
            student_id INT NOT NULL,
            document_name VARCHAR(100) NOT NULL,
            document_type VARCHAR(50) NOT NULL,
            file_path VARCHAR(255) NOT NULL,
            file_size INT NOT NULL,
            upload_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        // O'Level subjects/grades table (dynamic, self-healing)
        $pdo->exec("CREATE TABLE IF NOT EXISTS student_olevel_subjects (
            id INT AUTO_INCREMENT PRIMARY KEY,
            student_id INT NOT NULL,
            subject_name VARCHAR(100) NOT NULL,
            grade VARCHAR(10) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP NULL,
            INDEX idx_student_olevel_student (student_id),
            FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");


        // System settings table
        $pdo->exec("CREATE TABLE IF NOT EXISTS system_settings (
            id INT AUTO_INCREMENT PRIMARY KEY,
            setting_key VARCHAR(120) UNIQUE NOT NULL,
            setting_value TEXT NOT NULL,
            description TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        try { if (!portalColumnExistsSafe($pdo, 'system_settings', 'created_at')) { $pdo->exec("ALTER TABLE system_settings ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'system_settings', 'updated_at')) { $pdo->exec("ALTER TABLE system_settings ADD COLUMN updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"); } } catch (Throwable $e) {}

        // System assets table - Modified to support separate logos
        $pdo->exec("CREATE TABLE IF NOT EXISTS system_assets (
            id INT AUTO_INCREMENT PRIMARY KEY,
            asset_type VARCHAR(50) NOT NULL,
            file_path VARCHAR(255) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        

// =============================
// Faculty/Department/Programme + Sessions + Courses (Self-healing)
// =============================
try {
    // Faculties
    $pdo->exec("CREATE TABLE IF NOT EXISTS faculties (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(150) NOT NULL UNIQUE,
        code VARCHAR(30) NULL,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // Departments (belongs to faculty)
    $pdo->exec("CREATE TABLE IF NOT EXISTS departments (
        id INT AUTO_INCREMENT PRIMARY KEY,
        faculty_id INT NOT NULL,
        name VARCHAR(150) NOT NULL,
        code VARCHAR(30) NULL,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_dept (faculty_id, name),
        INDEX idx_dept_faculty (faculty_id),
        FOREIGN KEY (faculty_id) REFERENCES faculties(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // Programmes (belongs to department + faculty)
    $pdo->exec("CREATE TABLE IF NOT EXISTS programmes (
        id INT AUTO_INCREMENT PRIMARY KEY,
        faculty_id INT NOT NULL,
        department_id INT NOT NULL,
        name VARCHAR(180) NOT NULL,
        mode_of_study_type VARCHAR(20) NOT NULL DEFAULT 'Full-Time',
        mode_of_study_custom VARCHAR(180) NULL,
        code VARCHAR(30) NULL,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_prog (department_id, name),
        INDEX idx_prog_faculty (faculty_id),
        INDEX idx_prog_dept (department_id),
        INDEX idx_prog_mode (mode_of_study_type),
        FOREIGN KEY (faculty_id) REFERENCES faculties(id) ON DELETE CASCADE,
        FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // Academic sessions (promotion/session manager)
    $pdo->exec("CREATE TABLE IF NOT EXISTS academic_sessions (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(30) NOT NULL UNIQUE,
        start_date DATE NULL,
        end_date DATE NULL,
        is_current TINYINT(1) NOT NULL DEFAULT 0,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        registration_semester_access VARCHAR(20) NOT NULL DEFAULT 'both',
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // Self-heal academic session rows, names, and current-session state before any inserts/updates
    try { portalSelfHealAcademicSessions($pdo); } catch (Throwable $e) {}

    // Self-heal academic sessions with blank names so the unique session-name key never crashes on empty inserts
    try {
        $blankRows = [];
        try {
            $st = $pdo->query("SELECT id, name, is_current FROM academic_sessions WHERE COALESCE(TRIM(name),'')='' ORDER BY is_current DESC, id ASC");
            $blankRows = $st ? ($st->fetchAll(PDO::FETCH_ASSOC) ?: []) : [];
        } catch (Throwable $e) { $blankRows = []; }
        if ($blankRows) {
            $preferred = portalNormalizeSessionNameSafe((string)setting('academic_session', ''));
            $checkName = $pdo->prepare("SELECT COUNT(*) FROM academic_sessions WHERE name = ?");
            $rename = $pdo->prepare("UPDATE academic_sessions SET name = ? WHERE id = ?");
            foreach ($blankRows as $idx => $row) {
                $newName = '';
                if ($idx === 0 && $preferred !== '') {
                    $checkName->execute([$preferred]);
                    if ((int)$checkName->fetchColumn() === 0) {
                        $newName = $preferred;
                    }
                }
                if ($newName === '') {
                    $newName = 'Session-' . (int)($row['id'] ?? 0);
                    $seed = $newName;
                    $n = 1;
                    do {
                        $checkName->execute([$newName]);
                        $exists = (int)$checkName->fetchColumn() > 0;
                        if ($exists) {
                            $n++;
                            $newName = $seed . '-' . $n;
                        }
                    } while ($exists);
                }
                try { $rename->execute([$newName, (int)($row['id'] ?? 0)]); } catch (Throwable $e) {}
            }
        }
    } catch (Throwable $e) {}

    // Registrable courses
    $pdo->exec("CREATE TABLE IF NOT EXISTS registrable_courses (
        id INT AUTO_INCREMENT PRIMARY KEY,
        course_code VARCHAR(30) NOT NULL,
        course_title VARCHAR(255) NOT NULL,
        credit_unit INT NOT NULL DEFAULT 0,
        faculty_id INT NULL,
        department_id INT NULL,
        programme_id INT NULL,
        mode_of_study VARCHAR(100) NULL,
        level VARCHAR(10) NULL,
        semester ENUM('First','Second') NOT NULL DEFAULT 'First',
        session_name VARCHAR(30) NULL,
        year VARCHAR(10) NULL,
        course_type ENUM('C','E') NOT NULL DEFAULT 'C',
        registrable ENUM('Yes','No') NOT NULL DEFAULT 'Yes',
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_rc_filter (faculty_id, department_id, programme_id, level, semester, session_name, year),
        INDEX idx_rc_code (course_code),
        FOREIGN KEY (faculty_id) REFERENCES faculties(id) ON DELETE SET NULL,
        FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE SET NULL,
        FOREIGN KEY (programme_id) REFERENCES programmes(id) ON DELETE SET NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // Self-heal: some older installs had different credit column names (e.g., credit_load). Ensure credit_unit exists.
    try {
        $hasCreditUnit = false;
        try {
            $st = $pdo->query("SHOW COLUMNS FROM registrable_courses LIKE 'credit_unit'");
            $hasCreditUnit = ($st && $st->rowCount() > 0);
        } catch (Throwable $e) { $hasCreditUnit = false; }

        if (!$hasCreditUnit) {
            try { $pdo->exec("ALTER TABLE registrable_courses ADD COLUMN credit_unit INT NOT NULL DEFAULT 0 AFTER course_title"); } catch (Throwable $e) { /* ignore */ }
        }

        // Backfill credit_unit from legacy columns if present
        try {
            $hasCreditLoad = false;
            $st = $pdo->query("SHOW COLUMNS FROM registrable_courses LIKE 'credit_load'");
            $hasCreditLoad = ($st && $st->rowCount() > 0);
            if ($hasCreditLoad) {
                $pdo->exec("UPDATE registrable_courses SET credit_unit = credit_load WHERE (credit_unit IS NULL OR credit_unit = 0) AND credit_load IS NOT NULL");
            }
        } catch (Throwable $e) { /* ignore */ }

        try {
            $hasLegacyCredit = false;
            $st = $pdo->query("SHOW COLUMNS FROM registrable_courses LIKE 'credit'");
            $hasLegacyCredit = ($st && $st->rowCount() > 0);
            if ($hasLegacyCredit) {
                $pdo->exec("UPDATE registrable_courses SET credit_unit = credit WHERE (credit_unit IS NULL OR credit_unit = 0) AND credit IS NOT NULL");
            }
        } catch (Throwable $e) { /* ignore */ }
    } catch (Throwable $e) { /* ignore */ }

    // Self-heal: ensure mode_of_study column exists
    try {
        $hasModeOfStudy = false;
        try {
            $st = $pdo->query("SHOW COLUMNS FROM registrable_courses LIKE 'mode_of_study'");
            $hasModeOfStudy = ($st && $st->rowCount() > 0);
        } catch (Throwable $e) { $hasModeOfStudy = false; }

        if (!$hasModeOfStudy) {
            try { $pdo->exec("ALTER TABLE registrable_courses ADD COLUMN mode_of_study VARCHAR(100) NULL AFTER programme_id"); } catch (Throwable $e) { /* ignore */ }
        }
        try {
            $pdo->exec("UPDATE registrable_courses rc
                LEFT JOIN programmes p ON p.id = rc.programme_id
                SET rc.mode_of_study = CASE
                    WHEN COALESCE(TRIM(rc.mode_of_study), '') <> '' THEN TRIM(rc.mode_of_study)
                    WHEN p.id IS NOT NULL AND COALESCE(p.mode_of_study_type, '') = 'Others' AND COALESCE(TRIM(p.mode_of_study_custom), '') <> '' THEN TRIM(p.mode_of_study_custom)
                    WHEN p.id IS NOT NULL AND COALESCE(p.mode_of_study_type, '') IN ('Full-Time', 'Part-Time') THEN p.mode_of_study_type
                    WHEN LOWER(COALESCE(p.name, '')) LIKE '%weekend%' THEN 'Weekend'
                    WHEN LOWER(COALESCE(p.name, '')) LIKE '%sandwich%' THEN 'Sandwich'
                    WHEN LOWER(COALESCE(p.name, '')) LIKE '%distance%' THEN 'Distance Learning'
                    WHEN LOWER(COALESCE(p.name, '')) LIKE '%part%' OR LOWER(COALESCE(p.name, '')) LIKE '%pde%' THEN 'Part-Time'
                    ELSE 'Full-Time'
                END
                WHERE COALESCE(TRIM(rc.mode_of_study), '') = ''");
        } catch (Throwable $e) { /* ignore */ }
    } catch (Throwable $e) { /* ignore */ }

    // Self-heal: ensure "year" column exists (older installs may not have it)
    try {
        $hasYear = false;
        try {
            $st = $pdo->query("SHOW COLUMNS FROM registrable_courses LIKE 'year'");
            $hasYear = ($st && $st->rowCount() > 0);
        } catch (Throwable $e) { $hasYear = false; }

        if (!$hasYear) {
            try { $pdo->exec("ALTER TABLE registrable_courses ADD COLUMN year VARCHAR(10) NULL AFTER session_name"); } catch (Throwable $e) { /* ignore */ }
        }
    } catch (Throwable $e) { /* ignore */ }





    // Course registrations (course form manager)
    $pdo->exec("CREATE TABLE IF NOT EXISTS course_registrations (
        id INT AUTO_INCREMENT PRIMARY KEY,
        student_id INT NOT NULL,
        session_name VARCHAR(30) NULL,
        semester ENUM('First','Second') NOT NULL DEFAULT 'First',
        faculty_id INT NULL,
        department_id INT NULL,
        programme_id INT NULL,
        level VARCHAR(10) NULL,
        total_units INT NOT NULL DEFAULT 0,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_cr_student (student_id),
        INDEX idx_cr_filters (session_name, semester, faculty_id, department_id, programme_id, level),
        FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
        FOREIGN KEY (faculty_id) REFERENCES faculties(id) ON DELETE SET NULL,
        FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE SET NULL,
        FOREIGN KEY (programme_id) REFERENCES programmes(id) ON DELETE SET NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // Self-healing: if course_registrations table exists but was created without newer columns, add them safely
    try {
        $stmt = $pdo->prepare("SHOW COLUMNS FROM course_registrations LIKE 'level'");
        $stmt->execute();
        if ($stmt->rowCount() == 0) {
            $pdo->exec("ALTER TABLE course_registrations ADD COLUMN level VARCHAR(10) NULL AFTER programme_id");
        }
        $stmt = $pdo->prepare("SHOW COLUMNS FROM course_registrations LIKE 'session_name'");
        $stmt->execute();
        if ($stmt->rowCount() == 0) {
            $pdo->exec("ALTER TABLE course_registrations ADD COLUMN session_name VARCHAR(30) NULL AFTER student_id");
        }
        $stmt = $pdo->prepare("SHOW COLUMNS FROM course_registrations LIKE 'semester'");
        $stmt->execute();
        if ($stmt->rowCount() == 0) {
            $pdo->exec("ALTER TABLE course_registrations ADD COLUMN semester ENUM('First','Second') NOT NULL DEFAULT 'First' AFTER session_name");
        }
    } catch (Throwable $e) {
        // ignore self-heal failures silently to avoid breaking the whole app
    }


    $pdo->exec("CREATE TABLE IF NOT EXISTS course_registration_items (
        id INT AUTO_INCREMENT PRIMARY KEY,
        registration_id INT NOT NULL,
        course_id INT NULL,
        course_code VARCHAR(30) NOT NULL,
        course_title VARCHAR(255) NOT NULL,
        credit_unit INT NOT NULL DEFAULT 0,
        course_type ENUM('C','E') NOT NULL DEFAULT 'C',
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_cri_reg (registration_id),
        INDEX idx_cri_code (course_code),
        FOREIGN KEY (registration_id) REFERENCES course_registrations(id) ON DELETE CASCADE,
        FOREIGN KEY (course_id) REFERENCES registrable_courses(id) ON DELETE SET NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // Registration settings (open/close + min/max credit units)
    // ✅ Departmental rules: can be set per session+semester+faculty+department+level
    // Fallback order used during enforcement:
    // 1) exact match (session+semester+department+level)
    // 2) department-wide (session+semester+department, level NULL)
    // 3) global (session+semester, department NULL, level NULL)
    $pdo->exec("CREATE TABLE IF NOT EXISTS registration_settings (
        id INT AUTO_INCREMENT PRIMARY KEY,
        session_name VARCHAR(30) NULL,
        semester ENUM('First','Second') NOT NULL DEFAULT 'First',
        is_open ENUM('Yes','No') NOT NULL DEFAULT 'Yes',
        faculty_id INT NULL,
        department_id INT NULL,
        level VARCHAR(10) NULL,
        min_units INT NOT NULL DEFAULT 0,
        max_units INT NOT NULL DEFAULT 30,
        updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_regset (session_name, semester, department_id, level),
        INDEX idx_regset_lookup (session_name, semester, department_id, level),
        INDEX idx_regset_fac_dept (faculty_id, department_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // Self-heal older installs that used different column names (e.g. session instead of session_name)
    try {
        $st = $pdo->prepare("SHOW COLUMNS FROM registration_settings LIKE 'session_name'");
        $st->execute();
        $hasSessionName = ($st->rowCount() > 0);

        if (!$hasSessionName) {
            $st = $pdo->prepare("SHOW COLUMNS FROM registration_settings LIKE 'session'");
            $st->execute();
            $hasSessionLegacy = ($st->rowCount() > 0);

            try { $pdo->exec("ALTER TABLE registration_settings ADD COLUMN session_name VARCHAR(30) NULL AFTER id"); } catch (Throwable $e) {}
            if ($hasSessionLegacy) {
                try { $pdo->exec("UPDATE registration_settings SET session_name = COALESCE(session_name, session)"); } catch (Throwable $e) {}
            }
        }

        // Ensure new departmental columns exist
        $needCols = [
            'faculty_id' => "ALTER TABLE registration_settings ADD COLUMN faculty_id INT NULL AFTER is_open",
            'department_id' => "ALTER TABLE registration_settings ADD COLUMN department_id INT NULL AFTER faculty_id",
            'level' => "ALTER TABLE registration_settings ADD COLUMN level VARCHAR(10) NULL AFTER department_id",
            // ✅ add credit-unit limits (older installs may not have them)
            'min_units' => "ALTER TABLE registration_settings ADD COLUMN min_units INT NOT NULL DEFAULT 0 AFTER level",
            'max_units' => "ALTER TABLE registration_settings ADD COLUMN max_units INT NOT NULL DEFAULT 30 AFTER min_units",
        ];
        foreach ($needCols as $col => $sql) {
            $st = $pdo->prepare("SHOW COLUMNS FROM registration_settings LIKE ?");
            $st->execute([$col]);
            if ($st->rowCount() == 0) {
                try { $pdo->exec($sql); } catch (Throwable $e) {}
            }
        }

        // Ensure unique key exists in a compatible way
        try { $pdo->exec("ALTER TABLE registration_settings ADD UNIQUE KEY uniq_regset (session_name, semester, department_id, level)"); } catch (Throwable $e) {}
    } catch (Throwable $e) { /* ignore */ }

    // Seed default registration settings for current academic session (global fallback)
    try {
        $curSess = portalNormalizeSessionNameSafe((string)setting('academic_session',''));
        if ($curSess !== '') {
            foreach (['First','Second'] as $semTmp) {
                $st = $pdo->prepare("SELECT COUNT(*) FROM registration_settings WHERE COALESCE(session_name,'')=? AND semester=? AND department_id IS NULL AND level IS NULL");
                $st->execute([$curSess, $semTmp]);
                if ((int)$st->fetchColumn() === 0) {
                    $ins = $pdo->prepare("INSERT INTO registration_settings (session_name, semester, is_open, faculty_id, department_id, programme_id, level, min_units, max_units) VALUES (?,?,?,?,?,?,?,?,?)");
                    $ins->execute([$curSess, $semTmp, 'Yes', null, null, null, null, 0, 30]);
                }
            }
        }
    } catch (Throwable $e) {}



    // Self-heal students mapping + display columns
    try {
        $needCols = [
            'application_no'=> "ALTER TABLE students ADD COLUMN application_no VARCHAR(100) NULL AFTER registration_no",
            'admission_no'  => "ALTER TABLE students ADD COLUMN admission_no VARCHAR(100) NULL AFTER application_no",
            'matric_no'     => "ALTER TABLE students ADD COLUMN matric_no VARCHAR(100) NULL AFTER admission_no",
            'full_name'     => "ALTER TABLE students ADD COLUMN full_name VARCHAR(160) NULL AFTER other_name",
            'faculty'       => "ALTER TABLE students ADD COLUMN faculty VARCHAR(150) NULL",
            'department'    => "ALTER TABLE students ADD COLUMN department VARCHAR(150) NULL",
            'programme'     => "ALTER TABLE students ADD COLUMN programme VARCHAR(180) NULL",
            'faculty_id'    => "ALTER TABLE students ADD COLUMN faculty_id INT NULL",
            'department_id' => "ALTER TABLE students ADD COLUMN department_id INT NULL",
            'programme_id'  => "ALTER TABLE students ADD COLUMN programme_id INT NULL",
            'level'         => "ALTER TABLE students ADD COLUMN level VARCHAR(10) NULL",
            'session'       => "ALTER TABLE students ADD COLUMN session VARCHAR(30) NULL",
            'semester'      => "ALTER TABLE students ADD COLUMN semester VARCHAR(20) NULL"
        ];
        foreach ($needCols as $c => $sql) {
            $st = $pdo->prepare("SHOW COLUMNS FROM students LIKE ?");
            $st->execute([$c]);
            if ($st->rowCount() == 0) { try { $pdo->exec($sql); } catch (Throwable $e) {} }
        }
        try { $pdo->exec("ALTER TABLE students ADD UNIQUE KEY uniq_students_matric_no (matric_no)"); } catch (Throwable $e) {}
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS matric_number_sequences (
                id INT AUTO_INCREMENT PRIMARY KEY,
                root_key VARCHAR(80) NOT NULL UNIQUE,
                last_seq INT NOT NULL DEFAULT 0,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
    } catch (Throwable $e) {}

    // Keep a system setting for current academic session in sync (best-effort)
    try { portalSelfHealAcademicSessions($pdo); } catch (Throwable $e) {}
    try {
        $st = $pdo->query("SELECT name FROM academic_sessions WHERE is_current = 1 LIMIT 1");
        $cur = (string)($st->fetchColumn() ?? '');
        if ($cur !== '') {
            $up = $pdo->prepare("INSERT INTO system_settings (setting_key, setting_value, description)
                                 VALUES ('academic_session', ?, 'Current academic session')
                                 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
            $up->execute([$cur]);
        }
    } catch (Throwable $e) {}
} catch (Throwable $e) {
    // Do not block boot
}

// Check if default admin exists
        $stmt = $pdo->prepare("SELECT COUNT(*) FROM admins");
        $stmt->execute();
        $adminCount = $stmt->fetchColumn();

        if ($adminCount == 0) {
            // Create first admin with a strong temporary password, not a public default password.
            $initialAdminPassword = trim((string)honttechAppEnv('HONTTECH_DEFAULT_ADMIN_PASSWORD', ''));
            if ($initialAdminPassword === '') {
                $initialAdminPassword = honttechGenerateTemporaryPassword(16);
                error_log('Initial admin temporary password generated for username admin: ' . $initialAdminPassword . ' -- change it immediately after first login.');
            }
            $defaultPassword = password_hash($initialAdminPassword, PASSWORD_DEFAULT);
            $stmt = $pdo->prepare("INSERT INTO admins (username, password, email, full_name)
                       VALUES (?, ?, ?, ?)");
            $stmt->execute(['admin', $defaultPassword, 'admin@honttech.com.ng', 'System Administrator']);
        }

        // Check if system settings exist
        $stmt = $pdo->prepare("SELECT COUNT(*) FROM system_settings");
        $stmt->execute();
        $settingsCount = $stmt->fetchColumn();

        if ($settingsCount == 0) {
            // Create default settings
            $stmt = $pdo->prepare("INSERT INTO system_settings (setting_key, setting_value, description) VALUES
                       (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?)");
            $stmt->execute([
                'registration_fee', '10000', 'Registration fee in Naira',
                'payment_description', 'HontTech Registration Fee', 'Description for payment',
                'acceptance_fee', '15000', 'Acceptance fee in Naira',
                'hostel_fee', '25000', 'Hostel accommodation fee in Naira',
                'school_fee', '75000', 'School fee in Naira',
                'academic_session', '2023/2024', 'Current academic session',
                'application_deadline', '2023-12-31', 'Application deadline date',
                'email_notifications', 'enabled', 'Enable or disable email notifications',
                'payment_approval', 'automatic', 'Payment approval mode: automatic or manual',
                'installment_enabled', 'enabled', 'Enable or disable installment payment for school fees'
            ]);
        }


// Ensure required settings keys exist (self-healing for upgrades)
$requiredDefaults = [
    'portal_title' => ['HontTech System', 'Portal title shown on the sidebar/header'],

    // If enabled, the portal will add estimated gateway charges on top of the base fee,
    // so that the payer bears transaction charges instead of the school.
    'payer_bears_charges' => ['enabled', 'If enabled, payer bears estimated gateway charges (adds charges on top of base amount)'],

    'paystack_enabled' => ['enabled', 'Enable/disable Paystack gateway'],
    'paystack_public_key' => [defined('PAYSTACK_PUBLIC_KEY') ? PAYSTACK_PUBLIC_KEY : '', 'Paystack public key'],
    'paystack_secret_key' => [defined('PAYSTACK_SECRET_KEY') ? PAYSTACK_SECRET_KEY : '', 'Paystack secret key'],

    // Gateway charge estimates (admin can adjust anytime)
    // Paystack typical local estimate (example): 1.5% + NGN100, cap NGN2000, waive flat below NGN2500
    'paystack_charge_percent' => ['1.5', 'Estimated Paystack charge percent (for payer-bears-charges)'],
    'paystack_charge_flat' => ['100', 'Estimated Paystack flat charge (NGN) (for payer-bears-charges)'],
    'paystack_charge_cap' => ['2000', 'Estimated Paystack maximum charge cap (NGN) (0 = no cap)'],
    'paystack_charge_flat_waive_below' => ['2500', 'Waive Paystack flat charge if base amount is below this (0 = never waive)'],
    'online_payment_enabled' => ['enabled', 'Master switch for online payments (Paystack/Flutterwave/others)'],
    'default_payment_method' => ['auto', 'Default student payment method preference: auto or manual'],
    'default_gateway' => ['paystack', 'Default online payment gateway shown first to students'],
    'default_gateways' => ['paystack', 'Comma-separated list of default gateways shown first (e.g. paystack,flutterwave)'],
    'flutterwave_enabled' => ['disabled', 'Enable/disable Flutterwave gateway'],
    'flutterwave_public_key' => [defined('FLUTTERWAVE_PUBLIC_KEY') ? FLUTTERWAVE_PUBLIC_KEY : '', 'Flutterwave public key'],
    'flutterwave_secret_key' => [defined('FLUTTERWAVE_SECRET_KEY') ? FLUTTERWAVE_SECRET_KEY : '', 'Flutterwave secret key'],
    'flutterwave_currency' => ['NGN', 'Flutterwave currency (default NGN)'],

    // Flutterwave estimate defaults are set to 0 so admin can configure based on their pricing
    'flutterwave_charge_percent' => ['0', 'Estimated Flutterwave charge percent (for payer-bears-charges)'],
    'flutterwave_charge_flat' => ['0', 'Estimated Flutterwave flat charge (NGN) (for payer-bears-charges)'],
    'flutterwave_charge_cap' => ['0', 'Estimated Flutterwave maximum charge cap (NGN) (0 = no cap)'],
    'flutterwave_charge_flat_waive_below' => ['0', 'Waive Flutterwave flat charge if base amount is below this (0 = never waive)'],
    'monnify_enabled' => ['disabled', 'Enable/disable Monnify gateway'],
    'monnify_api_key' => [defined('MONNIFY_API_KEY') ? MONNIFY_API_KEY : '', 'Monnify API key'],
    'monnify_secret_key' => [defined('MONNIFY_SECRET_KEY') ? MONNIFY_SECRET_KEY : '', 'Monnify secret key'],
    'monnify_contract_code' => [defined('MONNIFY_CONTRACT_CODE') ? MONNIFY_CONTRACT_CODE : '', 'Monnify contract code'],
    'remita_enabled' => ['disabled', 'Enable/disable Remita gateway'],
    'remita_merchant_id' => [defined('REMITA_MERCHANT_ID') ? REMITA_MERCHANT_ID : '', 'Remita merchant id'],
    'remita_service_type_id' => [defined('REMITA_SERVICE_TYPE_ID') ? REMITA_SERVICE_TYPE_ID : '', 'Remita service type id'],
    'remita_api_key' => [defined('REMITA_API_KEY') ? REMITA_API_KEY : '', 'Remita API key'],
    'paypal_enabled' => ['disabled', 'Enable/disable PayPal gateway'],
    'paypal_client_id' => [defined('PAYPAL_CLIENT_ID') ? PAYPAL_CLIENT_ID : '', 'PayPal client id'],
    'paypal_client_secret' => [defined('PAYPAL_CLIENT_SECRET') ? PAYPAL_CLIENT_SECRET : '', 'PayPal client secret'],
'interswitch_enabled' => ['disabled', 'Enable/disable Interswitch WebPAY gateway'],
'interswitch_merchant_code' => ['', 'Interswitch Merchant Code (Quickteller Business)'],
'interswitch_pay_item_id' => ['', 'Interswitch Pay Item ID (Quickteller Business)'],
'interswitch_currency' => ['566', 'Interswitch currency numeric ISO code (566 = NGN)'],
'interswitch_mode' => ['LIVE', 'Interswitch mode: LIVE or TEST'],

    // Interswitch estimate defaults (set to 0; configure in admin settings if you want payer-bears-charges)
    'interswitch_charge_percent' => ['0', 'Estimated Interswitch charge percent (for payer-bears-charges)'],
    'interswitch_charge_flat' => ['0', 'Estimated Interswitch flat charge (NGN) (for payer-bears-charges)'],
    'interswitch_charge_cap' => ['0', 'Estimated Interswitch maximum charge cap (NGN) (0 = no cap)'],
    'interswitch_charge_flat_waive_below' => ['0', 'Waive Interswitch flat charge if base amount is below this (0 = never waive)'],

    'manual_payment_enabled' => ['enabled', 'Enable/disable manual payment option'],
    'manual_payment_instructions' => ['Pay into the school account and upload your teller/receipt for verification.', 'Instructions shown to students for manual payment'],
    'smtp_host' => [defined('SMTP_HOST') ? SMTP_HOST : 'smtp.gmail.com', 'SMTP host'],
    'smtp_port' => [defined('SMTP_PORT') ? (string)SMTP_PORT : '587', 'SMTP port'],
    'smtp_username' => [defined('SMTP_USERNAME') ? SMTP_USERNAME : '', 'SMTP username'],
    'smtp_password' => [defined('SMTP_PASSWORD') ? SMTP_PASSWORD : '', 'SMTP password (App password recommended)'],
    'smtp_from_email' => [defined('FROM_EMAIL') ? FROM_EMAIL : '', 'From email address'],
    'smtp_from_name' => [defined('FROM_NAME') ? FROM_NAME : APP_NAME, 'From name'],
    'smtp_reply_to' => [defined('REPLY_TO_EMAIL') ? REPLY_TO_EMAIL : '', 'Reply-to email'],
    'idcard_signatory_name' => ['Authorized Signatory', 'Name under signature on Student ID Card'],
    'admission_signatory_name' => ['Admissions Officer', 'Name under signature on Admission Letter'],
    'application_signatory_name' => ['Admissions Officer', 'Name under signature on Application Preview/Print'],
];

$gatewayRegistryForDefaults = portalPaymentGatewayRegistry();
foreach ($gatewayRegistryForDefaults as $providerKey => $providerDef) {
    $requiredDefaults[$providerKey . '_enabled'] = [$requiredDefaults[$providerKey . '_enabled'][0] ?? (!empty($providerDef['native_checkout']) && $providerKey === 'paystack' ? 'enabled' : 'disabled'), 'Enable/disable ' . (string)($providerDef['label'] ?? $providerKey) . ' gateway'];
    foreach ((array)($providerDef['fields'] ?? []) as $fieldKey => $fieldMeta) {
        $settingKey = $providerKey . '_' . $fieldKey;
        if (!isset($requiredDefaults[$settingKey])) {
            $requiredDefaults[$settingKey] = [(string)($fieldMeta['default'] ?? ''), (string)($fieldMeta['label'] ?? ucwords(str_replace('_', ' ', $fieldKey))) . ' for ' . (string)($providerDef['label'] ?? $providerKey)];
        }
    }
    foreach (['charge_percent', 'charge_flat', 'charge_cap', 'charge_flat_waive_below'] as $chargeKey) {
        $settingKey = $providerKey . '_' . $chargeKey;
        if (!isset($requiredDefaults[$settingKey])) {
            $chargeDefault = ($providerKey === 'paystack')
                ? ($chargeKey === 'charge_percent' ? '1.5' : ($chargeKey === 'charge_flat' ? '100' : ($chargeKey === 'charge_cap' ? '2000' : '2500')))
                : '0';
            $requiredDefaults[$settingKey] = [$chargeDefault, 'Estimated ' . (string)($providerDef['label'] ?? $providerKey) . ' ' . str_replace('_', ' ', $chargeKey) . ' for payer-bears-charges calculations'];
        }
    }
}

foreach ($requiredDefaults as $k => $arr) {
    $val = (string)$arr[0];
    $desc = (string)$arr[1];
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM system_settings WHERE setting_key = ?");
    $stmt->execute([$k]);
    if ((int)$stmt->fetchColumn() === 0) {
        $ins = $pdo->prepare("INSERT INTO system_settings (setting_key, setting_value, description) VALUES (?,?,?)");
        $ins->execute([$k, $val, $desc]);
    }
}


        // =============================
        // Messaging + Notifications schema (self-healing)
        // =============================
        // Add offer decision columns to students
        $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'offer_status'");
        $stmt->execute();
        if ($stmt->rowCount() == 0) {
            honttechSafeAddColumn($pdo, 'students', 'offer_status', "ENUM('Pending','Accepted','Rejected') DEFAULT 'Pending'", 'acceptance_fee_status');
            honttechSafeAddColumn($pdo, 'students', 'offer_decision_date', 'DATETIME NULL', 'offer_status');
        }

        // Self-healing: ensure students.level exists (used for course registration/results/filters)
        $stmt = $pdo->prepare("SHOW COLUMNS FROM students LIKE 'level'");
        $stmt->execute();
        if ($stmt->rowCount() == 0) {
            // Use VARCHAR so it works for systems that store levels like 100/200 or ND1/HND2 etc.
            honttechSafeAddColumn($pdo, 'students', 'level', 'VARCHAR(10) NULL', 'programme_id');
        }

        // Notifications table
        $pdo->exec("CREATE TABLE IF NOT EXISTS notifications (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_role ENUM('admin','student') NOT NULL,
            user_id INT NOT NULL,
            title VARCHAR(200) NOT NULL,
            body TEXT NULL,
            link VARCHAR(255) NULL,
            is_read TINYINT DEFAULT 0,
            read_at DATETIME NULL,
            archived TINYINT DEFAULT 0,
            archived_at DATETIME NULL,
            created_at DATETIME NOT NULL,
            INDEX idx_user (user_role, user_id),
            INDEX idx_archived (archived, archived_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        // Messages (header + recipients) for individual + broadcast
        $pdo->exec("CREATE TABLE IF NOT EXISTS messages (
            id INT AUTO_INCREMENT PRIMARY KEY,
            sender_role ENUM('admin','student') NOT NULL,
            sender_id INT NOT NULL,
            subject VARCHAR(200) NOT NULL,
            body TEXT NOT NULL,
            created_at DATETIME NOT NULL,
            INDEX idx_sender (sender_role, sender_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        $pdo->exec("CREATE TABLE IF NOT EXISTS message_recipients (
            id INT AUTO_INCREMENT PRIMARY KEY,
            message_id INT NOT NULL,
            recipient_role ENUM('admin','student') NOT NULL,
            recipient_id INT NOT NULL,
            is_read TINYINT DEFAULT 0,
            read_at DATETIME NULL,
            created_at DATETIME NOT NULL,
            INDEX idx_recipient (recipient_role, recipient_id, is_read),
            FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        // ---- Schema self-heal for older databases (prevent missing column errors) ----
        try {
            // notifications table columns
            $stmt = $pdo->prepare("SHOW COLUMNS FROM notifications LIKE 'user_role'");
            $stmt->execute();
            if ($stmt->rowCount() == 0) {
                $pdo->exec("ALTER TABLE notifications ADD COLUMN user_role VARCHAR(20) NOT NULL DEFAULT 'student' AFTER id");
                // Backfill from older column name if present
                $stmt2 = $pdo->prepare("SHOW COLUMNS FROM notifications LIKE 'user_type'");
                $stmt2->execute();
                if ($stmt2->rowCount() > 0) {
                    $pdo->exec("UPDATE notifications SET user_role = COALESCE(user_role, user_type)");
                }
            }
            $stmt = $pdo->prepare("SHOW COLUMNS FROM notifications LIKE 'user_id'");
            $stmt->execute();
            if ($stmt->rowCount() == 0) {
                $pdo->exec("ALTER TABLE notifications ADD COLUMN user_id INT NOT NULL DEFAULT 0");
            }
            $stmt = $pdo->prepare("SHOW COLUMNS FROM notifications LIKE 'archived'");
            $stmt->execute();
            if ($stmt->rowCount() == 0) {
                $pdo->exec("ALTER TABLE notifications ADD COLUMN archived TINYINT NOT NULL DEFAULT 0");
            }
            $stmt = $pdo->prepare("SHOW COLUMNS FROM notifications LIKE 'archived_at'");
            $stmt->execute();
            if ($stmt->rowCount() == 0) {
                $pdo->exec("ALTER TABLE notifications ADD COLUMN archived_at DATETIME NULL");
            }

            // messages table columns
            $stmt = $pdo->prepare("SHOW COLUMNS FROM messages LIKE 'sender_role'");
            $stmt->execute();
            if ($stmt->rowCount() == 0) {
                $pdo->exec("ALTER TABLE messages ADD COLUMN sender_role VARCHAR(20) NOT NULL DEFAULT 'admin'");
            }
            $stmt = $pdo->prepare("SHOW COLUMNS FROM messages LIKE 'sender_id'");
            $stmt->execute();
            if ($stmt->rowCount() == 0) {
                $pdo->exec("ALTER TABLE messages ADD COLUMN sender_id INT NOT NULL DEFAULT 0");
            }

            // message_recipients table columns
            $stmt = $pdo->prepare("SHOW COLUMNS FROM message_recipients LIKE 'recipient_role'");
            $stmt->execute();
            if ($stmt->rowCount() == 0) {
                $pdo->exec("ALTER TABLE message_recipients ADD COLUMN recipient_role VARCHAR(20) NOT NULL DEFAULT 'student'");
                $stmt2 = $pdo->prepare("SHOW COLUMNS FROM message_recipients LIKE 'user_role'");
                $stmt2->execute();
                if ($stmt2->rowCount() > 0) {
                    $pdo->exec("UPDATE message_recipients SET recipient_role = COALESCE(recipient_role, user_role)");
                }
            }
            $stmt = $pdo->prepare("SHOW COLUMNS FROM message_recipients LIKE 'recipient_id'");
            $stmt->execute();
            if ($stmt->rowCount() == 0) {
                $pdo->exec("ALTER TABLE message_recipients ADD COLUMN recipient_id INT NOT NULL DEFAULT 0");
            }
        } catch (Throwable $e) {
            // Silent fail: schema self-heal should never break the app
        }

// Create upload directory if it doesn't exist
        if (!is_dir(UPLOAD_DIR)) {
            mkdir(UPLOAD_DIR, 0755, true);
        }

        return true;
    } catch (Throwable $e) {
        error_log("Database initialization error: " . $e->getMessage());
        try { honttechEnsureSystemSettingsTable($pdo); } catch (Throwable $ignored) {}
        try { if (function_exists('honttechEnsureCoreSchema') && honttechEnsureCoreSchema($pdo)) { return true; } } catch (Throwable $ignored) {}
        return honttechDatabaseHasCoreTables($pdo);
    }
}
}


// Initialize database
$initResult = initializeDatabase();
if (!$initResult && function_exists('honttechEnsureCoreSchema')) {
    $initResult = honttechEnsureCoreSchema($pdo);
}
try { portalEnsureLateFeeSupport($pdo); } catch (Throwable $e) { /* ignore */ }
try { $pdo->exec("ALTER TABLE students MODIFY registration_no VARCHAR(100) NOT NULL"); } catch (Throwable $e) { /* ignore */ }
try { $pdo->exec("ALTER TABLE students MODIFY application_no VARCHAR(100) NULL"); } catch (Throwable $e) { /* ignore */ }
try { $pdo->exec("ALTER TABLE students MODIFY admission_no VARCHAR(100) NULL"); } catch (Throwable $e) { /* ignore */ }
try { $pdo->exec("ALTER TABLE students MODIFY matric_no VARCHAR(100) NULL"); } catch (Throwable $e) { /* ignore */ }
try { $pdo->exec("ALTER TABLE students ADD UNIQUE KEY uniq_students_application_no (application_no)"); } catch (Throwable $e) { /* ignore */ }
try { $pdo->exec("ALTER TABLE students ADD UNIQUE KEY uniq_students_admission_no (admission_no)"); } catch (Throwable $e) { /* ignore */ }
try { $pdo->exec("ALTER TABLE students ADD UNIQUE KEY uniq_students_matric_no (matric_no)"); } catch (Throwable $e) { /* ignore */ }
try { $pdo->exec("UPDATE students SET application_no = registration_no WHERE (application_no IS NULL OR application_no = '') AND registration_no IS NOT NULL AND registration_no <> ''"); } catch (Throwable $e) { /* ignore */ }

// Extra self-healing for new modules (roles/results)
try { ensureStaffAclSchema($pdo); } catch (Throwable $e) { /* ignore */ }
if (!function_exists('ensureResultsSchema')) {
    function ensureResultsSchema(PDO $pdo): void {
        // results submissions (per course per session/semester)
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS result_submissions (
                id INT AUTO_INCREMENT PRIMARY KEY,
                course_id INT NOT NULL,
                session VARCHAR(30) NOT NULL,
                semester VARCHAR(20) NOT NULL,
                lecturer_id INT NOT NULL,
                status VARCHAR(30) NOT NULL DEFAULT 'draft', -- draft|submitted|pending_review|reviewed|approved|rejected|returned|published
                submitted_at DATETIME NULL,
                reviewed_by INT NULL,
                reviewed_at DATETIME NULL,
                approved_by INT NULL,
                approved_at DATETIME NULL,
                rejected_by INT NULL,
                rejected_at DATETIME NULL,
                returned_by INT NULL,
                returned_at DATETIME NULL,
                published_by INT NULL,
                published_at DATETIME NULL,
                approval_note TEXT NULL,
                rejection_reason TEXT NULL,
                correction_note TEXT NULL,
                remark VARCHAR(255) NULL,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_submission (course_id, session, semester, lecturer_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'reviewed_by')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN reviewed_by INT NULL AFTER submitted_at"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'reviewed_at')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN reviewed_at DATETIME NULL AFTER reviewed_by"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'rejected_by')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN rejected_by INT NULL AFTER approved_at"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'rejected_at')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN rejected_at DATETIME NULL AFTER rejected_by"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'returned_by')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN returned_by INT NULL AFTER rejected_at"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'returned_at')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN returned_at DATETIME NULL AFTER returned_by"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'approval_note')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN approval_note TEXT NULL AFTER published_at"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'rejection_reason')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN rejection_reason TEXT NULL AFTER approval_note"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'correction_note')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN correction_note TEXT NULL AFTER rejection_reason"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'result_submissions', 'updated_at')) { $pdo->exec("ALTER TABLE result_submissions ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER remark"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'result_submissions') && !portalIndexExistsSafe($pdo, 'result_submissions', 'idx_result_submissions_status')) { $pdo->exec("CREATE INDEX idx_result_submissions_status ON result_submissions(status, session, semester)"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'result_submissions') && !portalIndexExistsSafe($pdo, 'result_submissions', 'idx_result_submissions_course')) { $pdo->exec("CREATE INDEX idx_result_submissions_course ON result_submissions(course_id, session, semester)"); } } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS result_submission_audit (
                id INT AUTO_INCREMENT PRIMARY KEY,
                submission_id INT NOT NULL,
                action_type VARCHAR(40) NOT NULL,
                action_note TEXT NULL,
                actor_id INT NULL,
                actor_name VARCHAR(190) NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_result_submission_audit_submission (submission_id),
                INDEX idx_result_submission_audit_action (action_type)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        // individual results
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS results (
                id INT AUTO_INCREMENT PRIMARY KEY,
                submission_id INT NULL,
                course_id INT NOT NULL,
                student_id INT NOT NULL,
                session VARCHAR(30) NOT NULL,
                semester VARCHAR(20) NOT NULL,
                ca_score DECIMAL(5,2) NOT NULL DEFAULT 0,
                exam_score DECIMAL(5,2) NOT NULL DEFAULT 0,
                total_score DECIMAL(5,2) NOT NULL DEFAULT 0,
                grade VARCHAR(2) NULL,
                grade_point DECIMAL(4,2) NULL,
                status VARCHAR(20) NOT NULL DEFAULT 'draft',
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_course_student (course_id, student_id, session, semester)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }
        try { if (portalTableExistsSafe($pdo, 'results') && !portalIndexExistsSafe($pdo, 'results', 'idx_results_submission_status')) { $pdo->exec("CREATE INDEX idx_results_submission_status ON results(submission_id, status)"); } } catch (Throwable $e) {}

        // assign lecturers to courses (self-heal on registrable_courses)
        try { $pdo->exec("ALTER TABLE registrable_courses ADD COLUMN lecturer_staff_id INT NULL AFTER registrable"); } catch (Throwable $e) { /* ignore */ }
    }
}
try { ensureResultsSchema($pdo); } catch (Throwable $e) { /* ignore */ }


if (!$initResult) {
    // Last-resort repair: do not stop on optional legacy migrations. Only block if the absolute core tables/columns cannot be created/read.
    try { honttechEnsureSystemSettingsTable($pdo); } catch (Throwable $e) { error_log('Final system_settings repair failed: ' . $e->getMessage()); }
    try { if (function_exists('honttechEnsureCoreSchema')) { $initResult = honttechEnsureCoreSchema($pdo); } } catch (Throwable $e) { error_log('Final core schema repair failed: ' . $e->getMessage()); }
    try { if (!$initResult && function_exists('honttechDatabaseHasCoreTables')) { $initResult = honttechDatabaseHasCoreTables($pdo); } } catch (Throwable $e) { error_log('Final core schema verification failed: ' . $e->getMessage()); }
}

if (!$initResult) {
    error_log('Database initialization did not complete after final core-schema repair. Check DB credentials and CREATE/ALTER/SELECT/INSERT/UPDATE/DELETE permissions for user: ' . DB_USER . ' on database: ' . DB_NAME);
    http_response_code(500);
    $safeDbName = function_exists('e') ? e((string)DB_NAME) : htmlspecialchars((string)DB_NAME, ENT_QUOTES, 'UTF-8');
    $safeDbUser = function_exists('e') ? e((string)DB_USER) : htmlspecialchars((string)DB_USER, ENT_QUOTES, 'UTF-8');
    die(APP_DEBUG
        ? 'Database setup could not complete because the application could not create/read the minimum tables in database <strong>' . $safeDbName . '</strong> with MySQL user <strong>' . $safeDbUser . '</strong>. Confirm DB_HOST, DB_PORT, DB_SOCKET, DB_NAME, DB_USER and DB_PASS. ALTER and INDEX repairs are retried later and logged; they are not required for the bootstrap check.'
        : 'Database setup could not be completed. Please confirm DB_HOST, DB_PORT, DB_SOCKET, DB_NAME, DB_USER and DB_PASS, and make sure the MySQL user can connect to the selected database and create/read tables.');
}


// =============================
// System Settings + Flash helpers (self-healing friendly)
// =============================
if (!function_exists('getSystemSettingValue')) {
    function getSystemSettingValue(PDO $pdo, string $key, $default = ''): string {
        try {
            $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = ? LIMIT 1");
            $stmt->execute([$key]);
            $val = $stmt->fetchColumn();
            if ($val === false || $val === null) return (string)$default;
            return (string)$val;
        } catch (Throwable $e) {
            return (string)$default;
        }
    }
}
if (!function_exists('setSystemSettingValue')) {
    function setSystemSettingValue(PDO $pdo, string $key, string $value, string $description = ''): void {
        try {
            $stmt = $pdo->prepare("INSERT INTO system_settings (setting_key, setting_value, description) VALUES (?,?,?)
                                   ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), description = CASE WHEN VALUES(description) <> '' THEN VALUES(description) ELSE description END, updated_at = CURRENT_TIMESTAMP");
            $stmt->execute([$key, $value, $description]);
        } catch (Throwable $e) {}
    }
}
if (isset($pdo) && ($pdo instanceof PDO) && function_exists('portalSynchronizeCurrentSessionAcrossPortal')) { try { portalSynchronizeCurrentSessionAcrossPortal($pdo); } catch (Throwable $e) {} }

if (!function_exists('setting')) {
    function setting(string $key, $default = ''): string {
        global $pdo;
        return getSystemSettingValue($pdo, $key, $default);
    }
}


if (!function_exists('portalNormalizeHexColor')) {
    function portalNormalizeHexColor($value, string $default = '#003366'): string {
        $value = strtoupper(trim((string)$value));
        if ($value === '') return strtoupper($default);
        if ($value[0] !== '#') $value = '#' . $value;
        if (preg_match('/^#([A-F0-9]{3})$/', $value, $m)) {
            $hex = $m[1];
            return '#' . $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
        }
        if (preg_match('/^#([A-F0-9]{6})$/', $value)) {
            return $value;
        }
        return strtoupper($default);
    }
}
if (!function_exists('portalHexToRgba')) {
    function portalHexToRgba($hex, float $alpha = 1): string {
        $hex = portalNormalizeHexColor($hex, '#003366');
        $alpha = max(0, min(1, $alpha));
        $hex = ltrim($hex, '#');
        return 'rgba(' . hexdec(substr($hex, 0, 2)) . ', ' . hexdec(substr($hex, 2, 2)) . ', ' . hexdec(substr($hex, 4, 2)) . ', ' . rtrim(rtrim(number_format($alpha, 3, '.', ''), '0'), '.') . ')';
    }
}
if (!function_exists('portalContrastColor')) {
    function portalContrastColor($hex, string $dark = '#0F172A', string $light = '#FFFFFF'): string {
        $hex = portalNormalizeHexColor($hex, '#003366');
        $rgb = ltrim($hex, '#');
        $r = hexdec(substr($rgb, 0, 2));
        $g = hexdec(substr($rgb, 2, 2));
        $b = hexdec(substr($rgb, 4, 2));
        $yiq = (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
        return $yiq >= 160 ? $dark : $light;
    }
}
if (!function_exists('portalBrandingDefaults')) {
    function portalBrandingDefaults(?PDO $pdo = null): array {
        $legacyPrimary = $pdo instanceof PDO ? getSystemSettingValue($pdo, 'theme_color_1', '#003366') : '#003366';
        $legacyAccent = $pdo instanceof PDO ? getSystemSettingValue($pdo, 'theme_color_2', '#f0a500') : '#f0a500';
        $legacyBg = $pdo instanceof PDO ? getSystemSettingValue($pdo, 'theme_color_3', '#f4f7f6') : '#f4f7f6';
        $legacySurface = $pdo instanceof PDO ? getSystemSettingValue($pdo, 'theme_color_4', '#ffffff') : '#ffffff';
        $legacyText = $pdo instanceof PDO ? getSystemSettingValue($pdo, 'theme_color_5', '#333333') : '#333333';
        return [
            'branding_primary_color' => portalNormalizeHexColor($legacyPrimary, '#003366'),
            'branding_secondary_color' => portalNormalizeHexColor('#0B5ED7', '#0B5ED7'),
            'branding_accent_color' => portalNormalizeHexColor($legacyAccent, '#f0a500'),
            'branding_sidebar_bg_color' => portalNormalizeHexColor($legacyPrimary, '#003366'),
            'branding_header_bg_color' => portalNormalizeHexColor($legacyPrimary, '#003366'),
            'branding_button_color' => portalNormalizeHexColor($legacyPrimary, '#003366'),
            'branding_link_color' => portalNormalizeHexColor($legacyAccent, '#f0a500'),
            'branding_card_header_color' => portalNormalizeHexColor($legacyPrimary, '#003366'),
            'branding_card_background_color' => portalNormalizeHexColor($legacyPrimary, '#003366'),
            'branding_card_text_color' => portalNormalizeHexColor(portalContrastColor($legacyPrimary), '#FFFFFF'),
            'branding_table_header_color' => portalNormalizeHexColor($legacyPrimary, '#003366'),
            'branding_badge_success_color' => portalNormalizeHexColor('#198754', '#198754'),
            'branding_badge_warning_color' => portalNormalizeHexColor('#F59E0B', '#F59E0B'),
            'branding_badge_danger_color' => portalNormalizeHexColor('#DC3545', '#DC3545'),
            'branding_surface_color' => portalNormalizeHexColor($legacySurface, '#FFFFFF'),
            'branding_text_color' => portalNormalizeHexColor($legacyText, '#333333'),
            'branding_background_color' => portalNormalizeHexColor($legacyBg, '#F4F7F6'),
            'branding_apply_scope' => 'global',
            'branding_apply_admin_pages' => '1',
            'branding_apply_student_pages' => '1',
            'branding_apply_login_page' => '1',
            'branding_apply_sidebar' => '1',
            'branding_apply_header' => '1',
            'branding_apply_dashboard_cards' => '1',
            'branding_apply_forms' => '1',
            'branding_apply_tables' => '1',
            'branding_apply_buttons' => '1',
            'branding_apply_links' => '1',
            'branding_apply_status_badges' => '0',
        ];
    }
}
if (!function_exists('portalBrandingEnsureSettings')) {
    function portalBrandingEnsureSettings(PDO $pdo): void {
        foreach (portalBrandingDefaults($pdo) as $key => $default) {
            if (trim((string)getSystemSettingValue($pdo, $key, '')) === '') {
                $description = 'Portal branding setting for ' . str_replace('_', ' ', $key);
                setSystemSettingValue($pdo, $key, (string)$default, $description);
            }
        }
    }
}
if (!function_exists('portalBrandingSettings')) {
    function portalBrandingSettings(PDO $pdo): array {
        portalBrandingEnsureSettings($pdo);
        $defaults = portalBrandingDefaults($pdo);
        $data = [];
        foreach ($defaults as $key => $default) {
            $value = getSystemSettingValue($pdo, $key, (string)$default);
            if (str_contains($key, '_color')) {
                $data[$key] = portalNormalizeHexColor($value, (string)$default);
            } elseif (str_starts_with($key, 'branding_apply_') && $key !== 'branding_apply_scope') {
                $data[$key] = ((string)$value === '1' || strtolower((string)$value) === 'enabled') ? '1' : '0';
            } elseif ($key === 'branding_apply_scope') {
                $data[$key] = in_array((string)$value, ['global', 'selected'], true) ? (string)$value : 'global';
            } else {
                $data[$key] = (string)$value;
            }
        }
        return $data;
    }
}
if (!function_exists('portalBrandingPageContext')) {
    function portalBrandingPageContext(string $page = ''): string {
        $page = strtolower(trim($page !== '' ? $page : (string)($_GET['page'] ?? '')));
        if (in_array($page, ['login', 'forgot_password', 'reset_password', 'verify_reset_code'], true)) {
            return 'login';
        }
        $userType = strtolower(trim((string)($_SESSION['user_type'] ?? '')));
        if ($userType === 'admin') return 'admin';
        if ($userType === 'student') return 'student';
        if (str_starts_with($page, 'admin_') || in_array($page, ['reports','messages','notifications','notification_settings','feature_toggles','staff_users','staff_roles','grading_result_settings','transcript_settings'], true)) {
            return 'admin';
        }
        return 'login';
    }
}
if (!function_exists('portalBrandingAppliesToCurrentPage')) {
    function portalBrandingAppliesToCurrentPage(PDO $pdo, string $page = ''): bool {
        $settings = portalBrandingSettings($pdo);
        if (($settings['branding_apply_scope'] ?? 'global') === 'global') return true;
        $context = portalBrandingPageContext($page);
        if ($context === 'admin') return ($settings['branding_apply_admin_pages'] ?? '0') === '1';
        if ($context === 'student') return ($settings['branding_apply_student_pages'] ?? '0') === '1';
        return ($settings['branding_apply_login_page'] ?? '0') === '1';
    }
}
if (!function_exists('portalBrandingSectionActive')) {
    function portalBrandingSectionActive(array $settings, string $sectionKey): bool {
        if (($settings['branding_apply_scope'] ?? 'global') === 'global') return true;
        return ($settings[$sectionKey] ?? '0') === '1';
    }
}
if (!function_exists('portalBrandingColorFieldKeys')) {
    function portalBrandingColorFieldKeys(): array {
        return [
            'branding_primary_color',
            'branding_secondary_color',
            'branding_accent_color',
            'branding_sidebar_bg_color',
            'branding_header_bg_color',
            'branding_button_color',
            'branding_link_color',
            'branding_card_header_color',
            'branding_card_background_color',
            'branding_card_text_color',
            'branding_table_header_color',
            'branding_badge_success_color',
            'branding_badge_warning_color',
            'branding_badge_danger_color',
            'branding_surface_color',
            'branding_text_color',
            'branding_background_color',
        ];
    }
}

if (!function_exists('portalAdminThemeDefaults')) {
    function portalAdminThemeDefaults(?PDO $pdo = null): array {
        $branding = $pdo instanceof PDO ? portalBrandingDefaults($pdo) : portalBrandingDefaults(null);
        $primary = portalNormalizeHexColor((string)($branding['branding_primary_color'] ?? '#003366'), '#003366');
        $secondary = portalNormalizeHexColor((string)($branding['branding_secondary_color'] ?? '#0B5ED7'), '#0B5ED7');
        $surface = portalNormalizeHexColor((string)($branding['branding_surface_color'] ?? '#FFFFFF'), '#FFFFFF');
        $background = portalNormalizeHexColor((string)($branding['branding_background_color'] ?? '#F4F7F6'), '#F4F7F6');
        $text = portalNormalizeHexColor((string)($branding['branding_text_color'] ?? '#333333'), '#333333');
        $muted = portalNormalizeHexColor('#64748B', '#64748B');
        $adminCardBg = portalNormalizeHexColor($surface, '#FFFFFF');
        $adminStatBg = portalNormalizeHexColor((string)($branding['branding_card_background_color'] ?? $primary), $primary);
        return [
            'admin_theme_background_color' => $background,
            'admin_theme_card_background_color' => $adminCardBg,
            'admin_theme_border_color' => portalNormalizeHexColor('#D9E2EC', '#D9E2EC'),
            'admin_theme_section_header_bg_color' => $primary,
            'admin_theme_sidebar_bg_color' => portalNormalizeHexColor((string)($branding['branding_sidebar_bg_color'] ?? $primary), $primary),
            'admin_theme_sidebar_active_color' => $secondary,
            'admin_theme_sidebar_text_color' => portalNormalizeHexColor(portalContrastColor((string)($branding['branding_sidebar_bg_color'] ?? $primary)), '#FFFFFF'),
            'admin_theme_stat_card_background_color' => $adminStatBg,
            'admin_theme_stat_card_number_color' => portalNormalizeHexColor(portalContrastColor($adminStatBg), '#FFFFFF'),
            'admin_theme_stat_card_label_color' => portalNormalizeHexColor(portalContrastColor($adminStatBg), '#FFFFFF'),
            'admin_theme_card_title_color' => $primary,
            'admin_theme_heading_color' => $primary,
            'admin_theme_body_text_color' => $text,
            'admin_theme_muted_text_color' => $muted,
            'admin_theme_button_color' => portalNormalizeHexColor((string)($branding['branding_button_color'] ?? $primary), $primary),
            'admin_theme_button_text_color' => portalNormalizeHexColor(portalContrastColor((string)($branding['branding_button_color'] ?? $primary)), '#FFFFFF'),
            'admin_theme_tab_active_color' => $primary,
            'admin_theme_tab_inactive_color' => portalNormalizeHexColor('#E8EEF7', '#E8EEF7'),
            'admin_theme_tab_text_color' => $primary,
            'admin_theme_table_header_bg_color' => portalNormalizeHexColor((string)($branding['branding_table_header_color'] ?? $primary), $primary),
            'admin_theme_table_header_text_color' => portalNormalizeHexColor(portalContrastColor((string)($branding['branding_table_header_color'] ?? $primary)), '#FFFFFF'),
            'admin_theme_badge_accent_color' => portalNormalizeHexColor((string)($branding['branding_badge_success_color'] ?? '#198754'), '#198754'),
        ];
    }
}
if (!function_exists('portalAdminThemeColorFieldKeys')) {
    function portalAdminThemeColorFieldKeys(): array {
        return [
            'admin_theme_background_color',
            'admin_theme_card_background_color',
            'admin_theme_border_color',
            'admin_theme_section_header_bg_color',
            'admin_theme_sidebar_bg_color',
            'admin_theme_sidebar_active_color',
            'admin_theme_sidebar_text_color',
            'admin_theme_stat_card_background_color',
            'admin_theme_stat_card_number_color',
            'admin_theme_stat_card_label_color',
            'admin_theme_card_title_color',
            'admin_theme_heading_color',
            'admin_theme_body_text_color',
            'admin_theme_muted_text_color',
            'admin_theme_button_color',
            'admin_theme_button_text_color',
            'admin_theme_tab_active_color',
            'admin_theme_tab_inactive_color',
            'admin_theme_tab_text_color',
            'admin_theme_table_header_bg_color',
            'admin_theme_table_header_text_color',
            'admin_theme_badge_accent_color',
        ];
    }
}
if (!function_exists('portalAdminThemeEnsureSettings')) {
    function portalAdminThemeEnsureSettings(PDO $pdo): void {
        foreach (portalAdminThemeDefaults($pdo) as $key => $default) {
            if (trim((string)getSystemSettingValue($pdo, $key, '')) === '') {
                setSystemSettingValue($pdo, $key, (string)$default, 'Admin theme setting for ' . str_replace('_', ' ', $key));
            }
        }
    }
}
if (!function_exists('portalAdminThemeSettings')) {
    function portalAdminThemeSettings(PDO $pdo): array {
        portalAdminThemeEnsureSettings($pdo);
        $defaults = portalAdminThemeDefaults($pdo);
        $data = [];
        foreach ($defaults as $key => $default) {
            $data[$key] = portalNormalizeHexColor(getSystemSettingValue($pdo, $key, (string)$default), (string)$default);
        }
        return $data;
    }
}



if (!function_exists('portalDashboardDesignRuleLayoutOptions')) {
    function portalDashboardDesignRuleLayoutOptions(): array {
        return [
            'standard' => 'Standard Layout',
            'compact' => 'Compact Layout',
            'card-heavy' => 'Card-Heavy Layout',
            'academic-summary' => 'Academic Summary Layout',
            'elearning-focused' => 'E-Learning Focused Layout',
            'finance-focused' => 'Finance-Focused Layout',
            'simple' => 'Simple Layout',
            'banner-cards' => 'Banner + Cards Layout',
        ];
    }
}

if (!function_exists('portalDashboardDesignRuleColorFields')) {
    function portalDashboardDesignRuleColorFields(): array {
        return [
            'primary_color',
            'secondary_color',
            'accent_color',
            'background_color',
            'card_background_color',
            'card_border_color',
            'card_title_color',
            'body_text_color',
            'heading_color',
            'muted_text_color',
            'header_color',
            'header_text_color',
            'section_header_color',
            'card_header_color',
            'sidebar_color',
            'sidebar_text_color',
            'sidebar_inactive_color',
            'button_color',
            'button_text_color',
            'badge_color',
            'badge_success_color',
            'badge_warning_color',
            'badge_danger_color',
            'link_color',
            'stat_card_background_color',
            'stat_number_color',
            'stat_label_color',
            'tab_active_color',
            'tab_inactive_color',
            'tab_text_color',
            'table_header_bg_color',
            'table_header_text_color',
            'alert_info_color',
        ];
    }
}
if (!function_exists('portalDashboardDesignRuleVisibilityFields')) {
    function portalDashboardDesignRuleVisibilityFields(): array {
        return [
            'show_hostel' => 'Show Hostel / Accommodation',
            'show_clearance' => 'Show Clearance',
            'show_cbt' => 'Show CBT',
            'show_lms' => 'Show LMS',
            'show_library' => 'Show Library',
            'show_support' => 'Show Support Cards',
        ];
    }
}
if (!function_exists('portalDashboardDesignRuleColorDefaults')) {
    function portalDashboardDesignRuleColorDefaults(PDO $pdo): array {
        $branding = function_exists('portalBrandingDefaults') ? portalBrandingDefaults($pdo) : [];
        $primary = portalNormalizeHexColor((string)($branding['branding_primary_color'] ?? '#003366'), '#003366');
        $secondary = portalNormalizeHexColor((string)($branding['branding_secondary_color'] ?? '#0B5ED7'), '#0B5ED7');
        $accent = portalNormalizeHexColor((string)($branding['branding_accent_color'] ?? '#F0A500'), '#F0A500');
        $background = portalNormalizeHexColor((string)($branding['branding_background_color'] ?? '#F4F7F6'), '#F4F7F6');
        $surface = portalNormalizeHexColor((string)($branding['branding_surface_color'] ?? '#FFFFFF'), '#FFFFFF');
        $text = portalNormalizeHexColor((string)($branding['branding_text_color'] ?? '#333333'), '#333333');
        $muted = portalNormalizeHexColor((string)($branding['branding_muted_text_color'] ?? '#64748B'), '#64748B');
        $button = portalNormalizeHexColor((string)($branding['branding_button_color'] ?? $primary), $primary);
        $sectionHeader = portalNormalizeHexColor((string)($branding['branding_card_header_color'] ?? $primary), $primary);
        $tableHeader = portalNormalizeHexColor((string)($branding['branding_table_header_color'] ?? $primary), $primary);
        $statBg = portalNormalizeHexColor((string)($branding['branding_card_background_color'] ?? $primary), $primary);
        $badgeSuccess = portalNormalizeHexColor((string)($branding['branding_badge_success_color'] ?? '#198754'), '#198754');
        $badgeWarning = portalNormalizeHexColor((string)($branding['branding_badge_warning_color'] ?? '#F59E0B'), '#F59E0B');
        $badgeDanger = portalNormalizeHexColor((string)($branding['branding_badge_danger_color'] ?? '#DC3545'), '#DC3545');
        $alertInfo = portalNormalizeHexColor((string)($branding['branding_alert_info_color'] ?? $secondary), $secondary);
        $buttonText = function_exists('portalContrastColor') ? portalContrastColor($button) : '#FFFFFF';
        $statText = function_exists('portalContrastColor') ? portalContrastColor($statBg) : '#FFFFFF';
        $tableHeaderText = function_exists('portalContrastColor') ? portalContrastColor($tableHeader) : '#FFFFFF';
        $sidebarText = portalNormalizeHexColor((string)($branding['branding_sidebar_text_color'] ?? (function_exists('portalContrastColor') ? portalContrastColor((string)($branding['branding_sidebar_bg_color'] ?? $primary)) : '#FFFFFF')), '#FFFFFF');
        $sidebarInactive = portalNormalizeHexColor((string)($branding['branding_sidebar_inactive_color'] ?? (strcasecmp($sidebarText, '#FFFFFF') === 0 ? '#D9E2EC' : '#64748B')), (strcasecmp($sidebarText, '#FFFFFF') === 0 ? '#D9E2EC' : '#64748B'));
        return [
            'primary_color' => $primary,
            'secondary_color' => $secondary,
            'accent_color' => $accent,
            'background_color' => $background,
            'card_background_color' => $surface,
            'card_border_color' => portalNormalizeHexColor((string)($branding['branding_border_color'] ?? '#D9E2EC'), '#D9E2EC'),
            'card_title_color' => portalNormalizeHexColor((string)($branding['branding_card_title_color'] ?? $primary), $primary),
            'body_text_color' => $text,
            'heading_color' => portalNormalizeHexColor((string)($branding['branding_heading_color'] ?? $primary), $primary),
            'muted_text_color' => $muted,
            'header_color' => portalNormalizeHexColor((string)($branding['branding_header_bg_color'] ?? $primary), $primary),
            'header_text_color' => portalNormalizeHexColor((string)($branding['branding_header_text_color'] ?? '#FFFFFF'), '#FFFFFF'),
            'section_header_color' => $sectionHeader,
            'card_header_color' => $sectionHeader,
            'sidebar_color' => portalNormalizeHexColor((string)($branding['branding_sidebar_bg_color'] ?? $primary), $primary),
            'sidebar_text_color' => $sidebarText,
            'sidebar_inactive_color' => $sidebarInactive,
            'button_color' => $button,
            'button_text_color' => portalNormalizeHexColor((string)($branding['branding_button_text_color'] ?? $buttonText), $buttonText),
            'badge_color' => $badgeSuccess,
            'badge_success_color' => $badgeSuccess,
            'badge_warning_color' => $badgeWarning,
            'badge_danger_color' => $badgeDanger,
            'link_color' => portalNormalizeHexColor((string)($branding['branding_link_color'] ?? $accent), $accent),
            'stat_card_background_color' => $statBg,
            'stat_number_color' => portalNormalizeHexColor((string)($branding['branding_stat_number_color'] ?? $statText), $statText),
            'stat_label_color' => portalNormalizeHexColor((string)($branding['branding_stat_label_color'] ?? $statText), $statText),
            'tab_active_color' => portalNormalizeHexColor((string)($branding['branding_tab_active_color'] ?? $primary), $primary),
            'tab_inactive_color' => portalNormalizeHexColor((string)($branding['branding_tab_inactive_color'] ?? '#E8EEF7'), '#E8EEF7'),
            'tab_text_color' => portalNormalizeHexColor((string)($branding['branding_tab_text_color'] ?? $primary), $primary),
            'table_header_bg_color' => $tableHeader,
            'table_header_text_color' => portalNormalizeHexColor((string)($branding['branding_table_header_text_color'] ?? $tableHeaderText), $tableHeaderText),
            'alert_info_color' => $alertInfo,
        ];
    }
}

if (!function_exists('portalEnsureDashboardDesignRulesSchema')) {
    function portalEnsureDashboardDesignRulesSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS dashboard_design_rules (
                id INT AUTO_INCREMENT PRIMARY KEY,
                rule_name VARCHAR(190) NOT NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                mode_of_study VARCHAR(100) NULL,
                level VARCHAR(40) NULL,
                layout_type VARCHAR(60) NOT NULL DEFAULT 'standard',
                primary_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                secondary_color VARCHAR(7) NOT NULL DEFAULT '#0B5ED7',
                accent_color VARCHAR(7) NOT NULL DEFAULT '#F0A500',
                background_color VARCHAR(7) NOT NULL DEFAULT '#F4F7F6',
                card_background_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF',
                card_border_color VARCHAR(7) NOT NULL DEFAULT '#D9E2EC',
                card_title_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                body_text_color VARCHAR(7) NOT NULL DEFAULT '#333333',
                heading_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                muted_text_color VARCHAR(7) NOT NULL DEFAULT '#64748B',
                header_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                header_text_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF',
                section_header_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                card_header_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                sidebar_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                sidebar_text_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF',
                sidebar_inactive_color VARCHAR(7) NOT NULL DEFAULT '#D9E2EC',
                button_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                button_text_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF',
                badge_color VARCHAR(7) NOT NULL DEFAULT '#198754',
                badge_success_color VARCHAR(7) NOT NULL DEFAULT '#198754',
                badge_warning_color VARCHAR(7) NOT NULL DEFAULT '#F59E0B',
                badge_danger_color VARCHAR(7) NOT NULL DEFAULT '#DC3545',
                link_color VARCHAR(7) NOT NULL DEFAULT '#F0A500',
                stat_card_background_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                stat_number_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF',
                stat_label_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF',
                tab_active_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                tab_inactive_color VARCHAR(7) NOT NULL DEFAULT '#E8EEF7',
                tab_text_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                table_header_bg_color VARCHAR(7) NOT NULL DEFAULT '#003366',
                table_header_text_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF',
                alert_info_color VARCHAR(7) NOT NULL DEFAULT '#0B5ED7',
                notice_banner_text VARCHAR(255) NULL,
                icon_preset VARCHAR(60) NULL,
                quick_link_style VARCHAR(60) NULL,
                show_hostel TINYINT(1) NOT NULL DEFAULT 1,
                show_clearance TINYINT(1) NOT NULL DEFAULT 1,
                show_cbt TINYINT(1) NOT NULL DEFAULT 1,
                show_lms TINYINT(1) NOT NULL DEFAULT 1,
                show_library TINYINT(1) NOT NULL DEFAULT 1,
                show_support TINYINT(1) NOT NULL DEFAULT 1,
                is_active TINYINT(1) NOT NULL DEFAULT 1,
                is_default TINYINT(1) NOT NULL DEFAULT 0,
                sort_order INT NOT NULL DEFAULT 0,
                created_by INT NULL,
                updated_by INT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_dashboard_scope_rule (faculty_id, department_id, programme_id, mode_of_study, level, is_active),
                INDEX idx_dashboard_default (is_default, is_active),
                INDEX idx_dashboard_layout (layout_type),
                INDEX idx_dashboard_scope_order (is_active, is_default, sort_order, id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        $columns = [
            'rule_name' => "ALTER TABLE dashboard_design_rules ADD COLUMN rule_name VARCHAR(190) NOT NULL DEFAULT 'Student Dashboard Rule' AFTER id",
            'faculty_id' => "ALTER TABLE dashboard_design_rules ADD COLUMN faculty_id INT NULL AFTER rule_name",
            'department_id' => "ALTER TABLE dashboard_design_rules ADD COLUMN department_id INT NULL AFTER faculty_id",
            'programme_id' => "ALTER TABLE dashboard_design_rules ADD COLUMN programme_id INT NULL AFTER department_id",
            'mode_of_study' => "ALTER TABLE dashboard_design_rules ADD COLUMN mode_of_study VARCHAR(100) NULL AFTER programme_id",
            'level' => "ALTER TABLE dashboard_design_rules ADD COLUMN level VARCHAR(40) NULL AFTER mode_of_study",
            'layout_type' => "ALTER TABLE dashboard_design_rules ADD COLUMN layout_type VARCHAR(60) NOT NULL DEFAULT 'standard' AFTER level",
            'primary_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN primary_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER layout_type",
            'secondary_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN secondary_color VARCHAR(7) NOT NULL DEFAULT '#0B5ED7' AFTER primary_color",
            'accent_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN accent_color VARCHAR(7) NOT NULL DEFAULT '#F0A500' AFTER secondary_color",
            'background_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN background_color VARCHAR(7) NOT NULL DEFAULT '#F4F7F6' AFTER accent_color",
            'card_background_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN card_background_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF' AFTER background_color",
            'card_border_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN card_border_color VARCHAR(7) NOT NULL DEFAULT '#D9E2EC' AFTER card_background_color",
            'card_title_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN card_title_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER card_border_color",
            'body_text_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN body_text_color VARCHAR(7) NOT NULL DEFAULT '#333333' AFTER card_title_color",
            'heading_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN heading_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER body_text_color",
            'muted_text_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN muted_text_color VARCHAR(7) NOT NULL DEFAULT '#64748B' AFTER heading_color",
            'header_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN header_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER muted_text_color",
            'header_text_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN header_text_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF' AFTER header_color",
            'section_header_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN section_header_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER header_text_color",
            'card_header_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN card_header_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER section_header_color",
            'sidebar_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN sidebar_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER card_header_color",
            'sidebar_text_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN sidebar_text_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF' AFTER sidebar_color",
            'sidebar_inactive_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN sidebar_inactive_color VARCHAR(7) NOT NULL DEFAULT '#D9E2EC' AFTER sidebar_text_color",
            'button_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN button_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER sidebar_inactive_color",
            'button_text_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN button_text_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF' AFTER button_color",
            'badge_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN badge_color VARCHAR(7) NOT NULL DEFAULT '#198754' AFTER button_text_color",
            'badge_success_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN badge_success_color VARCHAR(7) NOT NULL DEFAULT '#198754' AFTER badge_color",
            'badge_warning_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN badge_warning_color VARCHAR(7) NOT NULL DEFAULT '#F59E0B' AFTER badge_success_color",
            'badge_danger_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN badge_danger_color VARCHAR(7) NOT NULL DEFAULT '#DC3545' AFTER badge_warning_color",
            'link_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN link_color VARCHAR(7) NOT NULL DEFAULT '#F0A500' AFTER badge_danger_color",
            'stat_card_background_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN stat_card_background_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER link_color",
            'stat_number_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN stat_number_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF' AFTER stat_card_background_color",
            'stat_label_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN stat_label_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF' AFTER stat_number_color",
            'tab_active_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN tab_active_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER stat_label_color",
            'tab_inactive_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN tab_inactive_color VARCHAR(7) NOT NULL DEFAULT '#E8EEF7' AFTER tab_active_color",
            'tab_text_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN tab_text_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER tab_inactive_color",
            'table_header_bg_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN table_header_bg_color VARCHAR(7) NOT NULL DEFAULT '#003366' AFTER tab_text_color",
            'table_header_text_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN table_header_text_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF' AFTER table_header_bg_color",
            'alert_info_color' => "ALTER TABLE dashboard_design_rules ADD COLUMN alert_info_color VARCHAR(7) NOT NULL DEFAULT '#0B5ED7' AFTER table_header_text_color",
            'notice_banner_text' => "ALTER TABLE dashboard_design_rules ADD COLUMN notice_banner_text VARCHAR(255) NULL AFTER alert_info_color",
            'icon_preset' => "ALTER TABLE dashboard_design_rules ADD COLUMN icon_preset VARCHAR(60) NULL AFTER notice_banner_text",
            'quick_link_style' => "ALTER TABLE dashboard_design_rules ADD COLUMN quick_link_style VARCHAR(60) NULL AFTER icon_preset",
            'show_hostel' => "ALTER TABLE dashboard_design_rules ADD COLUMN show_hostel TINYINT(1) NOT NULL DEFAULT 1 AFTER quick_link_style",
            'show_clearance' => "ALTER TABLE dashboard_design_rules ADD COLUMN show_clearance TINYINT(1) NOT NULL DEFAULT 1 AFTER show_hostel",
            'show_cbt' => "ALTER TABLE dashboard_design_rules ADD COLUMN show_cbt TINYINT(1) NOT NULL DEFAULT 1 AFTER show_clearance",
            'show_lms' => "ALTER TABLE dashboard_design_rules ADD COLUMN show_lms TINYINT(1) NOT NULL DEFAULT 1 AFTER show_cbt",
            'show_library' => "ALTER TABLE dashboard_design_rules ADD COLUMN show_library TINYINT(1) NOT NULL DEFAULT 1 AFTER show_lms",
            'show_support' => "ALTER TABLE dashboard_design_rules ADD COLUMN show_support TINYINT(1) NOT NULL DEFAULT 1 AFTER show_library",
            'is_active' => "ALTER TABLE dashboard_design_rules ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER show_support",
            'is_default' => "ALTER TABLE dashboard_design_rules ADD COLUMN is_default TINYINT(1) NOT NULL DEFAULT 0 AFTER is_active",
            'sort_order' => "ALTER TABLE dashboard_design_rules ADD COLUMN sort_order INT NOT NULL DEFAULT 0 AFTER is_default",
            'created_by' => "ALTER TABLE dashboard_design_rules ADD COLUMN created_by INT NULL AFTER sort_order",
            'updated_by' => "ALTER TABLE dashboard_design_rules ADD COLUMN updated_by INT NULL AFTER created_by",
            'created_at' => "ALTER TABLE dashboard_design_rules ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP AFTER updated_by",
            'updated_at' => "ALTER TABLE dashboard_design_rules ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at",
        ];
        foreach ($columns as $column => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, 'dashboard_design_rules', $column)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) {}
        }

        foreach ([
            "ALTER TABLE dashboard_design_rules ADD INDEX idx_dashboard_scope_rule (faculty_id, department_id, programme_id, mode_of_study, level, is_active)",
            "ALTER TABLE dashboard_design_rules ADD INDEX idx_dashboard_default (is_default, is_active)",
            "ALTER TABLE dashboard_design_rules ADD INDEX idx_dashboard_layout (layout_type)",
            "ALTER TABLE dashboard_design_rules ADD INDEX idx_dashboard_scope_order (is_active, is_default, sort_order, id)"
        ] as $sql) {
            try { $pdo->exec($sql); } catch (Throwable $e) {}
        }

        try {
            if (portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'department_id') && portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'faculty_id')) {
                $pdo->exec("UPDATE dashboard_design_rules r
                    INNER JOIN departments d ON d.id = r.department_id
                    SET r.faculty_id = d.faculty_id
                    WHERE r.department_id IS NOT NULL AND r.department_id <> 0 AND (r.faculty_id IS NULL OR r.faculty_id = 0)");
            }
        } catch (Throwable $e) {}
        try {
            if (portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'programme_id')) {
                $pdo->exec("UPDATE dashboard_design_rules r
                    INNER JOIN programmes p ON p.id = r.programme_id
                    INNER JOIN departments d ON d.id = p.department_id
                    SET r.department_id = p.department_id,
                        r.faculty_id = d.faculty_id
                    WHERE r.programme_id IS NOT NULL AND r.programme_id <> 0
                      AND ((r.department_id IS NULL OR r.department_id = 0) OR (r.faculty_id IS NULL OR r.faculty_id = 0))");
            }
        } catch (Throwable $e) {}
        try {
            if (portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'badge_success_color') && portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'badge_color')) {
                $pdo->exec("UPDATE dashboard_design_rules
                    SET badge_success_color = badge_color
                    WHERE COALESCE(TRIM(badge_success_color), '') = '' AND COALESCE(TRIM(badge_color), '') <> ''");
            }
        } catch (Throwable $e) {}
        try {
            if (portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'section_header_color') && portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'card_header_color')) {
                $pdo->exec("UPDATE dashboard_design_rules
                    SET section_header_color = card_header_color
                    WHERE COALESCE(TRIM(section_header_color), '') = '' AND COALESCE(TRIM(card_header_color), '') <> ''");
            }
        } catch (Throwable $e) {}
        try {
            if (portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'sidebar_text_color') && portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'sidebar_color')) {
                $pdo->exec("UPDATE dashboard_design_rules
                    SET sidebar_text_color = '#FFFFFF'
                    WHERE COALESCE(TRIM(sidebar_text_color), '') = '' AND UPPER(COALESCE(TRIM(sidebar_color), '')) <> ''");
            }
        } catch (Throwable $e) {}
        try {
            if (portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'sidebar_inactive_color') && portalColumnExistsSafe($pdo, 'dashboard_design_rules', 'sidebar_text_color')) {
                $pdo->exec("UPDATE dashboard_design_rules
                    SET sidebar_inactive_color = CASE WHEN UPPER(COALESCE(TRIM(sidebar_text_color), '#FFFFFF')) = '#FFFFFF' THEN '#D9E2EC' ELSE '#64748B' END
                    WHERE COALESCE(TRIM(sidebar_inactive_color), '') = ''");
            }
        } catch (Throwable $e) {}

        $colorDefaults = portalDashboardDesignRuleColorDefaults($pdo);
        foreach ($colorDefaults as $column => $defaultValue) {
            try {
                if (portalColumnExistsSafe($pdo, 'dashboard_design_rules', $column)) {
                    $stmt = $pdo->prepare("UPDATE dashboard_design_rules SET `$column` = ? WHERE COALESCE(TRIM(`$column`), '') = ''");
                    $stmt->execute([(string)$defaultValue]);
                }
            } catch (Throwable $e) {}
        }
    }
}
if (!function_exists('portalNormalizeDashboardRuleLayoutType')) {

    function portalNormalizeDashboardRuleLayoutType($value): string {
        $value = strtolower(trim((string)$value));
        $allowed = array_keys(portalDashboardDesignRuleLayoutOptions());
        return in_array($value, $allowed, true) ? $value : 'standard';
    }
}
if (!function_exists('portalNormalizeDashboardRuleLevel')) {
    function portalNormalizeDashboardRuleLevel(PDO $pdo, $value): string {
        $value = trim((string)$value);
        if ($value === '') return '';
        $value = preg_replace('/\s+/u', ' ', $value) ?: $value;
        $lookup = strtolower($value);
        foreach (portalAcademicLevelOptions($pdo) as $levelOption) {
            $levelOption = trim((string)$levelOption);
            if ($levelOption !== '' && strtolower($levelOption) === $lookup) {
                return $levelOption;
            }
        }
        return strtoupper($value);
    }
}
if (!function_exists('portalNormalizeDashboardRuleMode')) {
    function portalNormalizeDashboardRuleMode($value): string {
        $value = trim((string)$value);
        if ($value === '') return '';
        return portalNormalizeRegistrableCourseModeOfStudy($value, $value);
    }
}

if (!function_exists('portalDashboardDesignRuleSpecificityScore')) {
    function portalDashboardDesignRuleSpecificityScore(array $row): int {
        $score = 0;
        if ((int)($row['faculty_id'] ?? 0) > 0) $score += 80;
        if ((int)($row['department_id'] ?? 0) > 0) $score += 160;
        if ((int)($row['programme_id'] ?? 0) > 0) $score += 320;
        if (trim((string)($row['mode_of_study'] ?? '')) !== '') $score += 40;
        if (trim((string)($row['level'] ?? '')) !== '') $score += 20;
        if ($score === 0) $score = ((int)($row['is_default'] ?? 0) === 1) ? 10 : 5;
        if (((int)($row['is_default'] ?? 0) === 1) && $score > 0) $score += 1;
        return $score;
    }
}
if (!function_exists('portalDashboardDesignRuleMatchesStudent')) {
    function portalDashboardDesignRuleMatchesStudent(PDO $pdo, array $rule, array $studentScope): bool {
        $ruleFacultyId = (int)($rule['faculty_id'] ?? 0);
        $studentFacultyId = (int)($studentScope['faculty_id'] ?? 0);
        if ($ruleFacultyId > 0 && $ruleFacultyId !== $studentFacultyId) return false;

        $ruleDepartmentId = (int)($rule['department_id'] ?? 0);
        $studentDepartmentId = (int)($studentScope['department_id'] ?? 0);
        if ($ruleDepartmentId > 0 && $ruleDepartmentId !== $studentDepartmentId) return false;

        $ruleProgrammeId = (int)($rule['programme_id'] ?? 0);
        $studentProgrammeId = (int)($studentScope['programme_id'] ?? 0);
        if ($ruleProgrammeId > 0) {
            if ($studentProgrammeId <= 0) return false;
            if ($ruleProgrammeId !== $studentProgrammeId) {
                if (!function_exists('portalProgrammesShareScopeName')) return false;
                if (!portalProgrammesShareScopeName($pdo, $ruleProgrammeId, $studentProgrammeId)) return false;
            }
        }

        $ruleMode = portalNormalizeDashboardRuleMode((string)($rule['mode_of_study'] ?? ''));
        $studentMode = portalNormalizeDashboardRuleMode((string)($studentScope['mode_of_study'] ?? ''));
        if ($ruleMode !== '' && strcasecmp($ruleMode, $studentMode) !== 0) return false;

        $ruleLevel = strtoupper(trim((string)($rule['level'] ?? '')));
        $studentLevel = strtoupper(trim((string)($studentScope['level'] ?? '')));
        if ($ruleLevel !== '' && $ruleLevel !== $studentLevel) return false;

        return true;
    }
}
if (!function_exists('portalStudentDashboardScope')) {
    function portalStudentDashboardScope(PDO $pdo, array $student): array {
        $facultyId = (int)($student['faculty_id'] ?? 0);
        $departmentId = (int)($student['department_id'] ?? 0);
        $programmeId = (int)($student['programme_id'] ?? 0);
        if ($facultyId <= 0) {
            try { $facultyId = portalResolveFacultyIdByText($pdo, $student['school'] ?? $student['school_name'] ?? $student['faculty'] ?? ''); } catch (Throwable $e) { $facultyId = 0; }
        }
        if ($departmentId <= 0) {
            try { $departmentId = portalResolveDepartmentIdByText($pdo, $student['course_of_study'] ?? $student['course_in_view'] ?? $student['department'] ?? '', $facultyId); } catch (Throwable $e) { $departmentId = 0; }
        }
        if ($programmeId <= 0) {
            try { $programmeId = portalResolveProgrammeIdByText($pdo, $student['program'] ?? $student['programme_name_cache'] ?? $student['programme'] ?? '', $departmentId); } catch (Throwable $e) { $programmeId = 0; }
        }
        if ($programmeId > 0) {
            try {
                $stmt = $pdo->prepare("SELECT p.department_id, COALESCE(d.faculty_id, p.faculty_id, 0) AS faculty_id FROM programmes p LEFT JOIN departments d ON d.id = p.department_id WHERE p.id = ? LIMIT 1");
                $stmt->execute([$programmeId]);
                $programmeRow = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
                if ($programmeRow) {
                    if ($departmentId <= 0) $departmentId = (int)($programmeRow['department_id'] ?? 0);
                    if ($facultyId <= 0) $facultyId = (int)($programmeRow['faculty_id'] ?? 0);
                }
            } catch (Throwable $e) {}
        }
        if ($departmentId > 0 && $facultyId <= 0) {
            try {
                $stmt = $pdo->prepare("SELECT faculty_id FROM departments WHERE id = ? LIMIT 1");
                $stmt->execute([$departmentId]);
                $facultyId = (int)($stmt->fetchColumn() ?: 0);
            } catch (Throwable $e) {}
        }
        $mode = trim((string)($student['mode_of_study'] ?? ''));
        if ($mode === '' && $programmeId > 0) $mode = portalProgrammeModeOfStudyValueById($pdo, $programmeId, 'Full-Time');
        if ($mode === '') $mode = portalInferModeOfStudy((array)$student);
        $sessionName = trim((string)($student['session'] ?? $student['session_name'] ?? portalCurrentAcademicSessionName($pdo)));
        $level = function_exists('portalStudentResolvedCurrentLevel') ? portalStudentResolvedCurrentLevel($pdo, $student, $sessionName) : trim((string)($student['level'] ?? ''));
        return [
            'faculty_id' => $facultyId,
            'faculty_name' => $facultyId > 0 ? portalFacultyNameById($pdo, $facultyId, trim((string)($student['school'] ?? $student['school_name'] ?? $student['faculty'] ?? 'Faculty'))) : trim((string)($student['school'] ?? $student['school_name'] ?? $student['faculty'] ?? '')),
            'department_id' => $departmentId,
            'department_name' => $departmentId > 0 ? portalDepartmentNameById($pdo, $departmentId, trim((string)($student['course_of_study'] ?? $student['course_in_view'] ?? $student['department'] ?? 'Department'))) : trim((string)($student['course_of_study'] ?? $student['course_in_view'] ?? $student['department'] ?? '')),
            'programme_id' => $programmeId,
            'programme_name' => $programmeId > 0 ? portalProgrammeSelectorLabelById($pdo, $programmeId, $facultyId, $departmentId, trim((string)($student['programme'] ?? $student['program'] ?? 'Programme'))) : trim((string)($student['program'] ?? $student['programme_name_cache'] ?? $student['programme'] ?? '')),
            'mode_of_study' => portalNormalizeDashboardRuleMode($mode),
            'level' => portalNormalizeDashboardRuleLevel($pdo, $level),
        ];
    }
}

if (!function_exists('portalFetchDashboardDesignRules')) {
    function portalFetchDashboardDesignRules(PDO $pdo, bool $activeOnly = false): array {
        portalEnsureDashboardDesignRulesSchema($pdo);
        try {
            $sql = "SELECT r.*, p.name AS programme_name, d.name AS department_name,
                           COALESCE(f.name, rf.name, '') AS faculty_name
                    FROM dashboard_design_rules r
                    LEFT JOIN programmes p ON p.id = r.programme_id
                    LEFT JOIN departments d ON d.id = COALESCE(NULLIF(r.department_id, 0), p.department_id)
                    LEFT JOIN departments pd ON pd.id = p.department_id
                    LEFT JOIN faculties f ON f.id = COALESCE(NULLIF(r.faculty_id, 0), d.faculty_id)
                    LEFT JOIN faculties rf ON rf.id = pd.faculty_id";
            if ($activeOnly) {
                $sql .= " WHERE r.is_active = 1";
            }
            $sql .= " ORDER BY r.sort_order ASC, r.id DESC";
            $stmt = $pdo->prepare($sql);
            $stmt->execute([]);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $rows = [];
        }
        $defaults = portalDashboardDesignRuleColorDefaults($pdo);
        foreach ($rows as &$row) {
            $row['faculty_id'] = (int)($row['faculty_id'] ?? 0);
            $row['department_id'] = (int)($row['department_id'] ?? 0);
            $row['programme_id'] = (int)($row['programme_id'] ?? 0);
            $row['is_active'] = (int)($row['is_active'] ?? 0);
            $row['is_default'] = (int)($row['is_default'] ?? 0);
            $row['sort_order'] = (int)($row['sort_order'] ?? 0);
            $row['mode_of_study'] = portalNormalizeDashboardRuleMode((string)($row['mode_of_study'] ?? ''));
            $row['level'] = trim((string)($row['level'] ?? ''));
            $row['layout_type'] = portalNormalizeDashboardRuleLayoutType((string)($row['layout_type'] ?? 'standard'));
            foreach (portalDashboardDesignRuleColorFields() as $colorField) {
                $row[$colorField] = portalNormalizeHexColor((string)($row[$colorField] ?? ''), (string)($defaults[$colorField] ?? '#003366'));
            }
            foreach (array_keys(portalDashboardDesignRuleVisibilityFields()) as $toggleField) {
                $row[$toggleField] = (int)($row[$toggleField] ?? 1) === 1 ? 1 : 0;
            }
            $row['specificity_score'] = portalDashboardDesignRuleSpecificityScore($row);
            $row['faculty_name'] = $row['faculty_id'] > 0 ? portalFacultyNameById($pdo, $row['faculty_id'], trim((string)($row['faculty_name'] ?? 'Faculty'))) : trim((string)($row['faculty_name'] ?? ''));
            $row['department_name'] = $row['department_id'] > 0 ? portalDepartmentNameById($pdo, $row['department_id'], trim((string)($row['department_name'] ?? 'Department'))) : trim((string)($row['department_name'] ?? ''));
            $row['programme_name'] = $row['programme_id'] > 0 ? portalProgrammeSelectorLabelById($pdo, $row['programme_id'], $row['faculty_id'], $row['department_id'], trim((string)($row['programme_name'] ?? 'Programme'))) : '';
        }
        unset($row);
        usort($rows, static function(array $a, array $b): int {
            $score = (int)($b['specificity_score'] ?? 0) <=> (int)($a['specificity_score'] ?? 0);
            if ($score !== 0) return $score;
            $defaultCmp = (int)($b['is_default'] ?? 0) <=> (int)($a['is_default'] ?? 0);
            if ($defaultCmp !== 0) return $defaultCmp;
            $sortCmp = (int)($a['sort_order'] ?? 0) <=> (int)($b['sort_order'] ?? 0);
            if ($sortCmp !== 0) return $sortCmp;
            return (int)($b['id'] ?? 0) <=> (int)($a['id'] ?? 0);
        });
        return $rows;
    }
}
if (!function_exists('portalDashboardDesignRuleById')) {

    function portalDashboardDesignRuleById(PDO $pdo, int $id): ?array {
        if ($id <= 0) return null;
        foreach (portalFetchDashboardDesignRules($pdo, false) as $row) {
            if ((int)($row['id'] ?? 0) === $id) return $row;
        }
        return null;
    }
}

if (!function_exists('portalDashboardDesignRuleScopeText')) {
    function portalDashboardDesignRuleScopeText(PDO $pdo, array $row): string {
        $parts = [];
        $facultyId = (int)($row['faculty_id'] ?? 0);
        $departmentId = (int)($row['department_id'] ?? 0);
        $programmeId = (int)($row['programme_id'] ?? 0);
        if ($facultyId > 0) $parts[] = trim((string)($row['faculty_name'] ?? portalFacultyNameById($pdo, $facultyId, 'Faculty')));
        if ($departmentId > 0) $parts[] = trim((string)($row['department_name'] ?? portalDepartmentNameById($pdo, $departmentId, 'Department')));
        if ($programmeId > 0) $parts[] = trim((string)($row['programme_name'] ?? portalProgrammeSelectorLabelById($pdo, $programmeId, $facultyId, $departmentId, 'Programme')));
        $mode = trim((string)($row['mode_of_study'] ?? ''));
        if ($mode !== '') $parts[] = $mode;
        $level = trim((string)($row['level'] ?? ''));
        if ($level !== '') $parts[] = $level;
        $parts = array_values(array_filter($parts, static fn($v) => trim((string)$v) !== ''));
        return $parts ? implode(' + ', $parts) : 'Global Default';
    }
}

if (!function_exists('portalBuildDashboardDesignRulePayload')) {
    function portalBuildDashboardDesignRulePayload(PDO $pdo, array $input, ?array $existing = null): array {
        portalEnsureDashboardDesignRulesSchema($pdo);
        $existing = is_array($existing) ? $existing : [];
        $colorDefaults = portalDashboardDesignRuleColorDefaults($pdo);
        $ruleName = trim((string)($input['rule_name'] ?? ''));
        $facultyId = (int)($input['faculty_id'] ?? 0);
        $departmentId = (int)($input['department_id'] ?? 0);
        $programmeId = (int)($input['programme_id'] ?? 0);
        $mode = portalNormalizeDashboardRuleMode((string)($input['mode_of_study'] ?? ''));
        $level = portalNormalizeDashboardRuleLevel($pdo, (string)($input['level'] ?? ''));
        $layoutType = portalNormalizeDashboardRuleLayoutType((string)($input['layout_type'] ?? 'standard'));
        $isDefault = isset($input['is_default']) ? 1 : (int)($existing['is_default'] ?? 0);
        $isActive = isset($input['is_active']) ? 1 : 0;
        $sortOrder = (int)($input['sort_order'] ?? ($existing['sort_order'] ?? 0));
        $noticeBannerText = trim((string)($input['notice_banner_text'] ?? ''));
        if ($noticeBannerText !== '') {
            $noticeBannerText = function_exists('mb_substr') ? mb_substr($noticeBannerText, 0, 255) : substr($noticeBannerText, 0, 255);
        }
        if ($programmeId > 0) {
            try {
                $stmt = $pdo->prepare("SELECT p.department_id, COALESCE(d.faculty_id, p.faculty_id, 0) AS faculty_id FROM programmes p LEFT JOIN departments d ON d.id = p.department_id WHERE p.id = ? LIMIT 1");
                $stmt->execute([$programmeId]);
                $programmeRow = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
                if ($programmeRow) {
                    $departmentId = (int)($programmeRow['department_id'] ?? 0);
                    $facultyId = (int)($programmeRow['faculty_id'] ?? 0);
                }
            } catch (Throwable $e) {}
        } elseif ($departmentId > 0) {
            try {
                $stmt = $pdo->prepare("SELECT faculty_id FROM departments WHERE id = ? LIMIT 1");
                $stmt->execute([$departmentId]);
                $facultyId = (int)($stmt->fetchColumn() ?: $facultyId);
            } catch (Throwable $e) {}
        }
        if ($facultyId <= 0 && $departmentId <= 0 && $programmeId <= 0 && $mode === '' && $level === '') {
            $isDefault = 1;
        }
        $scopeForName = [
            'faculty_id' => $facultyId,
            'department_id' => $departmentId,
            'programme_id' => $programmeId,
            'mode_of_study' => $mode,
            'level' => $level,
        ];
        $payload = [
            'rule_name' => $ruleName !== '' ? $ruleName : portalDashboardDesignRuleScopeText($pdo, $scopeForName),
            'faculty_id' => $facultyId > 0 ? $facultyId : null,
            'department_id' => $departmentId > 0 ? $departmentId : null,
            'programme_id' => $programmeId > 0 ? $programmeId : null,
            'mode_of_study' => $mode !== '' ? $mode : null,
            'level' => $level !== '' ? $level : null,
            'layout_type' => $layoutType,
            'notice_banner_text' => $noticeBannerText !== '' ? $noticeBannerText : null,
            'is_active' => $isActive,
            'is_default' => $isDefault,
            'sort_order' => $sortOrder,
        ];
        foreach (portalDashboardDesignRuleColorFields() as $colorField) {
            $payload[$colorField] = portalNormalizeHexColor((string)($input[$colorField] ?? $existing[$colorField] ?? $colorDefaults[$colorField] ?? '#003366'), (string)($colorDefaults[$colorField] ?? '#003366'));
        }
        foreach (array_keys(portalDashboardDesignRuleVisibilityFields()) as $toggleField) {
            $payload[$toggleField] = isset($input[$toggleField]) ? 1 : 0;
        }
        return $payload;
    }
}
if (!function_exists('portalSaveDashboardDesignRule')) {

    function portalSaveDashboardDesignRule(PDO $pdo, array $payload, int $adminId = 0, int $ruleId = 0): int {
        portalEnsureDashboardDesignRulesSchema($pdo);
        if (!empty($payload['is_default'])) {
            try {
                if ($ruleId > 0) {
                    $stmt = $pdo->prepare("UPDATE dashboard_design_rules SET is_default = 0, updated_by = ? WHERE id <> ?");
                    $stmt->execute([$adminId > 0 ? $adminId : null, $ruleId]);
                } else {
                    $stmt = $pdo->prepare("UPDATE dashboard_design_rules SET is_default = 0, updated_by = ?");
                    $stmt->execute([$adminId > 0 ? $adminId : null]);
                }
            } catch (Throwable $e) {}
        }

                $fields = [
            'rule_name','faculty_id','department_id','programme_id','mode_of_study','level','layout_type',
            'primary_color','secondary_color','accent_color','background_color','card_background_color',
            'card_border_color','card_title_color','body_text_color','heading_color','muted_text_color',
            'header_color','header_text_color','section_header_color','card_header_color','sidebar_color','sidebar_text_color','sidebar_inactive_color','button_color',
            'button_text_color','badge_color','badge_success_color','badge_warning_color','badge_danger_color',
            'link_color','stat_card_background_color','stat_number_color','stat_label_color',
            'tab_active_color','tab_inactive_color','tab_text_color','table_header_bg_color',
            'table_header_text_color','alert_info_color',
            'notice_banner_text','show_hostel','show_clearance','show_cbt','show_lms',
            'show_library','show_support','is_active','is_default','sort_order'
        ];

        if ($ruleId > 0) {
            $setParts = [];
            $values = [];
            foreach ($fields as $field) {
                $setParts[] = "`$field` = ?";
                $values[] = $payload[$field] ?? null;
            }
            $setParts[] = "updated_by = ?";
            $values[] = $adminId > 0 ? $adminId : null;
            $values[] = $ruleId;
            $sql = "UPDATE dashboard_design_rules SET " . implode(', ', $setParts) . " WHERE id = ?";
            $stmt = $pdo->prepare($sql);
            $stmt->execute($values);
            return $ruleId;
        }

        $insertFields = array_merge($fields, ['created_by','updated_by']);
        $placeholders = implode(', ', array_fill(0, count($insertFields), '?'));
        $sql = "INSERT INTO dashboard_design_rules (`" . implode('`,`', $insertFields) . "`) VALUES ($placeholders)";
        $values = [];
        foreach ($fields as $field) {
            $values[] = $payload[$field] ?? null;
        }
        $values[] = $adminId > 0 ? $adminId : null;
        $values[] = $adminId > 0 ? $adminId : null;
        $stmt = $pdo->prepare($sql);
        $stmt->execute($values);
        return (int)$pdo->lastInsertId();
    }
}

if (!function_exists('portalDefaultStudentDashboardTheme')) {
    function portalDefaultStudentDashboardTheme(PDO $pdo, array $studentScope = []): array {
        $defaults = portalDashboardDesignRuleColorDefaults($pdo);
        $programmeName = trim((string)($studentScope['programme_name'] ?? 'Student Programme'));
        $facultyName = trim((string)($studentScope['faculty_name'] ?? ''));
        $departmentName = trim((string)($studentScope['department_name'] ?? ''));
        $mode = trim((string)($studentScope['mode_of_study'] ?? ''));
        $level = trim((string)($studentScope['level'] ?? ''));
        $headerColor = (string)($defaults['header_color'] ?? '#003366');
        $sectionHeaderColor = (string)($defaults['section_header_color'] ?? $defaults['card_header_color'] ?? '#003366');
        $buttonColor = (string)($defaults['button_color'] ?? '#003366');
        $sidebarColor = (string)($defaults['sidebar_color'] ?? '#003366');
        $badgeSuccess = (string)($defaults['badge_success_color'] ?? $defaults['badge_color'] ?? '#198754');
        return [
            'id' => 0,
            'rule_name' => 'Global Default Dashboard',
            'faculty_id' => (int)($studentScope['faculty_id'] ?? 0),
            'faculty_name' => $facultyName,
            'department_id' => (int)($studentScope['department_id'] ?? 0),
            'department_name' => $departmentName,
            'programme_id' => (int)($studentScope['programme_id'] ?? 0),
            'programme_name' => $programmeName,
            'mode_of_study' => $mode,
            'level' => $level,
            'layout_type' => 'standard',
            'primary_color' => $defaults['primary_color'],
            'secondary_color' => $defaults['secondary_color'],
            'accent_color' => $defaults['accent_color'],
            'background_color' => $defaults['background_color'],
            'card_background_color' => $defaults['card_background_color'],
            'card_border_color' => $defaults['card_border_color'],
            'card_title_color' => $defaults['card_title_color'],
            'body_text_color' => $defaults['body_text_color'],
            'heading_color' => $defaults['heading_color'],
            'muted_text_color' => $defaults['muted_text_color'],
            'header_color' => $headerColor,
            'header_text_color' => (string)($defaults['header_text_color'] ?? '#FFFFFF'),
            'section_header_color' => $sectionHeaderColor,
            'card_header_color' => $defaults['card_header_color'],
            'sidebar_color' => $sidebarColor,
            'sidebar_text_color' => $defaults['sidebar_text_color'],
            'sidebar_inactive_color' => $defaults['sidebar_inactive_color'],
            'button_color' => $buttonColor,
            'button_text_color' => $defaults['button_text_color'],
            'badge_color' => $defaults['badge_color'],
            'badge_success_color' => $badgeSuccess,
            'badge_warning_color' => $defaults['badge_warning_color'],
            'badge_danger_color' => $defaults['badge_danger_color'],
            'link_color' => $defaults['link_color'],
            'stat_card_background_color' => $defaults['stat_card_background_color'],
            'stat_number_color' => $defaults['stat_number_color'],
            'stat_label_color' => $defaults['stat_label_color'],
            'tab_active_color' => $defaults['tab_active_color'],
            'tab_inactive_color' => $defaults['tab_inactive_color'],
            'tab_text_color' => $defaults['tab_text_color'],
            'table_header_bg_color' => $defaults['table_header_bg_color'],
            'table_header_text_color' => $defaults['table_header_text_color'],
            'alert_info_color' => $defaults['alert_info_color'],
            'notice_banner_text' => null,
            'is_active' => 1,
            'is_default' => 1,
            'show_hostel' => 1,
            'show_clearance' => 1,
            'show_cbt' => 1,
            'show_lms' => 1,
            'show_library' => 1,
            'show_support' => 1,
            'specificity_score' => 10,
            'source' => 'global-fallback',
            'header_text_color' => '#FFFFFF',
            'card_header_text_color' => function_exists('portalContrastColor') ? portalContrastColor($sectionHeaderColor) : '#FFFFFF',
            'sidebar_text_color' => (string)($defaults['sidebar_text_color'] ?? (function_exists('portalContrastColor') ? portalContrastColor($sidebarColor) : '#FFFFFF')),
            'sidebar_inactive_color' => (string)($defaults['sidebar_inactive_color'] ?? ((strcasecmp((string)($defaults['sidebar_text_color'] ?? '#FFFFFF'), '#FFFFFF') === 0) ? '#D9E2EC' : '#64748B')),
            'button_text_color_computed' => function_exists('portalContrastColor') ? portalContrastColor($buttonColor) : '#FFFFFF',
            'badge_text_color' => function_exists('portalContrastColor') ? portalContrastColor($badgeSuccess) : '#FFFFFF',
            'alert_info_text_color' => function_exists('portalContrastColor') ? portalContrastColor((string)($defaults['alert_info_color'] ?? '#0B5ED7')) : '#FFFFFF',
            'surface_color' => $defaults['card_background_color'],
            'text_color' => $defaults['body_text_color'],
            'body_class' => 'dashboard-layout-standard',
        ];
    }
}

if (!function_exists('portalResolveStudentDashboardDesignRule')) {
    function portalResolveStudentDashboardDesignRule(PDO $pdo, array $student): array {
        portalEnsureDashboardDesignRulesSchema($pdo);
        $studentScope = portalStudentDashboardScope($pdo, $student);
        $activeRules = portalFetchDashboardDesignRules($pdo, true);
        $matched = null;
        foreach ($activeRules as $rule) {
            if (portalDashboardDesignRuleMatchesStudent($pdo, $rule, $studentScope)) {
                $matched = $rule;
                break;
            }
        }
        if (!$matched) {
            $matched = portalDefaultStudentDashboardTheme($pdo, $studentScope);
        } else {
            $matched = array_merge(portalDefaultStudentDashboardTheme($pdo, $studentScope), $matched);
            $matched['source'] = ((int)($matched['id'] ?? 0) > 0) ? 'custom-rule' : 'global-fallback';
            $matched['faculty_name'] = trim((string)($studentScope['faculty_name'] ?? $matched['faculty_name'] ?? ''));
            $matched['department_name'] = trim((string)($studentScope['department_name'] ?? $matched['department_name'] ?? ''));
            $matched['programme_name'] = trim((string)($studentScope['programme_name'] ?? $matched['programme_name'] ?? ''));
            $matched['mode_of_study'] = trim((string)($studentScope['mode_of_study'] ?? $matched['mode_of_study'] ?? ''));
            $matched['level'] = trim((string)($studentScope['level'] ?? $matched['level'] ?? ''));
        }
        $matched['layout_type'] = portalNormalizeDashboardRuleLayoutType((string)($matched['layout_type'] ?? 'standard'));
        $matched['body_class'] = 'dashboard-layout-' . str_replace([' ', '+'], ['-', '-'], (string)$matched['layout_type']);
        $matched['header_text_color'] = portalNormalizeHexColor((string)($matched['header_text_color'] ?? ''), '#FFFFFF');
        $matched['card_header_text_color'] = function_exists('portalContrastColor') ? portalContrastColor((string)($matched['section_header_color'] ?? $matched['card_header_color'] ?? '#003366')) : '#FFFFFF';
        $matched['sidebar_text_color'] = portalNormalizeHexColor((string)($matched['sidebar_text_color'] ?? ''), function_exists('portalContrastColor') ? portalContrastColor((string)($matched['sidebar_color'] ?? '#003366')) : '#FFFFFF');
        $matched['sidebar_inactive_color'] = portalNormalizeHexColor((string)($matched['sidebar_inactive_color'] ?? ''), (strcasecmp((string)($matched['sidebar_text_color'] ?? '#FFFFFF'), '#FFFFFF') === 0 ? '#D9E2EC' : '#64748B'));
        $matched['button_text_color_computed'] = function_exists('portalContrastColor') ? portalContrastColor((string)($matched['button_color'] ?? '#003366')) : '#FFFFFF';
        $matched['badge_text_color'] = function_exists('portalContrastColor') ? portalContrastColor((string)($matched['badge_success_color'] ?? $matched['badge_color'] ?? '#198754')) : '#FFFFFF';
        $matched['alert_info_text_color'] = function_exists('portalContrastColor') ? portalContrastColor((string)($matched['alert_info_color'] ?? '#0B5ED7')) : '#FFFFFF';
        $matched['surface_color'] = (string)($matched['card_background_color'] ?? $matched['surface_color'] ?? '#FFFFFF');
        $matched['text_color'] = (string)($matched['body_text_color'] ?? $matched['text_color'] ?? '#333333');
        $matched['scope_text'] = portalDashboardDesignRuleScopeText($pdo, [
            'faculty_id' => $studentScope['faculty_id'] ?? 0,
            'faculty_name' => $studentScope['faculty_name'] ?? '',
            'department_id' => $studentScope['department_id'] ?? 0,
            'department_name' => $studentScope['department_name'] ?? '',
            'programme_id' => $studentScope['programme_id'] ?? 0,
            'programme_name' => $studentScope['programme_name'] ?? '',
            'mode_of_study' => $studentScope['mode_of_study'] ?? '',
            'level' => $studentScope['level'] ?? '',
        ]);
        return $matched;
    }
}
if (!function_exists('portalDashboardReorderStatCards')) {

    function portalDashboardReorderStatCards(array $cards, array $priorityLabels): array {
        if (!$cards || !$priorityLabels) return $cards;
        $priorityMap = [];
        $index = 0;
        foreach ($priorityLabels as $label) {
            $priorityMap[strtolower(trim((string)$label))] = $index++;
        }
        usort($cards, static function(array $a, array $b) use ($priorityMap): int {
            $labelA = strtolower(trim((string)($a['label'] ?? '')));
            $labelB = strtolower(trim((string)($b['label'] ?? '')));
            $rankA = $priorityMap[$labelA] ?? 9999;
            $rankB = $priorityMap[$labelB] ?? 9999;
            if ($rankA !== $rankB) return $rankA <=> $rankB;
            return 0;
        });
        return $cards;
    }
}


if (!function_exists('portalLazyLoaderSettingKey')) {
    function portalLazyLoaderSettingKey(): string {
        return 'lazy_loader_enabled';
    }
}
if (!function_exists('portalLazyLoaderEnabled')) {
    function portalLazyLoaderEnabled(PDO $pdo): bool {
        $value = strtolower(trim((string)getSystemSettingValue($pdo, portalLazyLoaderSettingKey(), '')));
        if ($value === '') {
            $value = strtolower(trim((string)getSystemSettingValue($pdo, 'resume_user_progress_enabled', 'disabled')));
        }
        return $value === 'enabled';
    }
}
if (!function_exists('portalResumeProgressSettingKey')) {
    function portalResumeProgressSettingKey(): string {
        return 'resume_user_progress_enabled';
    }
}
if (!function_exists('portalResumeProgressEnabled')) {
    function portalResumeProgressEnabled(PDO $pdo): bool {
        $value = strtolower(trim((string)getSystemSettingValue($pdo, portalResumeProgressSettingKey(), '')));
        if ($value === '') {
            $value = strtolower(trim((string)getSystemSettingValue($pdo, portalLazyLoaderSettingKey(), 'disabled')));
        }
        return $value === 'enabled';
    }
}
if (!function_exists('portalResumeEnsureSettings')) {
    function portalResumeEnsureSettings(PDO $pdo): void {
        $resumeValue = strtolower(trim((string)getSystemSettingValue($pdo, portalResumeProgressSettingKey(), '')));
        $lazyValue = strtolower(trim((string)getSystemSettingValue($pdo, portalLazyLoaderSettingKey(), '')));

        if ($resumeValue === '') {
            $resumeValue = $lazyValue !== '' ? $lazyValue : 'disabled';
            setSystemSettingValue($pdo, portalResumeProgressSettingKey(), $resumeValue, 'Enable or disable resume user progress after login');
        }
        if ($lazyValue === '') {
            $lazyValue = $resumeValue !== '' ? $resumeValue : 'disabled';
            setSystemSettingValue($pdo, portalLazyLoaderSettingKey(), $lazyValue, 'Enable or disable lazy loader progress tracking');
        }
    }
}
if (!function_exists('portalResumeEnsureSchema')) {
    function portalResumeEnsureSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS user_resume_progress (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_type VARCHAR(20) NOT NULL,
                user_id INT NOT NULL,
                page_name VARCHAR(120) NULL,
                last_url VARCHAR(2048) NOT NULL,
                state_json LONGTEXT NULL,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_user_resume (user_type, user_id),
                INDEX idx_resume_page (page_name),
                INDEX idx_resume_updated (updated_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'user_resume_progress', 'page_name')) { $pdo->exec("ALTER TABLE user_resume_progress ADD COLUMN page_name VARCHAR(120) NULL AFTER user_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'user_resume_progress', 'last_url')) { $pdo->exec("ALTER TABLE user_resume_progress ADD COLUMN last_url VARCHAR(2048) NOT NULL DEFAULT '' AFTER page_name"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'user_resume_progress', 'state_json')) { $pdo->exec("ALTER TABLE user_resume_progress ADD COLUMN state_json LONGTEXT NULL AFTER last_url"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'user_resume_progress', 'updated_at')) { $pdo->exec("ALTER TABLE user_resume_progress ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER state_json"); } } catch (Throwable $e) {}
    }
}
if (!function_exists('portalResumeProgressToken')) {
    function portalResumeProgressToken(): string {
        if (empty($_SESSION['portal_resume_progress_token'])) {
            try {
                $_SESSION['portal_resume_progress_token'] = bin2hex(random_bytes(24));
            } catch (Throwable $e) {
                $_SESSION['portal_resume_progress_token'] = honttechSecureToken(32);
            }
        }
        return (string)$_SESSION['portal_resume_progress_token'];
    }
}
if (!function_exists('portalResumeProgressTokenValid')) {
    function portalResumeProgressTokenValid(string $token): bool {
        $session = (string)($_SESSION['portal_resume_progress_token'] ?? '');
        return $session !== '' && $token !== '' && hash_equals($session, $token);
    }
}
if (!function_exists('portalResumePageMenuMap')) {
    function portalResumePageMenuMap(): array {
        return [
            'admin_dashboard' => 'menu_admin_dashboard',
            'admin_students' => 'menu_admin_students',
            'admin_applications' => 'menu_admin_applications',
            'admin_add_application' => 'menu_admin_add_application',
            'admin_payments' => 'menu_admin_payments',
            'admin_wallets' => 'menu_admin_wallets',
            'admin_settings' => 'menu_admin_settings',
            'theme_branding' => 'menu_admin_settings',
            'reports' => 'menu_reports',
            'messages' => 'menu_messages',
            'notifications' => 'menu_notifications',
            'admin_structure_manager' => 'menu_structure_manager',
            'admin_sessions_manager' => 'menu_sessions_manager',
            'admin_courses_manager' => 'menu_registrable_courses',
            'admin_numbering_settings' => 'menu_numbering_settings',
            'admin_matric_verifier' => 'menu_numbering_settings',
            'admin_pages' => 'menu_pages',
            'admin_graduated' => 'menu_graduated',
            'admin_registrations_manager' => 'menu_registrations_manager',
            'admin_yet_to_register' => 'menu_yet_to_register',
            'admin_registration_settings' => 'menu_registration_settings',
            'admin_previous_admission_record' => 'menu_previous_admission_record',
            'admin_clearance' => 'menu_admin_clearance',
            'results_manager' => 'menu_results_manager',
            'results_approvals' => 'menu_results_approvals',
            'download_scoresheet' => 'menu_download_scoresheet',
            'admin_transcript' => 'menu_transcript_admin',
            'staff_users' => 'menu_staff_users',
            'staff_roles' => 'menu_staff_roles',
            'lms_admin' => 'menu_lms_admin',
            'library_admin' => 'menu_library_admin',
            'cbt_admin' => 'menu_cbt_admin',
            'hrm_dashboard' => 'menu_hrm',
            'hrm_staff_management' => 'menu_hrm',
            'hrm_departments' => 'menu_hrm',
            'hrm_units' => 'menu_hrm',
            'hrm_designations' => 'menu_hrm',
            'hrm_attendance' => 'menu_hrm',
            'hrm_leave_management' => 'menu_hrm',
            'hrm_payroll' => 'menu_hrm',
            'hrm_recruitment' => 'menu_hrm',
            'hrm_performance_review' => 'menu_hrm',
            'hrm_staff_documents' => 'menu_hrm',
            'hrm_reports' => 'menu_hrm',
            'hrm_settings' => 'menu_hrm',
            'hrm_notifications' => 'menu_hrm',
            'notification_settings' => 'menu_notification_settings',
            'feature_toggles' => 'menu_feature_toggles',
        ];
    }
}
if (!function_exists('portalResumeDisallowedAction')) {
    function portalResumeDisallowedAction(string $action): bool {
        $action = strtolower(trim($action));
        if ($action === '') return false;
        $blocked = [
            'logout','login','delete','remove','drop','approve','reject','publish','unpublish','verify_payment',
            'return_superadmin','return_admin','download','export','print','generate_id_card'
        ];
        foreach ($blocked as $needle) {
            if ($action === $needle) return true;
        }
        return false;
    }
}
if (!function_exists('portalResumeNormalizeUrl')) {
    function portalResumeNormalizeUrl($rawUrl): string {
        $rawUrl = html_entity_decode(trim((string)$rawUrl), ENT_QUOTES, 'UTF-8');
        if ($rawUrl === '') return '';
        $rawUrl = preg_replace('/[\x00-\x1F\x7F]+/u', '', $rawUrl) ?: $rawUrl;

        if (preg_match('~^https?://~i', $rawUrl)) {
            $parts = parse_url($rawUrl);
            $rawUrl = ($parts['path'] ?? '') . (!empty($parts['query']) ? ('?' . $parts['query']) : '') . (!empty($parts['fragment']) ? ('#' . $parts['fragment']) : '');
        }
        if (str_starts_with($rawUrl, 'index.php')) {
            $rawUrl = substr($rawUrl, strlen('index.php')) ?: '';
        }
        if ($rawUrl !== '' && $rawUrl[0] === '/') {
            $parts = parse_url($rawUrl);
            $rawUrl = (!empty($parts['query']) ? ('?' . $parts['query']) : '') . (!empty($parts['fragment']) ? ('#' . $parts['fragment']) : '');
        }
        if ($rawUrl === '' || $rawUrl[0] !== '?') {
            if (strpos($rawUrl, '?') !== false) {
                $rawUrl = strstr($rawUrl, '?') ?: '';
            }
        }
        if ($rawUrl === '' || $rawUrl[0] !== '?') return '';

        $fragment = '';
        if (strpos($rawUrl, '#') !== false) {
            [$rawUrl, $fragment] = explode('#', $rawUrl, 2);
            $fragment = preg_replace('/[^a-zA-Z0-9_\-:.]+/', '', (string)$fragment) ?: '';
        }
        $queryString = substr($rawUrl, 1);
        if ($queryString === '') return '';
        parse_str($queryString, $params);
        if (!$params || !is_array($params)) return '';

        $safe = [];
        foreach ($params as $key => $value) {
            if (is_array($value)) continue;
            $key = trim((string)$key);
            if ($key === '') continue;
            $safe[$key] = trim((string)$value);
        }

        $page = trim((string)($safe['page'] ?? ''));
        if ($page === 'course_registration') $page = 'student_course_registration';
        if ($page === 'course_form_verify') $page = 'public_course_form';
        if ($page === '') return '';
        $safe['page'] = $page;

        $action = trim((string)($safe['action'] ?? ''));
        if ($action !== '' && portalResumeDisallowedAction($action)) return '';
        if ($action === '') unset($safe['action']);

        $forbiddenPages = ['home','login','admin_login','logout','forgot_password','reset_password'];
        if (in_array($page, $forbiddenPages, true)) return '';

        $query = http_build_query($safe, '', '&', PHP_QUERY_RFC3986);
        if ($query === '') return '';
        $normalized = '?' . $query;
        if ($fragment !== '') $normalized .= '#' . $fragment;
        return strlen($normalized) > 2048 ? substr($normalized, 0, 2048) : $normalized;
    }
}
if (!function_exists('portalResumePageFromUrl')) {
    function portalResumePageFromUrl(string $url): string {
        $normalized = portalResumeNormalizeUrl($url);
        if ($normalized === '') return '';
        $parts = parse_url($normalized);
        parse_str((string)($parts['query'] ?? ''), $params);
        return trim((string)($params['page'] ?? ''));
    }
}
if (!function_exists('portalResumeStatePayloadFromUrl')) {
    function portalResumeStatePayloadFromUrl(string $url): array {
        $normalized = portalResumeNormalizeUrl($url);
        if ($normalized === '') return [];
        $parts = parse_url($normalized);
        parse_str((string)($parts['query'] ?? ''), $params);
        $keys = ['page','tab','subtab','step','stage','mode','fees_tab','semester','level','session_name','academic_session','id','reg_id','transaction_id'];
        $payload = [];
        foreach ($keys as $key) {
            if (isset($params[$key]) && !is_array($params[$key])) {
                $payload[$key] = trim((string)$params[$key]);
            }
        }
        if (!empty($parts['fragment'])) {
            $payload['hash'] = preg_replace('/[^a-zA-Z0-9_\-:.]+/', '', (string)$parts['fragment']) ?: '';
        }
        return $payload;
    }
}
if (!function_exists('portalResumeUserCanAccessUrl')) {
    function portalResumeUserCanAccessUrl(PDO $pdo, string $userType, string $url): bool {
        $normalized = portalResumeNormalizeUrl($url);
        if ($normalized === '') return false;
        $page = portalResumePageFromUrl($normalized);
        if ($page === '') return false;

        if ($userType === 'admin') {
            $map = portalResumePageMenuMap();
            if (isset($map[$page]) && function_exists('menuEnabled') && !menuEnabled($pdo, $map[$page])) {
                return false;
            }
            return !in_array($page, ['login','admin_login','logout','forgot_password','reset_password'], true);
        }

        if (str_starts_with($page, 'admin_') || str_ends_with($page, '_admin')) return false;
        if (str_starts_with($page, 'hrm_')) return false;
        if (isset(portalResumePageMenuMap()[$page])) return false;
        $blockedStudentPages = ['admin_login','login','logout','forgot_password','reset_password','reports','messages','notifications','feature_toggles','notification_settings','staff_users','staff_roles','results_manager','results_approvals','download_scoresheet'];
        return !in_array($page, $blockedStudentPages, true);
    }
}
if (!function_exists('portalResumeCurrentActor')) {
    function portalResumeCurrentActor(): array {
        $userType = (string)($_SESSION['user_type'] ?? '');
        if ($userType === 'admin') {
            $userId = (int)(($_SESSION['admin_id'] ?? 0) ?: ($_SESSION['user_id'] ?? 0));
            return ['user_type' => 'admin', 'user_id' => $userId];
        }
        if (!empty($_SESSION['user_id'])) {
            $userId = (int)(($_SESSION['student_id'] ?? 0) ?: ($_SESSION['user_id'] ?? 0));
            return ['user_type' => 'student', 'user_id' => $userId];
        }
        return ['user_type' => '', 'user_id' => 0];
    }
}
if (!function_exists('portalResumeSaveState')) {
    function portalResumeSaveState(PDO $pdo, string $userType, int $userId, string $url, array $extra = []): void {
        if ($userId <= 0 || !in_array($userType, ['student','admin'], true)) return;
        if (!portalLazyLoaderEnabled($pdo)) return;
        $normalized = portalResumeNormalizeUrl($url);
        if ($normalized === '' || !portalResumeUserCanAccessUrl($pdo, $userType, $normalized)) return;
        portalResumeEnsureSchema($pdo);
        $state = array_merge(portalResumeStatePayloadFromUrl($normalized), $extra);
        $sessionThemeMode = strtolower(trim((string)($_SESSION['portal_theme_mode'] ?? '')));
        if ($sessionThemeMode !== '' && in_array($sessionThemeMode, ['light','dark'], true) && empty($state['theme_mode'])) {
            $state['theme_mode'] = $sessionThemeMode;
        }
        $page = trim((string)($state['page'] ?? portalResumePageFromUrl($normalized)));
        try {
            $stmt = $pdo->prepare("INSERT INTO user_resume_progress (user_type, user_id, page_name, last_url, state_json, updated_at)
                VALUES (?, ?, ?, ?, ?, NOW())
                ON DUPLICATE KEY UPDATE page_name = VALUES(page_name), last_url = VALUES(last_url), state_json = VALUES(state_json), updated_at = NOW()");
            $stmt->execute([$userType, $userId, $page !== '' ? $page : null, $normalized, json_encode($state, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),]);
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalResumeLoadState')) {
    function portalResumeLoadState(PDO $pdo, string $userType, int $userId): array {
        if ($userId <= 0 || !in_array($userType, ['student','admin'], true)) return [];
        portalResumeEnsureSchema($pdo);
        try {
            $stmt = $pdo->prepare("SELECT * FROM user_resume_progress WHERE user_type = ? AND user_id = ? LIMIT 1");
            $stmt->execute([$userType, $userId]);
            return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}
if (!function_exists('portalResumeResolveLoginRedirect')) {
    function portalResumeResolveLoginRedirect(PDO $pdo, string $userType, int $userId, string $fallback = ''): string {
        $fallback = trim((string)$fallback);
        if ($fallback === '') {
            $fallback = $userType === 'admin' ? '?page=admin_dashboard' : '?page=student_dashboard';
        }
        if (!portalResumeProgressEnabled($pdo)) return $fallback;
        $row = portalResumeLoadState($pdo, $userType, $userId);
        $url = trim((string)($row['last_url'] ?? ''));
        if ($url === '' || !portalResumeUserCanAccessUrl($pdo, $userType, $url)) {
            return $fallback;
        }
        if ($url !== $fallback) {
            try { flash('info', 'We resumed your last safe page so you can continue from where you stopped.'); } catch (Throwable $e) {}
        }
        return $url;
    }
}
if (!function_exists('portalResumeTrackCurrentPage')) {
    function portalResumeTrackCurrentPage(PDO $pdo, string $resolvedPage, array $requestParams = []): void {
        if ($_SERVER['REQUEST_METHOD'] !== 'GET') return;
        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower((string)$_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') return;
        $accept = strtolower((string)($_SERVER['HTTP_ACCEPT'] ?? ''));
        if ($accept !== '' && str_contains($accept, 'application/json')) return;
        $actor = portalResumeCurrentActor();
        if (($actor['user_id'] ?? 0) <= 0 || ($actor['user_type'] ?? '') === '') return;
        $params = [];
        foreach ($requestParams as $key => $value) {
            if (is_array($value)) continue;
            $params[(string)$key] = (string)$value;
        }
        $params['page'] = $resolvedPage;
        $url = '?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
        portalResumeSaveState($pdo, (string)$actor['user_type'], (int)$actor['user_id'], $url, ['tracked_at' => date('c')]);
    }
}
if (!function_exists('portalResumeTrackClientUrl')) {
    function portalResumeTrackClientUrl(PDO $pdo, string $url, array $extra = []): void {
        $actor = portalResumeCurrentActor();
        if (($actor['user_id'] ?? 0) <= 0 || ($actor['user_type'] ?? '') === '') return;
        $payload = array_merge(['tracked_at' => date('c')], $extra);
        portalResumeSaveState($pdo, (string)$actor['user_type'], (int)$actor['user_id'], $url, $payload);
    }
}
try { if (isset($pdo) && ($pdo instanceof PDO)) { portalResumeEnsureSchema($pdo); portalResumeEnsureSettings($pdo); portalBrandingEnsureSettings($pdo); portalEnsureDashboardDesignRulesSchema($pdo); } } catch (Throwable $e) {}

if (!function_exists('portalThemeFeatureSettingKey')) {
    function portalThemeFeatureSettingKey(): string {
        return 'theme_mode_enabled';
    }
}
if (!function_exists('portalThemeAllowedModes')) {
    function portalThemeAllowedModes(): array {
        return ['light', 'dark'];
    }
}
if (!function_exists('portalThemeNormalizeMode')) {
    function portalThemeNormalizeMode($value, string $fallback = 'light'): string {
        $value = strtolower(trim((string)$value));
        return in_array($value, portalThemeAllowedModes(), true) ? $value : $fallback;
    }
}
if (!function_exists('portalThemeEnsureSettings')) {
    function portalThemeEnsureSettings(PDO $pdo): void {
        $current = strtolower(trim((string)getSystemSettingValue($pdo, portalThemeFeatureSettingKey(), '')));
        if ($current === '') {
            setSystemSettingValue($pdo, portalThemeFeatureSettingKey(), 'enabled', 'Enable or disable the student/admin Light Mode and Dark Mode switcher globally');
        }
    }
}
if (!function_exists('portalThemeFeatureEnabled')) {
    function portalThemeFeatureEnabled(PDO $pdo): bool {
        portalThemeEnsureSettings($pdo);
        return strtolower(trim((string)getSystemSettingValue($pdo, portalThemeFeatureSettingKey(), 'enabled'))) === 'enabled';
    }
}
if (!function_exists('portalThemeEnsureSchema')) {
    function portalThemeEnsureSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS user_theme_preferences (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_type VARCHAR(20) NOT NULL,
                user_id INT NOT NULL,
                theme_mode VARCHAR(10) NOT NULL DEFAULT 'light',
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_user_theme (user_type, user_id),
                INDEX idx_user_theme_mode (theme_mode),
                INDEX idx_user_theme_updated (updated_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'user_theme_preferences', 'theme_mode')) { $pdo->exec("ALTER TABLE user_theme_preferences ADD COLUMN theme_mode VARCHAR(10) NOT NULL DEFAULT 'light' AFTER user_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'user_theme_preferences', 'updated_at')) { $pdo->exec("ALTER TABLE user_theme_preferences ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER theme_mode"); } } catch (Throwable $e) {}
    }
}
if (!function_exists('portalThemePreferenceToken')) {
    function portalThemePreferenceToken(): string {
        if (empty($_SESSION['portal_theme_preference_token'])) {
            try {
                $_SESSION['portal_theme_preference_token'] = bin2hex(random_bytes(24));
            } catch (Throwable $e) {
                $_SESSION['portal_theme_preference_token'] = honttechSecureToken(32);
            }
        }
        return (string)$_SESSION['portal_theme_preference_token'];
    }
}
if (!function_exists('portalThemePreferenceTokenValid')) {
    function portalThemePreferenceTokenValid(string $token): bool {
        $session = (string)($_SESSION['portal_theme_preference_token'] ?? '');
        return $session !== '' && $token !== '' && hash_equals($session, $token);
    }
}
if (!function_exists('portalThemeLoadPreference')) {
    function portalThemeLoadPreference(PDO $pdo, string $userType, int $userId): string {
        if ($userId <= 0 || !in_array($userType, ['student','admin'], true)) return '';
        portalThemeEnsureSchema($pdo);
        try {
            $stmt = $pdo->prepare("SELECT theme_mode FROM user_theme_preferences WHERE user_type = ? AND user_id = ? LIMIT 1");
            $stmt->execute([$userType, $userId]);
            $mode = $stmt->fetchColumn();
            if ($mode !== false && $mode !== null) {
                return portalThemeNormalizeMode((string)$mode, '');
            }
        } catch (Throwable $e) {}
        try {
            $resumeRow = portalResumeLoadState($pdo, $userType, $userId);
            if (!empty($resumeRow['state_json'])) {
                $state = json_decode((string)$resumeRow['state_json'], true);
                if (is_array($state) && !empty($state['theme_mode'])) {
                    return portalThemeNormalizeMode((string)$state['theme_mode'], '');
                }
            }
        } catch (Throwable $e) {}
        return '';
    }
}
if (!function_exists('portalThemeSavePreference')) {
    function portalThemeSavePreference(PDO $pdo, string $userType, int $userId, string $mode): void {
        if ($userId <= 0 || !in_array($userType, ['student','admin'], true)) return;
        $mode = portalThemeNormalizeMode($mode, 'light');
        portalThemeEnsureSchema($pdo);
        try {
            $stmt = $pdo->prepare("INSERT INTO user_theme_preferences (user_type, user_id, theme_mode, updated_at)
                VALUES (?, ?, ?, NOW())
                ON DUPLICATE KEY UPDATE theme_mode = VALUES(theme_mode), updated_at = NOW()");
            $stmt->execute([$userType, $userId, $mode]);
        } catch (Throwable $e) {}
        try {
            $resumeRow = portalResumeLoadState($pdo, $userType, $userId);
            $lastUrl = trim((string)($resumeRow['last_url'] ?? ''));
            if ($lastUrl !== '') {
                $state = [];
                if (!empty($resumeRow['state_json'])) {
                    $decoded = json_decode((string)$resumeRow['state_json'], true);
                    if (is_array($decoded)) $state = $decoded;
                }
                $state['theme_mode'] = $mode;
                $page = trim((string)($resumeRow['page_name'] ?? portalResumePageFromUrl($lastUrl)));
                $stmt = $pdo->prepare("UPDATE user_resume_progress SET page_name = ?, state_json = ?, updated_at = NOW() WHERE user_type = ? AND user_id = ?");
                $stmt->execute([$page !== '' ? $page : null, json_encode($state, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), $userType, $userId]);
            }
        } catch (Throwable $e) {}
        $_SESSION['portal_theme_mode'] = $mode;
    }
}
if (!function_exists('portalThemeInitializeForAuthenticatedUser')) {
    function portalThemeInitializeForAuthenticatedUser(PDO $pdo, string $userType, int $userId): string {
        portalThemeEnsureSettings($pdo);
        if (!portalThemeFeatureEnabled($pdo)) {
            $_SESSION['portal_theme_mode'] = 'light';
            return 'light';
        }
        $stored = portalThemeLoadPreference($pdo, $userType, $userId);
        $mode = $stored !== '' ? $stored : portalThemeNormalizeMode((string)($_SESSION['portal_theme_mode'] ?? ''), 'light');
        $_SESSION['portal_theme_mode'] = $mode;
        return $mode;
    }
}
if (!function_exists('portalThemeInitializeForCurrentUser')) {
    function portalThemeInitializeForCurrentUser(PDO $pdo): string {
        portalThemeEnsureSchema($pdo);
        portalThemeEnsureSettings($pdo);
        if (!portalThemeFeatureEnabled($pdo)) {
            $_SESSION['portal_theme_mode'] = 'light';
            return 'light';
        }
        $actor = portalResumeCurrentActor();
        if (($actor['user_id'] ?? 0) > 0 && ($actor['user_type'] ?? '') !== '') {
            return portalThemeInitializeForAuthenticatedUser($pdo, (string)$actor['user_type'], (int)$actor['user_id']);
        }
        $mode = portalThemeNormalizeMode((string)($_SESSION['portal_theme_mode'] ?? ''), 'light');
        $_SESSION['portal_theme_mode'] = $mode;
        return $mode;
    }
}
if (!function_exists('portalThemeCurrentMode')) {
    function portalThemeCurrentMode(PDO $pdo, string $default = 'light'): string {
        if (!portalThemeFeatureEnabled($pdo)) return 'light';
        $sessionMode = portalThemeNormalizeMode((string)($_SESSION['portal_theme_mode'] ?? ''), '');
        if ($sessionMode !== '') return $sessionMode;
        return portalThemeInitializeForCurrentUser($pdo) ?: $default;
    }
}
try {
    if (isset($pdo) && ($pdo instanceof PDO)) {
        portalThemeEnsureSchema($pdo);
        portalThemeEnsureSettings($pdo);
        portalThemeInitializeForCurrentUser($pdo);
    }
} catch (Throwable $e) {}


if (!function_exists('portalManagedContentToggleDefaults')) {
    function portalManagedContentToggleDefaults(): array {
        return [
            'home' => ['setting_key' => 'home_page_enabled', 'label' => 'Homepage'],
            'about' => ['setting_key' => 'about_page_enabled', 'label' => 'About Page'],
            'registration' => ['setting_key' => 'registration_form_enabled', 'label' => 'Registration'],
            'application_form' => ['setting_key' => 'application_form_enabled', 'label' => 'Application Form'],
            'admission_letter' => ['setting_key' => 'admission_letter_enabled', 'label' => 'Admission Letter'],
            'certificate' => ['setting_key' => 'certificate_enabled', 'label' => 'Certificate'],
            'student_id_card' => ['setting_key' => 'student_id_card_enabled', 'label' => 'Student ID Card'],
            'staff_id_card' => ['setting_key' => 'staff_id_card_enabled', 'label' => 'Staff ID Card'],
            'transcript' => ['setting_key' => 'transcript_page_enabled', 'label' => 'Transcript'],
        ];
    }
}
if (!function_exists('portalManagedContentMeta')) {
    function portalManagedContentMeta(string $slug): array {
        $map = portalManagedContentToggleDefaults();
        return $map[$slug] ?? ['setting_key' => '', 'label' => ucwords(str_replace('_', ' ', $slug))];
    }
}
if (!function_exists('portalManagedContentSettingKey')) {
    function portalManagedContentSettingKey(string $slug): string {
        $meta = portalManagedContentMeta($slug);
        return (string)($meta['setting_key'] ?? '');
    }
}
if (!function_exists('portalManagedContentEnabled')) {
    function portalManagedContentEnabled(PDO $pdo, string $slug): bool {
        $key = portalManagedContentSettingKey($slug);
        if ($key === '') return true;
        return strtoupper(trim((string)getSystemSettingValue($pdo, $key, 'Yes'))) !== 'NO';
    }
}
if (!function_exists('portalManagedContentDisabledMessage')) {
    function portalManagedContentDisabledMessage(PDO $pdo, string $slug): string {
        $meta = portalManagedContentMeta($slug);
        $label = trim((string)($meta['label'] ?? ucwords(str_replace('_', ' ', $slug))));
        return $label !== '' ? ($label . ' is currently disabled by the administrator.') : 'This page is currently disabled by the administrator.';
    }
}

if (!function_exists('portalPageDefaults')) {
    function portalPageDefaults(): array {
        return [
            'home_banner_title' => 'Honest Technologies University',
            'home_banner_subtitle' => 'Ahmadu Bello University, Zaria',
            'home_affiliation_text' => 'In affiliation with Dahift Global Ltd',
            'home_form_title' => 'Application Form for Admission',
            'home_instruction_text' => 'Write clearly and legibly in CAPITAL LETTERS using a biro or ballpoint pen. Attach all relevant credentials and a photocopy of the receipt of payment. Applicants who fail to attach evidence of payment or to complete the form properly will be disqualified.',
            'home_apply_button_text' => 'Apply Now',
            'home_apply_button_link' => '?page=register',
            'home_login_button_text' => 'Login',
            'home_login_button_link' => '?page=login',
            'home_card_1_icon' => 'bi bi-mortarboard-fill',
            'home_card_1_title' => 'Quality Education',
            'home_card_1_text' => "Get quality education from one of Nigeria's premier universities.",
            'home_card_2_icon' => 'bi bi-people-fill',
            'home_card_2_title' => 'Expert Faculty',
            'home_card_2_text' => 'Learn from experienced and dedicated faculty members.',
            'home_card_3_icon' => 'bi bi-building',
            'home_card_3_title' => 'Modern Facilities',
            'home_card_3_text' => 'Access to modern learning facilities and resources.',
            'about_page_title' => 'About HontTech Programme',
            'about_intro_title' => 'HontTech',
            'about_intro_text' => 'HontTech is the programme brand presented across this portal for student application, registration, and academic management workflows.',
            'about_programme_title' => 'About the Programme',
            'about_programme_text' => 'The HontTech programme supports student application, admission, registration, and fee management workflows across the portal.',
            'about_affiliation_title' => 'Dahift Global Ltd Affiliation',
            'about_affiliation_text' => 'This portal supports the academic and administrative workflows configured by the institution for qualified candidates and enrolled students.',
            'about_benefits_title' => 'Programme Benefits',
            'about_benefits_list' => "Direct entry admission into 200 level in any Nigerian university\nComprehensive curriculum covering relevant subjects\nExperienced and qualified lecturers\nConducive learning environment\nModern learning facilities",
            'about_requirements_title' => 'Admission Requirements',
            'about_requirements_list' => "Minimum of 5 O'Level credits in relevant subjects\nPassport photograph\nBirth certificate or declaration of age\nTestimonial from last school attended\nLocal government identification letter",
            'home_page_enabled' => 'Yes',
            'about_page_enabled' => 'Yes',
            'registration_form_enabled' => 'Yes',
            'application_form_enabled' => 'Yes',
            'admission_letter_enabled' => 'Yes',
            'certificate_enabled' => 'Yes',
            'student_id_card_enabled' => 'Yes',
            'staff_id_card_enabled' => 'Yes',
            'transcript_page_enabled' => 'Yes',
            'registration_form_disabled_message' => 'Student registration is currently disabled by the administrator. Please try again later.',
        ];
    }
}
if (!function_exists('portalPageContent')) {
    function portalPageContent(PDO $pdo): array {
        $defaults = portalPageDefaults();
        $data = $defaults;
        foreach ($defaults as $key => $default) {
            $data[$key] = getSystemSettingValue($pdo, $key, $default);
        }
        return $data;
    }
}

if (!function_exists('portalEnsureRegistrationPageScopeSchema')) {
    function portalEnsureRegistrationPageScopeSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS registration_page_scopes (
                id INT AUTO_INCREMENT PRIMARY KEY,
                session_name VARCHAR(40) NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                mode_of_study VARCHAR(30) NULL,
                mode_of_study_custom VARCHAR(180) NULL,
                is_enabled VARCHAR(3) NOT NULL DEFAULT 'Yes',
                disabled_message VARCHAR(255) NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_rps_scope (session_name, faculty_id, department_id, programme_id, mode_of_study),
                INDEX idx_rps_enabled (is_enabled)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'registration_page_scopes', 'mode_of_study')) { $pdo->exec("ALTER TABLE registration_page_scopes ADD COLUMN mode_of_study VARCHAR(30) NULL AFTER programme_id"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'registration_page_scopes', 'mode_of_study_custom')) { $pdo->exec("ALTER TABLE registration_page_scopes ADD COLUMN mode_of_study_custom VARCHAR(180) NULL AFTER mode_of_study"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'registration_page_scopes') && !portalIndexExistsSafe($pdo, 'registration_page_scopes', 'idx_rps_scope_mode')) { $pdo->exec("CREATE INDEX idx_rps_scope_mode ON registration_page_scopes(session_name, faculty_id, department_id, programme_id, mode_of_study)"); } } catch (Throwable $e) {}
        try {
            $pdo->exec("UPDATE registration_page_scopes
                SET mode_of_study_custom = CASE
                        WHEN COALESCE(TRIM(mode_of_study_custom), '') <> '' THEN TRIM(mode_of_study_custom)
                        WHEN COALESCE(TRIM(mode_of_study), '') NOT IN ('', 'Full-Time', 'Part-Time', 'Others') THEN TRIM(mode_of_study)
                        ELSE mode_of_study_custom
                    END,
                    mode_of_study = CASE
                        WHEN COALESCE(TRIM(mode_of_study), '') IN ('Full-Time', 'Part-Time', 'Others') THEN TRIM(mode_of_study)
                        WHEN COALESCE(TRIM(mode_of_study), '') = '' THEN NULL
                        ELSE 'Others'
                    END
                WHERE COALESCE(TRIM(mode_of_study), '') <> '' OR COALESCE(TRIM(mode_of_study_custom), '') <> ''");
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalNormalizeRegistrationScopeModeInput')) {
    function portalNormalizeRegistrationScopeModeInput($mode, $custom = ''): array {
        $mode = trim((string)$mode);
        $custom = trim((string)$custom);
        $custom = preg_replace('/\s+/u', ' ', $custom) ?: $custom;
        if ($mode === '' || $mode === '0') {
            return ['mode_of_study' => '', 'mode_of_study_custom' => '', 'display' => 'All Modes of Study'];
        }
        if (!in_array($mode, ['Full-Time', 'Part-Time', 'Others'], true)) {
            if ($custom === '' && $mode !== '') {
                $custom = $mode;
            }
            $mode = 'Others';
        }
        if ($mode === 'Others') {
            if ($custom === '') {
                throw new RuntimeException('Please enter the custom Mode of Study name when Others is selected.');
            }
            return ['mode_of_study' => 'Others', 'mode_of_study_custom' => $custom, 'display' => $custom];
        }
        return ['mode_of_study' => $mode, 'mode_of_study_custom' => '', 'display' => $mode];
    }
}
if (!function_exists('portalRegistrationScopeModeDisplay')) {
    function portalRegistrationScopeModeDisplay(array $row): string {
        $mode = trim((string)($row['mode_of_study'] ?? ''));
        $custom = trim((string)($row['mode_of_study_custom'] ?? ''));
        if ($mode === 'Others' && $custom !== '') return $custom;
        if (in_array($mode, ['Full-Time', 'Part-Time'], true)) return $mode;
        if ($custom !== '') return $custom;
        if ($mode !== '') return $mode;
        return 'All Modes of Study';
    }
}
if (!function_exists('portalRegistrationScopeModeMatches')) {
    function portalRegistrationScopeModeMatches(array $row, string $contextMode): bool {
        $contextMode = trim((string)$contextMode);
        $rowMode = trim((string)($row['mode_of_study'] ?? ''));
        $rowCustom = trim((string)($row['mode_of_study_custom'] ?? ''));
        if ($rowMode === '' && $rowCustom === '') return true;
        if ($contextMode === '') return false;
        if ($rowMode === 'Others') return strcasecmp($rowCustom, $contextMode) === 0;
        if (in_array($rowMode, ['Full-Time', 'Part-Time'], true)) return strcasecmp($rowMode, $contextMode) === 0;
        return strcasecmp($rowMode, $contextMode) === 0;
    }
}
if (!function_exists('portalNormalizeRegistrationPageScopeRow')) {
    function portalNormalizeRegistrationPageScopeRow(PDO $pdo, array $row): array {
        portalEnsureRegistrationPageScopeSchema($pdo);
        $sessionName = trim((string)($row['session_name'] ?? ''));
        $facultyId = max(0, (int)($row['faculty_id'] ?? 0));
        $departmentId = max(0, (int)($row['department_id'] ?? 0));
        $programmeId = max(0, (int)($row['programme_id'] ?? 0));
        $modeMeta = portalNormalizeRegistrationScopeModeInput($row['mode_of_study'] ?? '', $row['mode_of_study_custom'] ?? '');
        $isEnabled = strtoupper(trim((string)($row['is_enabled'] ?? 'Yes'))) === 'NO' ? 'No' : 'Yes';
        $disabledMessage = trim((string)($row['disabled_message'] ?? ''));

        if ($programmeId > 0) {
            try {
                $stmt = $pdo->prepare("SELECT p.id, p.department_id, d.faculty_id FROM programmes p LEFT JOIN departments d ON d.id = p.department_id WHERE p.id = ? LIMIT 1");
                $stmt->execute([$programmeId]);
                $prog = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
                if ($prog) {
                    $departmentId = (int)($prog['department_id'] ?? 0);
                    $facultyId = (int)($prog['faculty_id'] ?? 0);
                } else {
                    $programmeId = 0;
                }
            } catch (Throwable $e) {
                $programmeId = 0;
            }
        }
        if ($departmentId > 0) {
            try {
                $stmt = $pdo->prepare("SELECT id, faculty_id FROM departments WHERE id = ? LIMIT 1");
                $stmt->execute([$departmentId]);
                $dept = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
                if ($dept) {
                    $facultyId = (int)($dept['faculty_id'] ?? 0);
                } else {
                    $departmentId = 0;
                }
            } catch (Throwable $e) {
                $departmentId = 0;
            }
        }
        if ($facultyId > 0) {
            try {
                $stmt = $pdo->prepare("SELECT id FROM faculties WHERE id = ? LIMIT 1");
                $stmt->execute([$facultyId]);
                if (!(int)($stmt->fetchColumn() ?: 0)) {
                    $facultyId = 0;
                }
            } catch (Throwable $e) {
                $facultyId = 0;
            }
        }

        return [
            'session_name' => $sessionName,
            'faculty_id' => $facultyId,
            'department_id' => $departmentId,
            'programme_id' => $programmeId,
            'mode_of_study' => $modeMeta['mode_of_study'],
            'mode_of_study_custom' => $modeMeta['mode_of_study_custom'],
            'is_enabled' => $isEnabled,
            'disabled_message' => $disabledMessage,
        ];
    }
}
if (!function_exists('portalRegistrationPageScopeRows')) {
    function portalRegistrationPageScopeRows(PDO $pdo): array {
        portalEnsureRegistrationPageScopeSchema($pdo);
        try {
            $sql = "SELECT rps.*, f.name AS faculty_name, d.name AS department_name, p.name AS programme_name
                    FROM registration_page_scopes rps
                    LEFT JOIN faculties f ON f.id = rps.faculty_id
                    LEFT JOIN departments d ON d.id = rps.department_id
                    LEFT JOIN programmes p ON p.id = rps.programme_id
                    ORDER BY COALESCE(NULLIF(rps.session_name,''), 'ZZZZ') DESC,
                             COALESCE(f.name, ''), COALESCE(d.name, ''), COALESCE(p.name, ''),
                             COALESCE(NULLIF(rps.mode_of_study_custom,''), NULLIF(rps.mode_of_study,''), ''), rps.id ASC";
            $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($rows as &$row) {
                $row = portalNormalizeRegistrationPageScopeRow($pdo, (array)$row) + (array)$row;
                $row['programme_name'] = portalProgrammeSelectorLabelById($pdo, (int)($row['programme_id'] ?? 0), (int)($row['faculty_id'] ?? 0), (int)($row['department_id'] ?? 0), trim((string)($row['programme_name'] ?? '')));
                $row['mode_of_study_display'] = portalRegistrationScopeModeDisplay((array)$row);
            }
            unset($row);
            return $rows;
        } catch (Throwable $e) {
            return [];
        }
    }
}
if (!function_exists('portalSaveRegistrationPageScopeRowsFromPost')) {
    function portalSaveRegistrationPageScopeRowsFromPost(PDO $pdo, array $post): void {
        portalEnsureRegistrationPageScopeSchema($pdo);
        $sessions = $post['registration_scope_session_name'] ?? [];
        $faculties = $post['registration_scope_faculty_id'] ?? [];
        $departments = $post['registration_scope_department_id'] ?? [];
        $programmes = $post['registration_scope_programme_id'] ?? [];
        $modes = $post['registration_scope_mode_of_study'] ?? [];
        $modeCustoms = $post['registration_scope_mode_of_study_custom'] ?? [];
        $statuses = $post['registration_scope_is_enabled'] ?? [];
        $messages = $post['registration_scope_disabled_message'] ?? [];
        $maxRows = max(count($sessions), count($faculties), count($departments), count($programmes), count($modes), count($modeCustoms), count($statuses), count($messages));
        $rows = [];
        for ($i = 0; $i < $maxRows; $i++) {
            $raw = [
                'session_name' => $sessions[$i] ?? '',
                'faculty_id' => $faculties[$i] ?? 0,
                'department_id' => $departments[$i] ?? 0,
                'programme_id' => $programmes[$i] ?? 0,
                'mode_of_study' => $modes[$i] ?? '',
                'mode_of_study_custom' => $modeCustoms[$i] ?? '',
                'is_enabled' => $statuses[$i] ?? 'Yes',
                'disabled_message' => $messages[$i] ?? '',
            ];
            $norm = portalNormalizeRegistrationPageScopeRow($pdo, $raw);
            if ($norm['session_name'] === '' && (int)$norm['faculty_id'] === 0 && (int)$norm['department_id'] === 0 && (int)$norm['programme_id'] === 0 && trim((string)($norm['mode_of_study'] ?? '')) === '' && trim((string)($norm['mode_of_study_custom'] ?? '')) === '') {
                continue;
            }
            $programmeScopeKey = '';
            if ((int)$norm['programme_id'] > 0) {
                $programmeScopeKey = portalNormalizeAcademicOptionName(portalProgrammeScopeNameById($pdo, (int)$norm['programme_id']));
            }
            $dupKey = implode('|', [
                $norm['session_name'],
                (string)$norm['faculty_id'],
                (string)$norm['department_id'],
                $programmeScopeKey !== '' ? ('scope:' . $programmeScopeKey) : (string)$norm['programme_id'],
                (string)$norm['mode_of_study'],
                (string)$norm['mode_of_study_custom'],
            ]);
            $rows[$dupKey] = $norm;
        }
        try {
            $pdo->beginTransaction();
            $pdo->exec("DELETE FROM registration_page_scopes");
            if ($rows) {
                $stmt = $pdo->prepare("INSERT INTO registration_page_scopes (session_name, faculty_id, department_id, programme_id, mode_of_study, mode_of_study_custom, is_enabled, disabled_message) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
                foreach (array_values($rows) as $row) {
                    $stmt->execute([
                        $row['session_name'] !== '' ? $row['session_name'] : null,
                        (int)$row['faculty_id'] > 0 ? (int)$row['faculty_id'] : null,
                        (int)$row['department_id'] > 0 ? (int)$row['department_id'] : null,
                        (int)$row['programme_id'] > 0 ? (int)$row['programme_id'] : null,
                        $row['mode_of_study'] !== '' ? $row['mode_of_study'] : null,
                        $row['mode_of_study_custom'] !== '' ? $row['mode_of_study_custom'] : null,
                        $row['is_enabled'],
                        $row['disabled_message'] !== '' ? $row['disabled_message'] : null,
                    ]);
                }
            }
            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
        }
    }
}
if (!function_exists('portalRegistrationPageAvailability')) {
    function portalRegistrationPageAvailability(PDO $pdo, array $context = []): array {
        portalEnsureRegistrationPageScopeSchema($pdo);
        $defaultMessage = trim((string)getSystemSettingValue($pdo, 'registration_form_disabled_message', 'Student registration is currently disabled by the administrator. Please try again later.'));
        if ($defaultMessage === '') {
            $defaultMessage = 'Student registration is currently disabled by the administrator. Please try again later.';
        }
        $globalEnabled = strtoupper(trim((string)getSystemSettingValue($pdo, 'registration_form_enabled', 'Yes'))) !== 'NO';
        $ctx = portalNormalizeRegistrationPageScopeRow($pdo, [
            'session_name' => $context['session_name'] ?? portalCurrentAcademicSessionName($pdo),
            'faculty_id' => $context['faculty_id'] ?? 0,
            'department_id' => $context['department_id'] ?? 0,
            'programme_id' => $context['programme_id'] ?? 0,
            'mode_of_study' => '',
            'mode_of_study_custom' => '',
            'is_enabled' => 'Yes',
            'disabled_message' => '',
        ]);
        $ctxModeOfStudy = trim((string)($context['mode_of_study'] ?? ''));
        if ($ctxModeOfStudy === '' && (int)($ctx['programme_id'] ?? 0) > 0) {
            try {
                $ctxModeOfStudy = trim((string)portalProgrammeModeOfStudyValueById($pdo, (int)$ctx['programme_id'], ''));
            } catch (Throwable $e) {
                $ctxModeOfStudy = '';
            }
        }
        $best = null;
        $bestScore = -1;
        foreach (portalRegistrationPageScopeRows($pdo) as $row) {
            $rowSession = trim((string)($row['session_name'] ?? ''));
            $rowFaculty = (int)($row['faculty_id'] ?? 0);
            $rowDepartment = (int)($row['department_id'] ?? 0);
            $rowProgramme = (int)($row['programme_id'] ?? 0);
            if ($rowSession !== '' && $rowSession !== (string)$ctx['session_name']) continue;
            if ($rowFaculty > 0 && ((int)$ctx['faculty_id'] <= 0 || $rowFaculty !== (int)$ctx['faculty_id'])) continue;
            if ($rowDepartment > 0 && ((int)$ctx['department_id'] <= 0 || $rowDepartment !== (int)$ctx['department_id'])) continue;
            if ($rowProgramme > 0) {
                $ctxProgrammeId = (int)($ctx['programme_id'] ?? 0);
                if ($ctxProgrammeId <= 0) continue;
                if ($rowProgramme !== $ctxProgrammeId && !portalProgrammesShareScopeName($pdo, $rowProgramme, $ctxProgrammeId)) continue;
            }
            if (!portalRegistrationScopeModeMatches((array)$row, $ctxModeOfStudy)) continue;
            $rowHasMode = trim((string)($row['mode_of_study'] ?? '')) !== '' || trim((string)($row['mode_of_study_custom'] ?? '')) !== '';
            $score = ($rowSession !== '' ? 1 : 0)
                + ($rowFaculty > 0 ? 2 : 0)
                + ($rowDepartment > 0 ? 4 : 0)
                + ($rowProgramme > 0 ? 8 : 0)
                + ($rowHasMode ? 16 : 0);
            if ($score > $bestScore) {
                $bestScore = $score;
                $best = $row;
            }
        }
        if (is_array($best)) {
            $enabled = strtoupper(trim((string)($best['is_enabled'] ?? 'Yes'))) !== 'NO';
            $message = trim((string)($best['disabled_message'] ?? ''));
            if ($message === '') $message = $defaultMessage;
            return [
                'enabled' => $enabled,
                'message' => $message,
                'matched_rule' => $best,
                'context' => $ctx + ['mode_of_study' => $ctxModeOfStudy],
            ];
        }
        return [
            'enabled' => $globalEnabled,
            'message' => $defaultMessage,
            'matched_rule' => null,
            'context' => $ctx + ['mode_of_study' => $ctxModeOfStudy],
        ];
    }
}

if (!function_exists('portalPageLines')) {
    function portalPageLines(string $text): array {
        $lines = preg_split('/\r\n|\r|\n/', (string)$text) ?: [];
        $out = [];
        foreach ($lines as $line) {
            $line = trim((string)$line);
            if ($line !== '') $out[] = $line;
        }
        return $out;
    }
}
if (!function_exists('portalRenderText')) {
    function portalRenderText(string $text): string {
        return nl2br(htmlspecialchars(trim((string)$text), ENT_QUOTES, 'UTF-8'));
    }
}

if (!function_exists('portalDocumentContentDefaults')) {
    function portalDocumentContentDefaults(): array {
        return [
            'application_form' => [
                'title' => 'Application Form',
                'heading' => 'Application Form',
                'subheading' => 'Complete all sections carefully and review before submission.',
                'body_text' => 'Please provide accurate student biodata, academic details, contact information, and supporting records in the sections below.',
                'footer_text' => 'All submitted information is subject to verification by the institution.',
                'labels_text' => "Registration No.\nMatric Number\nSurname\nFirst Name\nOther Name",
                'notice_text' => 'You can edit your application a limited number of times before final lock.',
                'signature_title' => 'Authorized Signatory',
                'signature_name' => 'Admissions Officer',
            ],
            'admission_letter' => [
                'title' => 'Admission Letter',
                'heading' => 'ADMISSION LETTER',
                'subheading' => 'Honest Technologies University Examination',
                'body_text' => 'This is to certify that the candidate named on this document has been offered provisional admission for the approved academic session.',
                'footer_text' => 'This admission remains subject to verification and fulfilment of all institutional requirements.',
                'labels_text' => "Name\nCourse\nAdmission Number\nMatric Number\nAcademic Session\nAdmission Status",
                'notice_text' => 'Please keep this letter safely and present it whenever requested.',
                'signature_title' => 'Admissions Signatory',
                'signature_name' => 'Admissions Officer',
            ],
            'certificate' => [
                'title' => 'Certificate',
                'heading' => 'CERTIFICATE',
                'subheading' => 'Official Certificate Content',
                'body_text' => 'This certificate confirms successful completion of the approved programme.',
                'footer_text' => 'Issued by the institution for official use only.',
                'labels_text' => "Certificate No.\nStudent Name\nProgramme\nClass of Award\nDate Issued",
                'notice_text' => 'Any alteration renders this certificate invalid.',
                'signature_title' => 'Certificate Signatory',
                'signature_name' => 'Registrar',
            ],
            'student_id_card' => [
                'title' => 'Student ID Card',
                'heading' => 'Student ID Card',
                'subheading' => 'Official Student Identity Card',
                'body_text' => 'This card identifies the named holder as a valid student of the institution.',
                'footer_text' => 'For verification, present this card on demand while on campus.',
                'labels_text' => "ID No\nMatric No\nDOB\nPhone\nEmail\nJoin\nExpire",
                'notice_text' => 'This card remains the property of the institution.',
                'signature_title' => 'Authorized Signatory',
                'signature_name' => 'Authorized Signatory',
            ],
            'staff_id_card' => [
                'title' => 'Staff ID Card',
                'heading' => 'Staff ID Card',
                'subheading' => 'Official Staff Identity Card',
                'body_text' => 'This card identifies the named holder as an authorized member of staff.',
                'footer_text' => 'This card remains the property of the institution.',
                'labels_text' => "Staff ID\nFull Name\nDepartment\nUnit\nDesignation\nJoin\nExpire",
                'notice_text' => 'Return this card to the institution when employment ends.',
                'signature_title' => 'Authorized Signatory',
                'signature_name' => 'Human Resources',
            ],
            'transcript' => [
                'title' => 'Transcript',
                'heading' => 'ACADEMIC TRANSCRIPT',
                'subheading' => 'Official Academic Record',
                'body_text' => 'This transcript contains the approved academic record, semester results, cumulative GPA, and classification for the named student.',
                'footer_text' => 'This transcript is generated from the portal result records and remains subject to institutional approval.',
                'labels_text' => "Student Name\nMatric Number\nProgramme\nDepartment\nSession\nSemester\nCGPA\nClassification",
                'notice_text' => 'Only approved result records should appear on the transcript.',
                'signature_title' => 'Transcript Signatory',
                'signature_name' => 'Registrar',
            ],
        ];
    }
}
if (!function_exists('portalDocumentContent')) {
    function portalDocumentContent(PDO $pdo, string $slug): array {
        $defaults = portalDocumentContentDefaults();
        $slug = array_key_exists($slug, $defaults) ? $slug : 'application_form';
        $data = $defaults[$slug];
        foreach ($data as $field => $default) {
            $key = 'doc_' . $slug . '_' . $field;
            $data[$field] = getSystemSettingValue($pdo, $key, (string)$default);
        }
        return $data;
    }
}
if (!function_exists('portalManagedDocumentTabs')) {
    function portalManagedDocumentTabs(): array {
        return [
            'application_form' => 'Application Form',
            'admission_letter' => 'Admission Letter',
            'certificate' => 'Certificate',
            'student_id_card' => 'Student ID Card',
            'staff_id_card' => 'Staff ID Card',
            'transcript' => 'Transcript',
        ];
    }
}
if (!function_exists('portalHasAnyStaffPermission')) {
    function portalHasAnyStaffPermission(PDO $pdo, array $permKeys): bool {
        if (!isAdmin()) return false;
        if (isSuperadmin($pdo)) return true;
        foreach ($permKeys as $permKey) {
            if ($permKey !== '' && hasStaffPermission($pdo, (string)$permKey)) {
                return true;
            }
        }
        return false;
    }
}
if (!function_exists('portalCanManageAcademicResultPages')) {
    function portalCanManageAcademicResultPages(PDO $pdo): bool {
        return portalHasAnyStaffPermission($pdo, ['academics_manage', 'results_approve', 'results_publish', 'scoresheet_manage']);
    }
}
if (!function_exists('portalRequireAcademicResultPagesAccess')) {
    function portalRequireAcademicResultPagesAccess(PDO $pdo): void {
        if (!portalCanManageAcademicResultPages($pdo)) {
            flash('danger', 'Access denied. Permission required: academics_manage or result management access.');
            redirect('?page=admin_dashboard');
        }
    }
}
if (!function_exists('portalGradingResultSettingsDefaults')) {
    function portalGradingResultSettingsDefaults(): array {
        return [
            'result_ca_label' => 'CA',
            'result_ca_max_score' => '40',
            'result_minimum_ca_score' => '0',
            'result_minimum_ca_action' => 'fail',
            'result_exam_label' => 'EXAM',
            'result_exam_max_score' => '60',
            'result_exam_minimum_score' => '0',
            'result_exam_minimum_required' => '0',
            'result_exam_below_minimum_action' => 'fail',
            'result_pass_mark' => '40',
            'result_below_pass_mark_action' => 'fail',
            'result_grade_a_min' => '70',
            'result_grade_a_point' => '5.00',
            'result_grade_a_remark' => 'Excellent',
            'result_grade_b_min' => '60',
            'result_grade_b_point' => '4.00',
            'result_grade_b_remark' => 'Very Good',
            'result_grade_c_min' => '50',
            'result_grade_c_point' => '3.00',
            'result_grade_c_remark' => 'Good',
            'result_grade_d_min' => '45',
            'result_grade_d_point' => '2.00',
            'result_grade_d_remark' => 'Fair',
            'result_grade_e_min' => '40',
            'result_grade_e_point' => '1.00',
            'result_grade_e_remark' => 'Pass',
            'result_grade_f_min' => '0',
            'result_grade_f_point' => '0.00',
            'result_grade_f_remark' => 'Fail',
            'result_cgp_label' => 'CGP',
            'result_cgpa_label' => 'CGPA',
            'result_cgp_decimal_places' => '2',
            'result_cgp_rounding_mode' => 'half_up',
            'result_cgpa_decimal_places' => '2',
            'result_cgpa_rounding_mode' => 'half_up',
            'result_classification_rules' => "4.50 - 5.00 : First Class\n3.50 - 4.49 : Second Class Upper\n2.40 - 3.49 : Second Class Lower\n1.50 - 2.39 : Third Class\n1.00 - 1.49 : Pass\n0.00 - 0.99 : Fail",
        ];
    }
}
if (!function_exists('portalResultRuleActionOptions')) {
    function portalResultRuleActionOptions(bool $includeIncomplete = false): array {
        $options = [
            'fail' => 'Fail',
            'carry_over' => 'Carry Over',
        ];
        if ($includeIncomplete) {
            $options['incomplete'] = 'Incomplete';
        }
        return $options;
    }
}
if (!function_exists('portalNormalizeResultRuleAction')) {
    function portalNormalizeResultRuleAction($value, bool $includeIncomplete = false): string {
        $raw = strtolower(trim((string)$value));
        $map = [
            'fail' => 'fail',
            'f' => 'fail',
            'carry_over' => 'carry_over',
            'carry over' => 'carry_over',
            'carry-over' => 'carry_over',
            'carryover' => 'carry_over',
            'co' => 'carry_over',
            'incomplete' => 'incomplete',
        ];
        $raw = $map[$raw] ?? $raw;
        $options = portalResultRuleActionOptions($includeIncomplete);
        return array_key_exists($raw, $options) ? $raw : 'fail';
    }
}
if (!function_exists('portalResultRuleActionLabel')) {
    function portalResultRuleActionLabel($value, bool $includeIncomplete = false): string {
        $key = portalNormalizeResultRuleAction($value, $includeIncomplete);
        $options = portalResultRuleActionOptions($includeIncomplete);
        return (string)($options[$key] ?? 'Fail');
    }
}
if (!function_exists('portalNormalizeBooleanSettingValue')) {
    function portalNormalizeBooleanSettingValue($value): string {
        $raw = strtolower(trim((string)$value));
        return in_array($raw, ['1', 'yes', 'true', 'on', 'enabled'], true) ? '1' : '0';
    }
}
if (!function_exists('portalResultRoundingModeOptions')) {
    function portalResultRoundingModeOptions(): array {
        return [
            'half_up' => 'Half Up',
            'half_down' => 'Half Down',
            'half_even' => 'Half Even',
        ];
    }
}
if (!function_exists('portalNormalizeResultRoundingMode')) {
    function portalNormalizeResultRoundingMode($value): string {
        $raw = strtolower(trim((string)$value));
        return array_key_exists($raw, portalResultRoundingModeOptions()) ? $raw : 'half_up';
    }
}
if (!function_exists('portalApplyRoundedDecimal')) {
    function portalApplyRoundedDecimal(float $value, int $decimalPlaces = 2, string $mode = 'half_up'): float {
        $decimalPlaces = max(0, min(6, $decimalPlaces));
        $mode = portalNormalizeResultRoundingMode($mode);
        $phpMode = match ($mode) {
            'half_down' => PHP_ROUND_HALF_DOWN,
            'half_even' => PHP_ROUND_HALF_EVEN,
            default => PHP_ROUND_HALF_UP,
        };
        return round($value, $decimalPlaces, $phpMode);
    }
}
if (!function_exists('portalBuildGradeScaleRowsFromSettings')) {
    function portalBuildGradeScaleRowsFromSettings(array $settings): array {
        $rows = [
            ['grade' => 'A', 'min' => (float)($settings['result_grade_a_min'] ?? 70), 'point' => (float)($settings['result_grade_a_point'] ?? 5), 'remark' => trim((string)($settings['result_grade_a_remark'] ?? 'Excellent'))],
            ['grade' => 'B', 'min' => (float)($settings['result_grade_b_min'] ?? 60), 'point' => (float)($settings['result_grade_b_point'] ?? 4), 'remark' => trim((string)($settings['result_grade_b_remark'] ?? 'Very Good'))],
            ['grade' => 'C', 'min' => (float)($settings['result_grade_c_min'] ?? 50), 'point' => (float)($settings['result_grade_c_point'] ?? 3), 'remark' => trim((string)($settings['result_grade_c_remark'] ?? 'Good'))],
            ['grade' => 'D', 'min' => (float)($settings['result_grade_d_min'] ?? 45), 'point' => (float)($settings['result_grade_d_point'] ?? 2), 'remark' => trim((string)($settings['result_grade_d_remark'] ?? 'Fair'))],
            ['grade' => 'E', 'min' => (float)($settings['result_grade_e_min'] ?? 40), 'point' => (float)($settings['result_grade_e_point'] ?? 1), 'remark' => trim((string)($settings['result_grade_e_remark'] ?? 'Pass'))],
            ['grade' => 'F', 'min' => (float)($settings['result_grade_f_min'] ?? 0), 'point' => (float)($settings['result_grade_f_point'] ?? 0), 'remark' => trim((string)($settings['result_grade_f_remark'] ?? 'Fail'))],
        ];
        usort($rows, static function(array $a, array $b): int {
            return ($b['min'] <=> $a['min']);
        });
        return $rows;
    }
}
if (!function_exists('portalGradeScaleRangePrecision')) {
    function portalGradeScaleRangePrecision(array $rows, float $maxScore): int {
        $values = [$maxScore];
        foreach ($rows as $row) {
            $values[] = (float)($row['min'] ?? 0);
        }
        foreach ($values as $value) {
            if (abs($value - round($value)) > 0.00001) {
                return 2;
            }
        }
        return 0;
    }
}
if (!function_exists('portalFormatGradeScaleValue')) {
    function portalFormatGradeScaleValue(float $value, int $precision = 0): string {
        return rtrim(rtrim(number_format($value, $precision, '.', ''), '0'), '.');
    }
}
if (!function_exists('portalFormatGradeScaleRangeLabel')) {
    function portalFormatGradeScaleRangeLabel(float $lower, float $upper, int $precision = 0): string {
        return portalFormatGradeScaleValue($lower, $precision) . '–' . portalFormatGradeScaleValue($upper, $precision);
    }
}
if (!function_exists('portalBuildGradeScaleRangeRowsFromSettings')) {
    function portalBuildGradeScaleRangeRowsFromSettings(array $settings): array {
        $rows = portalBuildGradeScaleRowsFromSettings($settings);
        $maxScore = max(0.0, (float)($settings['result_ca_max_score'] ?? 40) + (float)($settings['result_exam_max_score'] ?? 60));
        $precision = portalGradeScaleRangePrecision($rows, $maxScore);
        $step = $precision > 0 ? (1 / (10 ** $precision)) : 1.0;
        foreach ($rows as $index => &$row) {
            $lower = (float)($row['min'] ?? 0);
            $upper = $index === 0
                ? $maxScore
                : ((float)($rows[$index - 1]['min'] ?? 0) - $step);
            if ($upper < $lower) {
                $upper = $lower;
            }
            if ($precision > 0) {
                $lower = round($lower, $precision);
                $upper = round($upper, $precision);
            } else {
                $lower = round($lower);
                $upper = round($upper);
            }
            $row['range_min'] = $lower;
            $row['range_max'] = $upper;
            $row['range_label'] = portalFormatGradeScaleRangeLabel($lower, $upper, $precision);
            $row['range_precision'] = $precision;
        }
        unset($row);
        return $rows;
    }
}
if (!function_exists('portalGradeScaleRangeRows')) {
    function portalGradeScaleRangeRows(PDO $pdo): array {
        return portalBuildGradeScaleRangeRowsFromSettings(portalGradingResultSettings($pdo));
    }
}
if (!function_exists('portalFormatGradeScaleSummaryText')) {
    function portalFormatGradeScaleSummaryText(array $rows): string {
        return implode(' | ', array_map(static function(array $row): string {
            $base = (string)($row['grade'] ?? '') . ' = ' . (string)($row['range_label'] ?? '')
                . ' (' . rtrim(rtrim(number_format((float)($row['point'] ?? 0), 2, '.', ''), '0'), '.') . ' GP)';
            $remark = trim((string)($row['remark'] ?? ''));
            return $remark !== '' ? ($base . ' - ' . $remark) : $base;
        }, $rows));
    }
}
if (!function_exists('portalGradingResultSettingsKeyList')) {
    function portalGradingResultSettingsKeyList(): array {
        return array_keys(portalGradingResultSettingsDefaults());
    }
}
if (!function_exists('portalGradingResultSettings')) {
    function portalGradingResultSettings(PDO $pdo): array {
        $data = portalGradingResultSettingsDefaults();
        foreach ($data as $key => $defaultValue) {
            $data[$key] = getSystemSettingValue($pdo, $key, (string)$defaultValue);
        }
        $data['result_minimum_ca_action'] = portalNormalizeResultRuleAction($data['result_minimum_ca_action'] ?? 'fail');
        $data['result_exam_minimum_required'] = portalNormalizeBooleanSettingValue($data['result_exam_minimum_required'] ?? '0');
        $data['result_exam_below_minimum_action'] = portalNormalizeResultRuleAction($data['result_exam_below_minimum_action'] ?? 'fail');
        $data['result_below_pass_mark_action'] = portalNormalizeResultRuleAction($data['result_below_pass_mark_action'] ?? 'fail');
        $data['result_cgp_rounding_mode'] = portalNormalizeResultRoundingMode($data['result_cgp_rounding_mode'] ?? 'half_up');
        $data['result_cgpa_rounding_mode'] = portalNormalizeResultRoundingMode($data['result_cgpa_rounding_mode'] ?? 'half_up');
        return $data;
    }
}
if (!function_exists('portalGradingResultSettingsUpdatedMeta')) {
    function portalGradingResultSettingsUpdatedMeta(PDO $pdo): array {
        $updatedBy = trim((string)getSystemSettingValue($pdo, 'result_settings_last_updated_by', ''));
        $storedOn = trim((string)getSystemSettingValue($pdo, 'result_settings_last_updated_on', ''));
        $updatedOn = '';
        if ($storedOn !== '') {
            $ts = strtotime($storedOn);
            if ($ts) {
                $updatedOn = date('Y-m-d H:i', $ts);
            } else {
                $updatedOn = $storedOn;
            }
        }
        if ($updatedOn === '') {
            $keys = portalGradingResultSettingsKeyList();
            if ($keys) {
                try {
                    $ph = implode(',', array_fill(0, count($keys), '?'));
                    $stmt = $pdo->prepare("SELECT MAX(updated_at) FROM system_settings WHERE setting_key IN ($ph)");
                    $stmt->execute($keys);
                    $raw = (string)$stmt->fetchColumn();
                    if ($raw !== '' && $raw !== '0000-00-00 00:00:00') {
                        $ts = strtotime($raw);
                        if ($ts) {
                            $updatedOn = date('Y-m-d H:i', $ts);
                        }
                    }
                } catch (Throwable $e) {}
            }
        }
        return [
            'updated_by' => $updatedBy,
            'updated_on' => $updatedOn,
        ];
    }
}
if (!function_exists('portalGradeScaleRows')) {
    function portalGradeScaleRows(PDO $pdo): array {
        return portalBuildGradeScaleRowsFromSettings(portalGradingResultSettings($pdo));
    }
}
if (!function_exists('portalGradeFromTotal')) {
    function portalGradeFromTotal(PDO $pdo, float $total): array {
        foreach (portalGradeScaleRows($pdo) as $row) {
            if ($total >= (float)($row['min'] ?? 0)) {
                return [(string)($row['grade'] ?? 'F'), (float)($row['point'] ?? 0)];
            }
        }
        return ['F', 0.0];
    }
}

if (!function_exists('portalGradeRemarkFromTotal')) {
    function portalGradeRemarkFromTotal(PDO $pdo, float $total, string $fallback = ''): string {
        foreach (portalGradeScaleRows($pdo) as $row) {
            if ($total >= (float)($row['min'] ?? 0)) {
                $remark = trim((string)($row['remark'] ?? ''));
                return $remark !== '' ? $remark : $fallback;
            }
        }
        return $fallback;
    }
}
if (!function_exists('portalResultAssessmentFromSettings')) {
    function portalResultAssessmentFromSettings(array $settings, float $caScore, float $examScore, ?float $totalScore = null): array {
        $total = $totalScore === null ? ($caScore + $examScore) : (float)$totalScore;
        $gradeRows = portalBuildGradeScaleRowsFromSettings($settings);
        $failureGrade = ['grade' => 'F', 'point' => 0.0, 'remark' => trim((string)($settings['result_grade_f_remark'] ?? 'Fail'))];
        foreach ($gradeRows as $row) {
            if (strtoupper((string)($row['grade'] ?? '')) === 'F') {
                $failureGrade = [
                    'grade' => (string)($row['grade'] ?? 'F'),
                    'point' => (float)($row['point'] ?? 0),
                    'remark' => trim((string)($row['remark'] ?? 'Fail')),
                ];
                break;
            }
        }

        $computedGrade = $failureGrade;
        foreach ($gradeRows as $row) {
            if ($total >= (float)($row['min'] ?? 0)) {
                $computedGrade = [
                    'grade' => (string)($row['grade'] ?? 'F'),
                    'point' => (float)($row['point'] ?? 0),
                    'remark' => trim((string)($row['remark'] ?? '')),
                ];
                break;
            }
        }

        $minimumCa = max(0.0, (float)($settings['result_minimum_ca_score'] ?? 0));
        $minimumCaAction = portalNormalizeResultRuleAction($settings['result_minimum_ca_action'] ?? 'fail');
        $minimumExam = max(0.0, (float)($settings['result_exam_minimum_score'] ?? 0));
        $examRequired = portalNormalizeBooleanSettingValue($settings['result_exam_minimum_required'] ?? '0') === '1';
        $minimumExamAction = portalNormalizeResultRuleAction($settings['result_exam_below_minimum_action'] ?? 'fail');
        $passMark = max(0.0, (float)($settings['result_pass_mark'] ?? 40));
        $belowPassMarkAction = portalNormalizeResultRuleAction($settings['result_below_pass_mark_action'] ?? 'fail');

        $failedMinimumCa = $caScore < $minimumCa;
        $failedMinimumExam = $examRequired && ($examScore < $minimumExam);
        $failedPassMark = !$failedMinimumCa && !$failedMinimumExam && ($total < $passMark);

        $status = 'PASS';
        $remark = trim((string)($computedGrade['remark'] ?? ''));
        $grade = (string)($computedGrade['grade'] ?? 'F');
        $gradePoint = (float)($computedGrade['point'] ?? 0);
        $appliedRule = 'grade_scale';

        if ($failedMinimumCa) {
            $status = strtoupper(str_replace('_', ' ', $minimumCaAction));
            $remark = portalResultRuleActionLabel($minimumCaAction);
            $grade = (string)$failureGrade['grade'];
            $gradePoint = (float)$failureGrade['point'];
            $appliedRule = 'minimum_ca';
        } elseif ($failedMinimumExam) {
            $status = strtoupper(str_replace('_', ' ', $minimumExamAction));
            $remark = portalResultRuleActionLabel($minimumExamAction);
            $grade = (string)$failureGrade['grade'];
            $gradePoint = (float)$failureGrade['point'];
            $appliedRule = 'minimum_exam';
        } elseif ($failedPassMark) {
            $status = strtoupper(str_replace('_', ' ', $belowPassMarkAction));
            $remark = portalResultRuleActionLabel($belowPassMarkAction);
            $appliedRule = 'below_pass_mark';
        } else {
            $status = 'PASS';
            if ($remark === '') {
                $remark = 'Pass';
            }
        }

        return [
            'total_score' => $total,
            'grade' => $grade,
            'grade_point' => $gradePoint,
            'remark' => $remark,
            'status' => $status,
            'failed_minimum_ca' => $failedMinimumCa,
            'failed_minimum_exam' => $failedMinimumExam,
            'failed_pass_mark' => $failedPassMark,
            'applied_rule' => $appliedRule,
        ];
    }
}
if (!function_exists('portalResultMinimumCaScore')) {
    function portalResultMinimumCaScore(PDO $pdo): float {
        $settings = portalGradingResultSettings($pdo);
        $minimum = isset($settings['result_minimum_ca_score']) ? (float)$settings['result_minimum_ca_score'] : 0.0;
        return $minimum < 0 ? 0.0 : $minimum;
    }
}
if (!function_exists('portalValidateGradingResultSettingsInput')) {
    function portalValidateGradingResultSettingsInput(array $input): array {
        $defaults = portalGradingResultSettingsDefaults();
        $clean = [];
        foreach ($defaults as $key => $defaultValue) {
            $clean[$key] = array_key_exists($key, $input) ? trim((string)$input[$key]) : (string)$defaultValue;
        }

        $clean['result_ca_label'] = $clean['result_ca_label'] !== '' ? $clean['result_ca_label'] : 'CA';
        $clean['result_exam_label'] = $clean['result_exam_label'] !== '' ? $clean['result_exam_label'] : 'EXAM';
        $clean['result_cgp_label'] = $clean['result_cgp_label'] !== '' ? $clean['result_cgp_label'] : 'CGP';
        $clean['result_cgpa_label'] = $clean['result_cgpa_label'] !== '' ? $clean['result_cgpa_label'] : 'CGPA';

        $maxCa = isset($clean['result_ca_max_score']) ? (float)$clean['result_ca_max_score'] : 40.0;
        $minimumCa = isset($clean['result_minimum_ca_score']) ? (float)$clean['result_minimum_ca_score'] : 0.0;
        $maxExam = isset($clean['result_exam_max_score']) ? (float)$clean['result_exam_max_score'] : 60.0;
        $minimumExam = isset($clean['result_exam_minimum_score']) ? (float)$clean['result_exam_minimum_score'] : 0.0;
        $passMark = isset($clean['result_pass_mark']) ? (float)$clean['result_pass_mark'] : 40.0;

        if ($maxCa < 0) {
            throw new RuntimeException('Maximum CA Score cannot be less than 0.');
        }
        if ($minimumCa < 0) {
            throw new RuntimeException('Minimum CA Score cannot be less than 0.');
        }
        if ($minimumCa > $maxCa) {
            throw new RuntimeException('Minimum CA Score cannot be greater than Maximum CA Score.');
        }
        if ($maxExam < 0) {
            throw new RuntimeException('Maximum EXAM Score cannot be less than 0.');
        }
        if ($minimumExam < 0) {
            throw new RuntimeException('Minimum EXAM Score cannot be less than 0.');
        }
        if ($minimumExam > $maxExam) {
            throw new RuntimeException('Minimum EXAM Score cannot be greater than Maximum EXAM Score.');
        }

        $totalObtainable = $maxCa + $maxExam;
        if ($passMark < 0) {
            throw new RuntimeException('Pass Mark cannot be less than 0.');
        }
        if ($passMark > $totalObtainable) {
            throw new RuntimeException('Pass Mark cannot be greater than the total obtainable score.');
        }

        $gradeMins = [];
        $gradePoints = [];
        foreach (['a', 'b', 'c', 'd', 'e', 'f'] as $suffix) {
            $gradeMins[$suffix] = isset($clean['result_grade_' . $suffix . '_min']) ? (float)$clean['result_grade_' . $suffix . '_min'] : 0.0;
            $gradePoints[$suffix] = isset($clean['result_grade_' . $suffix . '_point']) ? (float)$clean['result_grade_' . $suffix . '_point'] : 0.0;
            if ($gradeMins[$suffix] < 0) {
                throw new RuntimeException('Grade minimum values cannot be less than 0.');
            }
            if ($gradeMins[$suffix] > $totalObtainable) {
                throw new RuntimeException('Grade minimum values cannot exceed the total obtainable score.');
            }
            if ($gradePoints[$suffix] < 0) {
                throw new RuntimeException('Grade points must be valid numeric values greater than or equal to 0.');
            }
        }
        if (!($gradeMins['a'] >= $gradeMins['b'] && $gradeMins['b'] >= $gradeMins['c'] && $gradeMins['c'] >= $gradeMins['d'] && $gradeMins['d'] >= $gradeMins['e'] && $gradeMins['e'] >= $gradeMins['f'])) {
            throw new RuntimeException('Grade minimums must remain logically ordered from A down to F.');
        }

        $clean['result_minimum_ca_action'] = portalNormalizeResultRuleAction($clean['result_minimum_ca_action'] ?? 'fail');
        $clean['result_exam_minimum_required'] = portalNormalizeBooleanSettingValue($clean['result_exam_minimum_required'] ?? '0');
        $clean['result_exam_below_minimum_action'] = portalNormalizeResultRuleAction($clean['result_exam_below_minimum_action'] ?? 'fail');
        $clean['result_below_pass_mark_action'] = portalNormalizeResultRuleAction($clean['result_below_pass_mark_action'] ?? 'fail');
        $clean['result_cgp_decimal_places'] = (string)max(0, min(6, (int)($clean['result_cgp_decimal_places'] ?? 2)));
        $clean['result_cgpa_decimal_places'] = (string)max(0, min(6, (int)($clean['result_cgpa_decimal_places'] ?? 2)));
        $clean['result_cgp_rounding_mode'] = portalNormalizeResultRoundingMode($clean['result_cgp_rounding_mode'] ?? 'half_up');
        $clean['result_cgpa_rounding_mode'] = portalNormalizeResultRoundingMode($clean['result_cgpa_rounding_mode'] ?? 'half_up');

        return [
            'clean' => $clean,
            'total_obtainable' => $totalObtainable,
        ];
    }
}
if (!function_exists('portalResultAssessment')) {
    function portalResultAssessment(PDO $pdo, float $caScore, float $examScore, ?float $totalScore = null): array {
        return portalResultAssessmentFromSettings(portalGradingResultSettings($pdo), $caScore, $examScore, $totalScore);
    }
}

if (!function_exists('portalResultSubmissionStatusBadgeClass')) {
    function portalResultSubmissionStatusBadgeClass(string $status): string {
        $key = strtolower(trim($status));
        return match ($key) {
            'approved', 'published' => 'success',
            'rejected' => 'danger',
            'returned', 'returned_for_correction' => 'warning text-dark',
            'reviewed' => 'info text-dark',
            'submitted', 'pending_review' => 'primary',
            default => 'secondary',
        };
    }
}
if (!function_exists('portalResultSubmissionStatusLabel')) {
    function portalResultSubmissionStatusLabel(string $status): string {
        $key = strtolower(trim($status));
        return match ($key) {
            'pending_review' => 'Pending Review',
            'returned', 'returned_for_correction' => 'Returned for Correction',
            default => ucwords(str_replace('_', ' ', $key !== '' ? $key : 'draft')),
        };
    }
}

if (!function_exists('portalNormalizeResultSubmissionStatusFilter')) {
    function portalNormalizeResultSubmissionStatusFilter($status): string {
        $raw = strtolower(trim((string)$status));
        if ($raw === '') return '';
        $raw = preg_replace('/[\s\-]+/u', '_', $raw) ?: $raw;
        return match ($raw) {
            '', 'all', 'all_statuses' => '',
            'submitted', 'pending', 'pending_review' => 'submitted',
            'reviewed' => 'reviewed',
            'approved' => 'approved',
            'rejected' => 'rejected',
            'returned', 'returned_for_correction' => 'returned',
            'published' => 'published',
            default => '',
        };
    }
}
if (!function_exists('portalResultSubmissionStatusFilterSql')) {
    function portalResultSubmissionStatusFilterSql(string $status, string $column = 'rs.status'): array {
        $canonical = portalNormalizeResultSubmissionStatusFilter($status);
        if ($canonical === '') {
            return ['status' => '', 'sql' => '', 'params' => []];
        }
        $normalizedColumn = "LOWER(TRIM($column))";
        return match ($canonical) {
            'submitted' => [
                'status' => 'submitted',
                'sql' => $normalizedColumn . " IN (?, ?)",
                'params' => ['submitted', 'pending_review'],
            ],
            'returned' => [
                'status' => 'returned',
                'sql' => $normalizedColumn . " IN (?, ?)",
                'params' => ['returned', 'returned_for_correction'],
            ],
            default => [
                'status' => $canonical,
                'sql' => $normalizedColumn . " = ?",
                'params' => [$canonical],
            ],
        };
    }
}
if (!function_exists('portalResultApprovalsDebugLog')) {
    function portalResultApprovalsDebugLog(string $message, array $context = []): void {
        $safe = [];
        foreach ($context as $key => $value) {
            if (is_bool($value)) {
                $safe[$key] = $value;
                continue;
            }
            if (is_scalar($value) || $value === null) {
                $str = trim((string)$value);
                if ($str !== '' && strlen($str) > 180) {
                    $str = substr($str, 0, 180) . '…';
                }
                $safe[$key] = $str;
            }
        }
        error_log('[ResultApprovals] ' . $message . ' ' . json_encode($safe, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    }
}

if (!function_exists('portalResultSubmissionAuditLog')) {
    function portalResultSubmissionAuditLog(PDO $pdo, int $submissionId, string $actionType, string $note = '', int $actorId = 0, string $actorName = ''): void {
        if ($submissionId <= 0) return;
        try {
            $st = $pdo->prepare("INSERT INTO result_submission_audit (submission_id, action_type, action_note, actor_id, actor_name) VALUES (?, ?, ?, ?, ?)");
            $st->execute([$submissionId, $actionType, $note !== '' ? $note : null, $actorId > 0 ? $actorId : null, $actorName !== '' ? $actorName : null]);
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalResultSubmissionHistory')) {
    function portalResultSubmissionHistory(PDO $pdo, int $submissionId): array {
        if ($submissionId <= 0) return [];
        try {
            $st = $pdo->prepare("SELECT * FROM result_submission_audit WHERE submission_id = ? ORDER BY created_at DESC, id DESC");
            $st->execute([$submissionId]);
            return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}
if (!function_exists('portalResultSubmissionMetrics')) {
    function portalResultSubmissionMetrics(PDO $pdo, array $submission, array $rows = []): array {
        $submissionId = (int)($submission['id'] ?? 0);
        $courseId = (int)($submission['course_id'] ?? 0);
        $session = trim((string)($submission['session'] ?? ''));
        $semester = trim((string)($submission['semester'] ?? ''));
        $courseCredit = 0.0;
        try {
            $creditExpr = portalColumnExistsSafe($pdo, 'registrable_courses', 'credit_load') ? "COALESCE(credit_unit, credit_load, 0)" : "COALESCE(credit_unit, 0)";
            $st = $pdo->prepare("SELECT {$creditExpr} FROM registrable_courses WHERE id = ? LIMIT 1");
            $st->execute([$courseId]);
            $courseCredit = (float)($st->fetchColumn() ?: 0);
        } catch (Throwable $e) {}
        $registered = 0;
        try {
            $st = $pdo->prepare("SELECT COUNT(DISTINCT cri.registration_id)
                FROM course_registration_items cri
                INNER JOIN course_registrations cr ON cr.id = cri.registration_id
                WHERE cri.course_id = ? AND COALESCE(cr.session_name,'') = ? AND COALESCE(cr.semester,'') = ? AND COALESCE(cr.status,'submitted') <> 'cancelled'");
            $st->execute([$courseId, $session, $semester]);
            $registered = (int)($st->fetchColumn() ?: 0);
        } catch (Throwable $e) {}
        if (!$rows && $submissionId > 0) {
            try {
                $st = $pdo->prepare("SELECT * FROM results WHERE submission_id = ?");
                $st->execute([$submissionId]);
                $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) { $rows = []; }
        }
        $scored = 0; $missing = 0; $pass = 0; $fail = 0; $carryover = 0; $sumTotal = 0.0; $gradeDist = [];
        $settings = portalGradingResultSettings($pdo);
        $caMax = max(0.0, (float)($settings['result_ca_max_score'] ?? 40));
        $examMax = max(0.0, (float)($settings['result_exam_max_score'] ?? 60));
        foreach ($rows as $r) {
            $ca = (float)($r['ca_score'] ?? 0);
            $exam = (float)($r['exam_score'] ?? 0);
            $assessment = portalResultAssessment($pdo, $ca, $exam, (float)($r['total_score'] ?? ($ca + $exam)));
            $hasScore = !($ca <= 0 && $exam <= 0);
            if ($hasScore) $scored++;
            $sumTotal += (float)($assessment['total_score'] ?? 0);
            $grade = trim((string)($assessment['grade'] ?? ''));
            if ($grade !== '') $gradeDist[$grade] = ($gradeDist[$grade] ?? 0) + 1;
            if (!empty($assessment['failed_minimum_ca'])) $carryover++;
            if (($assessment['status'] ?? '') === 'PASS') $pass++; else $fail++;
        }
        $missing = max(0, $registered - $scored);
        return [
            'registered_students' => $registered,
            'scored_students' => $scored,
            'missing_scores' => $missing,
            'average_score' => $scored > 0 ? round($sumTotal / max(1, $scored), 2) : 0,
            'pass_count' => $pass,
            'fail_count' => $fail,
            'carryover_count' => $carryover,
            'grade_distribution' => $gradeDist,
            'course_credit_unit' => $courseCredit,
            'ca_max_score' => $caMax,
            'exam_max_score' => $examMax,
        ];
    }
}
if (!function_exists('portalResultSubmissionWarnings')) {
    function portalResultSubmissionWarnings(PDO $pdo, array $submission, array $rows = []): array {
        $warnings = [];
        $metrics = portalResultSubmissionMetrics($pdo, $submission, $rows);
        $caMax = (float)($metrics['ca_max_score'] ?? 40);
        $examMax = (float)($metrics['exam_max_score'] ?? 60);
        if (($metrics['missing_scores'] ?? 0) > 0) {
            $warnings[] = ['level' => 'warning', 'text' => ((int)$metrics['missing_scores']) . ' registered student(s) do not yet have scored rows.'];
        }
        $seen = [];
        foreach ($rows as $r) {
            $sid = (int)($r['student_id'] ?? 0);
            if ($sid > 0) {
                if (isset($seen[$sid])) $warnings[] = ['level' => 'danger', 'text' => 'Duplicate result row detected for student ID ' . $sid . '.'];
                $seen[$sid] = true;
            }
            $ca = (float)($r['ca_score'] ?? 0);
            $exam = (float)($r['exam_score'] ?? 0);
            $total = (float)($r['total_score'] ?? ($ca + $exam));
            if ($ca < 0 || $ca > $caMax) $warnings[] = ['level' => 'danger', 'text' => 'One or more CA values are outside the configured limit.'];
            if ($exam < 0 || $exam > $examMax) $warnings[] = ['level' => 'danger', 'text' => 'One or more Exam values are outside the configured limit.'];
            if (abs(($ca + $exam) - $total) > 0.05) $warnings[] = ['level' => 'warning', 'text' => 'One or more result totals do not match CA + Exam.'];
            $assessment = portalResultAssessment($pdo, $ca, $exam, $total);
            if (trim((string)($r['grade'] ?? '')) !== '' && trim((string)($r['grade'] ?? '')) !== trim((string)($assessment['grade'] ?? ''))) {
                $warnings[] = ['level' => 'warning', 'text' => 'Detected grade mismatch against the current grading configuration.'];
            }
        }
        $unique = [];
        $final = [];
        foreach ($warnings as $w) {
            $key = ($w['level'] ?? 'warning') . '|' . ($w['text'] ?? '');
            if (isset($unique[$key])) continue;
            $unique[$key] = true;
            $final[] = $w;
        }
        return $final;
    }
}
if (!function_exists('portalTranscriptSettingsDefaults')) {
    function portalTranscriptSettingsDefaults(): array {
        return [
            'transcript_generation_enabled' => 'Yes',
            'transcript_print_enabled' => 'Yes',
            'transcript_download_enabled' => 'Yes',
            'transcript_result_source' => 'published_results',
            'transcript_session_aggregation' => 'all_sessions',
            'transcript_semester_aggregation' => 'both_semesters',
            'transcript_display_fields' => "Student Name\nMatric Number\nProgramme\nDepartment\nSession\nSemester\nCourse Code\nCourse Title\nCredit Unit\nCA\nEXAM\nTotal\nGrade\nGrade Point\nCGPA\nClassification\nRemarks",
            'transcript_show_remarks' => 'Yes',
            'transcript_show_classification' => 'Yes',
            'transcript_header_text' => 'Official Academic Transcript',
            'transcript_title_text' => 'Academic Transcript',
            'transcript_approval_title' => 'Registrar',
            'transcript_approval_name' => 'Authorized Signatory',
            'transcript_signature_label' => 'Official Signature',
        ];
    }
}
if (!function_exists('portalTranscriptSettings')) {
    function portalTranscriptSettings(PDO $pdo): array {
        $data = portalTranscriptSettingsDefaults();
        foreach ($data as $key => $defaultValue) {
            $data[$key] = getSystemSettingValue($pdo, $key, (string)$defaultValue);
        }
        $data['transcript_page_enabled'] = getSystemSettingValue($pdo, 'transcript_page_enabled', 'Yes');
        return $data;
    }
}


if (!function_exists('portalEnsureStudentResultsPageSupport')) {
    function portalEnsureStudentResultsPageSupport(PDO $pdo): void {
        try { ensureResultsSchema($pdo); } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'results') && !portalIndexExistsSafe($pdo, 'results', 'idx_results_student_scope')) { $pdo->exec("CREATE INDEX idx_results_student_scope ON results(student_id, session, semester, status)"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'results') && !portalIndexExistsSafe($pdo, 'results', 'idx_results_student_course')) { $pdo->exec("CREATE INDEX idx_results_student_course ON results(student_id, course_id, session, semester)"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'course_registrations') && !portalIndexExistsSafe($pdo, 'course_registrations', 'idx_cr_student_scope')) { $pdo->exec("CREATE INDEX idx_cr_student_scope ON course_registrations(student_id, session_name, semester, level)"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'course_registration_items') && !portalIndexExistsSafe($pdo, 'course_registration_items', 'idx_cri_registration_course')) { $pdo->exec("CREATE INDEX idx_cri_registration_course ON course_registration_items(registration_id, course_id)"); } } catch (Throwable $e) {}
    }
}
if (!function_exists('portalStudentResultClassification')) {
    function portalStudentResultClassification(PDO $pdo, float $cgpa): string {
        $settings = portalGradingResultSettings($pdo);
        $rawRules = trim((string)($settings['result_classification_rules'] ?? ''));
        if ($rawRules === '') return '—';
        foreach (preg_split('/\r\n|\r|\n/', $rawRules) as $line) {
            $line = trim((string)$line);
            if ($line === '' || strpos($line, ':') === false) continue;
            [$rangePart, $labelPart] = array_map('trim', explode(':', $line, 2));
            if ($labelPart === '') continue;
            if (preg_match('/([0-9]+(?:\.[0-9]+)?)\s*-\s*([0-9]+(?:\.[0-9]+)?)/', $rangePart, $m)) {
                $min = (float)$m[1];
                $max = (float)$m[2];
                if ($cgpa >= $min && $cgpa <= $max) return $labelPart;
            }
        }
        return '—';
    }
}

if (!function_exists('portalPaymentProofStatusLabel')) {
    function portalPaymentProofStatusLabel(array $tx): string {
        $proof = trim((string)($tx['manual_proof_path'] ?? ''));
        $status = strtolower(trim((string)($tx['status'] ?? '')));
        $method = strtolower(trim((string)($tx['payment_method'] ?? '')));
        if ($method !== 'manual' && $proof === '') return 'No Proof Uploaded';
        if ($proof === '') return 'No Proof Uploaded';
        if ($status === 'rejected') return 'Rejected';
        if ($status === 'pending') return 'Pending Approval';
        if (in_array($status, ['approved', 'successful'], true)) return 'Approved';
        return 'Proof Uploaded';
    }
}
if (!function_exists('portalPaymentApprovalStatusLabel')) {
    function portalPaymentApprovalStatusLabel(array $tx): string {
        $status = strtolower(trim((string)($tx['status'] ?? 'pending')));
        $walletCreditStatus = strtolower(trim((string)($tx['wallet_credit_status'] ?? '')));
        $type = strtolower(trim((string)($tx['transaction_type'] ?? '')));
        if ($type === 'wallet funding' && $walletCreditStatus === 'credited') return 'Credited';
        if ($status === 'successful') return 'Successful';
        if ($status === 'approved') return 'Approved';
        if ($status === 'pending') return 'Pending';
        if ($status === 'rejected') return 'Rejected';
        if ($status === 'failed') return 'Failed';
        return ucfirst($status !== '' ? $status : 'pending');
    }
}
if (!function_exists('portalPaymentEnteredAmount')) {
    function portalPaymentEnteredAmount(array $tx): float {
        $amountPaid = (float)($tx['amount_paid'] ?? 0);
        if ($amountPaid > 0) return $amountPaid;
        return (float)($tx['amount'] ?? 0);
    }
}
if (!function_exists('portalPaymentApprovedAmount')) {
    function portalPaymentApprovedAmount(array $tx): float {
        $approved = (float)($tx['approved_amount'] ?? 0);
        if ($approved > 0) return $approved;
        if (strcasecmp((string)($tx['transaction_type'] ?? ''), 'Wallet Funding') === 0) {
            $credited = (float)($tx['wallet_credited_amount'] ?? 0);
            if ($credited > 0) return $credited;
        }
        if (portalPaymentIsApproved($tx)) {
            return (float)($tx['amount'] ?? 0);
        }
        return 0.0;
    }
}
if (!function_exists('portalPaymentDisplayStatusClass')) {
    function portalPaymentDisplayStatusClass(array $tx): string {
        $label = strtolower(trim((string)portalPaymentApprovalStatusLabel($tx)));
        if ($label === 'pending approval' || $label === 'pending') return 'pending';
        if ($label === 'approved') return 'approved';
        if ($label === 'successful') return 'successful';
        if ($label === 'credited') return 'credited';
        if ($label === 'rejected') return 'rejected';
        if ($label === 'failed') return 'failed';
        return preg_replace('/[^a-z0-9-]+/', '-', str_replace(' ', '-', $label)) ?: 'pending';
    }
}
if (!function_exists('portalWalletFundingCreditReference')) {
    function portalWalletFundingCreditReference(int $paymentId): string {
        return 'wallet_funding_tx:' . max(0, $paymentId);
    }
}
if (!function_exists('portalSynchronizeWalletFundingCredit')) {
    function portalSynchronizeWalletFundingCredit(PDO $pdo, int $paymentId, float $overrideAmount = 0, string $narration = ''): array {
        try { ensurePortalExtensionSupport($pdo); } catch (Throwable $e) {}

        if ($paymentId <= 0) {
            return ['ok' => false, 'message' => 'Invalid wallet funding transaction.'];
        }

        try {
            $pdo->beginTransaction();

            $st = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ? LIMIT 1 FOR UPDATE");
            $st->execute([$paymentId]);
            $tx = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$tx) {
                throw new RuntimeException('Wallet funding transaction was not found.');
            }

            $type = strtolower(trim((string)($tx['transaction_type'] ?? '')));
            if ($type !== 'wallet funding') {
                $pdo->commit();
                return ['ok' => false, 'skipped' => true, 'message' => 'Transaction is not wallet funding.', 'transaction' => $tx];
            }

            $status = strtolower(trim((string)($tx['status'] ?? 'pending')));
            if (!in_array($status, ['approved', 'successful', 'credited'], true)) {
                $walletState = ($status === 'rejected') ? 'Rejected' : 'Pending';
                $pdo->prepare("UPDATE payment_transactions SET wallet_credit_status = ? WHERE id = ?")->execute([$walletState, $paymentId]);
                $pdo->commit();
                return ['ok' => false, 'message' => 'Transaction is not approved for wallet credit yet.', 'transaction' => $tx];
            }

            $creditReference = trim((string)($tx['wallet_credit_reference'] ?? ''));
            if ($creditReference === '') {
                $creditReference = portalWalletFundingCreditReference((int)$tx['id']);
            }

            ensureWallet($pdo, (int)$tx['student_id']);
            $walletLock = $pdo->prepare("SELECT id FROM wallets WHERE student_id = ? LIMIT 1 FOR UPDATE");
            $walletLock->execute([(int)$tx['student_id']]);

            $chk = $pdo->prepare("SELECT id, amount, created_at FROM wallet_transactions WHERE related_transaction_id = ? AND type = 'credit' ORDER BY id DESC LIMIT 1 FOR UPDATE");
            $chk->execute([$creditReference]);
            $existingCredit = $chk->fetch(PDO::FETCH_ASSOC) ?: null;

            if ($existingCredit || strtolower(trim((string)($tx['wallet_credit_status'] ?? ''))) === 'credited' || !empty($tx['wallet_credited_at'])) {
                $creditedAmount = $existingCredit ? (float)($existingCredit['amount'] ?? 0) : portalPaymentApprovedAmount($tx);
                if ($creditedAmount <= 0) $creditedAmount = (float)($tx['amount'] ?? 0);
                $pdo->prepare("UPDATE payment_transactions SET wallet_credit_status = 'Credited', wallet_credit_reference = ?, wallet_credited_amount = CASE WHEN COALESCE(wallet_credited_amount, 0) <= 0 THEN ? ELSE wallet_credited_amount END, wallet_credited_at = COALESCE(wallet_credited_at, NOW()) WHERE id = ?")
                    ->execute([$creditReference, $creditedAmount, $paymentId]);
                $pdo->commit();
                return ['ok' => true, 'already_credited' => true, 'credited_amount' => $creditedAmount, 'transaction' => $tx];
            }

            $creditAmount = $overrideAmount > 0 ? round($overrideAmount, 2) : 0.0;
            if ($creditAmount <= 0) {
                $creditAmount = (float)($tx['approved_amount'] ?? 0);
            }
            if ($creditAmount <= 0) {
                $creditAmount = (float)($tx['amount'] ?? 0);
            }
            if ($creditAmount <= 0) {
                throw new RuntimeException('Wallet credit amount is invalid.');
            }

            $walletNote = trim($narration) !== '' ? trim($narration) : 'Wallet funding credited';
            $beforeBalance = getWalletBalance($pdo, (int)$tx['student_id']);
            $afterBalance = $beforeBalance + $creditAmount;

            $pdo->prepare("UPDATE wallets SET balance = balance + ? WHERE student_id = ?")
                ->execute([$creditAmount, (int)$tx['student_id']]);

            $walletTxId = portalInsertWalletTransaction($pdo, (int)$tx['student_id'], 'credit', $creditAmount, $walletNote, $creditReference, (string)($tx['reference'] ?? $creditReference), $beforeBalance, $afterBalance);

            $pdo->prepare("UPDATE payment_transactions SET amount = ?, approved_amount = CASE WHEN COALESCE(approved_amount, 0) <= 0 THEN ? ELSE approved_amount END, wallet_credit_status = 'Credited', wallet_credited_amount = ?, wallet_credited_at = NOW(), wallet_credit_reference = ?, approval_date = COALESCE(approval_date, NOW()) WHERE id = ?")
                ->execute([$creditAmount, $creditAmount, $creditAmount, $creditReference, $paymentId]);

            $pdo->commit();
            try { if (!empty($walletTxId)) { portalSendWalletTransactionNotificationById($pdo, (int)$walletTxId); } } catch (Throwable $e) {}
            return ['ok' => true, 'already_credited' => false, 'credited_amount' => $creditAmount, 'transaction' => $tx];
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            error_log('Wallet funding sync failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => $e->getMessage()];
        }
    }
}
if (!function_exists('portalPaymentSettledAmount')) {
    function portalPaymentSettledAmount(array $tx): float {
        $settled = (float)($tx['settled_amount'] ?? 0);
        if ($settled > 0) return $settled;
        if (portalPaymentIsApproved($tx)) {
            $amount = (float)($tx['amount'] ?? 0);
            if ($amount > 0) return $amount;
        }
        return 0.0;
    }
}
if (!function_exists('portalPaymentTypeKeyFromTransaction')) {
    function portalPaymentTypeKeyFromTransaction(array $tx): string {
        $type = strtolower(trim((string)($tx['transaction_type'] ?? '')));
        if ($type === 'school fee') return 'school';
        if ($type === 'acceptance fee') return 'acceptance';
        if ($type === 'registration fee') return 'registration';
        if ($type === 'hostel fee') return 'hostel';
        if ($type === 'assigned payment' || str_starts_with($type, 'custom:')) return 'assigned';
        if ($type === 'wallet funding') return 'wallet_funding';
        return 'other';
    }
}
if (!function_exists('portalPaymentReconciliationReference')) {
    function portalPaymentReconciliationReference(int $paymentId): string {
        return 'payment_recon:' . max(0, $paymentId);
    }
}
if (!function_exists('portalPaymentWalletCreditReference')) {
    function portalPaymentWalletCreditReference(int $paymentId, string $kind = 'excess'): string {
        $kind = strtolower(trim($kind)) !== '' ? strtolower(trim($kind)) : 'excess';
        return 'payment_wallet_' . preg_replace('/[^a-z0-9_:-]+/i', '_', $kind) . ':' . max(0, $paymentId);
    }
}
if (!function_exists('portalFindAssignedPaymentForTransaction')) {
    function portalFindAssignedPaymentForTransaction(PDO $pdo, array $tx): array {
        $studentId = (int)($tx['student_id'] ?? 0);
        if ($studentId <= 0) return [];
        $reference = trim((string)($tx['reference'] ?? ''));
        if ($reference !== '') {
            try {
                $st = $pdo->prepare("SELECT * FROM assigned_payments WHERE student_id = ? AND reference = ? ORDER BY id DESC LIMIT 1");
                $st->execute([$studentId, $reference]);
                $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                if ($row) return $row;
            } catch (Throwable $e) {}
        }
        $type = trim((string)($tx['transaction_type'] ?? ''));
        if (stripos($type, 'Custom:') === 0) {
            $title = trim(substr($type, 7));
            if ($title !== '') {
                try {
                    $st = $pdo->prepare("SELECT * FROM assigned_payments WHERE student_id = ? AND LOWER(TRIM(title)) = LOWER(TRIM(?)) ORDER BY id DESC LIMIT 1");
                    $st->execute([$studentId, $title]);
                    $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                    if ($row) return $row;
                } catch (Throwable $e) {}
            }
        }
        return [];
    }
}
if (!function_exists('portalApprovedFeeTransactionsTotal')) {
    function portalApprovedFeeTransactionsTotal(PDO $pdo, int $studentId, string $transactionType, string $sessionName = '', string $semesterName = '', int $excludePaymentId = 0, array $fallbackStudent = []): float {
        $total = 0.0;
        if ($studentId <= 0) return 0.0;
        try {
            $st = $pdo->prepare("SELECT * FROM payment_transactions WHERE student_id = ? AND transaction_type = ? ORDER BY COALESCE(payment_date, created_at) ASC, id ASC");
            $st->execute([$studentId, $transactionType]);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($rows as $row) {
                if ($excludePaymentId > 0 && (int)($row['id'] ?? 0) === $excludePaymentId) continue;
                if (!portalPaymentIsApproved((array)$row)) continue;
                if ($sessionName !== '' || $semesterName !== '') {
                    if (!portalTransactionMatchesPeriod((array)$row, $sessionName, $semesterName, $fallbackStudent)) continue;
                }
                $total += portalPaymentSettledAmount((array)$row);
            }
        } catch (Throwable $e) {}
        return round($total, 2);
    }
}
if (!function_exists('portalPaymentOutstandingSummary')) {
    function portalPaymentOutstandingSummary(PDO $pdo, array $tx): array {
        $studentId = (int)($tx['student_id'] ?? 0);
        $typeKey = portalPaymentTypeKeyFromTransaction($tx);
        $sessionName = portalPaymentAcademicSession($tx);
        $semesterName = portalPaymentSemester($tx);
        $student = [];
        if ($studentId > 0) {
            try {
                $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $st->execute([$studentId]);
                $student = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) { $student = []; }
        }

        $required = 0.0;
        $alreadyPaid = 0.0;
        $lateFeeOutstanding = 0.0;
        $principalOutstanding = 0.0;
        $title = bestPaymentType($tx);

        if ($typeKey === 'school') {
            $summary = portalSchoolFeePeriodSummary($pdo, $studentId, $sessionName, $semesterName, $student);
            $required = (float)($summary['configured_amount'] ?? 0);
            $alreadyPaid = (float)($summary['principal_paid'] ?? $summary['total_paid'] ?? 0);
            $lateFeeOutstanding = (float)($summary['late_fee_balance'] ?? 0);
            $principalOutstanding = (float)($summary['principal_outstanding'] ?? max(0, $required - $alreadyPaid));
            return [
                'type_key' => $typeKey,
                'title' => $title,
                'required_amount' => round($required, 2),
                'already_paid' => round($alreadyPaid, 2),
                'principal_outstanding' => round($principalOutstanding, 2),
                'late_fee_outstanding' => round($lateFeeOutstanding, 2),
                'outstanding_amount' => round($principalOutstanding + $lateFeeOutstanding, 2),
                'student' => $student,
                'session_name' => $sessionName,
                'semester_name' => $semesterName,
            ];
        }

        if ($typeKey === 'acceptance') {
            $required = (float)portalStudentManagedFeeAmount($pdo, 'acceptance', $student, $sessionName, $semesterName);
            $alreadyPaid = portalApprovedFeeTransactionsTotal($pdo, $studentId, 'Acceptance Fee', '', '', (int)($tx['id'] ?? 0), $student);
        } elseif ($typeKey === 'registration') {
            $required = (float)portalStudentManagedFeeAmount($pdo, 'registration', $student, $sessionName, $semesterName);
            $alreadyPaid = portalApprovedFeeTransactionsTotal($pdo, $studentId, 'Registration Fee', $sessionName, $semesterName, (int)($tx['id'] ?? 0), $student);
        } elseif ($typeKey === 'hostel') {
            $required = (float)portalStudentManagedFeeAmount($pdo, 'hostel', $student, $sessionName, $semesterName);
            $alreadyPaid = portalApprovedFeeTransactionsTotal($pdo, $studentId, 'Hostel Fee', $sessionName, $semesterName, (int)($tx['id'] ?? 0), $student);
        } elseif ($typeKey === 'assigned') {
            $assigned = portalFindAssignedPaymentForTransaction($pdo, $tx);
            if ($assigned) {
                $required = (float)($assigned['amount'] ?? 0);
                $title = trim((string)($assigned['title'] ?? $title));
            } else {
                $required = (float)($tx['amount'] ?? 0);
            }
            $alreadyPaid = portalApprovedFeeTransactionsTotal($pdo, $studentId, (string)($tx['transaction_type'] ?? ''), '', '', (int)($tx['id'] ?? 0), $student);
        } else {
            $required = (float)($tx['amount'] ?? 0);
            $alreadyPaid = 0.0;
        }

        $principalOutstanding = max(0.0, $required - $alreadyPaid);
        return [
            'type_key' => $typeKey,
            'title' => $title,
            'required_amount' => round($required, 2),
            'already_paid' => round($alreadyPaid, 2),
            'principal_outstanding' => round($principalOutstanding, 2),
            'late_fee_outstanding' => 0.0,
            'outstanding_amount' => round($principalOutstanding, 2),
            'student' => $student,
            'session_name' => $sessionName,
            'semester_name' => $semesterName,
        ];
    }
}
if (!function_exists('portalIdempotentWalletCreditForPayment')) {
    function portalIdempotentWalletCreditForPayment(PDO $pdo, int $studentId, float $amount, string $narration, string $relatedRef, string $paymentReference = ''): array {
        $amount = round($amount, 2);
        if ($studentId <= 0 || $amount <= 0 || trim($relatedRef) === '') {
            return ['ok' => false, 'message' => 'Invalid wallet credit request.'];
        }
        ensureWallet($pdo, $studentId);
        $pdo->prepare("SELECT id FROM wallets WHERE student_id = ? LIMIT 1 FOR UPDATE")->execute([$studentId]);
        $chk = $pdo->prepare("SELECT id, amount FROM wallet_transactions WHERE related_transaction_id = ? AND type = 'credit' ORDER BY id DESC LIMIT 1 FOR UPDATE");
        $chk->execute([$relatedRef]);
        $existing = $chk->fetch(PDO::FETCH_ASSOC) ?: null;
        if ($existing) {
            return ['ok' => true, 'already_credited' => true, 'credited_amount' => (float)($existing['amount'] ?? 0), 'wallet_transaction_id' => (int)($existing['id'] ?? 0)];
        }
        $before = getWalletBalance($pdo, $studentId);
        $after = $before + $amount;
        $pdo->prepare("UPDATE wallets SET balance = balance + ? WHERE student_id = ?")->execute([$amount, $studentId]);
        $walletTxId = portalInsertWalletTransaction($pdo, $studentId, 'credit', $amount, $narration, $relatedRef, $paymentReference, $before, $after);
        return ['ok' => true, 'already_credited' => false, 'credited_amount' => $amount, 'wallet_transaction_id' => (int)$walletTxId];
    }
}
if (!function_exists('portalReconcileFeePayment')) {
    function portalReconcileFeePayment(PDO $pdo, int $paymentId, float $confirmedAmount = 0, string $source = 'system', array $options = []): array {
        try { ensurePortalExtensionSupport($pdo); } catch (Throwable $e) {}
        if ($paymentId <= 0) {
            return ['ok' => false, 'message' => 'Invalid payment transaction.'];
        }
        $startedTx = false;
        try {
            if (!$pdo->inTransaction()) {
                $pdo->beginTransaction();
                $startedTx = true;
            }
            $st = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ? LIMIT 1 FOR UPDATE");
            $st->execute([$paymentId]);
            $tx = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$tx) {
                throw new RuntimeException('Payment transaction was not found.');
            }

            $typeKey = portalPaymentTypeKeyFromTransaction($tx);
            if ($typeKey === 'wallet_funding') {
                if ($startedTx && $pdo->inTransaction()) $pdo->commit();
                return ['ok' => true, 'skipped' => true, 'message' => 'Wallet funding does not require fee reconciliation.', 'transaction' => $tx];
            }

            $lockedAt = trim((string)($tx['reconciliation_locked_at'] ?? ''));
            $currentRecon = trim((string)($tx['reconciliation_status'] ?? ''));
            if ($lockedAt !== '' && $currentRecon !== '' && strcasecmp($currentRecon, 'Pending') !== 0) {
                if ($startedTx && $pdo->inTransaction()) $pdo->commit();
                return [
                    'ok' => true,
                    'already_processed' => true,
                    'message' => 'Payment reconciliation has already been completed.',
                    'transaction' => $tx,
                    'should_settle_fee' => portalPaymentIsApproved($tx),
                    'issue_receipt' => portalPaymentIsApproved($tx),
                    'flash_type' => portalPaymentIsApproved($tx) ? 'success' : 'warning',
                    'result' => strtolower($currentRecon),
                ];
            }

            $confirmed = round($confirmedAmount > 0 ? $confirmedAmount : (float)($tx['approved_amount'] ?? ($tx['amount_paid'] ?? ($tx['amount'] ?? 0))), 2);
            if ($confirmed <= 0) {
                $confirmed = round((float)($tx['amount'] ?? 0), 2);
            }
            if ($confirmed <= 0) {
                throw new RuntimeException('Confirmed payment amount is invalid.');
            }

            $summary = portalPaymentOutstandingSummary($pdo, $tx);
            $outstandingBefore = round((float)($summary['outstanding_amount'] ?? 0), 2);
            $title = trim((string)($summary['title'] ?? bestPaymentType($tx)));
            $studentId = (int)($tx['student_id'] ?? 0);
            $reconRef = portalPaymentReconciliationReference($paymentId);
            $notes = trim((string)($options['notes'] ?? ''));
            $approvedBy = (int)($options['approved_by'] ?? 0);
            $underpaymentToWallet = !array_key_exists('underpayment_to_wallet', $options) || !empty($options['underpayment_to_wallet']);

            $status = 'Approved';
            $reconStatus = 'Exact';
            $settledAmount = 0.0;
            $outstandingAfter = 0.0;
            $excessAmount = 0.0;
            $underpaymentAmount = 0.0;
            $shouldSettle = false;
            $issueReceipt = false;
            $walletCreditAmount = 0.0;
            $walletCreditRef = '';
            $walletTxId = 0;
            $flashType = 'success';
            $message = '';

            if ($outstandingBefore <= 0) {
                $status = 'Rejected';
                $reconStatus = 'NoOutstandingWalletCredited';
                $walletCreditAmount = $confirmed;
                $walletCreditRef = portalPaymentWalletCreditReference($paymentId, 'already_settled');
                $flashType = 'info';
                $message = 'This fee had already been fully settled. The received amount has been credited to the wallet for reuse.';
            } elseif ($confirmed < $outstandingBefore) {
                $status = 'Rejected';
                $reconStatus = $underpaymentToWallet ? 'UnderpaidWallet' : 'UnderpaidRejected';
                $underpaymentAmount = $confirmed;
                $walletCreditAmount = $underpaymentToWallet ? $confirmed : 0.0;
                $walletCreditRef = $walletCreditAmount > 0 ? portalPaymentWalletCreditReference($paymentId, 'underpay') : '';
                $outstandingAfter = $outstandingBefore;
                $flashType = 'warning';
                $message = 'Your payment is below the required amount for this fee. Kindly pay the exact assigned amount. This payment has not been accepted as full settlement.';
                if ($underpaymentToWallet) {
                    $message .= ' The amount paid has been credited to your wallet/account balance for reuse.';
                }
            } else {
                $shouldSettle = true;
                $issueReceipt = true;
                $settledAmount = round(min($confirmed, $outstandingBefore), 2);
                $outstandingAfter = round(max(0.0, $outstandingBefore - $settledAmount), 2);
                $excessAmount = round(max(0.0, $confirmed - $settledAmount), 2);
                $reconStatus = $excessAmount > 0 ? 'Overpaid' : 'Exact';
                $walletCreditAmount = $excessAmount;
                $walletCreditRef = $walletCreditAmount > 0 ? portalPaymentWalletCreditReference($paymentId, 'excess') : '';
                $status = 'Approved';
                $message = $excessAmount > 0
                    ? 'Payment settled successfully. The excess amount has been credited to the wallet automatically.'
                    : 'Payment settled successfully.';
            }

            if ($walletCreditAmount > 0 && $studentId > 0) {
                $walletNarration = $reconStatus === 'Overpaid'
                    ? 'Excess from overpayment for ' . $title
                    : (($reconStatus === 'NoOutstandingWalletCredited')
                        ? 'Payment received after full settlement for ' . $title
                        : 'Underpaid amount moved to wallet for ' . $title);
                $walletResult = portalIdempotentWalletCreditForPayment($pdo, $studentId, $walletCreditAmount, $walletNarration, $walletCreditRef, (string)($tx['reference'] ?? ''));
                if (!$walletResult['ok']) {
                    throw new RuntimeException((string)($walletResult['message'] ?? 'Unable to credit wallet.'));
                }
                $walletTxId = (int)($walletResult['wallet_transaction_id'] ?? 0);
            }

            $schoolPrincipalSettled = (float)($tx['school_fee_base_amount'] ?? 0);
            $schoolLateSettled = (float)($tx['school_fee_late_fee_amount'] ?? 0);
            if ($typeKey === 'school' && $settledAmount > 0) {
                $lateOutstanding = round((float)($summary['late_fee_outstanding'] ?? 0), 2);
                $schoolLateSettled = round(min($lateOutstanding, $settledAmount), 2);
                $schoolPrincipalSettled = round(max(0.0, $settledAmount - $schoolLateSettled), 2);
            }

            $appendNote = '[' . strtoupper($source) . ' reconciliation] ' . $message;
            if ($notes !== '') $appendNote .= ' ' . $notes;
            $approvalNotes = trim((string)($tx['approval_notes'] ?? ''));
            $approvalNotes = $approvalNotes !== '' ? ($approvalNotes . "\n" . $appendNote) : $appendNote;

            $updateSql = "UPDATE payment_transactions SET
                status = ?,
                amount = ?,
                approved_amount = ?,
                amount_paid = CASE WHEN COALESCE(amount_paid, 0) <= 0 THEN ? ELSE amount_paid END,
                received_fee_amount = ?,
                settled_amount = ?,
                fee_outstanding_before = ?,
                fee_outstanding_after = ?,
                excess_amount = ?,
                underpayment_amount = ?,
                reconciliation_status = ?,
                reconciliation_reference = ?,
                reconciliation_locked_at = NOW(),
                credited_to_wallet_flag = ?,
                wallet_credit_status = CASE
                    WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' THEN wallet_credit_status
                    WHEN ? > 0 THEN 'Credited'
                    ELSE wallet_credit_status
                END,
                wallet_credited_amount = CASE WHEN ? > 0 THEN ? ELSE COALESCE(wallet_credited_amount, 0) END,
                wallet_credited_at = CASE WHEN ? > 0 THEN COALESCE(wallet_credited_at, NOW()) ELSE wallet_credited_at END,
                wallet_credit_reference = CASE WHEN ? > 0 THEN ? ELSE wallet_credit_reference END,
                school_fee_base_amount = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'school fee' THEN ? ELSE school_fee_base_amount END,
                school_fee_late_fee_amount = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'school fee' THEN ? ELSE school_fee_late_fee_amount END,
                approval_date = COALESCE(approval_date, NOW()),
                approved_by = CASE WHEN ? > 0 THEN ? ELSE approved_by END,
                approval_notes = ?
                WHERE id = ?";
            $pdo->prepare($updateSql)->execute([
                $status,
                $shouldSettle ? $settledAmount : 0,
                $confirmed,
                $confirmed,
                $confirmed,
                $settledAmount,
                $outstandingBefore,
                $outstandingAfter,
                $excessAmount,
                $underpaymentAmount,
                $reconStatus,
                $reconRef,
                $walletCreditAmount > 0 ? 1 : 0,
                $walletCreditAmount,
                $walletCreditAmount,
                $walletCreditAmount,
                $walletCreditAmount,
                $walletCreditAmount,
                $walletCreditRef !== '' ? $walletCreditRef : null,
                $schoolPrincipalSettled,
                $schoolLateSettled,
                $approvedBy,
                $approvedBy,
                $approvalNotes,
                $paymentId
            ]);

            $st = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ? LIMIT 1");
            $st->execute([$paymentId]);
            $updated = $st->fetch(PDO::FETCH_ASSOC) ?: $tx;

            if ($startedTx && $pdo->inTransaction()) $pdo->commit();
            if ($walletTxId > 0) {
                try { portalSendWalletTransactionNotificationById($pdo, $walletTxId); } catch (Throwable $e) {}
            }
            if ($studentId > 0 && $message !== '' && (!$shouldSettle || $walletCreditAmount > 0)) {
                try {
                    $title = $shouldSettle ? 'Payment Reconciled' : 'Payment Not Accepted as Full Settlement';
                    createNotification($pdo, 'student', $studentId, $title, $message, '?page=' . portalStudentPaymentPageFromTransaction($updated));
                } catch (Throwable $e) {}
            }

            return [
                'ok' => true,
                'already_processed' => false,
                'transaction' => $updated,
                'result' => strtolower($reconStatus),
                'message' => $message,
                'flash_type' => $flashType,
                'should_settle_fee' => $shouldSettle,
                'issue_receipt' => $issueReceipt,
                'wallet_credited_amount' => $walletCreditAmount,
                'settled_amount' => $settledAmount,
                'confirmed_amount' => $confirmed,
                'outstanding_before' => $outstandingBefore,
                'outstanding_after' => $outstandingAfter,
            ];
        } catch (Throwable $e) {
            if ($startedTx && $pdo->inTransaction()) {
                $pdo->rollBack();
            }
            error_log('Payment reconciliation failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => $e->getMessage()];
        }
    }
}

if (!function_exists('portalManualPaymentScopeKey')) {
    function portalManualPaymentScopeKey(int $studentId, string $paymentType, string $sessionName, string $semesterName, int $installmentNumber = 1, int $totalInstallments = 1, int $assignedId = 0): string {
        return implode('|', [
            'manual',
            $studentId,
            strtolower(trim($paymentType)),
            trim($sessionName),
            portalNormalizeSemesterName($semesterName),
            max(1, $installmentNumber),
            max(1, $totalInstallments),
            max(0, $assignedId),
        ]);
    }
}
if (!function_exists('portalSafeRemoveFile')) {
    function portalSafeRemoveFile(?string $path): void {
        $p = trim((string)$path);
        if ($p === '') return;
        if (@is_file($p)) {
            @unlink($p);
        }
    }
}
if (!function_exists('portalValidatedManualProofUpload')) {
    function portalValidatedManualProofUpload(array $file): array {
        if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
            throw new RuntimeException('Please upload your proof of payment.');
        }
        if ((int)($file['size'] ?? 0) <= 0) {
            throw new RuntimeException('Uploaded proof file is empty.');
        }
        if ((int)($file['size'] ?? 0) > (defined('MAX_FILE_SIZE') ? MAX_FILE_SIZE : 5242880)) {
            throw new RuntimeException('Proof file is too large.');
        }
        $ext = strtolower((string)pathinfo((string)($file['name'] ?? ''), PATHINFO_EXTENSION));
        $allowedExt = ['jpg','jpeg','png','pdf'];
        if (!in_array($ext, $allowedExt, true)) {
            throw new RuntimeException('Invalid proof file type. Upload JPG, PNG or PDF only.');
        }
        $mime = '';
        try {
            if (function_exists('finfo_open')) {
                $fi = finfo_open(FILEINFO_MIME_TYPE);
                if ($fi) {
                    $mime = (string)finfo_file($fi, (string)($file['tmp_name'] ?? ''));
                    finfo_close($fi);
                }
            }
        } catch (Throwable $e) {
            $mime = '';
        }
        $allowedMime = ['image/jpeg','image/png','application/pdf'];
        if ($mime !== '' && !in_array($mime, $allowedMime, true)) {
            throw new RuntimeException('Invalid proof file content. Upload JPG, PNG or PDF only.');
        }
        if (!is_dir(UPLOAD_DIR)) { @mkdir(UPLOAD_DIR, 0775, true); }
        honttechHardenWritableDirectory((string)UPLOAD_DIR, false);
        $subdir = rtrim((string)UPLOAD_DIR, '/') . '/manual_payments';
        if (!is_dir($subdir)) { @mkdir($subdir, 0775, true); }
        honttechHardenWritableDirectory($subdir, false);
        $safeName = 'manual_proof_' . time() . '_' . bin2hex(random_bytes(5)) . '.' . $ext;
        $dest = $subdir . '/' . $safeName;
        if (!move_uploaded_file((string)$file['tmp_name'], $dest)) {
            throw new RuntimeException('Failed to upload proof. Please try again.');
        }
        return [
            'path' => $dest,
            'original_name' => (string)($file['name'] ?? ''),
        ];
    }
}
if (!function_exists('portalStudentPaymentPageFromTransaction')) {
    function portalStudentPaymentPageFromTransaction(array $tx): string {
        $type = strtolower(trim((string)($tx['transaction_type'] ?? '')));
        if ($type === 'acceptance fee') return 'acceptance_payment';
        if ($type === 'hostel fee') return 'hostel_payment';
        if ($type === 'school fee') return 'school_payment';
        if (strpos($type, 'custom:') === 0 || $type === 'assigned payment') return 'payment&tab=other';
        return 'payment';
    }
}
if (!function_exists('portalManualPaymentStatusClass')) {
    function portalManualPaymentStatusClass(string $label): string {
        $map = [
            'No Proof Uploaded' => 'secondary',
            'Proof Uploaded' => 'info',
            'Pending Approval' => 'warning',
            'Approved' => 'success',
            'Rejected' => 'danger',
            'Reupload Locked' => 'dark',
        ];
        return $map[$label] ?? 'secondary';
    }
}
if (!function_exists('ensurePortalExtensionSupport')) {
    function ensurePortalExtensionSupport(PDO $pdo): void {
        $paymentCols = [
            'amount_paid' => "ALTER TABLE payment_transactions ADD COLUMN amount_paid DECIMAL(12,2) NULL AFTER amount",
            'approved_amount' => "ALTER TABLE payment_transactions ADD COLUMN approved_amount DECIMAL(12,2) NULL AFTER amount_paid",
            'received_fee_amount' => "ALTER TABLE payment_transactions ADD COLUMN received_fee_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER approved_amount",
            'settled_amount' => "ALTER TABLE payment_transactions ADD COLUMN settled_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER received_fee_amount",
            'fee_outstanding_before' => "ALTER TABLE payment_transactions ADD COLUMN fee_outstanding_before DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER settled_amount",
            'fee_outstanding_after' => "ALTER TABLE payment_transactions ADD COLUMN fee_outstanding_after DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER fee_outstanding_before",
            'excess_amount' => "ALTER TABLE payment_transactions ADD COLUMN excess_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER fee_outstanding_after",
            'underpayment_amount' => "ALTER TABLE payment_transactions ADD COLUMN underpayment_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER excess_amount",
            'reconciliation_status' => "ALTER TABLE payment_transactions ADD COLUMN reconciliation_status VARCHAR(40) NOT NULL DEFAULT 'Pending' AFTER underpayment_amount",
            'reconciliation_reference' => "ALTER TABLE payment_transactions ADD COLUMN reconciliation_reference VARCHAR(120) NULL AFTER reconciliation_status",
            'reconciliation_locked_at' => "ALTER TABLE payment_transactions ADD COLUMN reconciliation_locked_at DATETIME NULL AFTER reconciliation_reference",
            'credited_to_wallet_flag' => "ALTER TABLE payment_transactions ADD COLUMN credited_to_wallet_flag TINYINT(1) NOT NULL DEFAULT 0 AFTER reconciliation_locked_at",
            'manual_scope_key' => "ALTER TABLE payment_transactions ADD COLUMN manual_scope_key VARCHAR(255) NULL AFTER manual_notes",
            'manual_proof_original_name' => "ALTER TABLE payment_transactions ADD COLUMN manual_proof_original_name VARCHAR(255) NULL AFTER manual_proof_path",
            'manual_proof_uploaded_at' => "ALTER TABLE payment_transactions ADD COLUMN manual_proof_uploaded_at DATETIME NULL AFTER manual_proof_original_name",
            'proof_locked_at' => "ALTER TABLE payment_transactions ADD COLUMN proof_locked_at DATETIME NULL AFTER manual_proof_uploaded_at",
            'wallet_credit_status' => "ALTER TABLE payment_transactions ADD COLUMN wallet_credit_status VARCHAR(30) NOT NULL DEFAULT 'Pending' AFTER proof_locked_at",
            'wallet_credited_amount' => "ALTER TABLE payment_transactions ADD COLUMN wallet_credited_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER wallet_credit_status",
            'wallet_credited_at' => "ALTER TABLE payment_transactions ADD COLUMN wallet_credited_at DATETIME NULL AFTER wallet_credited_amount",
            'wallet_credit_reference' => "ALTER TABLE payment_transactions ADD COLUMN wallet_credit_reference VARCHAR(120) NULL AFTER wallet_credited_at",
        ];
        foreach ($paymentCols as $col => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, 'payment_transactions', $col)) { $pdo->exec($sql); }
            } catch (Throwable $e) {}
        }
        try { $pdo->exec("CREATE INDEX idx_payment_manual_scope ON payment_transactions (manual_scope_key(191))"); } catch (Throwable $e) {}
        try { $pdo->exec("CREATE INDEX idx_payment_wallet_credit_status ON payment_transactions (wallet_credit_status, wallet_credited_at)"); } catch (Throwable $e) {}
        try { $pdo->exec("CREATE INDEX idx_payment_wallet_credit_reference ON payment_transactions (wallet_credit_reference)"); } catch (Throwable $e) {}
        try { $pdo->exec("CREATE INDEX idx_payment_reconciliation_status ON payment_transactions (reconciliation_status, reconciliation_locked_at)"); } catch (Throwable $e) {}
        try { $pdo->exec("CREATE INDEX idx_payment_reconciliation_reference ON payment_transactions (reconciliation_reference)"); } catch (Throwable $e) {}
        try { $pdo->exec("UPDATE payment_transactions SET amount_paid = COALESCE(NULLIF(amount_paid, 0), NULLIF(charged_amount, 0), amount) WHERE amount_paid IS NULL OR amount_paid = 0"); } catch (Throwable $e) {}
        try { $pdo->exec("UPDATE payment_transactions SET approved_amount = COALESCE(NULLIF(approved_amount, 0), amount) WHERE approved_amount IS NULL AND LOWER(COALESCE(status, '')) IN ('approved','successful')"); } catch (Throwable $e) {}
        try { $pdo->exec("UPDATE payment_transactions SET received_fee_amount = COALESCE(NULLIF(received_fee_amount, 0), NULLIF(approved_amount, 0), NULLIF(amount_paid, 0), amount) WHERE COALESCE(transaction_type,'') <> 'Wallet Funding' AND COALESCE(received_fee_amount, 0) <= 0"); } catch (Throwable $e) {}
        try { $pdo->exec("UPDATE payment_transactions SET settled_amount = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' THEN 0 ELSE COALESCE(NULLIF(settled_amount, 0), CASE WHEN LOWER(COALESCE(status,'')) IN ('approved','successful') THEN amount ELSE 0 END, 0) END WHERE settled_amount IS NULL OR settled_amount < 0"); } catch (Throwable $e) {}
        try { $pdo->exec("UPDATE payment_transactions SET reconciliation_status = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' THEN COALESCE(NULLIF(reconciliation_status,''), 'WalletFunding') WHEN LOWER(COALESCE(status,'')) = 'rejected' THEN COALESCE(NULLIF(reconciliation_status,''), 'Rejected') WHEN LOWER(COALESCE(status,'')) IN ('approved','successful') AND COALESCE(settled_amount,0) > 0 THEN COALESCE(NULLIF(reconciliation_status,''), 'SettledLegacy') ELSE COALESCE(NULLIF(reconciliation_status,''), 'Pending') END"); } catch (Throwable $e) {}
        try { $pdo->exec("UPDATE payment_transactions SET wallet_credit_status = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' AND LOWER(COALESCE(status,'')) = 'rejected' THEN 'Rejected' WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' THEN COALESCE(NULLIF(wallet_credit_status,''), 'Pending') ELSE wallet_credit_status END WHERE LOWER(COALESCE(transaction_type,'')) = 'wallet funding'"); } catch (Throwable $e) {}

        try { if (!portalColumnExistsSafe($pdo, 'programmes', 'duration_years')) { $pdo->exec("ALTER TABLE programmes ADD COLUMN duration_years INT NULL AFTER code"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'graduation_status')) { $pdo->exec("ALTER TABLE students ADD COLUMN graduation_status ENUM('Active','Graduated') NOT NULL DEFAULT 'Active' AFTER account_status"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'graduated_at')) { $pdo->exec("ALTER TABLE students ADD COLUMN graduated_at DATETIME NULL AFTER graduation_status"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'graduation_year')) { $pdo->exec("ALTER TABLE students ADD COLUMN graduation_year VARCHAR(10) NULL AFTER graduated_at"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'students', 'graduation_restored_at')) { $pdo->exec("ALTER TABLE students ADD COLUMN graduation_restored_at DATETIME NULL AFTER graduation_year"); } } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS graduation_restore_logs (
                id INT AUTO_INCREMENT PRIMARY KEY,
                student_id INT NOT NULL,
                restored_by INT NULL,
                reason TEXT NULL,
                restored_at DATETIME NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_grad_restore_student (student_id),
                INDEX idx_grad_restore_admin (restored_by)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalProgrammeDurationYears')) {
    function portalProgrammeDurationYears(array $student, ?array $programme = null): int {
        $duration = (int)($programme['duration_years'] ?? $student['duration_years'] ?? 0);
        if ($duration > 0) return $duration;
        $name = strtoupper(trim((string)($programme['name'] ?? $student['programme_name'] ?? $student['programme'] ?? '')));
        if ($name === '') return 1;
        if (strpos($name, 'Honttech') !== false || strpos($name, 'PDE') !== false || strpos($name, 'PRE-DEGREE') !== false) return 1;
        if (strpos($name, 'HND') !== false || preg_match('/\bND\b/', $name)) return 2;
        if (strpos($name, 'NCE') !== false) return 3;
        if (strpos($name, 'DEGREE') !== false || strpos($name, 'BSC') !== false || strpos($name, 'B.A') !== false || strpos($name, 'B.ED') !== false || strpos($name, 'BTECH') !== false) return 4;
        return 4;
    }
}
if (!function_exists('portalStudentStartAcademicYearNumber')) {
    function portalStudentStartAcademicYearNumber(array $student): int {
        foreach (['session','session_name','application_no','registration_no','admission_no','matric_no'] as $field) {
            $year = portalAcademicYearFromValue($student[$field] ?? '');
            if ($year !== '') return (int)$year;
        }
        try {
            if (!empty($student['created_at'])) return (int)date('Y', strtotime((string)$student['created_at']));
        } catch (Throwable $e) {}
        return (int)date('Y');
    }
}
if (!function_exists('portalCurrentAcademicYearNumber')) {
    function portalCurrentAcademicYearNumber(PDO $pdo): int {
        return (int)portalAcademicYearNumber($pdo, portalCurrentAcademicSessionName($pdo));
    }
}
if (!function_exists('portalCompletedProgrammeYears')) {
    function portalCompletedProgrammeYears(PDO $pdo, array $student): int {
        $start = portalStudentStartAcademicYearNumber($student);
        $current = portalCurrentAcademicYearNumber($pdo);
        return max(1, ($current - $start) + 1);
    }
}
if (!function_exists('portalSyncGraduationStatuses')) {
    function portalSyncGraduationStatuses(PDO $pdo): void {
        try { ensurePortalExtensionSupport($pdo); } catch (Throwable $e) {}
        try {
            $sql = "SELECT s.id, s.session, s.session_name, s.application_no, s.registration_no, s.admission_no, s.matric_no, s.created_at,
                           s.programme, s.programme_id, s.graduation_status,
                           p.name AS programme_name, p.duration_years
                    FROM students s
                    LEFT JOIN programmes p ON p.id = s.programme_id";
            $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($rows as $row) {
                $duration = portalProgrammeDurationYears($row, $row);
                $completed = portalCompletedProgrammeYears($pdo, $row);
                $currentStatus = (string)($row['graduation_status'] ?? 'Active');
                if ($duration > 0 && $completed >= $duration) {
                    if ($currentStatus !== 'Graduated') {
                        $pdo->prepare("UPDATE students SET graduation_status='Graduated', graduated_at=COALESCE(graduated_at, NOW()), graduation_year=? WHERE id=?")
                            ->execute([(string)portalCurrentAcademicYearNumber($pdo), (int)$row['id']]);
                    }
                } elseif ($currentStatus !== 'Active') {
                    $pdo->prepare("UPDATE students SET graduation_status='Active', graduated_at=NULL, graduation_year=NULL WHERE id=?")
                        ->execute([(int)$row['id']]);
                }
            }
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalFetchGraduatedRows')) {
    function portalFetchGraduatedRows(PDO $pdo, array $filters = []): array {
        try { portalSyncGraduationStatuses($pdo); } catch (Throwable $e) {}
        $where = ["COALESCE(s.graduation_status,'Active') = 'Graduated'"];
        $params = [];
        if (!empty($filters['faculty_id'])) { $where[] = 's.faculty_id = ?'; $params[] = (int)$filters['faculty_id']; }
        if (!empty($filters['department_id'])) { $where[] = 's.department_id = ?'; $params[] = (int)$filters['department_id']; }
        $year = trim((string)($filters['year'] ?? ''));
        if ($year !== '') { $where[] = 'COALESCE(s.graduation_year, "") = ?'; $params[] = $year; }
        $sql = "SELECT s.*, 
                       COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.faculty),''), 'Not Set') AS faculty_name,
                       COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.department),''), 'Not Set') AS department_name,
                       COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme),''), 'Not Set') AS programme_name,
                       p.duration_years
                FROM students s
                LEFT JOIN faculties f ON f.id = s.faculty_id
                LEFT JOIN departments d ON d.id = s.department_id
                LEFT JOIN programmes p ON p.id = s.programme_id
                WHERE " . implode(' AND ', $where) . "
                ORDER BY COALESCE(s.graduated_at, s.created_at) DESC, s.id DESC";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        foreach ($rows as &$row) {
            $row['student_name'] = full_name($row);
            $row['duration_years'] = portalProgrammeDurationYears($row, $row);
            $row['completed_years'] = portalCompletedProgrammeYears($pdo, $row);
        }
        unset($row);
        return $rows;
    }
}

if (!function_exists('flash')) {
    function flash(string $type, string $message): void {
        if (!isset($_SESSION['flash_messages'])) $_SESSION['flash_messages'] = [];
        $_SESSION['flash_messages'][] = ['type' => $type, 'message' => $message];
    }
}
if (!function_exists('renderFlashes')) {
    function renderFlashes(): void {
        if (empty($_SESSION['flash_messages'])) return;
        foreach ($_SESSION['flash_messages'] as $m) {
            $type = preg_replace('/[^a-z]/i', '', (string)($m['type'] ?? 'info'));
            $msg  = (string)($m['message'] ?? '');
            if ($msg === '') continue;
            echo '<div class="alert alert-' . htmlspecialchars($type) . '">' . $msg . '</div>';
        }
        unset($_SESSION['flash_messages']);
    }
}

// Notification housekeeping: delete archived notifications older than 30 days
function cleanupOldNotifications(PDO $pdo): void {
    try {
        // Only delete notifications that have been archived (viewed) and are older than 30 days
        $stmt = $pdo->prepare("
            DELETE FROM notifications
            WHERE archived = 1
              AND archived_at IS NOT NULL
              AND archived_at < DATE_SUB(NOW(), INTERVAL 30 DAY)
        ");
        $stmt->execute();
    } catch (Throwable $e) {
        // Fail silently to avoid breaking the app if the table doesn't exist yet
    }
}

// Cleanup notifications (auto-delete archived after 1 month)
if (isset($_SESSION['user_type']) && in_array($_SESSION['user_type'], ['admin','student'], true)) {
    cleanupOldNotifications($pdo);
}

// Helper functions

// Current logged-in user helpers
if (!function_exists('currentUserRole')) {
    function currentUserRole(): string {
        if (isset($_SESSION['user_type']) && in_array($_SESSION['user_type'], ['admin','student'], true)) {
            return $_SESSION['user_type'];
        }
        return 'guest';
    }
}

if (!function_exists('currentUserId')) {
    function currentUserId(): int {
        // Admin session keys may differ; support common patterns
        if (isset($_SESSION['user_type']) && $_SESSION['user_type'] === 'admin') {
            if (isset($_SESSION['admin_id'])) return (int)$_SESSION['admin_id'];
            if (isset($_SESSION['user_id']))  return (int)$_SESSION['user_id'];
            if (isset($_SESSION['id']))       return (int)$_SESSION['id'];
        }
        if (isset($_SESSION['user_type']) && $_SESSION['user_type'] === 'student') {
            if (isset($_SESSION['student_id'])) return (int)$_SESSION['student_id'];
            if (isset($_SESSION['user_id']))    return (int)$_SESSION['user_id'];
            if (isset($_SESSION['id']))         return (int)$_SESSION['id'];
        }
        return 0;
    }
}


function generateRegistrationNumber(array $context = []) {
    global $pdo;
    try {
        return portalGenerateAutoNumber($pdo, portalHasExplicitNumberFormat($pdo, 'registration', $context) ? 'registration' : 'application', $context);
    } catch (Throwable $e) {
        $year = date('Y');
        for ($i = 0; $i < 50; $i++) {
            $random = str_pad((string)random_int(0, 999999), 6, '0', STR_PAD_LEFT);
            $reg = "APP/{$year}/{$random}";
            try {
                $stmt = $pdo->prepare("SELECT COUNT(*) FROM students WHERE registration_no = ? OR application_no = ?");
                $stmt->execute([$reg, $reg]);
                if ((int)$stmt->fetchColumn() === 0) return $reg;
            } catch (Throwable $inner) {
                return $reg;
            }
        }
        return "APP/{$year}/" . str_pad((string)random_int(0, 999999), 6, '0', STR_PAD_LEFT);
    }
}

if (!function_exists('portalStudentApplicationNumber')) {
    function portalStudentApplicationNumber(array $student): string {
        $applicationNo = trim((string)($student['application_no'] ?? ''));
        if ($applicationNo !== '') return $applicationNo;
        return trim((string)($student['registration_no'] ?? ''));
    }
}

if (!function_exists('portalStudentAdmissionNumber')) {
    function portalStudentAdmissionNumber(array $student): string {
        return trim((string)($student['admission_no'] ?? ''));
    }
}

if (!function_exists('portalStudentAdmissionDisplay')) {
    function portalStudentAdmissionDisplay(array $student, string $fallback = 'Pending'): string {
        $admissionNo = portalStudentAdmissionNumber($student);
        return $admissionNo !== '' ? $admissionNo : $fallback;
    }
}

function generateTransactionId() {
    // Generate a random 6+ digit number
    return mt_rand(100000, 999999);
}

// Payment date helpers (guarded to prevent missing-function fatals)
if (!function_exists('getPaymentDateValue')) {
    function getPaymentDateValue(PDO $pdo, array $transaction): ?string {
        if (!empty($transaction['payment_date'])) {
            return (string)$transaction['payment_date'];
        }
        if (!empty($transaction['created_at'])) {
            return (string)$transaction['created_at'];
        }
        if (!empty($transaction['reference'])) {
            try {
                $stmt = $pdo->prepare("SELECT payment_date, created_at FROM payment_transactions WHERE reference = ? LIMIT 1");
                $stmt->execute([$transaction['reference']]);
                $row = $stmt->fetch(PDO::FETCH_ASSOC);
                if ($row) {
                    return !empty($row['payment_date']) ? (string)$row['payment_date'] : (!empty($row['created_at']) ? (string)$row['created_at'] : null);
                }
            } catch (Throwable $e) {
                // ignore
            }
        }
        return null;
    }
}

if (!function_exists('formatPaymentDate')) {
    function formatPaymentDate(PDO $pdo, array $transaction): string {
        $raw = getPaymentDateValue($pdo, $transaction);
        if (empty($raw)) return 'N/A';
        $ts = @strtotime($raw);
        if ($ts === false) return htmlspecialchars((string)$raw);
        return date('d M Y, h:i A', $ts);
    }
}


function generateQRCode($data) {
    // In a real implementation, you would use a QR code library
    // For now, we'll use a placeholder URL
    return "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=" . urlencode($data);
}


if (!function_exists('portalNotificationSettingsRowSafe')) {
    function portalNotificationSettingsRowSafe(PDO $pdo): array {
        try {
            $stmt = $pdo->query("SELECT * FROM notification_settings ORDER BY id ASC LIMIT 1");
            $row = $stmt ? ($stmt->fetch(PDO::FETCH_ASSOC) ?: []) : [];
            return is_array($row) ? $row : [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalResolveEmailTransportSettings')) {
    function portalResolveEmailTransportSettings(PDO $pdo): array {
        $legacy = portalNotificationSettingsRowSafe($pdo);
        $read = static function(array $systemKeys, array $legacyKeys = [], string $default = '') use ($pdo, $legacy): string {
            foreach ($systemKeys as $key) {
                $value = trim((string)(portalSettingValueSafe($pdo, $key, '') ?? ''));
                if ($value !== '') {
                    return $value;
                }
            }
            foreach ($legacyKeys as $key) {
                $value = trim((string)($legacy[$key] ?? ''));
                if ($value !== '') {
                    return $value;
                }
            }
            return $default;
        };

        $emailEnabled = strtolower($read(['email_notifications'], [], 'enabled'));
        if ($emailEnabled === '' && array_key_exists('email_enabled', $legacy)) {
            $emailEnabled = ((int)($legacy['email_enabled'] ?? 0) === 1) ? 'enabled' : 'disabled';
        }

        $smtpPort = $read(['smtp_port'], ['smtp_port'], '587');
        $smtpEncryption = strtolower($read(['smtp_encryption'], ['smtp_secure'], 'tls'));
        if ($smtpEncryption === '') {
            $smtpEncryption = ((int)$smtpPort === 465) ? 'ssl' : 'tls';
        }

        $smtpUser = $read(['smtp_username'], ['smtp_user'], '');
        $smtpFromEmail = $read(['smtp_from_email'], ['from_email'], $smtpUser);
        $smtpFromName = $read(['smtp_from_name'], ['from_name'], APP_NAME);

        return [
            'email_notifications' => $emailEnabled !== '' ? $emailEnabled : 'enabled',
            'smtp_host' => $read(['smtp_host'], ['smtp_host'], ''),
            'smtp_port' => $smtpPort,
            'smtp_username' => $smtpUser,
            'smtp_password' => $read(['smtp_password'], ['smtp_pass'], ''),
            'smtp_from_email' => $smtpFromEmail,
            'smtp_from_name' => $smtpFromName,
            'smtp_reply_to' => $read(['smtp_reply_to'], ['reply_to','reply_to_email'], ''),
            'smtp_encryption' => $smtpEncryption,
            'smtp_allow_self_signed' => $read(['smtp_allow_self_signed'], [], '0'),
        ];
    }
}

if (!function_exists('portalEnsureEmailDeliverySupport')) {
    function portalEnsureEmailDeliverySupport(PDO $pdo): void {
        try { portalEnsureEmailNotificationSchema($pdo); } catch (Throwable $e) {}
        try { portalEnsureNotificationTemplateSchema($pdo, 'email'); } catch (Throwable $e) {}
        try { portalSeedNotificationEmailTemplates($pdo); } catch (Throwable $e) {}
        try {
            $resolved = portalResolveEmailTransportSettings($pdo);
            $map = [
                'email_notifications' => ['key' => 'email_notifications', 'description' => 'Enable or disable email notifications'],
                'smtp_host' => ['key' => 'smtp_host', 'description' => 'SMTP host'],
                'smtp_port' => ['key' => 'smtp_port', 'description' => 'SMTP port'],
                'smtp_username' => ['key' => 'smtp_username', 'description' => 'SMTP username'],
                'smtp_password' => ['key' => 'smtp_password', 'description' => 'SMTP password'],
                'smtp_from_email' => ['key' => 'smtp_from_email', 'description' => 'SMTP sender email'],
                'smtp_from_name' => ['key' => 'smtp_from_name', 'description' => 'SMTP sender name'],
                'smtp_reply_to' => ['key' => 'smtp_reply_to', 'description' => 'SMTP reply-to email'],
                'smtp_encryption' => ['key' => 'smtp_encryption', 'description' => 'SMTP encryption'],
                'smtp_allow_self_signed' => ['key' => 'smtp_allow_self_signed', 'description' => 'Allow self-signed SSL certificate for SMTP'],
            ];
            foreach ($map as $resolvedKey => $meta) {
                $value = (string)($resolved[$resolvedKey] ?? '');
                $current = trim((string)portalSettingValueSafe($pdo, $meta['key'], ''));
                if ($current === '' && $value !== '') {
                    setSystemSettingValue($pdo, $meta['key'], $value, $meta['description']);
                }
            }
        } catch (Throwable $e) {}
    }
}

function notifyEmail(string $to, string $subject, string $body): bool {
    global $pdo;
    try { if (isset($pdo) && $pdo instanceof PDO) { portalEnsureEmailDeliverySupport($pdo); } } catch (Throwable $e) {}
    $to = trim((string)$to);
    if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
        $GLOBALS['portal_last_email_error'] = 'Recipient email address is missing or invalid.';
        return false;
    }
    try {
        $enabled = 'enabled';
        if (isset($pdo) && $pdo instanceof PDO) {
            $resolved = portalResolveEmailTransportSettings($pdo);
            $enabled = strtolower(trim((string)($resolved['email_notifications'] ?? 'enabled')));
        } else {
            $enabled = strtolower(trim((string)getSystemSettingValue($pdo, 'email_notifications', 'enabled')));
        }
        if (!in_array($enabled, ['', 'enabled', '1', 'true', 'yes', 'on'], true)) {
            $GLOBALS['portal_last_email_error'] = 'Email notifications are disabled.';
            return false;
        }
    } catch (Throwable $e) {}
    return sendEmail($to, $subject, $body);
}

function sendEmail($to, $subject, $body) {
    global $pdo;
    $GLOBALS['portal_last_email_error'] = '';

    $to = trim((string)$to);
    $subject = trim((string)$subject);
    $body = (string)$body;
    if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
        $GLOBALS['portal_last_email_error'] = 'Recipient email address is missing or invalid.';
        return false;
    }
    if ($subject === '') {
        $subject = APP_NAME . ' Notification';
    }

    $smtpHost = '';
    $smtpPort = 587;
    $smtpUser = '';
    $smtpPass = '';
    $smtpFrom = '';
    $smtpFromName = APP_NAME;
    $smtpEncryption = 'tls';
    $replyTo = '';

    try {
        $smtpHost = trim((string)(getSystemSettingValue($pdo, 'smtp_host', '') ?: ''));
        $smtpPort = (int)(getSystemSettingValue($pdo, 'smtp_port', '587') ?: 587);
        $smtpUser = trim((string)(getSystemSettingValue($pdo, 'smtp_username', '') ?: ''));
        $smtpPass = (string)(getSystemSettingValue($pdo, 'smtp_password', '') ?: '');
        $smtpFrom = trim((string)(getSystemSettingValue($pdo, 'smtp_from_email', '') ?: ''));
        $smtpFromName = trim((string)(getSystemSettingValue($pdo, 'smtp_from_name', APP_NAME) ?: APP_NAME));
        $smtpEncryption = strtolower(trim((string)(getSystemSettingValue($pdo, 'smtp_encryption', 'tls') ?: 'tls')));
        $replyTo = trim((string)(getSystemSettingValue($pdo, 'smtp_reply_to', '') ?: ''));
    } catch (Throwable $e) {}

    try {
        if (isset($pdo) && $pdo instanceof PDO) {
            portalEnsureEmailDeliverySupport($pdo);
            $resolvedEmail = portalResolveEmailTransportSettings($pdo);
            if ($smtpHost === '') $smtpHost = trim((string)($resolvedEmail['smtp_host'] ?? ''));
            if ($smtpPort <= 0 || $smtpPort === 587) $smtpPort = (int)($resolvedEmail['smtp_port'] ?? $smtpPort ?: 587);
            if ($smtpUser === '') $smtpUser = trim((string)($resolvedEmail['smtp_username'] ?? ''));
            if ($smtpPass === '') $smtpPass = (string)($resolvedEmail['smtp_password'] ?? '');
            if ($smtpFrom === '') $smtpFrom = trim((string)($resolvedEmail['smtp_from_email'] ?? ''));
            if ($smtpFromName === '' || $smtpFromName === APP_NAME) $smtpFromName = trim((string)($resolvedEmail['smtp_from_name'] ?? APP_NAME));
            if ($replyTo === '') $replyTo = trim((string)($resolvedEmail['smtp_reply_to'] ?? ''));
            if ($smtpEncryption === '' || $smtpEncryption === 'tls') $smtpEncryption = strtolower(trim((string)($resolvedEmail['smtp_encryption'] ?? $smtpEncryption ?: 'tls')));
        }
    } catch (Throwable $e) {}

    if ($smtpHost === '' && isset($pdo) && $pdo instanceof PDO) {
        try { $smtpHost = trim((string)portalNotificationSettingValueSafe($pdo, 'smtp_host', '')); } catch (Throwable $e) {}
    }
    if ($smtpPort <= 0 && isset($pdo) && $pdo instanceof PDO) {
        try { $smtpPort = (int)(portalNotificationSettingValueSafe($pdo, 'smtp_port', '587') ?: 587); } catch (Throwable $e) { $smtpPort = 587; }
    }
    if ($smtpUser === '' && isset($pdo) && $pdo instanceof PDO) {
        try { $smtpUser = trim((string)portalNotificationSettingValueSafe($pdo, 'smtp_user', '')); } catch (Throwable $e) {}
    }
    if ($smtpPass === '' && isset($pdo) && $pdo instanceof PDO) {
        try { $smtpPass = (string)portalNotificationSettingValueSafe($pdo, 'smtp_pass', ''); } catch (Throwable $e) {}
    }
    if ($smtpFrom === '' && isset($pdo) && $pdo instanceof PDO) {
        try { $smtpFrom = trim((string)portalNotificationSettingValueSafe($pdo, 'from_email', '')); } catch (Throwable $e) {}
    }
    if ($smtpFromName === '' && isset($pdo) && $pdo instanceof PDO) {
        try { $smtpFromName = trim((string)portalNotificationSettingValueSafe($pdo, 'from_name', APP_NAME)); } catch (Throwable $e) { $smtpFromName = APP_NAME; }
    }
    if ($smtpEncryption === '' && isset($pdo) && $pdo instanceof PDO) {
        try { $smtpEncryption = strtolower(trim((string)portalNotificationSettingValueSafe($pdo, 'smtp_secure', 'tls'))); } catch (Throwable $e) { $smtpEncryption = 'tls'; }
    }

    if ($smtpHost === '' && defined('SMTP_HOST')) $smtpHost = trim((string)SMTP_HOST);
    if (($smtpPort <= 0 || $smtpPort == 587) && defined('SMTP_PORT')) $smtpPort = (int)SMTP_PORT;
    if ($smtpUser === '' && defined('SMTP_USERNAME')) $smtpUser = trim((string)SMTP_USERNAME);
    if ($smtpPass === '' && defined('SMTP_PASSWORD')) $smtpPass = (string)SMTP_PASSWORD;
    if ($smtpFrom === '' && defined('FROM_EMAIL')) $smtpFrom = trim((string)FROM_EMAIL);
    if (($smtpFromName === '' || $smtpFromName === APP_NAME) && defined('FROM_NAME')) $smtpFromName = trim((string)FROM_NAME);
    if ($replyTo === '' && defined('REPLY_TO_EMAIL')) $replyTo = trim((string)REPLY_TO_EMAIL);
    if (($smtpEncryption === '' || $smtpEncryption === 'tls') && defined('SMTP_ENCRYPTION')) $smtpEncryption = strtolower(trim((string)SMTP_ENCRYPTION));

    if ($smtpPort <= 0) $smtpPort = 587;
    if ($smtpEncryption === '') $smtpEncryption = ($smtpPort === 465 ? 'ssl' : 'tls');
    if ($smtpFrom === '') $smtpFrom = ($smtpUser !== '' ? $smtpUser : '');
    if ($smtpFromName === '') $smtpFromName = APP_NAME;

    $lastError = '';

    try {
        $candidates = [
            __DIR__ . '/PHPMailer/src/Exception.php',
            __DIR__ . '/PHPMailer/PHPMailer/src/Exception.php',
            __DIR__ . '/phpmailer/src/Exception.php'
        ];
        $base = null;
        foreach ($candidates as $ex) {
            if (is_file($ex)) { $base = dirname(dirname($ex)); break; }
        }
        if ($base !== null && $smtpHost !== '' && $smtpFrom !== '') {
            require_once $base . '/src/Exception.php';
            require_once $base . '/src/PHPMailer.php';
            require_once $base . '/src/SMTP.php';

            $mail = new PHPMailer\PHPMailer\PHPMailer(true);
            $mail->isSMTP();
            $mail->Host = $smtpHost;
            $mail->SMTPAuth = ($smtpUser !== '' || $smtpPass !== '');
            if ($smtpUser !== '') $mail->Username = $smtpUser;
            if ($smtpPass !== '') $mail->Password = $smtpPass;
            $mail->Port = $smtpPort;
            $mail->CharSet = 'UTF-8';
            if ($smtpEncryption === 'ssl' || $smtpPort === 465) {
                $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
            } elseif ($smtpEncryption !== 'none') {
                $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
            }
            $allowSelfSigned = false;
            try { $allowSelfSigned = (string)(getSystemSettingValue($pdo,'smtp_allow_self_signed','0') ?? '0') === '1'; } catch (Throwable $e) {}
            try { if (!$allowSelfSigned && isset($resolvedEmail) && is_array($resolvedEmail)) { $allowSelfSigned = (string)($resolvedEmail['smtp_allow_self_signed'] ?? '0') === '1'; } } catch (Throwable $e) {}
            if ($allowSelfSigned) {
                $mail->SMTPOptions = [
                    'ssl' => [
                        'verify_peer' => false,
                        'verify_peer_name' => false,
                        'allow_self_signed' => true
                    ]
                ];
            }
            $mail->setFrom($smtpFrom, $smtpFromName ?: APP_NAME);
            if ($replyTo !== '' && filter_var($replyTo, FILTER_VALIDATE_EMAIL)) {
                $mail->addReplyTo($replyTo);
            }
            $mail->addAddress($to);
            $mail->isHTML(true);
            $mail->Subject = $subject;
            $mail->Body = $body;
            $mail->AltBody = strip_tags(preg_replace('/<br\s*\/?>/i', "\n", $body));
            $mail->send();
            $GLOBALS['portal_last_email_error'] = '';
            return true;
        }
    } catch (Throwable $e) {
        $lastError = $e->getMessage();
        error_log('PHPMailer send error: ' . $lastError);
    }

    if ($smtpHost !== '' && $smtpFrom !== '' && function_exists('portalAuthNativeSmtpSend')) {
        try {
            if (portalAuthNativeSmtpSend($smtpHost, $smtpPort > 0 ? $smtpPort : 587, $smtpEncryption, $smtpUser, $smtpPass, $smtpFrom, $smtpFromName, $to, $subject, $body, $replyTo)) {
                $GLOBALS['portal_last_email_error'] = '';
                return true;
            }
            if ($lastError === '') $lastError = 'Native SMTP send failed.';
        } catch (Throwable $e) {
            $lastError = $e->getMessage();
            error_log('Native SMTP send error: ' . $lastError);
        }
    }

    try {
        $headers = "MIME-Version: 1.0\r\n";
        $headers .= "Content-Type: text/html; charset=UTF-8\r\n";
        if ($smtpFrom !== '') {
            $headers .= 'From: ' . ($smtpFromName !== '' ? ($smtpFromName . ' <' . $smtpFrom . '>') : $smtpFrom) . "\r\n";
        }
        if ($replyTo !== '' && filter_var($replyTo, FILTER_VALIDATE_EMAIL)) {
            $headers .= "Reply-To: {$replyTo}\r\n";
        }
        if (@mail($to, $subject, $body, $headers)) {
            $GLOBALS['portal_last_email_error'] = '';
            return true;
        }
        if ($lastError === '') $lastError = 'mail() fallback failed.';
    } catch (Throwable $e) {
        $lastError = $e->getMessage();
        error_log('mail() send error: ' . $lastError);
    }

    $GLOBALS['portal_last_email_error'] = $lastError !== '' ? $lastError : 'Unable to send email.';
    return false;
}


// -------------------------
// Email helpers (logs + safe send wrappers)
// -------------------------
if (!function_exists('portalEnsureEmailNotificationSchema')) {
    function portalEnsureEmailNotificationSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS email_logs (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_type ENUM('student','admin') NOT NULL,
                user_id INT NOT NULL DEFAULT 0,
                student_id INT NOT NULL DEFAULT 0,
                email VARCHAR(190) NOT NULL,
                recipient_email VARCHAR(190) NOT NULL,
                context VARCHAR(190) NOT NULL,
                event_type VARCHAR(120) NOT NULL DEFAULT '',
                related_id INT NOT NULL DEFAULT 0,
                subject VARCHAR(255) NOT NULL,
                success TINYINT(1) NOT NULL DEFAULT 0,
                send_status VARCHAR(30) NOT NULL DEFAULT 'Failed',
                error_message TEXT NULL,
                sent_at DATETIME NULL,
                meta_json LONGTEXT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                KEY idx_user (user_type, user_id),
                KEY idx_student (student_id),
                KEY idx_email (recipient_email),
                KEY idx_event (event_type),
                KEY idx_ctx (context),
                KEY idx_created (created_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
        $cols = [
            'student_id' => "ALTER TABLE email_logs ADD COLUMN student_id INT NOT NULL DEFAULT 0 AFTER user_id",
            'recipient_email' => "ALTER TABLE email_logs ADD COLUMN recipient_email VARCHAR(190) NOT NULL DEFAULT '' AFTER email",
            'event_type' => "ALTER TABLE email_logs ADD COLUMN event_type VARCHAR(120) NOT NULL DEFAULT '' AFTER context",
            'related_id' => "ALTER TABLE email_logs ADD COLUMN related_id INT NOT NULL DEFAULT 0 AFTER event_type",
            'send_status' => "ALTER TABLE email_logs ADD COLUMN send_status VARCHAR(30) NOT NULL DEFAULT 'Failed' AFTER success",
            'error_message' => "ALTER TABLE email_logs ADD COLUMN error_message TEXT NULL AFTER send_status",
            'sent_at' => "ALTER TABLE email_logs ADD COLUMN sent_at DATETIME NULL AFTER error_message",
            'meta_json' => "ALTER TABLE email_logs ADD COLUMN meta_json LONGTEXT NULL AFTER sent_at",
        ];
        foreach ($cols as $col => $sql) {
            try { if (!portalColumnExistsSafe($pdo, 'email_logs', $col)) { $pdo->exec($sql); } } catch (Throwable $e) {}
        }
        try { if (!portalIndexExistsSafe($pdo, 'email_logs', 'idx_student')) { $pdo->exec("CREATE INDEX idx_student ON email_logs(student_id)"); } } catch (Throwable $e) {}
        try { if (!portalIndexExistsSafe($pdo, 'email_logs', 'idx_event')) { $pdo->exec("CREATE INDEX idx_event ON email_logs(event_type)"); } } catch (Throwable $e) {}
        try { setSystemSettingValue($pdo, 'email_notifications', portalSettingValueSafe($pdo, 'email_notifications', 'enabled'), 'Enable or disable student email notifications'); } catch (Throwable $e) {}
        try { setSystemSettingValue($pdo, 'smtp_host', portalSettingValueSafe($pdo, 'smtp_host', (defined('SMTP_HOST') ? (string)SMTP_HOST : '')), 'SMTP host'); } catch (Throwable $e) {}
        try { setSystemSettingValue($pdo, 'smtp_port', portalSettingValueSafe($pdo, 'smtp_port', (defined('SMTP_PORT') ? (string)SMTP_PORT : '587')), 'SMTP port'); } catch (Throwable $e) {}
        try { setSystemSettingValue($pdo, 'smtp_username', portalSettingValueSafe($pdo, 'smtp_username', (defined('SMTP_USERNAME') ? (string)SMTP_USERNAME : '')), 'SMTP username'); } catch (Throwable $e) {}
        try { setSystemSettingValue($pdo, 'smtp_password', portalSettingValueSafe($pdo, 'smtp_password', (defined('SMTP_PASSWORD') ? (string)SMTP_PASSWORD : '')), 'SMTP password'); } catch (Throwable $e) {}
        try { setSystemSettingValue($pdo, 'smtp_encryption', portalSettingValueSafe($pdo, 'smtp_encryption', (defined('SMTP_ENCRYPTION') ? (string)SMTP_ENCRYPTION : 'tls')), 'SMTP encryption'); } catch (Throwable $e) {}
        try { setSystemSettingValue($pdo, 'smtp_from_name', portalSettingValueSafe($pdo, 'smtp_from_name', (defined('FROM_NAME') ? (string)FROM_NAME : APP_NAME)), 'SMTP sender name'); } catch (Throwable $e) {}
        try { setSystemSettingValue($pdo, 'smtp_from_email', portalSettingValueSafe($pdo, 'smtp_from_email', (defined('FROM_EMAIL') ? (string)FROM_EMAIL : '')), 'SMTP sender email'); } catch (Throwable $e) {}
    }
}
function logEmailSend(PDO $pdo, string $userType, int $userId, string $email, string $context, string $subject, bool $success, string $eventType = '', int $relatedId = 0, string $errorMessage = '', array $meta = []): void {
    try {
        portalEnsureEmailNotificationSchema($pdo);
        $studentId = ($userType === 'student') ? $userId : 0;
        $stmt = $pdo->prepare("INSERT INTO email_logs (user_type, user_id, student_id, email, recipient_email, context, event_type, related_id, subject, success, send_status, error_message, sent_at, meta_json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
        $stmt->execute([
            $userType,
            $userId,
            $studentId,
            $email,
            $email,
            $context,
            $eventType,
            $relatedId,
            $subject,
            $success ? 1 : 0,
            $success ? 'Sent' : 'Failed',
            ($errorMessage !== '' ? $errorMessage : null),
            ($success ? date('Y-m-d H:i:s') : null),
            (!empty($meta) ? json_encode($meta, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE) : null),
        ]);
    } catch (Throwable $e) {}
}
function emailAlreadySent(PDO $pdo, string $userType, int $userId, string $context, int $withinSeconds = 86400): bool {
    try {
        portalEnsureEmailNotificationSchema($pdo);
        $stmt = $pdo->prepare("SELECT id FROM email_logs WHERE user_type=? AND user_id=? AND context=? AND success=1 AND created_at >= (NOW() - INTERVAL ? SECOND) LIMIT 1");
        $stmt->execute([$userType, $userId, $context, $withinSeconds]);
        return (bool)$stmt->fetchColumn();
    } catch (Throwable $e) {}
    return false;
}
if (!function_exists('portalInferEmailEventType')) {
    function portalInferEmailEventType(string $context): string {
        return portalNormalizeNotificationCommandKey(portalSmsEventTypeFromContext($context));
    }
}
if (!function_exists('sendLoggedSms')) {
    function sendLoggedSms(PDO $pdo, string $userType, int $userId, string $phone, string $context, string $message, string $eventType = ''): bool {
        $GLOBALS['portal_last_sms_error'] = '';
        $phone = trim((string)$phone);
        if ($phone === '') {
            $GLOBALS['portal_last_sms_error'] = 'Recipient phone number is missing or invalid.';
            return false;
        }
        $eventType = trim($eventType) !== '' ? trim($eventType) : portalSmsEventTypeFromContext($context);
        $eventType = portalNormalizeNotificationCommandKey($eventType);
        $decision = portalResolveNotificationTemplateDecision($pdo, 'sms', $eventType);
        if (($decision['status'] ?? '') !== 'enabled') {
            logSmsSend(
                $pdo,
                $userType,
                $userId,
                $phone,
                'template',
                $eventType,
                $context,
                false,
                '',
                '',
                (string)($decision['reason'] ?? 'SMS template unavailable.'),
                ['template_status' => (string)($decision['status'] ?? 'missing')]
            );
            $GLOBALS['portal_last_sms_error'] = (string)($decision['reason'] ?? 'SMS template unavailable.');
            return false;
        }
        if ($context !== '' && smsAlreadySent($pdo, $userType, $userId, $context, 86400)) {
            $GLOBALS['portal_last_sms_error'] = '';
            return true;
        }
        $message = portalNotificationSmsPayload($pdo, $eventType, $context, $message, [
            'message' => $message,
            'user_type' => $userType,
            'user_id' => (string)$userId,
        ]);
        $ok = sendSmsNotification($pdo, $phone, $message, $eventType, $context, $userType, $userId);
        if ($ok) {
            $GLOBALS['portal_last_sms_error'] = '';
        }
        return $ok;
    }
}
function sendLoggedEmail(PDO $pdo, string $userType, int $userId, string $email, string $context, string $subject, string $body): bool {
    $email = trim((string)$email);
    $eventType = portalInferEmailEventType($context);
    $eventType = portalNormalizeNotificationCommandKey($eventType);
    try { portalEnsureEmailDeliverySupport($pdo); } catch (Throwable $e) {}
    $emailDecision = portalResolveNotificationTemplateDecision($pdo, 'email', $eventType);
    $emailOk = false;
    $templateStatus = (string)($emailDecision['status'] ?? 'missing');

    if ($templateStatus === 'enabled') {
        if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
            if (emailAlreadySent($pdo, $userType, $userId, $context, 86400)) {
                $emailOk = true;
            } else {
                $emailOk = notifyEmail($email, $subject, $body);
                $error = trim((string)($GLOBALS['portal_last_email_error'] ?? ''));
                logEmailSend($pdo, $userType, $userId, $email, $context, $subject, $emailOk, $eventType, 0, $error, [
                    'template_status' => 'enabled',
                    'template_command' => (string)($emailDecision['command_key'] ?? $eventType),
                ]);
            }
        } else {
            logEmailSend($pdo, $userType, $userId, $email, $context, $subject, false, $eventType, 0, 'Recipient email address is missing or invalid.', [
                'template_status' => 'enabled',
                'template_command' => (string)($emailDecision['command_key'] ?? $eventType),
            ]);
        }
    } elseif (in_array($templateStatus, ['missing', 'error'], true)) {
        if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
            if (emailAlreadySent($pdo, $userType, $userId, $context, 86400)) {
                $emailOk = true;
            } else {
                $emailOk = notifyEmail($email, $subject, $body);
                $error = trim((string)($GLOBALS['portal_last_email_error'] ?? ''));
                logEmailSend($pdo, $userType, $userId, $email, $context, $subject, $emailOk, $eventType, 0, $error !== '' ? $error : (string)($emailDecision['reason'] ?? 'Email sent without a matching template after self-healing fallback.'), [
                    'template_status' => $templateStatus,
                    'template_command' => (string)($emailDecision['command_key'] ?? $eventType),
                    'fallback_mode' => 'direct_send',
                ]);
            }
        } else {
            logEmailSend($pdo, $userType, $userId, $email, $context, $subject, false, $eventType, 0, 'Recipient email address is missing or invalid.', [
                'template_status' => $templateStatus,
                'template_command' => (string)($emailDecision['command_key'] ?? $eventType),
                'fallback_mode' => 'direct_send',
            ]);
        }
    } else {
        logEmailSend($pdo, $userType, $userId, $email, $context, $subject, false, $eventType, 0, (string)($emailDecision['reason'] ?? 'Email template unavailable.'), [
            'template_status' => $templateStatus,
            'template_command' => (string)($emailDecision['command_key'] ?? $eventType),
        ]);
    }

    $phone = portalResolveNotificationPhone($pdo, $userType, $userId);
    $smsText = portalSmsStripHtml($subject, $body);
    $smsOk = false;
    if ($phone !== '' && $smsText !== '') {
        $smsOk = sendLoggedSms($pdo, $userType, $userId, $phone, $context, $smsText, $eventType);
    }

    return $emailOk || $smsOk;
}
if (!function_exists('portalNotificationMailSubjectPrefix')) {
    function portalNotificationMailSubjectPrefix(PDO $pdo): string {
        $title = trim((string)portalSettingValueSafe($pdo, 'portal_title', APP_NAME));
        return $title !== '' ? $title : APP_NAME;
    }
}
if (!function_exists('portalWrapNotificationEmail')) {
    function portalWrapNotificationEmail(PDO $pdo, string $heading, string $contentHtml): string {
        $portalName = htmlspecialchars(portalNotificationMailSubjectPrefix($pdo), ENT_QUOTES, 'UTF-8');
        return '<div style="font-family:Arial,sans-serif;line-height:1.6;color:#1f2937;max-width:720px;margin:0 auto;padding:24px;background:#ffffff;border:1px solid #e5e7eb;border-radius:12px">'
            . '<div style="font-size:24px;font-weight:700;margin-bottom:12px;color:#0f172a;">' . htmlspecialchars($heading, ENT_QUOTES, 'UTF-8') . '</div>'
            . $contentHtml
            . '<div style="margin-top:24px;padding-top:14px;border-top:1px solid #e5e7eb;color:#6b7280;font-size:13px">Regards,<br>' . $portalName . '</div>'
            . '</div>';
    }
}


if (!function_exists('portalNormalizeNotificationCommandKey')) {
    function portalNormalizeNotificationCommandKey(string $value): string {
        $key = strtolower(trim($value));
        $key = preg_replace('/[^a-z0-9]+/', '_', $key) ?: '';
        $key = preg_replace('/_+/', '_', $key) ?: $key;
        $key = trim($key, '_');
        if ($key === '') return 'general';
        $aliases = [
            'application_submission' => 'application_submitted',
            'application_status_change' => 'application_status_update',
            'admission_granted' => 'admission_given',
            'payment_receipt' => 'payment_success',
            'registration_payment' => 'payment_success',
            'acceptance_payment' => 'payment_success',
            'hostel_payment' => 'payment_success',
            'school_payment' => 'payment_success',
            'wallet_credit' => 'wallet_credited',
            'wallet_debit' => 'wallet_debited',
            'wallet_update' => 'wallet_updated',
            'login_alert' => 'login_notification',
            'course_form_first_submit' => 'course_form_submitted',
        ];
        return $aliases[$key] ?? $key;
    }
}
if (!function_exists('portalNotificationCommandCatalog')) {
    function portalNotificationCommandCatalog(): array {
        return [
            'application_submitted' => 'Application Submitted',
            'application_form_final_submit' => 'Application Final Submission',
            'application_status_update' => 'Application Status Update',
            'admission_given' => 'Admission Given',
            'admission_offer_accepted' => 'Admission Offer Accepted',
            'admission_offer_rejected' => 'Admission Offer Rejected',
            'payment_success' => 'Payment Success',
            'wallet_credited' => 'Wallet Credited',
            'wallet_debited' => 'Wallet Debited',
            'wallet_updated' => 'Wallet Updated',
            'course_form_submitted' => 'Course Form Submitted',
            'login_notification' => 'Login Notification',
            'login_otp' => 'Login OTP',
            'reset_otp' => 'Reset OTP',
            'staff_account_created' => 'Staff Account Created',
            'admission_letter' => 'Admission Letter',
            'assigned_payment' => 'Assigned Payment',
            'id_card_generated' => 'ID Card Generated',
            'test_sms' => 'Test SMS',
            'general' => 'General',
        ];
    }
}
if (!function_exists('portalNotificationLegacyKeysByCanonical')) {
    function portalNotificationLegacyKeysByCanonical(): array {
        return [
            'application_submitted' => ['application_submission'],
            'application_status_update' => ['application_status_change'],
            'admission_given' => ['admission_granted'],
            'payment_success' => ['payment_receipt', 'registration_payment', 'acceptance_payment', 'hostel_payment', 'school_payment'],
            'wallet_credited' => ['wallet_credit'],
            'wallet_debited' => ['wallet_debit'],
            'wallet_updated' => ['wallet_update'],
            'login_notification' => ['login_alert'],
            'course_form_submitted' => ['course_form_first_submit'],
        ];
    }
}
if (!function_exists('portalNotificationCommandCandidates')) {
    function portalNotificationCommandCandidates(string $commandKey): array {
        $normalized = portalNormalizeNotificationCommandKey($commandKey);
        $keys = [];
        if ($normalized !== '') {
            $keys[] = $normalized;
        }
        $legacy = portalNotificationLegacyKeysByCanonical();
        foreach (($legacy[$normalized] ?? []) as $oldKey) {
            $keys[] = strtolower(trim($oldKey));
        }
        if ($normalized === '') {
            $keys[] = 'general';
        }
        return array_values(array_unique(array_filter($keys)));
    }
}
if (!function_exists('portalTemplateRowEnabled')) {
    function portalTemplateRowEnabled(array $row): bool {
        if (array_key_exists('is_enabled', $row) && $row['is_enabled'] !== null && $row['is_enabled'] !== '') {
            return (int)$row['is_enabled'] === 1;
        }
        if (array_key_exists('is_active', $row) && $row['is_active'] !== null && $row['is_active'] !== '') {
            return (int)$row['is_active'] === 1;
        }
        $status = strtolower(trim((string)($row['status'] ?? '')));
        if ($status !== '') {
            return in_array($status, ['1', 'enabled', 'active', 'yes', 'true', 'on'], true);
        }
        return true;
    }
}
if (!function_exists('portalTemplateRowCommandKey')) {
    function portalTemplateRowCommandKey(array $row): string {
        return portalNormalizeNotificationCommandKey((string)($row['command_key'] ?? $row['event_key'] ?? $row['template_key'] ?? ''));
    }
}
if (!function_exists('portalEnsureNotificationTemplateSchema')) {
    function portalEnsureNotificationTemplateSchema(PDO $pdo, string $channel): void {
        $channel = strtolower(trim($channel));
        $isSms = ($channel === 'sms');
        $table = $isSms ? 'sms_templates' : 'email_templates';

        try {
            if ($isSms) {
                $pdo->exec("CREATE TABLE IF NOT EXISTS sms_templates (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    template_key VARCHAR(80) NOT NULL UNIQUE,
                    command_key VARCHAR(120) NULL,
                    event_key VARCHAR(120) NULL,
                    template_name VARCHAR(120) NOT NULL,
                    message_text TEXT NOT NULL,
                    body TEXT NULL,
                    content LONGTEXT NULL,
                    is_active TINYINT(1) NOT NULL DEFAULT 1,
                    is_enabled TINYINT(1) NULL,
                    status VARCHAR(20) NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                    INDEX idx_sms_tpl_command (command_key),
                    INDEX idx_sms_tpl_event (event_key),
                    INDEX idx_sms_tpl_enabled (is_enabled, is_active)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
            } else {
                $pdo->exec("CREATE TABLE IF NOT EXISTS email_templates (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    template_key VARCHAR(80) NOT NULL UNIQUE,
                    command_key VARCHAR(120) NULL,
                    event_key VARCHAR(120) NULL,
                    template_name VARCHAR(120) NOT NULL,
                    subject VARCHAR(255) NOT NULL,
                    body_html MEDIUMTEXT NULL,
                    body MEDIUMTEXT NULL,
                    content LONGTEXT NULL,
                    is_active TINYINT(1) NOT NULL DEFAULT 1,
                    is_enabled TINYINT(1) NULL,
                    status VARCHAR(20) NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                    INDEX idx_email_tpl_command (command_key),
                    INDEX idx_email_tpl_event (event_key),
                    INDEX idx_email_tpl_enabled (is_enabled, is_active)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
            }
        } catch (Throwable $e) {}

        $columns = $isSms ? [
            'command_key' => "ALTER TABLE sms_templates ADD COLUMN command_key VARCHAR(120) NULL AFTER template_key",
            'event_key' => "ALTER TABLE sms_templates ADD COLUMN event_key VARCHAR(120) NULL AFTER command_key",
            'body' => "ALTER TABLE sms_templates ADD COLUMN body TEXT NULL AFTER message_text",
            'content' => "ALTER TABLE sms_templates ADD COLUMN content LONGTEXT NULL AFTER body",
            'is_enabled' => "ALTER TABLE sms_templates ADD COLUMN is_enabled TINYINT(1) NULL AFTER is_active",
            'status' => "ALTER TABLE sms_templates ADD COLUMN status VARCHAR(20) NULL AFTER is_enabled",
            'created_at' => "ALTER TABLE sms_templates ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
            'updated_at' => "ALTER TABLE sms_templates ADD COLUMN updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP",
        ] : [
            'command_key' => "ALTER TABLE email_templates ADD COLUMN command_key VARCHAR(120) NULL AFTER template_key",
            'event_key' => "ALTER TABLE email_templates ADD COLUMN event_key VARCHAR(120) NULL AFTER command_key",
            'body' => "ALTER TABLE email_templates ADD COLUMN body MEDIUMTEXT NULL AFTER body_html",
            'content' => "ALTER TABLE email_templates ADD COLUMN content LONGTEXT NULL AFTER body",
            'is_enabled' => "ALTER TABLE email_templates ADD COLUMN is_enabled TINYINT(1) NULL AFTER is_active",
            'status' => "ALTER TABLE email_templates ADD COLUMN status VARCHAR(20) NULL AFTER is_enabled",
            'created_at' => "ALTER TABLE email_templates ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
            'updated_at' => "ALTER TABLE email_templates ADD COLUMN updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP",
        ];
        foreach ($columns as $column => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, $table, $column)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) {}
        }

        $indexes = $isSms ? [
            'idx_sms_tpl_command' => "CREATE INDEX idx_sms_tpl_command ON sms_templates(command_key)",
            'idx_sms_tpl_event' => "CREATE INDEX idx_sms_tpl_event ON sms_templates(event_key)",
            'idx_sms_tpl_enabled' => "CREATE INDEX idx_sms_tpl_enabled ON sms_templates(is_enabled, is_active)",
        ] : [
            'idx_email_tpl_command' => "CREATE INDEX idx_email_tpl_command ON email_templates(command_key)",
            'idx_email_tpl_event' => "CREATE INDEX idx_email_tpl_event ON email_templates(event_key)",
            'idx_email_tpl_enabled' => "CREATE INDEX idx_email_tpl_enabled ON email_templates(is_enabled, is_active)",
        ];
        foreach ($indexes as $indexName => $sql) {
            try {
                if (!portalIndexExistsSafe($pdo, $table, $indexName)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) {}
        }

        try {
            if ($isSms) {
                $pdo->exec("UPDATE sms_templates
                    SET body = COALESCE(NULLIF(body, ''), NULLIF(message_text, '')),
                        content = COALESCE(NULLIF(content, ''), NULLIF(body, ''), NULLIF(message_text, '')),
                        is_enabled = COALESCE(is_enabled, is_active, 1),
                        is_active = COALESCE(is_active, is_enabled, 1),
                        status = COALESCE(NULLIF(TRIM(status), ''), CASE WHEN COALESCE(is_enabled, is_active, 1) = 1 THEN 'enabled' ELSE 'disabled' END)");
            } else {
                $pdo->exec("UPDATE email_templates
                    SET body = COALESCE(NULLIF(body, ''), NULLIF(body_html, '')),
                        content = COALESCE(NULLIF(content, ''), NULLIF(body, ''), NULLIF(body_html, '')),
                        is_enabled = COALESCE(is_enabled, is_active, 1),
                        is_active = COALESCE(is_active, is_enabled, 1),
                        status = COALESCE(NULLIF(TRIM(status), ''), CASE WHEN COALESCE(is_enabled, is_active, 1) = 1 THEN 'enabled' ELSE 'disabled' END)");
            }
        } catch (Throwable $e) {}

        try {
            $rows = $pdo->query("SELECT * FROM {$table} ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
            $update = $pdo->prepare("UPDATE {$table}
                SET command_key = ?, event_key = ?, is_enabled = ?, is_active = ?, status = ?, content = ?, body = ?
                WHERE id = ?");
            foreach ($rows as $row) {
                $id = (int)($row['id'] ?? 0);
                if ($id <= 0) continue;
                $rawKey = (string)($row['command_key'] ?? $row['event_key'] ?? $row['template_key'] ?? '');
                $commandKey = portalNormalizeNotificationCommandKey($rawKey);
                if ($commandKey === '') {
                    $commandKey = 'general';
                }
                $enabled = portalTemplateRowEnabled($row) ? 1 : 0;
                $status = $enabled === 1 ? 'enabled' : 'disabled';
                $body = $isSms
                    ? trim((string)($row['body'] ?? $row['message_text'] ?? ''))
                    : (string)($row['body'] ?? $row['body_html'] ?? '');
                $content = (string)($row['content'] ?? $body);
                $update->execute([
                    $commandKey,
                    $commandKey,
                    $enabled,
                    $enabled,
                    $status,
                    $content,
                    $body,
                    $id,
                ]);
            }
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalResolveNotificationTemplateDecision')) {
    function portalResolveNotificationTemplateDecision(PDO $pdo, string $channel, string $commandKey): array {
        $channel = strtolower(trim($channel)) === 'sms' ? 'sms' : 'email';
        portalEnsureNotificationTemplateSchema($pdo, $channel);
        $table = $channel === 'sms' ? 'sms_templates' : 'email_templates';
        $candidates = portalNotificationCommandCandidates($commandKey);
        if (!$candidates) {
            $candidates = ['general'];
        }

        try {
            $placeholders = implode(',', array_fill(0, count($candidates), '?'));
            $sql = "SELECT * FROM {$table}
                WHERE command_key IN ({$placeholders})
                   OR event_key IN ({$placeholders})
                   OR template_key IN ({$placeholders})
                ORDER BY COALESCE(updated_at, created_at) DESC, id DESC";
            $params = array_merge($candidates, $candidates, $candidates);
            $st = $pdo->prepare($sql);
            $st->execute($params);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [
                'channel' => $channel,
                'command_key' => portalNormalizeNotificationCommandKey($commandKey),
                'status' => 'error',
                'reason' => 'Template lookup failed.',
                'template' => null,
                'row' => null,
            ];
        }

        $best = null;
        $bestRank = 999999;
        foreach ($rows as $row) {
            $rowKeys = [];
            foreach (['command_key', 'event_key', 'template_key'] as $field) {
                $candidate = trim((string)($row[$field] ?? ''));
                if ($candidate !== '') {
                    $rowKeys[] = portalNormalizeNotificationCommandKey($candidate);
                    $rowKeys[] = strtolower(trim($candidate));
                }
            }
            $rowKeys = array_values(array_unique(array_filter($rowKeys)));
            foreach ($candidates as $rank => $candidateKey) {
                if (!in_array($candidateKey, $rowKeys, true)) {
                    continue;
                }
                if ($best === null || $rank < $bestRank) {
                    $best = $row;
                    $bestRank = $rank;
                }
                break;
            }
        }

        $normalizedCommand = portalNormalizeNotificationCommandKey($commandKey);
        if (!$best) {
            return [
                'channel' => $channel,
                'command_key' => $normalizedCommand !== '' ? $normalizedCommand : 'general',
                'status' => 'missing',
                'reason' => 'Template missing for command: ' . ($normalizedCommand !== '' ? $normalizedCommand : 'general'),
                'template' => null,
                'row' => null,
            ];
        }

        $enabled = portalTemplateRowEnabled($best);
        $resolvedKey = portalTemplateRowCommandKey($best);
        if (!$enabled) {
            return [
                'channel' => $channel,
                'command_key' => $resolvedKey !== '' ? $resolvedKey : ($normalizedCommand !== '' ? $normalizedCommand : 'general'),
                'status' => 'disabled',
                'reason' => 'Template disabled for command: ' . ($resolvedKey !== '' ? $resolvedKey : ($normalizedCommand !== '' ? $normalizedCommand : 'general')),
                'template' => null,
                'row' => $best,
            ];
        }

        $template = [
            'id' => (int)($best['id'] ?? 0),
            'channel' => $channel,
            'command_key' => $resolvedKey !== '' ? $resolvedKey : ($normalizedCommand !== '' ? $normalizedCommand : 'general'),
            'template_key' => (string)($best['template_key'] ?? ''),
            'template_name' => (string)($best['template_name'] ?? ''),
            'subject' => $channel === 'email' ? trim((string)($best['subject'] ?? '')) : '',
            'body' => $channel === 'email'
                ? (string)($best['body_html'] ?? $best['body'] ?? $best['content'] ?? '')
                : trim((string)($best['message_text'] ?? $best['body'] ?? $best['content'] ?? '')),
        ];

        return [
            'channel' => $channel,
            'command_key' => $template['command_key'],
            'status' => 'enabled',
            'reason' => 'Template found and enabled for command: ' . $template['command_key'],
            'template' => $template,
            'row' => $best,
        ];
    }
}
if (!function_exists('resolveNotificationTemplate')) {
    function resolveNotificationTemplate(PDO $pdo, string $channel, string $commandKey): ?array {
        $decision = portalResolveNotificationTemplateDecision($pdo, $channel, $commandKey);
        return ($decision['status'] ?? '') === 'enabled' ? ($decision['template'] ?? null) : null;
    }
}

if (!function_exists('portalSeedNotificationEmailTemplates')) {
    function portalSeedNotificationEmailTemplates(PDO $pdo): void {
        try {
            $portalName = portalNotificationMailSubjectPrefix($pdo);
            $defaults = [
                'admission_offer_accepted' => [
                    'Admission Offer Accepted',
                    'Admission Offer Accepted - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>Your admission offer has been accepted successfully.</p><ul><li><strong>Application Number:</strong> {{application_number}}</li><li><strong>Admission Number:</strong> {{admission_number}}</li><li><strong>Session:</strong> {{session}}</li></ul><p><strong>Next Steps:</strong> {{next_steps}}</p>'
                ],
                'login_notification' => [
                    'Login Notification',
                    'New Login Notification - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>A new login was recorded on your student portal account.</p><ul><li><strong>Login Date/Time:</strong> {{login_datetime}}</li><li><strong>Login Method:</strong> {{login_method}}</li><li><strong>IP Address:</strong> {{ip_address}}</li><li><strong>Device Info:</strong> {{device_info}}</li></ul><p>If this login was not initiated by you, please change your password immediately and contact the portal administrator.</p>'
                ],
                'payment_receipt' => [
                    'Payment Receipt',
                    'Payment Receipt - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>Your payment has been received successfully.</p><ul><li><strong>Payment Item:</strong> {{payment_item}}</li><li><strong>Amount:</strong> {{amount}}</li><li><strong>Payment Reference:</strong> {{payment_reference}}</li><li><strong>Payment Date:</strong> {{payment_date}}</li><li><strong>Session:</strong> {{session}}</li><li><strong>Semester:</strong> {{semester}}</li></ul>{{receipt_link_html}}'
                ],
                'application_status_update' => [
                    'Application Status Update',
                    'Application Status Updated - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>Your application status has changed.</p><ul><li><strong>Application Number:</strong> {{application_number}}</li><li><strong>Old Status:</strong> {{old_status}}</li><li><strong>New Status:</strong> {{new_status}}</li><li><strong>Remarks:</strong> {{remarks}}</li></ul>'
                ],
                'staff_account_created' => [
                    'Staff Account Created',
                    'Staff Account Details - ' . $portalName,
                    '<p>Dear {{staff_full_name}},</p><p>Your portal account has been created successfully.</p><ul><li><strong>Username:</strong> {{username}}</li><li><strong>Email:</strong> {{email}}</li><li><strong>Role:</strong> {{role}}</li><li><strong>Assigned Faculty:</strong> {{faculty}}</li><li><strong>Assigned Department:</strong> {{department}}</li><li><strong>Login URL:</strong> <a href="{{login_url}}">{{login_url}}</a></li><li><strong>Temporary Password:</strong> {{temporary_password}}</li></ul><p>{{password_reset_note}}</p>'
                ],
                'admission_offer_rejected' => [
                    'Admission Offer Rejected',
                    'Admission Offer Rejected - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>Your admission offer decision has been recorded as <strong>Rejected</strong>.</p>'
                ],
                'application_form_final_submit' => [
                    'Application Final Submission',
                    'Application Locked (Final Submission) - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>Your application has been <strong>finally submitted</strong> and locked.</p>{{application_form_link_html}}'
                ],
                'wallet_updated' => [
                    'Wallet Updated',
                    'Wallet Updated - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>{{message}}</p>'
                ],
                'admission_letter' => [
                    'Admission Letter Ready',
                    'Admission Letter Ready - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>Your admission letter is now available on the portal.</p>{{admission_letter_link_html}}'
                ],
                'assigned_payment' => [
                    'Assigned Payment',
                    'Assigned Payment - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>{{message}}</p>'
                ],
                'id_card_generated' => [
                    'ID Card Generated',
                    'ID Card Generated - ' . $portalName,
                    '<p>Dear {{student_full_name}},</p><p>Your ID card has been generated successfully.</p>{{id_card_link_html}}'
                ],
            ];
            foreach ($defaults as $key => $row) {
                $st = $pdo->prepare("INSERT IGNORE INTO email_templates (template_key, template_name, subject, body_html, is_active) VALUES (?, ?, ?, ?, 1)");
                $st->execute([$key, $row[0], $row[1], $row[2]]);
            }
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalNotificationTemplateReplace')) {
    function portalNotificationTemplateReplace(string $template, array $vars): string {
        foreach ($vars as $key => $value) {
            $template = str_replace('{{' . $key . '}}', (string)$value, $template);
        }
        return preg_replace('/\{\{[a-zA-Z0-9_]+\}\}/', '', $template) ?? $template;
    }
}
if (!function_exists('portalNotificationTemplatePayload')) {
    function portalNotificationTemplatePayload(PDO $pdo, string $templateKey, string $fallbackHeading, string $fallbackSubject, string $fallbackBodyHtml, array $vars): array {
        portalSeedNotificationEmailTemplates($pdo);
        $commandKey = portalNormalizeNotificationCommandKey($templateKey);
        $subject = $fallbackSubject;
        $bodyHtml = $fallbackBodyHtml;
        try {
            $resolved = resolveNotificationTemplate($pdo, 'email', $commandKey);
            if (is_array($resolved)) {
                $tplSubject = trim((string)($resolved['subject'] ?? ''));
                $tplBody = (string)($resolved['body'] ?? '');
                if ($tplSubject !== '') {
                    $subject = portalNotificationTemplateReplace($tplSubject, $vars);
                }
                if (trim($tplBody) !== '') {
                    $bodyHtml = portalNotificationTemplateReplace($tplBody, $vars);
                }
            }
        } catch (Throwable $e) {}
        return [
            'subject' => $subject,
            'body' => portalWrapNotificationEmail($pdo, $fallbackHeading, $bodyHtml),
        ];
    }
}

if (!function_exists('portalSmsTemplateKeyCandidates')) {
    function portalSmsTemplateKeyCandidates(string $eventType, string $context = ''): array {
        $keys = [];
        $eventType = strtolower(trim($eventType));
        $contextEvent = strtolower(trim(portalSmsEventTypeFromContext($context)));
        $contextPrefix = strtolower(trim((string)strtok($context, ':')));
        foreach ([$eventType, $contextEvent, $contextPrefix] as $key) {
            if ($key !== '') $keys[] = $key;
        }
        $aliases = [
            'application_status_change' => 'application_status_update',
            'login_alert' => 'login_notification',
        ];
        foreach ($keys as $key) {
            if (isset($aliases[$key])) $keys[] = $aliases[$key];
        }
        $keys[] = 'general';
        return array_values(array_unique(array_filter($keys)));
    }
}
if (!function_exists('portalNotificationSmsPayload')) {
    function portalNotificationSmsPayload(PDO $pdo, string $eventType, string $context, string $fallbackMessage, array $vars = []): string {
        $fallbackMessage = trim((string)$fallbackMessage);
        try {
            $candidates = portalSmsTemplateKeyCandidates($eventType, $context);
            foreach ($candidates as $candidate) {
                $resolved = resolveNotificationTemplate($pdo, 'sms', $candidate);
                if (!is_array($resolved)) {
                    continue;
                }
                $tplMessage = trim((string)($resolved['body'] ?? ''));
                if ($tplMessage === '') {
                    continue;
                }
                $compiled = portalNotificationTemplateReplace($tplMessage, $vars + [
                    'message' => $fallbackMessage,
                    'event_type' => portalNormalizeNotificationCommandKey($eventType),
                    'context' => $context,
                    'portal_name' => portalNotificationMailSubjectPrefix($pdo),
                ]);
                $compiled = preg_replace('/[ \t]+/u', ' ', $compiled) ?: $compiled;
                $compiled = preg_replace('/\n{3,}/u', "\n\n", $compiled) ?: $compiled;
                $compiled = trim($compiled);
                if ($compiled !== '') {
                    return $compiled;
                }
            }
        } catch (Throwable $e) {}
        return $fallbackMessage;
    }
}

if (!function_exists('portalSendStaffAccountCreatedEmail')) {
    function portalSendStaffAccountCreatedEmail(PDO $pdo, array $staff, string $plainPassword = ''): bool {
        $email = trim((string)($staff['email'] ?? ''));
        if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
        $staffNameRaw = trim((string)($staff['full_name'] ?? $staff['username'] ?? 'Staff User'));
        $staffName = htmlspecialchars($staffNameRaw, ENT_QUOTES, 'UTF-8');
        $role = htmlspecialchars((string)($staff['role'] ?? 'Staff'), ENT_QUOTES, 'UTF-8');
        $faculty = trim((string)($staff['faculty_name'] ?? ''));
        $department = trim((string)($staff['department_name'] ?? ''));
        $loginUrl = appBaseUrl() . '/index.php?page=admin_login';
        $temporaryPassword = trim($plainPassword) !== '' ? trim($plainPassword) : 'the temporary password generated by the portal';
        $passwordResetNote = 'For security, please change your password after your first login.';
        $payload = portalNotificationTemplatePayload(
            $pdo,
            'staff_account_created',
            'Portal Account Created',
            'Staff Account Details - ' . portalNotificationMailSubjectPrefix($pdo),
            '<p>Dear ' . $staffName . ',</p>'
            . '<p>Your portal account has been created successfully.</p>'
            . '<ul>'
            . '<li><strong>Username:</strong> ' . htmlspecialchars((string)($staff['username'] ?? ''), ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>Email:</strong> ' . htmlspecialchars($email, ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>Role:</strong> ' . $role . '</li>'
            . '<li><strong>Assigned Faculty:</strong> ' . htmlspecialchars($faculty !== '' ? $faculty : 'Not assigned', ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>Assigned Department:</strong> ' . htmlspecialchars($department !== '' ? $department : 'Not assigned', ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>Login URL:</strong> <a href="' . htmlspecialchars($loginUrl, ENT_QUOTES, 'UTF-8') . '">' . htmlspecialchars($loginUrl, ENT_QUOTES, 'UTF-8') . '</a></li>'
            . '<li><strong>Temporary Password:</strong> ' . htmlspecialchars($temporaryPassword, ENT_QUOTES, 'UTF-8') . '</li>'
            . '</ul>'
            . '<p>' . htmlspecialchars($passwordResetNote, ENT_QUOTES, 'UTF-8') . '</p>',
            [
                'staff_full_name' => $staffName,
                'username' => htmlspecialchars((string)($staff['username'] ?? ''), ENT_QUOTES, 'UTF-8'),
                'email' => htmlspecialchars($email, ENT_QUOTES, 'UTF-8'),
                'role' => $role,
                'faculty' => htmlspecialchars($faculty !== '' ? $faculty : 'Not assigned', ENT_QUOTES, 'UTF-8'),
                'department' => htmlspecialchars($department !== '' ? $department : 'Not assigned', ENT_QUOTES, 'UTF-8'),
                'login_url' => htmlspecialchars($loginUrl, ENT_QUOTES, 'UTF-8'),
                'temporary_password' => htmlspecialchars($temporaryPassword, ENT_QUOTES, 'UTF-8'),
                'password_reset_note' => htmlspecialchars($passwordResetNote, ENT_QUOTES, 'UTF-8'),
            ]
        );
        $context = 'staff_account_created:' . (int)($staff['id'] ?? 0);
        return sendLoggedEmail($pdo, 'admin', (int)($staff['id'] ?? 0), $email, $context, $payload['subject'], $payload['body']);
    }
}

if (!function_exists('portalSendStudentLoginAlert')) {
    function portalSendStudentLoginAlert(PDO $pdo, array $student, string $method = 'password'): bool {
        $email = trim((string)($student['email'] ?? ''));
        if ($email === '') return false;
        $studentName = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
        $loginTime = date('d M Y, h:i A');
        $ip = trim((string)($_SERVER['REMOTE_ADDR'] ?? 'Unknown'));
        $device = trim((string)($_SERVER['HTTP_USER_AGENT'] ?? 'Unknown device'));
        $payload = portalNotificationTemplatePayload(
            $pdo,
            'login_notification',
            'New Login Notification',
            'New Login Notification - ' . portalNotificationMailSubjectPrefix($pdo),
            '<p>Dear ' . $studentName . ',</p>'
            . '<p>A new login was recorded on your student portal account.</p>'
            . '<ul>'
            . '<li><strong>Login Date/Time:</strong> ' . htmlspecialchars($loginTime, ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>Login Method:</strong> ' . htmlspecialchars(ucfirst($method), ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>IP Address:</strong> ' . htmlspecialchars($ip, ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>Device Info:</strong> ' . htmlspecialchars($device, ENT_QUOTES, 'UTF-8') . '</li>'
            . '</ul>'
            . '<p>If this login was not initiated by you, please change your password immediately and contact the portal administrator.</p>',
            [
                'student_full_name' => $studentName,
                'login_datetime' => htmlspecialchars($loginTime, ENT_QUOTES, 'UTF-8'),
                'login_method' => htmlspecialchars(ucfirst($method), ENT_QUOTES, 'UTF-8'),
                'ip_address' => htmlspecialchars($ip, ENT_QUOTES, 'UTF-8'),
                'device_info' => htmlspecialchars($device, ENT_QUOTES, 'UTF-8'),
            ]
        );
        $ctx = 'login_alert:' . strtolower($method) . ':' . date('YmdHis') . ':' . substr(md5($email . $ip . $device . microtime(true)), 0, 12);
        return sendLoggedEmail($pdo, 'student', (int)($student['id'] ?? 0), $email, $ctx, $payload['subject'], $payload['body']);
    }
}
if (!function_exists('portalSendStudentAdmissionOfferAcceptedEmail')) {
    function portalSendStudentAdmissionOfferAcceptedEmail(PDO $pdo, array $student): bool {
        $email = trim((string)($student['email'] ?? ''));
        if ($email === '') return false;
        $studentName = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
        $applicationNo = htmlspecialchars(portalStudentNumberDisplay($student, 'application'), ENT_QUOTES, 'UTF-8');
        $admissionNo = htmlspecialchars(portalStudentNumberDisplay($student, 'admission'), ENT_QUOTES, 'UTF-8');
        $session = htmlspecialchars((string)($student['session'] ?? $student['session_name'] ?? portalCurrentAcademicSessionName($pdo)), ENT_QUOTES, 'UTF-8');
        $nextSteps = 'Complete any pending school/clearance requirements and continue from your dashboard.';
        $payload = portalNotificationTemplatePayload(
            $pdo,
            'admission_offer_accepted',
            'Admission Offer Accepted',
            'Admission Offer Accepted - ' . portalNotificationMailSubjectPrefix($pdo),
            '<p>Dear ' . $studentName . ',</p>'
            . '<p>Your admission offer has been accepted successfully.</p>'
            . '<ul>'
            . '<li><strong>Application Number:</strong> ' . $applicationNo . '</li>'
            . '<li><strong>Admission Number:</strong> ' . $admissionNo . '</li>'
            . '<li><strong>Session:</strong> ' . $session . '</li>'
            . '</ul>'
            . '<p><strong>Next Steps:</strong> ' . htmlspecialchars($nextSteps, ENT_QUOTES, 'UTF-8') . '</p>',
            [
                'student_full_name' => $studentName,
                'application_number' => $applicationNo,
                'admission_number' => $admissionNo,
                'session' => $session,
                'next_steps' => htmlspecialchars($nextSteps, ENT_QUOTES, 'UTF-8'),
            ]
        );
        return sendLoggedEmail($pdo, 'student', (int)($student['id'] ?? 0), $email, 'admission_offer_accepted', $payload['subject'], $payload['body']);
    }
}
if (!function_exists('portalSendStudentReceiptNotification')) {
    function portalSendStudentReceiptNotification(PDO $pdo, array $student, array $transaction, string $receiptLink = ''): bool {
        $email = trim((string)($student['email'] ?? ''));
        if ($email === '') return false;
        $studentName = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
        $paymentItem = htmlspecialchars((string)($transaction['transaction_type'] ?? 'Payment'), ENT_QUOTES, 'UTF-8');
        $amount = number_format((float)($transaction['approved_amount'] ?? $transaction['amount_paid'] ?? $transaction['amount'] ?? 0), 2);
        $reference = htmlspecialchars((string)($transaction['reference'] ?? ''), ENT_QUOTES, 'UTF-8');
        $paymentDate = htmlspecialchars(formatPaymentDate($pdo, $transaction), ENT_QUOTES, 'UTF-8');
        $session = htmlspecialchars((string)($transaction['academic_session'] ?? $student['session'] ?? ''), ENT_QUOTES, 'UTF-8');
        $semester = htmlspecialchars((string)($transaction['semester'] ?? ''), ENT_QUOTES, 'UTF-8');
        $extraLink = $receiptLink !== '' ? '<p><a href="' . htmlspecialchars($receiptLink, ENT_QUOTES, 'UTF-8') . '">View Receipt</a></p>' : '';
        $payload = portalNotificationTemplatePayload(
            $pdo,
            'payment_receipt',
            'Payment Receipt',
            'Payment Receipt - ' . portalNotificationMailSubjectPrefix($pdo),
            '<p>Dear ' . $studentName . ',</p>'
            . '<p>Your payment has been received successfully.</p>'
            . '<ul>'
            . '<li><strong>Payment Item:</strong> ' . $paymentItem . '</li>'
            . '<li><strong>Amount:</strong> ₦' . $amount . '</li>'
            . '<li><strong>Payment Reference:</strong> ' . $reference . '</li>'
            . '<li><strong>Payment Date:</strong> ' . $paymentDate . '</li>'
            . ($session !== '' ? '<li><strong>Session:</strong> ' . $session . '</li>' : '')
            . ($semester !== '' ? '<li><strong>Semester:</strong> ' . $semester . '</li>' : '')
            . '</ul>'
            . $extraLink,
            [
                'student_full_name' => $studentName,
                'payment_item' => $paymentItem,
                'amount' => '₦' . $amount,
                'payment_reference' => $reference,
                'payment_date' => $paymentDate,
                'session' => $session,
                'semester' => $semester,
                'receipt_link_html' => $extraLink,
            ]
        );
        $context = 'receipt:' . (!empty($transaction['id']) ? (int)$transaction['id'] : md5($email . $reference . $paymentItem));
        return sendLoggedEmail($pdo, 'student', (int)($student['id'] ?? 0), $email, $context, $payload['subject'], $payload['body']);
    }
}
if (!function_exists('portalSendStudentApplicationStatusNotification')) {
    function portalSendStudentApplicationStatusNotification(PDO $pdo, array $student, string $oldStatus, string $newStatus, string $remarks = ''): bool {
        $email = trim((string)($student['email'] ?? ''));
        if ($email === '') return false;
        $studentName = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
        $applicationNo = htmlspecialchars(portalStudentNumberDisplay($student, 'application'), ENT_QUOTES, 'UTF-8');
        $payload = portalNotificationTemplatePayload(
            $pdo,
            'application_status_update',
            'Application Status Updated',
            'Application Status Updated - ' . portalNotificationMailSubjectPrefix($pdo),
            '<p>Dear ' . $studentName . ',</p>'
            . '<p>Your application status has changed.</p>'
            . '<ul>'
            . '<li><strong>Application Number:</strong> ' . $applicationNo . '</li>'
            . '<li><strong>Old Status:</strong> ' . htmlspecialchars($oldStatus !== '' ? $oldStatus : 'N/A', ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>New Status:</strong> ' . htmlspecialchars($newStatus, ENT_QUOTES, 'UTF-8') . '</li>'
            . ($remarks !== '' ? '<li><strong>Remarks:</strong> ' . nl2br(htmlspecialchars($remarks, ENT_QUOTES, 'UTF-8')) . '</li>' : '')
            . '</ul>',
            [
                'student_full_name' => $studentName,
                'application_number' => $applicationNo,
                'old_status' => htmlspecialchars($oldStatus !== '' ? $oldStatus : 'N/A', ENT_QUOTES, 'UTF-8'),
                'new_status' => htmlspecialchars($newStatus, ENT_QUOTES, 'UTF-8'),
                'remarks' => ($remarks !== '' ? nl2br(htmlspecialchars($remarks, ENT_QUOTES, 'UTF-8')) : 'Nil'),
            ]
        );
        $context = 'application_status_update:' . (int)($student['id'] ?? 0) . ':' . strtolower(trim($oldStatus)) . ':' . strtolower(trim($newStatus));
        return sendLoggedEmail($pdo, 'student', (int)($student['id'] ?? 0), $email, $context, $payload['subject'], $payload['body']);
    }
}



if (!function_exists('portalSendStudentApplicationSubmittedEmail')) {
    function portalSendStudentApplicationSubmittedEmail(PDO $pdo, array $student): bool {
        $email = trim((string)($student['email'] ?? ''));
        if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
        $studentName = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
        $applicationNo = htmlspecialchars(portalStudentNumberDisplay($student, 'application'), ENT_QUOTES, 'UTF-8');
        $school = htmlspecialchars((string)($student['school_name'] ?? $student['faculty_name'] ?? $student['school'] ?? 'Not Set'), ENT_QUOTES, 'UTF-8');
        $course = htmlspecialchars((string)($student['course_of_study'] ?? $student['course_in_view'] ?? 'Not Set'), ENT_QUOTES, 'UTF-8');
        $programme = htmlspecialchars((string)($student['programme_name_cache'] ?? $student['programme_name'] ?? $student['programme'] ?? 'Not Set'), ENT_QUOTES, 'UTF-8');
        $payload = portalNotificationTemplatePayload(
            $pdo,
            'application_submission',
            'Application Submitted Successfully',
            'Application Submitted - ' . portalNotificationMailSubjectPrefix($pdo),
            '<p>Dear ' . $studentName . ',</p>'
            . '<p>Your application has been submitted successfully and your record has been created on the portal.</p>'
            . '<ul>'
            . '<li><strong>Application Number:</strong> ' . $applicationNo . '</li>'
            . '<li><strong>School:</strong> ' . $school . '</li>'
            . '<li><strong>Course of Study:</strong> ' . $course . '</li>'
            . '<li><strong>Programme:</strong> ' . $programme . '</li>'
            . '</ul>'
            . '<p>Please continue from your dashboard to complete pending payments and other requirements.</p>',
            [
                'student_full_name' => $studentName,
                'application_number' => $applicationNo,
                'school' => $school,
                'course_of_study' => $course,
                'programme' => $programme,
            ]
        );
        $context = 'application_submission:' . (int)($student['id'] ?? 0);
        return sendLoggedEmail($pdo, 'student', (int)($student['id'] ?? 0), $email, $context, $payload['subject'], $payload['body']);
    }
}
if (!function_exists('portalSendStudentAdmissionGrantedEmail')) {
    function portalSendStudentAdmissionGrantedEmail(PDO $pdo, array $student, string $status = 'Approved', string $remarks = ''): bool {
        $email = trim((string)($student['email'] ?? ''));
        if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
        $studentName = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
        $applicationNo = htmlspecialchars(portalStudentNumberDisplay($student, 'application'), ENT_QUOTES, 'UTF-8');
        $admissionNo = htmlspecialchars(portalStudentNumberDisplay($student, 'admission'), ENT_QUOTES, 'UTF-8');
        $session = htmlspecialchars((string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)), ENT_QUOTES, 'UTF-8');
        $payload = portalNotificationTemplatePayload(
            $pdo,
            'admission_granted',
            'Admission Granted',
            'Admission Granted - ' . portalNotificationMailSubjectPrefix($pdo),
            '<p>Dear ' . $studentName . ',</p>'
            . '<p>Your application has been processed successfully and admission has been granted.</p>'
            . '<ul>'
            . '<li><strong>Application Number:</strong> ' . $applicationNo . '</li>'
            . '<li><strong>Admission Number:</strong> ' . $admissionNo . '</li>'
            . '<li><strong>Session:</strong> ' . $session . '</li>'
            . '<li><strong>Status:</strong> ' . htmlspecialchars($status, ENT_QUOTES, 'UTF-8') . '</li>'
            . ($remarks !== '' ? '<li><strong>Remarks:</strong> ' . nl2br(htmlspecialchars($remarks, ENT_QUOTES, 'UTF-8')) . '</li>' : '')
            . '</ul>'
            . '<p>Please login to your portal to review and continue with the next admission steps.</p>',
            [
                'student_full_name' => $studentName,
                'application_number' => $applicationNo,
                'admission_number' => $admissionNo,
                'session' => $session,
                'status' => htmlspecialchars($status, ENT_QUOTES, 'UTF-8'),
                'remarks' => ($remarks !== '' ? nl2br(htmlspecialchars($remarks, ENT_QUOTES, 'UTF-8')) : 'Nil'),
            ]
        );
        $context = 'admission_granted:' . (int)($student['id'] ?? 0) . ':' . strtolower(trim($status));
        return sendLoggedEmail($pdo, 'student', (int)($student['id'] ?? 0), $email, $context, $payload['subject'], $payload['body']);
    }
}
if (!function_exists('portalSendStudentCourseFormSubmittedEmail')) {
    function portalSendStudentCourseFormSubmittedEmail(PDO $pdo, array $student, array $registration, array $courses = []): bool {
        $email = trim((string)($student['email'] ?? ''));
        if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
        $studentName = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
        $session = htmlspecialchars((string)($registration['session_name'] ?? $student['session'] ?? portalCurrentAcademicSessionName($pdo)), ENT_QUOTES, 'UTF-8');
        $semester = htmlspecialchars((string)($registration['semester'] ?? $student['semester'] ?? 'First'), ENT_QUOTES, 'UTF-8');
        $level = htmlspecialchars((string)($registration['level'] ?? $student['level'] ?? 'Not Set'), ENT_QUOTES, 'UTF-8');
        $courseCount = count($courses);
        $totalUnits = (int)($registration['total_units'] ?? 0);
        $previewLink = appBaseUrl() . '/index.php?page=student_course_registration&tab=print';
        $payload = portalNotificationTemplatePayload(
            $pdo,
            'course_form_submitted',
            'Course Form Submitted',
            'Course Form Submitted - ' . portalNotificationMailSubjectPrefix($pdo),
            '<p>Dear ' . $studentName . ',</p>'
            . '<p>Your course registration form has been submitted successfully for the first time.</p>'
            . '<ul>'
            . '<li><strong>Session:</strong> ' . $session . '</li>'
            . '<li><strong>Semester:</strong> ' . $semester . '</li>'
            . '<li><strong>Level:</strong> ' . $level . '</li>'
            . '<li><strong>Selected Courses:</strong> ' . $courseCount . '</li>'
            . '<li><strong>Total Credit Units:</strong> ' . $totalUnits . '</li>'
            . '</ul>'
            . '<p><a href="' . htmlspecialchars($previewLink, ENT_QUOTES, 'UTF-8') . '">Open Course Registration</a></p>',
            [
                'student_full_name' => $studentName,
                'session' => $session,
                'semester' => $semester,
                'level' => $level,
                'course_count' => (string)$courseCount,
                'total_units' => (string)$totalUnits,
            ]
        );
        $context = 'course_form_first_submit:' . (int)($registration['id'] ?? 0);
        return sendLoggedEmail($pdo, 'student', (int)($student['id'] ?? 0), $email, $context, $payload['subject'], $payload['body']);
    }
}


// -------------------------
// OTP login helpers (email OTP)
// -------------------------
function createLoginOtp(PDO $pdo, string $userType, int $userId, string $email): string {
    // invalidate old OTPs
    try {
        $pdo->prepare("UPDATE otp_logins SET consumed=1 WHERE user_type=? AND user_id=? AND consumed=0")->execute([$userType, $userId]);
    } catch (Throwable $e) {}

    $otp = (string)random_int(100000, 999999);
    $hash = password_hash($otp, PASSWORD_DEFAULT);
    $expires = date('Y-m-d H:i:s', time() + 600); // 10 minutes
    $stmt = $pdo->prepare("INSERT INTO otp_logins (user_type, user_id, email, otp_hash, expires_at) VALUES (?,?,?,?,?)");
    $stmt->execute([$userType, $userId, $email, $hash, $expires]);
    return $otp;
}

function findActiveOtp(PDO $pdo, string $userType, int $userId): ?array {
    $stmt = $pdo->prepare("SELECT * FROM otp_logins WHERE user_type=? AND user_id=? AND consumed=0 AND expires_at >= NOW() ORDER BY id DESC LIMIT 1");
    $stmt->execute([$userType, $userId]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    return $row ?: null;
}

function verifyLoginOtp(PDO $pdo, int $otpRowId, string $otpInput): bool {
    $stmt = $pdo->prepare("SELECT * FROM otp_logins WHERE id=? LIMIT 1");
    $stmt->execute([$otpRowId]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$row) return false;
    if ((int)$row['consumed'] === 1) return false;
    if (strtotime((string)$row['expires_at']) < time()) return false;
    if ((int)$row['attempts'] >= 5) return false;

    $ok = password_verify($otpInput, (string)$row['otp_hash']);
    try {
        if ($ok) {
            $pdo->prepare("UPDATE otp_logins SET consumed=1 WHERE id=?")->execute([$otpRowId]);
        } else {
            $pdo->prepare("UPDATE otp_logins SET attempts = attempts + 1 WHERE id=?")->execute([$otpRowId]);
        }
    } catch (Throwable $e) {}
    return $ok;
}


// Build absolute base URL (best effort)
function appBaseUrl(): string {
    if (defined('APP_BASE_URL') && APP_BASE_URL) { return rtrim(APP_BASE_URL, '/'); }
    $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
    $scheme = $https ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $script = $_SERVER['SCRIPT_NAME'] ?? '';
    $dir = rtrim(str_replace('\\', '/', dirname($script)), '/');
    return $scheme . '://' . $host . ($dir ? $dir : '');
}

function createPasswordResetToken(PDO $pdo, string $userType, int $userId, string $email): string {
    $token = bin2hex(random_bytes(32));
    $hash = hash('sha256', $token);
    $expiresAt = date('Y-m-d H:i:s', time() + 3600); // 1 hour
    $stmt = $pdo->prepare("INSERT INTO password_resets (user_type, user_id, email, token_hash, expires_at) VALUES (?,?,?,?,?)");
    $stmt->execute([$userType, $userId, $email, $hash, $expiresAt]);
    return $token;
}

function findValidPasswordReset(PDO $pdo, string $token): ?array {
    $hash = hash('sha256', $token);
    $stmt = $pdo->prepare("SELECT * FROM password_resets WHERE token_hash = ? AND used_at IS NULL AND expires_at >= NOW() ORDER BY id DESC LIMIT 1");
    $stmt->execute([$hash]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    return $row ?: null;
}

function markPasswordResetUsed(PDO $pdo, int $id): void {
    $stmt = $pdo->prepare("UPDATE password_resets SET used_at = NOW() WHERE id = ?");
    $stmt->execute([$id]);
}

function updateUserPassword(PDO $pdo, string $userType, int $userId, string $newPassword): void {
    $hash = password_hash($newPassword, PASSWORD_DEFAULT);
    if ($userType === 'admin') {
        $stmt = $pdo->prepare("UPDATE admins SET password = ? WHERE id = ?");
        $stmt->execute([$hash, $userId]);
    } else {
        $stmt = $pdo->prepare("UPDATE students SET password = ? WHERE id = ?");
        $stmt->execute([$hash, $userId]);
    }
}


if (!function_exists('portalStripeCreateCheckoutSession')) {
    function portalStripeCreateCheckoutSession(PDO $pdo, array $args): array {
        $secret = portalGatewayConfigValue($pdo, 'stripe', 'secret_key', '');
        if ($secret === '') return ['ok' => false, 'message' => 'Stripe secret key is missing.'];
        $url = portalGatewayApiBaseUrl($pdo, 'stripe') . '/v1/checkout/sessions';
        $payload = [
            'mode' => 'payment',
            'success_url' => (string)$args['success_url'],
            'cancel_url' => (string)$args['cancel_url'],
            'client_reference_id' => (string)$args['reference'],
            'customer_email' => (string)$args['customer_email'],
            'metadata[reference]' => (string)$args['reference'],
            'metadata[payment_type]' => (string)($args['payment_type'] ?? ''),
            'metadata[transaction_id]' => (string)($args['transaction_id'] ?? ''),
            'line_items[0][price_data][currency]' => strtolower((string)$args['currency']),
            'line_items[0][price_data][unit_amount]' => (int)round(((float)$args['amount']) * 100),
            'line_items[0][price_data][product_data][name]' => (string)$args['description'],
            'line_items[0][quantity]' => 1,
        ];
        $res = portalGatewayHttpRequest('POST', $url, ['Authorization: Bearer ' . $secret, 'Content-Type: application/x-www-form-urlencoded'], $payload, true);
        if (!$res['ok'] || !is_array($res['json'])) {
            return ['ok' => false, 'message' => is_array($res['json']) ? (string)($res['json']['error']['message'] ?? 'Stripe initialization failed.') : 'Stripe initialization failed.'];
        }
        return ['ok' => true, 'data' => $res['json']];
    }
}

if (!function_exists('verifyStripePayment')) {
    function verifyStripePayment(PDO $pdo, string $reference, string $sessionId): array|false {
        if ($sessionId === '') return false;
        $secret = portalGatewayConfigValue($pdo, 'stripe', 'secret_key', '');
        if ($secret === '') return false;
        $url = portalGatewayApiBaseUrl($pdo, 'stripe') . '/v1/checkout/sessions/' . rawurlencode($sessionId);
        $res = portalGatewayHttpRequest('GET', $url, ['Authorization: Bearer ' . $secret], null, false);
        if (!$res['ok'] || !is_array($res['json'])) return false;
        $body = $res['json'];
        $paid = strtolower((string)($body['payment_status'] ?? '')) === 'paid';
        $matchedReference = (string)($body['client_reference_id'] ?? ($body['metadata']['reference'] ?? ''));
        if (!$paid || ($matchedReference !== '' && $matchedReference !== $reference)) return false;
        $pending = $_SESSION['pending_payments'][$reference] ?? [];
        return [
            'data' => [
                'status' => 'success',
                'reference' => $reference,
                'amount' => (int)($body['amount_total'] ?? round(((float)($pending['charged_amount'] ?? $pending['amount'] ?? 0)) * 100)),
                'paid_at' => date('c', (int)($body['created'] ?? time())),
                'metadata' => [
                    'student_id' => (int)($pending['student_id'] ?? ($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0)),
                    'transaction_id' => (string)($pending['transaction_id'] ?? ''),
                    'base_amount' => (float)($pending['base_amount'] ?? 0),
                    'gateway_fee' => (float)($pending['gateway_fee'] ?? 0),
                    'charged_amount' => (float)($pending['charged_amount'] ?? $pending['amount'] ?? 0),
                    'installment_number' => (int)($pending['installment_number'] ?? 1),
                    'total_installments' => (int)($pending['total_installments'] ?? 1),
                    'academic_session' => (string)($pending['academic_session'] ?? ''),
                    'semester' => (string)($pending['semester'] ?? ''),
                ],
            ],
        ];
    }
}

if (!function_exists('portalPayPalAccessToken')) {
    function portalPayPalAccessToken(PDO $pdo): array {
        $clientId = portalGatewayConfigValue($pdo, 'paypal', 'client_id', '');
        $clientSecret = portalGatewayConfigValue($pdo, 'paypal', 'client_secret', '');
        if ($clientId === '' || $clientSecret === '') return ['ok' => false, 'message' => 'PayPal client credentials are missing.'];
        $url = portalGatewayApiBaseUrl($pdo, 'paypal') . '/v1/oauth2/token';
        $headers = ['Authorization: Basic ' . base64_encode($clientId . ':' . $clientSecret), 'Content-Type: application/x-www-form-urlencoded'];
        $res = portalGatewayHttpRequest('POST', $url, $headers, ['grant_type' => 'client_credentials'], true);
        if (!$res['ok'] || !is_array($res['json']) || empty($res['json']['access_token'])) {
            return ['ok' => false, 'message' => 'Unable to obtain PayPal access token.'];
        }
        return ['ok' => true, 'token' => (string)$res['json']['access_token']];
    }
}

if (!function_exists('portalPayPalCreateOrder')) {
    function portalPayPalCreateOrder(PDO $pdo, array $args): array {
        $token = portalPayPalAccessToken($pdo);
        if (!$token['ok']) return $token;
        $url = portalGatewayApiBaseUrl($pdo, 'paypal') . '/v2/checkout/orders';
        $payload = [
            'intent' => 'CAPTURE',
            'purchase_units' => [[
                'reference_id' => (string)$args['reference'],
                'custom_id' => (string)$args['reference'],
                'description' => (string)$args['description'],
                'amount' => ['currency_code' => strtoupper((string)$args['currency']), 'value' => number_format((float)$args['amount'], 2, '.', '')],
            ]],
            'application_context' => ['brand_name' => APP_NAME, 'user_action' => 'PAY_NOW', 'return_url' => (string)$args['success_url'], 'cancel_url' => (string)$args['cancel_url']],
        ];
        $res = portalGatewayHttpRequest('POST', $url, ['Authorization: Bearer ' . $token['token'], 'Content-Type: application/json'], $payload, false);
        if (!$res['ok'] || !is_array($res['json'])) return ['ok' => false, 'message' => 'PayPal order creation failed.'];
        return ['ok' => true, 'data' => $res['json']];
    }
}

if (!function_exists('portalPayPalGetOrder')) {
    function portalPayPalGetOrder(PDO $pdo, string $orderId): array|false {
        $token = portalPayPalAccessToken($pdo);
        if (!$token['ok']) return false;
        $url = portalGatewayApiBaseUrl($pdo, 'paypal') . '/v2/checkout/orders/' . rawurlencode($orderId);
        $res = portalGatewayHttpRequest('GET', $url, ['Authorization: Bearer ' . $token['token']], null, false);
        return ($res['ok'] && is_array($res['json'])) ? $res['json'] : false;
    }
}

if (!function_exists('verifyPayPalPayment')) {
    function verifyPayPalPayment(PDO $pdo, string $reference, string $orderId): array|false {
        if ($orderId === '') return false;
        $token = portalPayPalAccessToken($pdo);
        if (!$token['ok']) return false;
        $captureUrl = portalGatewayApiBaseUrl($pdo, 'paypal') . '/v2/checkout/orders/' . rawurlencode($orderId) . '/capture';
        $capture = portalGatewayHttpRequest('POST', $captureUrl, ['Authorization: Bearer ' . $token['token'], 'Content-Type: application/json'], '{}', false);
        $body = ($capture['ok'] && is_array($capture['json'])) ? $capture['json'] : null;
        if (!$body) {
            $body = portalPayPalGetOrder($pdo, $orderId);
            if (!is_array($body)) return false;
        }
        $completed = strtoupper((string)($body['status'] ?? '')) === 'COMPLETED';
        $captureNode = $body['purchase_units'][0]['payments']['captures'][0] ?? null;
        if (!$completed && (!is_array($captureNode) || strtoupper((string)($captureNode['status'] ?? '')) !== 'COMPLETED')) return false;
        $amount = (float)($captureNode['amount']['value'] ?? $body['purchase_units'][0]['amount']['value'] ?? 0);
        $pending = $_SESSION['pending_payments'][$reference] ?? [];
        return ['data' => ['status' => 'success', 'reference' => $reference, 'amount' => (int)round(($amount > 0 ? $amount : (float)($pending['charged_amount'] ?? $pending['amount'] ?? 0)) * 100), 'paid_at' => date('c'), 'metadata' => ['student_id' => (int)($pending['student_id'] ?? ($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0)), 'transaction_id' => (string)($pending['transaction_id'] ?? ''), 'base_amount' => (float)($pending['base_amount'] ?? 0), 'gateway_fee' => (float)($pending['gateway_fee'] ?? 0), 'charged_amount' => (float)($pending['charged_amount'] ?? $pending['amount'] ?? 0), 'installment_number' => (int)($pending['installment_number'] ?? 1), 'total_installments' => (int)($pending['total_installments'] ?? 1), 'academic_session' => (string)($pending['academic_session'] ?? ''), 'semester' => (string)($pending['semester'] ?? '')]]];
    }
}

if (!function_exists('portalMonnifyBasicAuthHeader')) {
    function portalMonnifyBasicAuthHeader(PDO $pdo): string {
        $apiKey = portalGatewayConfigValue($pdo, 'monnify', 'api_key', '');
        $secret = portalGatewayConfigValue($pdo, 'monnify', 'secret_key', '');
        return ($apiKey !== '' && $secret !== '') ? ('Authorization: Basic ' . base64_encode($apiKey . ':' . $secret)) : '';
    }
}

if (!function_exists('portalMonnifyInitializeTransaction')) {
    function portalMonnifyInitializeTransaction(PDO $pdo, array $args): array {
        $auth = portalMonnifyBasicAuthHeader($pdo);
        $contractCode = portalGatewayConfigValue($pdo, 'monnify', 'contract_code', '');
        if ($auth === '' || $contractCode === '') return ['ok' => false, 'message' => 'Monnify credentials are missing.'];
        $url = portalGatewayApiBaseUrl($pdo, 'monnify') . '/api/v1/merchant/transactions/init-transaction';
        $payload = ['amount' => (float)$args['amount'], 'customerName' => (string)$args['customer_name'], 'customerEmail' => (string)$args['customer_email'], 'paymentReference' => (string)$args['reference'], 'paymentDescription' => (string)$args['description'], 'currencyCode' => strtoupper((string)$args['currency']), 'contractCode' => $contractCode, 'redirectUrl' => (string)$args['success_url']];
        $res = portalGatewayHttpRequest('POST', $url, [$auth, 'Content-Type: application/json'], $payload, false);
        if (!$res['ok'] || !is_array($res['json'])) return ['ok' => false, 'message' => 'Monnify transaction initialization failed.'];
        return ['ok' => true, 'data' => $res['json']];
    }
}

if (!function_exists('verifyMonnifyPayment')) {
    function verifyMonnifyPayment(PDO $pdo, string $reference): array|false {
        $auth = portalMonnifyBasicAuthHeader($pdo);
        if ($auth === '') return false;
        $base = portalGatewayApiBaseUrl($pdo, 'monnify');
        $queryUrls = [$base . '/api/v2/merchant/transactions/query?paymentReference=' . rawurlencode($reference), $base . '/api/v2/transactions/query?paymentReference=' . rawurlencode($reference)];
        $body = null;
        foreach ($queryUrls as $url) {
            $res = portalGatewayHttpRequest('GET', $url, [$auth], null, false);
            if ($res['ok'] && is_array($res['json'])) { $body = $res['json']; break; }
        }
        if (!is_array($body)) return false;
        $responseBody = (array)($body['responseBody'] ?? []);
        $paymentStatus = strtoupper(trim((string)($responseBody['paymentStatus'] ?? '')));
        if (!in_array($paymentStatus, ['PAID','OVERPAID','PARTIALLY_PAID'], true)) return false;
        $amount = (float)($responseBody['amountPaid'] ?? $responseBody['totalPayable'] ?? 0);
        $pending = $_SESSION['pending_payments'][$reference] ?? [];
        return ['data' => ['status' => 'success', 'reference' => $reference, 'amount' => (int)round(($amount > 0 ? $amount : (float)($pending['charged_amount'] ?? $pending['amount'] ?? 0)) * 100), 'paid_at' => !empty($responseBody['paidOn']) ? date('c', strtotime((string)$responseBody['paidOn'])) : date('c'), 'metadata' => ['student_id' => (int)($pending['student_id'] ?? ($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0)), 'transaction_id' => (string)($pending['transaction_id'] ?? ''), 'base_amount' => (float)($pending['base_amount'] ?? 0), 'gateway_fee' => (float)($pending['gateway_fee'] ?? 0), 'charged_amount' => (float)($pending['charged_amount'] ?? $pending['amount'] ?? 0), 'installment_number' => (int)($pending['installment_number'] ?? 1), 'total_installments' => (int)($pending['total_installments'] ?? 1), 'academic_session' => (string)($pending['academic_session'] ?? ''), 'semester' => (string)($pending['semester'] ?? '')]]];
    }
}

function verifyPaystackPayment($reference) {
    global $pdo;
    $curl = curl_init();

    curl_setopt_array($curl, array(
        CURLOPT_URL => "https://api.paystack.co/transaction/verify/" . rawurlencode($reference),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "GET",
        CURLOPT_HTTPHEADER => array(
            "Authorization: Bearer " . getSystemSettingValue($pdo, 'paystack_secret_key', defined('PAYSTACK_SECRET_KEY') ? PAYSTACK_SECRET_KEY : ''),
            "Cache-Control: no-cache",
        ),
    ));

    $response = curl_exec($curl);
    $err = curl_error($curl);
    curl_close($curl);

    if ($err) {
        return false;
    } else {
        $result = json_decode($response, true);
        return $result['data']['status'] === 'success' ? $result : false;
    }
}


function verifyInterswitchPayment(string $reference): ?array {
    global $pdo;
    $pending = $_SESSION['pending_payments'][$reference] ?? null;
    $expectedMinor = null;
    if (is_array($pending) && isset($pending['amount'])) {
        $expectedMinor = (int)round(((float)$pending['amount']) * 100);
    }
    $merchantCode = trim(getSystemSettingValue($pdo, 'interswitch_merchant_code', ''));
    $mode = strtoupper(trim(getSystemSettingValue($pdo, 'interswitch_mode', 'LIVE')));
    $base = ($mode === 'TEST') ? 'https://qa.interswitchng.com' : 'https://webpay.interswitchng.com';
    $amt = $expectedMinor ?? 0;

    if (empty($merchantCode) || $amt <= 0) return null;

    $url = $base . '/collections/api/v1/gettransaction.json?merchantcode=' . rawurlencode($merchantCode)
         . '&transactionreference=' . rawurlencode($reference)
         . '&amount=' . rawurlencode((string)$amt);

    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
    ]);
    $resp = curl_exec($ch);
    $err = curl_error($ch);
    curl_close($ch);

    if ($err || !$resp) return null;

    $data = json_decode($resp, true);
    if (!is_array($data)) return null;

    $respCode = (string)($data['ResponseCode'] ?? '');
    $amountPaid = (int)($data['Amount'] ?? 0);

    $isSuccess = in_array($respCode, ['00','0000'], true);

    if ($isSuccess) {
        if ($expectedMinor !== null && $expectedMinor > 0 && $amountPaid !== $expectedMinor) {
            return null;
        }
        $meta = [
            'student_id' => (int)($pending['student_id'] ?? ($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0)),
            'transaction_id' => (string)($pending['transaction_id'] ?? ''),
            'installment_number' => (int)($pending['installment_number'] ?? 1),
            'total_installments' => (int)($pending['total_installments'] ?? 1),
        ];
        return [
            'data' => [
                'status' => 'success',
                'reference' => $reference,
                'amount' => $amountPaid,
                'paid_at' => $data['TransactionDate'] ?? date('c'),
                'metadata' => $meta,
            ]
        ];
    }
    return null;
}


if (!function_exists('verifyFlutterwavePayment')) {
    function verifyFlutterwavePayment(string $tx_ref) {
        global $pdo;
        $secret = getSystemSettingValue($pdo, 'flutterwave_secret_key', defined('FLUTTERWAVE_SECRET_KEY') ? FLUTTERWAVE_SECRET_KEY : '');
        if (empty($secret)) return false;

        $curl = curl_init();
        curl_setopt_array($curl, [
            CURLOPT_URL => "https://api.flutterwave.com/v3/transactions/verify_by_reference?tx_ref=" . rawurlencode($tx_ref),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer " . $secret,
                "Content-Type: application/json",
            ],
        ]);
        $resp = curl_exec($curl);
        $err = curl_error($curl);
        curl_close($curl);
        if ($err) return false;

        $j = json_decode($resp, true);
        // Flutterwave: status=success and data.status=successful
        if (!is_array($j)) return false;
        $ok = (($j['status'] ?? '') === 'success') && (strtolower((string)($j['data']['status'] ?? '')) === 'successful');
        return $ok ? $j : false;
    }
}

function isLoggedIn() {
    return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
}

function isAdmin() {
    return isset($_SESSION['user_type']) && $_SESSION['user_type'] === 'admin';
}


function isStudent() {
    return isset($_SESSION['user_type']) && $_SESSION['user_type'] === 'student';
}

if (!function_exists('portalEnforceStudentAccountStatus')) {
    function portalEnforceStudentAccountStatus(PDO $pdo): void {
        if (!isset($_SESSION['user_type']) || $_SESSION['user_type'] !== 'student') return;
        if (!empty($_SESSION['is_impersonating'])) return;
        $studentId = (int)($_SESSION['user_id'] ?? 0);
        if ($studentId <= 0) return;

        $page = trim((string)($_GET['page'] ?? 'student_dashboard'));
        $allowedPages = ['login', 'logout', 'forgot_password', 'reset_password'];
        if (in_array($page, $allowedPages, true)) return;

        try {
            $st = $pdo->prepare("SELECT account_status FROM students WHERE id = ? LIMIT 1");
            $st->execute([$studentId]);
            $status = portalNormalizeStudentAccountStatus((string)($st->fetchColumn() ?: 'Active'));
            if (in_array($status, ['Suspended', 'Blocked'], true)) {
                $_SESSION = array_diff_key($_SESSION, array_flip(['user_id','user_type','student_id','registration_no','otp_pending']));
                flash('danger', 'Your account has been ' . strtolower($status) . '. Please contact the administration.');
                redirect('?page=login');
            }
        } catch (Throwable $e) {
        }
    }
}

if (isset($pdo) && ($pdo instanceof PDO)) {
    portalEnforceStudentAccountStatus($pdo);
}

// -------------------------
// ADVANCED STUDENT AUTH HELPERS (SELF-HEALING + OTP + REMEMBER ME)
// -------------------------
if (!function_exists('portalAuthWantsJson')) {
    function portalAuthWantsJson(): bool {
        if ((string)($_POST['ajax'] ?? '') === '1' || (string)($_GET['ajax'] ?? '') === '1') return true;
        $xrw = strtolower((string)($_SERVER['HTTP_X_REQUESTED_WITH'] ?? ''));
        if ($xrw === 'xmlhttprequest') return true;
        $accept = strtolower((string)($_SERVER['HTTP_ACCEPT'] ?? ''));
        return strpos($accept, 'application/json') !== false;
    }
}

if (!function_exists('portalAuthJson')) {
    function portalAuthJson(array $payload): void {
        header('Content-Type: application/json; charset=UTF-8');
        echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        exit;
    }
}

if (!function_exists('portalAuthRespond')) {
    function portalAuthRespond(bool $ok, string $message, string $redirect = '', array $extra = []): void {
        if (portalAuthWantsJson()) {
            portalAuthJson(array_merge(['ok' => $ok, 'message' => $message, 'redirect' => $redirect], $extra));
        }
        if ($message !== '') {
            $_SESSION[$ok ? 'flash_success' : 'flash_error'] = $message;
        }
        redirect($redirect !== '' ? $redirect : '?page=login');
    }
}

if (!function_exists('portalAuthTableExists')) {
    function portalAuthTableExists(PDO $pdo, string $table): bool {
        try {
            $st = $pdo->prepare("SHOW TABLES LIKE ?");
            $st->execute([$table]);
            return (bool)$st->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('portalAuthColumnExists')) {
    function portalAuthColumnExists(PDO $pdo, string $table, string $column): bool {
        try {
            $st = $pdo->prepare("SHOW COLUMNS FROM `$table` LIKE ?");
            $st->execute([$column]);
            return $st->rowCount() > 0;
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('portalAuthAddColumn')) {
    function portalAuthAddColumn(PDO $pdo, string $table, string $column, string $definition): void {
        if (!portalAuthColumnExists($pdo, $table, $column)) {
            $pdo->exec("ALTER TABLE `$table` ADD COLUMN {$definition}");
        }
    }
}

if (!function_exists('portalAuthSettingValue')) {
    function portalAuthSettingValue(PDO $pdo, string $key, string $default = ''): string {
        try {
            if (function_exists('getSystemSettingValue')) {
                $val = getSystemSettingValue($pdo, $key, $default);
                return ($val !== null && $val !== '') ? (string)$val : $default;
            }
        } catch (Throwable $e) {}
        try {
            $st = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = ? LIMIT 1");
            $st->execute([$key]);
            $val = $st->fetchColumn();
            return ($val !== false && $val !== null && $val !== '') ? (string)$val : $default;
        } catch (Throwable $e) {
            return $default;
        }
    }
}

if (!function_exists('portalAuthMailLogFile')) {
    function portalAuthMailLogFile(): string {
        $uploadDir = defined('UPLOAD_DIR') ? (string)UPLOAD_DIR : 'uploads/';
        $uploadDir = rtrim($uploadDir, '/\\');
        $dir = __DIR__ . DIRECTORY_SEPARATOR . $uploadDir;
        if (!is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        return $dir . DIRECTORY_SEPARATOR . 'mail_log.txt';
    }
}

if (!function_exists('portalAuthMailLog')) {
    function portalAuthMailLog(string $message): void {
        $file = portalAuthMailLogFile();
        @file_put_contents($file, '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL, FILE_APPEND);
    }
}

if (!function_exists('portalAuthOtpDeliveryFailureMessage')) {
    function portalAuthOtpDeliveryFailureMessage(string $defaultMessage = 'Unable to send OTP right now. Please try again later.'): string {
        $emailError = trim((string)($GLOBALS['portal_last_email_error'] ?? ''));
        $smsError = trim((string)($GLOBALS['portal_last_sms_error'] ?? ''));

        if ($emailError !== '' && stripos($emailError, 'Recipient email address') !== false) {
            return 'No valid email address is linked to this account. Please contact the administration.';
        }
        if ($smsError !== '' && stripos($smsError, 'Recipient phone number') !== false && $emailError === '') {
            return 'No valid phone number is linked to this account for SMS delivery. Please contact the administration.';
        }
        if ($emailError !== '' || $smsError !== '') {
            return 'Reset delivery is not configured correctly right now. Please contact the administration or try again later.';
        }

        return $defaultMessage;
    }
}

if (!function_exists('portalAuthStrongPassword')) {
    function portalAuthStrongPassword(string $password): bool {
        if (strlen($password) < 8) return false;
        if (!preg_match('/[a-z]/', $password)) return false;
        if (!preg_match('/[A-Z]/', $password)) return false;
        if (!preg_match('/[0-9]/', $password)) return false;
        return true;
    }
}

if (!function_exists('portalAuthStudentName')) {
    function portalAuthStudentName(array $student): string {
        if (function_exists('studentFullName')) {
            return trim((string)studentFullName($student));
        }
        $surname = trim((string)($student['surname'] ?? ''));
        $first = trim((string)($student['first_name'] ?? $student['firstname'] ?? ''));
        $other = trim((string)($student['other_name'] ?? $student['othername'] ?? ''));
        return trim($surname . ($first !== '' ? ', ' . $first : '') . ($other !== '' ? ' ' . $other : ''));
    }
}

if (!function_exists('portalAuthCreateNotification')) {
    function portalAuthCreateNotification(PDO $pdo, string $userType, int $userId, string $title, string $message): void {
        try {
            if (function_exists('createNotification')) {
                createNotification($pdo, $userType, $userId, $title, $message, '');
                return;
            }
        } catch (Throwable $e) {}
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS notifications (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_type VARCHAR(20) NOT NULL,
                user_id INT NOT NULL,
                title VARCHAR(200) NOT NULL,
                message TEXT NOT NULL,
                is_read TINYINT(1) NOT NULL DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
            $st = $pdo->prepare("INSERT INTO notifications (user_type, user_id, title, message, is_read) VALUES (?,?,?,?,0)");
            $st->execute([$userType, $userId, $title, $message]);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalAuthLogActivity')) {
    function portalAuthLogActivity(PDO $pdo, string $userType, int $userId, string $action, array $meta = []): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS activity_log (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_type VARCHAR(20) NOT NULL,
                user_id INT NOT NULL,
                action VARCHAR(100) NOT NULL,
                ip_address VARCHAR(64) NULL,
                user_agent VARCHAR(255) NULL,
                meta_json TEXT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                KEY idx_user (user_type, user_id),
                KEY idx_action (action),
                KEY idx_created (created_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
            $ip = (string)($_SERVER['REMOTE_ADDR'] ?? '');
            $ua = substr((string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255);
            $st = $pdo->prepare("INSERT INTO activity_log (user_type, user_id, action, ip_address, user_agent, meta_json) VALUES (?,?,?,?,?,?)");
            $st->execute([$userType, $userId, $action, $ip, $ua, $meta ? json_encode($meta) : null]);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalAuthNormalizePhone')) {
    function portalAuthNormalizePhone(string $phone): string {
        $digits = preg_replace('/\D+/', '', $phone);
        if ($digits === '') return '';
        if (strpos($digits, '234') === 0) return $digits;
        if (strpos($digits, '0') === 0) return '234' . substr($digits, 1);
        return $digits;
    }
}


if (!function_exists('portalFindStudentByPhoneIdentity')) {
    function portalFindStudentByPhoneIdentity(PDO $pdo, string $phone): ?array {
        $phone = trim($phone);
        if ($phone === '') return null;

        $plainDigits = preg_replace('/\D+/', '', $phone);
        $normalized = portalAuthNormalizePhone($phone);
        $normalizedDigits = preg_replace('/\D+/', '', $normalized);

        $columns = [];
        foreach (['mobile_phone', 'phone', 'phone_number', 'phone_no', 'mobile', 'mobile_number'] as $col) {
            if (portalAuthColumnExists($pdo, 'students', $col)) $columns[] = $col;
        }
        if (!$columns) return null;

        $parts = [];
        $params = [];
        foreach ($columns as $col) {
            $parts[] = "`{$col}` = ?";
            $params[] = $phone;
            if ($normalized !== '' && $normalized !== $phone) {
                $parts[] = "`{$col}` = ?";
                $params[] = $normalized;
            }
            if ($plainDigits !== '') {
                $parts[] = "REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(`{$col}`,''),' ',''),'-',''),'+',''),'(',''),')','') = ?";
                $params[] = $plainDigits;
            }
            if ($normalizedDigits !== '' && $normalizedDigits !== $plainDigits) {
                $parts[] = "REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(`{$col}`,''),' ',''),'-',''),'+',''),'(',''),')','') = ?";
                $params[] = $normalizedDigits;
            }
        }

        $sql = "SELECT * FROM students WHERE (" . implode(' OR ', $parts) . ") LIMIT 1";
        $st = $pdo->prepare($sql);
        $st->execute($params);
        $row = $st->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }
}

if (!function_exists('portalSyncStudentAccountChannels')) {
    function portalSyncStudentAccountChannels(PDO $pdo, int $studentId, string $email, string $phone): void {
        if ($studentId <= 0) return;
        $updates = [];
        $params = [];
        $email = strtolower(trim($email));
        $normalizedPhone = portalAuthNormalizePhone($phone);

        if (portalAuthColumnExists($pdo, 'students', 'email') && $email !== '') {
            $updates[] = 'email = ?';
            $params[] = $email;
        }
        if (portalAuthColumnExists($pdo, 'students', 'phone') && $normalizedPhone !== '') {
            $updates[] = 'phone = ?';
            $params[] = $normalizedPhone;
        }
        if (portalAuthColumnExists($pdo, 'students', 'mobile_phone') && trim($phone) !== '') {
            $updates[] = 'mobile_phone = ?';
            $params[] = trim($phone);
        }
        if (!$updates) return;
        $params[] = $studentId;
        $pdo->prepare('UPDATE students SET ' . implode(', ', $updates) . ' WHERE id = ?')->execute($params);
    }
}

if (!function_exists('portalStudentSelectableColumns')) {
    function portalStudentSelectableColumns(PDO $pdo, array $requested = ['*']): string {
        if ($requested === ['*']) {
            return '*';
        }

        static $studentColumns = null;
        if ($studentColumns === null) {
            $studentColumns = [];
            try {
                $stmt = $pdo->query("SHOW COLUMNS FROM `students`");
                foreach (($stmt ? $stmt->fetchAll(PDO::FETCH_ASSOC) : []) as $columnRow) {
                    $field = (string)($columnRow['Field'] ?? '');
                    if ($field !== '') {
                        $studentColumns[$field] = true;
                    }
                }
            } catch (Throwable $e) {
                $studentColumns = [];
            }
        }

        $safe = [];
        foreach ($requested as $column) {
            $column = trim((string)$column);
            if ($column === '') {
                continue;
            }
            if ($column === '*' || $column === 'students.*') {
                return '*';
            }
            if (isset($studentColumns[$column])) {
                $safe[] = "`{$column}`";
            }
        }

        if (!$safe) {
            return '*';
        }

        return implode(', ', array_values(array_unique($safe)));
    }
}

if (!function_exists('portalStudentIdentityLooksMeaningful')) {
    function portalStudentIdentityLooksMeaningful(string $identity): bool {
        $identity = trim($identity);
        if ($identity === '') return false;

        if (filter_var($identity, FILTER_VALIDATE_EMAIL)) {
            return true;
        }

        $digits = preg_replace('/\D+/', '', $identity);
        if ($digits !== '' && strlen($digits) >= 7) {
            return true;
        }

        return mb_strlen($identity) >= 4;
    }
}

if (!function_exists('portalFindStudentByIdentity')) {
    function portalFindStudentByIdentity(PDO $pdo, string $identity, array $selectColumns = ['*']): ?array {
        $identity = trim($identity);
        if ($identity === '') return null;

        $select = portalStudentSelectableColumns($pdo, $selectColumns);
        $identityLower = strtolower($identity);
        $normalizedPhone = portalAuthNormalizePhone($identity);
        $digitsOnly = preg_replace('/\D+/', '', $identity);
        $isNumericId = ctype_digit($identity);

        $lookupGroups = [];

        if (filter_var($identityLower, FILTER_VALIDATE_EMAIL) && portalAuthColumnExists($pdo, 'students', 'email')) {
            $lookupGroups[] = ['sql' => "SELECT {$select} FROM students WHERE LOWER(`email`) = ? LIMIT 1", 'params' => [$identityLower]];
        }

        $phoneColumns = [];
        foreach (['mobile_phone', 'phone'] as $column) {
            if (portalAuthColumnExists($pdo, 'students', $column)) {
                $phoneColumns[] = $column;
            }
        }
        if ($phoneColumns && ($normalizedPhone !== '' || $digitsOnly !== '')) {
            $phoneValues = array_values(array_unique(array_filter([$identity, $normalizedPhone, $digitsOnly], static fn($v) => trim((string)$v) !== '')));
            foreach ($phoneColumns as $column) {
                foreach ($phoneValues as $value) {
                    $lookupGroups[] = ['sql' => "SELECT {$select} FROM students WHERE `{$column}` = ? LIMIT 1", 'params' => [$value]];
                }
            }
        }

        foreach (['matric_no', 'matriculation', 'student_id', 'registration_no', 'admission_no', 'application_no', 'jamb_number'] as $column) {
            if (portalAuthColumnExists($pdo, 'students', $column)) {
                $lookupGroups[] = ['sql' => "SELECT {$select} FROM students WHERE `{$column}` = ? LIMIT 1", 'params' => [$identity]];
            }
        }

        if ($isNumericId && portalAuthColumnExists($pdo, 'students', 'id')) {
            $lookupGroups[] = ['sql' => "SELECT {$select} FROM students WHERE `id` = ? LIMIT 1", 'params' => [(int)$identity]];
        }

        foreach ($lookupGroups as $lookup) {
            try {
                $stmt = $pdo->prepare((string)$lookup['sql']);
                $stmt->execute((array)$lookup['params']);
                $row = $stmt->fetch(PDO::FETCH_ASSOC);
                if ($row) {
                    return $row;
                }
            } catch (Throwable $e) {
            }
        }

        return null;
    }
}

if (!function_exists('portalAuthEnsureSchema')) {
    function portalAuthEnsureSchema(PDO $pdo): void {
        if (!portalAuthTableExists($pdo, 'students')) return;

        $studentColumns = [
            'name' => "name VARCHAR(150) NULL AFTER matric_no",
            'student_id' => "student_id VARCHAR(100) NULL AFTER name",
            'jamb_number' => "jamb_number VARCHAR(100) NULL AFTER student_id",
            'matriculation' => "matriculation VARCHAR(100) NULL AFTER matric_no",
            'phone' => "phone VARCHAR(30) NULL AFTER mobile_phone",
            'passport_photo' => "passport_photo VARCHAR(255) NULL AFTER profile_image",
            'otp_code' => "otp_code VARCHAR(20) NULL AFTER password",
            'otp_expiry' => "otp_expiry DATETIME NULL AFTER otp_code",
            'otp_last_sent_at' => "otp_last_sent_at DATETIME NULL AFTER otp_expiry",
            'otp_blocked_until' => "otp_blocked_until DATETIME NULL AFTER otp_last_sent_at",
            'otp_attempts' => "otp_attempts INT NOT NULL DEFAULT 0 AFTER otp_blocked_until",
            'otp_consumed' => "otp_consumed TINYINT(1) NOT NULL DEFAULT 0 AFTER otp_attempts",
            'reset_otp_code' => "reset_otp_code VARCHAR(20) NULL AFTER otp_consumed",
            'reset_otp_expiry' => "reset_otp_expiry DATETIME NULL AFTER reset_otp_code",
            'reset_otp_last_sent_at' => "reset_otp_last_sent_at DATETIME NULL AFTER reset_otp_expiry",
            'reset_otp_blocked_until' => "reset_otp_blocked_until DATETIME NULL AFTER reset_otp_last_sent_at",
            'reset_otp_attempts' => "reset_otp_attempts INT NOT NULL DEFAULT 0 AFTER reset_otp_blocked_until",
            'reset_otp_consumed' => "reset_otp_consumed TINYINT(1) NOT NULL DEFAULT 0 AFTER reset_otp_attempts",
            'reset_send_count' => "reset_send_count INT NOT NULL DEFAULT 0 AFTER reset_otp_consumed",
            'reset_send_window_start' => "reset_send_window_start DATETIME NULL AFTER reset_send_count",
            'email_verification_token' => "email_verification_token VARCHAR(120) NULL AFTER reset_send_window_start",
            'email_verified_at' => "email_verified_at DATETIME NULL AFTER email_verification_token",
            'failed_login_attempts' => "failed_login_attempts INT NOT NULL DEFAULT 0 AFTER email_verified_at",
            'locked_until' => "locked_until DATETIME NULL AFTER failed_login_attempts",
            'remember_token' => "remember_token VARCHAR(255) NULL AFTER locked_until",
            'remember_expires_at' => "remember_expires_at DATETIME NULL AFTER remember_token",
            'registration_date' => "registration_date DATETIME NULL AFTER remember_expires_at",
            'last_login' => "last_login DATETIME NULL AFTER registration_date",
            'updated_at' => "updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP",
        ];
        foreach ($studentColumns as $column => $definition) {
            try { portalAuthAddColumn($pdo, 'students', $column, $definition); } catch (Throwable $e) {}
        }

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS activity_log (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_type VARCHAR(20) NOT NULL,
                user_id INT NOT NULL,
                action VARCHAR(100) NOT NULL,
                ip_address VARCHAR(64) NULL,
                user_agent VARCHAR(255) NULL,
                meta_json TEXT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                KEY idx_user (user_type, user_id),
                KEY idx_action (action),
                KEY idx_created (created_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS notifications (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_role VARCHAR(20) NOT NULL DEFAULT 'student',
                user_id INT NOT NULL DEFAULT 0,
                title VARCHAR(200) NOT NULL,
                body TEXT NULL,
                link VARCHAR(255) NULL,
                is_read TINYINT(1) NOT NULL DEFAULT 0,
                read_at DATETIME NULL,
                archived TINYINT(1) NOT NULL DEFAULT 0,
                archived_at DATETIME NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try { $pdo->exec("ALTER TABLE students ADD UNIQUE KEY uniq_students_remember_token (remember_token)"); } catch (Throwable $e) {}

        $studentIndexes = [
            'idx_students_email' => ['email'],
            'idx_students_mobile_phone' => ['mobile_phone'],
            'idx_students_phone' => ['phone'],
            'idx_students_matric_no' => ['matric_no'],
            'idx_students_matriculation' => ['matriculation'],
            'idx_students_student_id' => ['student_id'],
            'idx_students_registration_no' => ['registration_no'],
            'idx_students_admission_no' => ['admission_no'],
            'idx_students_application_no' => ['application_no'],
        ];
        foreach ($studentIndexes as $indexName => $columns) {
            try {
                if (function_exists('portalIndexExistsSafe') && portalIndexExistsSafe($pdo, 'students', $indexName)) {
                    continue;
                }
                $usable = [];
                foreach ($columns as $column) {
                    if (portalAuthColumnExists($pdo, 'students', $column)) {
                        $usable[] = "`{$column}`";
                    }
                }
                if (!$usable) {
                    continue;
                }
                $pdo->exec("CREATE INDEX {$indexName} ON students(" . implode(', ', $usable) . ")");
            } catch (Throwable $e) {}
        }
    }
}

if (!function_exists('portalAuthCookieName')) {
    function portalAuthCookieName(): string {
        return 'student_remember';
    }
}

if (!function_exists('portalAuthClearRememberCookie')) {
    function portalAuthClearRememberCookie(): void {
        setcookie(portalAuthCookieName(), '', [
            'expires' => time() - 3600,
            'path' => '/',
            'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
            'httponly' => true,
            'samesite' => 'Lax'
        ]);
    }
}

if (!function_exists('portalAuthIssueRememberToken')) {
    function portalAuthIssueRememberToken(PDO $pdo, int $studentId, int $days = 30): void {
        $selector = bin2hex(random_bytes(8));
        $validator = bin2hex(random_bytes(16));
        $hash = hash('sha256', $validator);
        $expiresAt = date('Y-m-d H:i:s', time() + ($days * 86400));
        $stored = $selector . ':' . $hash;

        try {
            $st = $pdo->prepare("UPDATE students SET remember_token = ?, remember_expires_at = ? WHERE id = ?");
            $st->execute([$stored, $expiresAt, $studentId]);
            setcookie(portalAuthCookieName(), $selector . ':' . $validator, [
                'expires' => time() + ($days * 86400),
                'path' => '/',
                'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
                'httponly' => true,
                'samesite' => 'Lax'
            ]);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalAuthStudentLogin')) {
    function portalAuthStudentLogin(PDO $pdo, array $student, string $method = 'password', bool $remember = false): void {
        try {
            $pdo->prepare("UPDATE students SET last_login = NOW(), failed_login_attempts = 0, locked_until = NULL WHERE id = ?")
                ->execute([(int)$student['id']]);
        } catch (Throwable $e) {}

        session_regenerate_id(true);
        $_SESSION['user_id'] = (int)$student['id'];
        $_SESSION['student_id'] = (int)$student['id'];
        $_SESSION['registration_no'] = (string)($student['registration_no'] ?? '');
        $_SESSION['full_name'] = portalAuthStudentName($student);
        $_SESSION['user_type'] = 'student';
        $_SESSION['last_activity'] = time();

        if ($remember) {
            portalAuthIssueRememberToken($pdo, (int)$student['id'], 30);
        }

        portalAuthLogActivity($pdo, 'student', (int)$student['id'], 'login_' . $method, ['remember_me' => $remember ? 1 : 0]);
        portalAuthCreateNotification($pdo, 'student', (int)$student['id'], 'Login Successful', 'You signed into your student portal successfully.');
        try { portalSendStudentLoginAlert($pdo, $student, $method); } catch (Throwable $e) {}
    }
}

if (!function_exists('portalAuthTryRememberLogin')) {
    function portalAuthTryRememberLogin(PDO $pdo): void {
        if (!empty($_SESSION['user_id']) || empty($_COOKIE[portalAuthCookieName()])) return;
        $raw = trim((string)$_COOKIE[portalAuthCookieName()]);
        if ($raw === '' || strpos($raw, ':') === false) return;

        [$selector, $validator] = explode(':', $raw, 2);
        if ($selector === '' || $validator === '') return;

        try {
            $st = $pdo->prepare("SELECT * FROM students WHERE remember_token LIKE ? AND remember_expires_at IS NOT NULL AND remember_expires_at >= NOW() LIMIT 1");
            $st->execute([$selector . ':%']);
            $student = $st->fetch(PDO::FETCH_ASSOC);
            if (!$student) return;

            $parts = explode(':', (string)$student['remember_token'], 2);
            if (count($parts) !== 2) return;
            $hash = (string)$parts[1];
            if (!hash_equals($hash, hash('sha256', $validator))) return;

            portalAuthStudentLogin($pdo, $student, 'remember', false);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalAuthEnforceSessionTimeout')) {
    function portalAuthEnforceSessionTimeout(): void {
        if (empty($_SESSION['user_id'])) return;
        $last = (int)($_SESSION['last_activity'] ?? 0);
        if ($last > 0 && (time() - $last) > 3600) {
            $userType = (string)($_SESSION['user_type'] ?? 'student');
            portalAuthClearRememberCookie();
            $_SESSION = [];
            if (session_status() === PHP_SESSION_ACTIVE) {
                session_destroy();
            }
            if (portalAuthWantsJson()) {
                portalAuthJson(['ok' => false, 'message' => 'Session expired. Please login again.', 'redirect' => ($userType === 'admin' ? '?page=admin_login' : '?page=login')]);
            }
            header('Location: ' . ($userType === 'admin' ? '?page=admin_login' : '?page=login'));
            exit;
        }
        $_SESSION['last_activity'] = time();
    }
}

if (!function_exists('portalAuthNativeSmtpSend')) {
    function portalAuthNativeSmtpSend(string $host, int $port, string $encryption, string $username, string $password, string $fromEmail, string $fromName, string $toEmail, string $subject, string $htmlBody, string $replyTo = ''): bool {
        $remote = $host . ':' . $port;
        $allowSelfSigned = in_array(strtolower((string)honttechAppEnv('SMTP_ALLOW_SELF_SIGNED', '0')), ['1','true','yes','on'], true);
        $ctx = stream_context_create([
            'ssl' => [
                'verify_peer' => !$allowSelfSigned,
                'verify_peer_name' => !$allowSelfSigned,
                'allow_self_signed' => $allowSelfSigned
            ]
        ]);
        $fp = @stream_socket_client($remote, $errno, $errstr, 15, STREAM_CLIENT_CONNECT, $ctx);
        if (!$fp) return false;

        $read = function() use ($fp): string {
            $data = '';
            while (!feof($fp)) {
                $line = fgets($fp, 515);
                if ($line === false) break;
                $data .= $line;
                if (preg_match('/^\d{3}\s/', $line)) break;
            }
            return $data;
        };
        $send = function(string $command) use ($fp): void {
            fwrite($fp, $command . "\r\n");
        };

        $greet = $read();
        if (!preg_match('/^220/', $greet)) { fclose($fp); return false; }

        $send('EHLO localhost');
        $ehlo = $read();

        if (strtolower($encryption) === 'tls' && stripos($ehlo, 'STARTTLS') !== false) {
            $send('STARTTLS');
            $resp = $read();
            if (!preg_match('/^220/', $resp)) { fclose($fp); return false; }
            if (!@stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { fclose($fp); return false; }
            $send('EHLO localhost');
            $ehlo = $read();
        }

        if ($username !== '' && $password !== '') {
            $send('AUTH LOGIN');
            if (!preg_match('/^334/', $read())) { fclose($fp); return false; }
            $send(base64_encode($username));
            if (!preg_match('/^334/', $read())) { fclose($fp); return false; }
            $send(base64_encode($password));
            if (!preg_match('/^235/', $read())) { fclose($fp); return false; }
        }

        $send("MAIL FROM:<{$fromEmail}>");
        if (!preg_match('/^250/', $read())) { fclose($fp); return false; }
        $send("RCPT TO:<{$toEmail}>");
        if (!preg_match('/^250/', $read())) { fclose($fp); return false; }
        $send('DATA');
        if (!preg_match('/^354/', $read())) { fclose($fp); return false; }

        $boundary = 'b' . bin2hex(random_bytes(8));
        $headers = [];
        $headers[] = "From: " . ($fromName !== '' ? "{$fromName} <{$fromEmail}>" : $fromEmail);
        $headers[] = "To: <{$toEmail}>";
        $headers[] = "Subject: " . mb_encode_mimeheader($subject, 'UTF-8');
        $headers[] = "MIME-Version: 1.0";
        $headers[] = "Content-Type: multipart/alternative; boundary=\"{$boundary}\"";
        if ($replyTo !== '') $headers[] = "Reply-To: {$replyTo}";

        $textBody = strip_tags(str_replace(['<br>','<br/>','<br />'], "\n", $htmlBody));
        $message = implode("\r\n", $headers) . "\r\n\r\n";
        $message .= "--{$boundary}\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n{$textBody}\r\n\r\n";
        $message .= "--{$boundary}\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n{$htmlBody}\r\n\r\n";
        $message .= "--{$boundary}--\r\n";

        fwrite($fp, $message . "\r\n.\r\n");
        $dataResp = $read();
        $send('QUIT');
        fclose($fp);
        return preg_match('/^250/', $dataResp) === 1;
    }
}

if (!function_exists('portalAuthSendEmail')) {
    function portalAuthSendEmail(PDO $pdo, string $to, string $subject, string $body): bool {
        try {
            if (function_exists('sendEmail') && sendEmail($to, $subject, $body)) {
                return true;
            }
        } catch (Throwable $e) {}

        $host = trim((string)(getenv('SMTP_HOST') ?: portalAuthSettingValue($pdo, 'smtp_host', (defined('SMTP_HOST') ? (string)SMTP_HOST : ''))));
        $port = (int)(getenv('SMTP_PORT') ?: portalAuthSettingValue($pdo, 'smtp_port', (defined('SMTP_PORT') ? (string)SMTP_PORT : '587')));
        $user = trim((string)(getenv('SMTP_USERNAME') ?: portalAuthSettingValue($pdo, 'smtp_username', (defined('SMTP_USERNAME') ? (string)SMTP_USERNAME : ''))));
        $pass = (string)(getenv('SMTP_PASSWORD') ?: portalAuthSettingValue($pdo, 'smtp_password', (defined('SMTP_PASSWORD') ? (string)SMTP_PASSWORD : '')));
        $fromEmail = trim((string)(getenv('FROM_EMAIL') ?: portalAuthSettingValue($pdo, 'smtp_from_email', (defined('FROM_EMAIL') ? (string)FROM_EMAIL : $user))));
        $fromName = trim((string)(getenv('FROM_NAME') ?: portalAuthSettingValue($pdo, 'smtp_from_name', (defined('FROM_NAME') ? (string)FROM_NAME : APP_NAME))));
        $replyTo = trim((string)(getenv('REPLY_TO_EMAIL') ?: (defined('REPLY_TO_EMAIL') ? (string)REPLY_TO_EMAIL : '')));
        $enc = trim((string)(getenv('SMTP_ENCRYPTION') ?: portalAuthSettingValue($pdo, 'smtp_encryption', 'tls')));

        if ($host === '') $host = trim((string)portalNotificationSettingValueSafe($pdo, 'smtp_host', ''));
        if ($port <= 0) $port = (int)(portalNotificationSettingValueSafe($pdo, 'smtp_port', '587') ?: 587);
        if ($user === '') $user = trim((string)portalNotificationSettingValueSafe($pdo, 'smtp_user', ''));
        if ($pass === '') $pass = (string)portalNotificationSettingValueSafe($pdo, 'smtp_pass', '');
        if ($fromEmail === '') $fromEmail = trim((string)portalNotificationSettingValueSafe($pdo, 'from_email', $user));
        if ($fromName === '') $fromName = trim((string)portalNotificationSettingValueSafe($pdo, 'from_name', APP_NAME));
        if ($enc === '') $enc = trim((string)portalNotificationSettingValueSafe($pdo, 'smtp_secure', 'tls'));

        if ($host !== '' && $fromEmail !== '') {
            try {
                if (portalAuthNativeSmtpSend($host, $port > 0 ? $port : 587, $enc, $user, $pass, $fromEmail, $fromName, $to, $subject, $body, $replyTo)) {
                    return true;
                }
            } catch (Throwable $e) {
                portalAuthMailLog('Native SMTP failed for ' . $to . ': ' . $e->getMessage());
            }
        }

        $headers = "MIME-Version: 1.0\r\n";
        $headers .= "Content-Type: text/html; charset=UTF-8\r\n";
        if ($fromEmail !== '') {
            $headers .= "From: " . ($fromName !== '' ? "{$fromName} <{$fromEmail}>" : $fromEmail) . "\r\n";
        }
        if ($replyTo !== '') $headers .= "Reply-To: {$replyTo}\r\n";
        $ok = @mail($to, $subject, $body, $headers);
        if (!$ok && function_exists('sendEmailNotification')) {
            try { $ok = sendEmailNotification($pdo, $to, $subject, $body); } catch (Throwable $e) {}
        }
        if (!$ok) portalAuthMailLog('mail() failed for ' . $to . ' subject=' . $subject);
        return $ok;
    }
}

if (!function_exists('portalAuthIssueStudentOtp')) {
    function portalAuthIssueStudentOtp(PDO $pdo, array $student, string $mode = 'login'): array {
        $id = (int)$student['id'];
        $codeCol = $mode === 'reset' ? 'reset_otp_code' : 'otp_code';
        $expCol = $mode === 'reset' ? 'reset_otp_expiry' : 'otp_expiry';
        $sentCol = $mode === 'reset' ? 'reset_otp_last_sent_at' : 'otp_last_sent_at';
        $blockCol = $mode === 'reset' ? 'reset_otp_blocked_until' : 'otp_blocked_until';
        $attemptsCol = $mode === 'reset' ? 'reset_otp_attempts' : 'otp_attempts';
        $consumedCol = $mode === 'reset' ? 'reset_otp_consumed' : 'otp_consumed';

        $blockedUntil = !empty($student[$blockCol]) ? strtotime((string)$student[$blockCol]) : 0;
        if ($blockedUntil > time()) {
            return ['ok' => false, 'message' => 'OTP requests are temporarily blocked. Please try again later.'];
        }

        $lastSent = !empty($student[$sentCol]) ? strtotime((string)$student[$sentCol]) : 0;
        if ($lastSent > 0 && (time() - $lastSent) < 60) {
            $until = date('Y-m-d H:i:s', time() + 300);
            $pdo->prepare("UPDATE students SET {$blockCol} = ? WHERE id = ?")->execute([$until, $id]);
            return ['ok' => false, 'message' => 'You requested too quickly. OTP has been blocked for 5 minutes.'];
        }

        $activeOtp = trim((string)($student[$codeCol] ?? ''));
        $activeExpiry = !empty($student[$expCol]) ? strtotime((string)$student[$expCol]) : 0;
        $activeConsumed = (int)($student[$consumedCol] ?? 0) === 1;

        if ($activeOtp !== '' && $activeExpiry >= time() && !$activeConsumed) {
            $otp = $activeOtp; // reuse active OTP
            $expiry = date('Y-m-d H:i:s', $activeExpiry);
            $pdo->prepare("UPDATE students SET {$sentCol} = NOW(), {$attemptsCol} = 0 WHERE id = ?")->execute([$id]);
        } else {
            $otp = (string)random_int(100000, 999999);
            $expiry = date('Y-m-d H:i:s', time() + 600);
            $pdo->prepare("UPDATE students SET {$codeCol} = ?, {$expCol} = ?, {$sentCol} = NOW(), {$blockCol} = NULL, {$attemptsCol} = 0, {$consumedCol} = 0 WHERE id = ?")
                ->execute([$otp, $expiry, $id]);
        }

        return ['ok' => true, 'otp' => $otp, 'expiry' => $expiry];
    }
}

if (!function_exists('portalAuthVerifyStudentOtp')) {
    function portalAuthVerifyStudentOtp(PDO $pdo, array $student, string $otpInput, string $mode = 'login'): bool {
        $id = (int)$student['id'];
        $codeCol = $mode === 'reset' ? 'reset_otp_code' : 'otp_code';
        $expCol = $mode === 'reset' ? 'reset_otp_expiry' : 'otp_expiry';
        $blockCol = $mode === 'reset' ? 'reset_otp_blocked_until' : 'otp_blocked_until';
        $attemptsCol = $mode === 'reset' ? 'reset_otp_attempts' : 'otp_attempts';
        $consumedCol = $mode === 'reset' ? 'reset_otp_consumed' : 'otp_consumed';

        $blockedUntil = !empty($student[$blockCol]) ? strtotime((string)$student[$blockCol]) : 0;
        if ($blockedUntil > time()) return false;

        $expiry = !empty($student[$expCol]) ? strtotime((string)$student[$expCol]) : 0;
        $realOtp = trim((string)($student[$codeCol] ?? ''));
        $consumed = (int)($student[$consumedCol] ?? 0) === 1;

        if ($realOtp === '' || $expiry < time() || $consumed) return false;

        if (!hash_equals($realOtp, trim($otpInput))) {
            $attempts = (int)($student[$attemptsCol] ?? 0) + 1;
            $blockUntilValue = null;
            if ($attempts >= 5) {
                $attempts = 0;
                $blockUntilValue = date('Y-m-d H:i:s', time() + 300);
            }
            if ($blockUntilValue !== null) {
                $pdo->prepare("UPDATE students SET {$attemptsCol} = ?, {$blockCol} = ? WHERE id = ?")->execute([$attempts, $blockUntilValue, $id]);
            } else {
                $pdo->prepare("UPDATE students SET {$attemptsCol} = ? WHERE id = ?")->execute([$attempts, $id]);
            }
            return false;
        }

        if ($mode === 'login') {
            $pdo->prepare("UPDATE students SET {$consumedCol} = 1, {$codeCol} = NULL, {$expCol} = NULL, {$attemptsCol} = 0, {$blockCol} = NULL WHERE id = ?")
                ->execute([$id]);
        } else {
            $pdo->prepare("UPDATE students SET {$attemptsCol} = 0, {$blockCol} = NULL WHERE id = ?")->execute([$id]);
        }

        return true;
    }
}

try { portalAuthEnsureSchema($pdo); } catch (Throwable $e) { portalAuthMailLog('Auth schema self-heal failed: ' . $e->getMessage()); }
try { portalAuthTryRememberLogin($pdo); } catch (Throwable $e) {}
try { portalAuthEnforceSessionTimeout(); } catch (Throwable $e) {}



// -------------------------
// STAFF ROLES / PERMISSIONS (SELF-HEALING)
// -------------------------
if (!function_exists('portalIsSuperadminRoleName')) {
    function portalIsSuperadminRoleName(string $role): bool {
        $r = strtolower(trim($role));
        if ($r === '') return false;
        $normalized = preg_replace('/[^a-z0-9]+/i', ' ', $r);
        $normalized = trim(preg_replace('/\s+/', ' ', (string)$normalized));
        if (in_array($normalized, ['superadmin', 'super admin', 'super administrator'], true)) {
            return true;
        }
        return (strpos($normalized, 'super') !== false && strpos($normalized, 'admin') !== false);
    }
}
if (!function_exists('portalNormalizeStaffRoleName')) {
    function portalNormalizeStaffRoleName(string $role): string {
        $role = trim($role);
        if ($role === '') return 'Admin';
        return portalIsSuperadminRoleName($role) ? 'Superadmin' : $role;
    }
}
if (!function_exists('portalEnsureStaffRoleExists')) {
    function portalEnsureStaffRoleExists(PDO $pdo, string $roleName, string $description = ''): void {
        $roleName = portalNormalizeStaffRoleName($roleName);
        if ($roleName === '') return;
        try {
            $st = $pdo->prepare("INSERT IGNORE INTO staff_roles (role_name, description) VALUES (?, ?)");
            $st->execute([$roleName, $description !== '' ? $description : (($roleName === 'Superadmin') ? 'Full system access' : 'Custom staff role')]);
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalSyncAdminSuperadminAssignments')) {
    function portalSyncAdminSuperadminAssignments(PDO $pdo): void {
        try {
            if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'role')) {
                $pdo->exec("UPDATE admins SET role = 'Admin' WHERE role IS NULL OR TRIM(role) = ''");
                $pdo->exec("UPDATE admins SET role = 'Superadmin' WHERE LOWER(TRIM(COALESCE(role, ''))) IN ('superadmin','super admin','super-admin','super administrator','superadministrator','super_admin')");
                try {
                    $pdo->exec("UPDATE admins SET role = 'Superadmin' WHERE LOWER(REPLACE(REPLACE(REPLACE(TRIM(COALESCE(username,'')), '_', ''), '-', ''), ' ', '')) = 'superadmin'");
                } catch (Throwable $e) {}
                try {
                    $pdo->exec("UPDATE admins SET role = 'Superadmin' WHERE LOWER(REPLACE(REPLACE(REPLACE(TRIM(COALESCE(full_name,'')), '_', ''), '-', ''), ' ', '')) = 'superadmin'");
                } catch (Throwable $e) {}
            }
        } catch (Throwable $e) {}

        try {
            if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'is_superadmin')) {
                $pdo->exec("UPDATE admins SET is_superadmin = 1 WHERE is_superadmin = 1 OR LOWER(TRIM(COALESCE(role, ''))) IN ('superadmin','super admin','super-admin','super administrator','superadministrator','super_admin')");
                try {
                    $pdo->exec("UPDATE admins SET is_superadmin = 1 WHERE LOWER(REPLACE(REPLACE(REPLACE(TRIM(COALESCE(username,'')), '_', ''), '-', ''), ' ', '')) = 'superadmin'");
                } catch (Throwable $e) {}
                try {
                    $pdo->exec("UPDATE admins SET is_superadmin = 1 WHERE LOWER(REPLACE(REPLACE(REPLACE(TRIM(COALESCE(full_name,'')), '_', ''), '-', ''), ' ', '')) = 'superadmin'");
                } catch (Throwable $e) {}
            }
        } catch (Throwable $e) {}

        try {
            $roles = $pdo->query("SELECT DISTINCT TRIM(COALESCE(role, '')) AS role_name FROM admins WHERE TRIM(COALESCE(role, '')) <> ''")->fetchAll(PDO::FETCH_COLUMN) ?: [];
            foreach ($roles as $roleName) {
                portalEnsureStaffRoleExists($pdo, (string)$roleName);
            }
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalApplyAdminSessionFromRow')) {
    function portalApplyAdminSessionFromRow(PDO $pdo, array $row): void {
        $_SESSION['user_id'] = (int)($row['id'] ?? 0);
        $_SESSION['admin_id'] = (int)($row['id'] ?? 0);
        $_SESSION['username'] = (string)($row['username'] ?? '');
        $_SESSION['full_name'] = (string)($row['full_name'] ?? '');
        $_SESSION['email'] = (string)($row['email'] ?? ($_SESSION['email'] ?? ''));
        $_SESSION['user_type'] = 'admin';
        $_SESSION['staff_role'] = portalNormalizeStaffRoleName((string)($row['role'] ?? 'Admin'));
        $_SESSION['is_superadmin'] = (!empty($row['is_superadmin']) || portalIsSuperadminRoleName((string)($row['role'] ?? ''))) ? 1 : 0;
        $_SESSION['last_activity'] = time();
    }
}
if (!function_exists('portalCurrentAdminId')) {
    function portalCurrentAdminId(): int {
        if (!isAdmin()) return 0;
        return (int)(($_SESSION['admin_id'] ?? 0) ?: ($_SESSION['user_id'] ?? 0));
    }
}
if (!function_exists('portalEnsureSuperadminAclRows')) {
    function portalEnsureSuperadminAclRows(PDO $pdo): void {
        $permKeys = [
            'staff_users_manage',
            'staff_roles_manage',
            'academics_manage',
            'registrations_manage',
            'results_compute',
            'results_approve',
            'results_publish',
            'scoresheet_manage',
            'finance_manage',
            'clearance_manage',
            'library_manage',
            'cbt_manage',
            'lms_manage',
            'hrm_manage',
            'attendance_manage'
        ];
        foreach ($permKeys as $pk) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO staff_permissions (role_name, perm_key, allowed) VALUES ('Superadmin', ?, 1)");
                $st->execute([$pk]);
            } catch (Throwable $e) {}
        }
        $menuKeys = [
            'menu_admin_dashboard',
            'menu_admin_students',
            'menu_admin_applications',
            'menu_admin_add_application',
            'menu_admin_payments',
            'menu_admin_wallets',
            'menu_admin_settings',
            'menu_reports',
            'menu_messages',
            'menu_notifications',
            'menu_structure_manager',
            'menu_sessions_manager',
            'menu_registrable_courses',
            'menu_numbering_settings',
            'menu_registrations_manager',
            'menu_yet_to_register',
            'menu_registration_settings',
            'menu_grading_result_settings',
            'menu_results_manager',
            'menu_results_approvals',
            'menu_download_scoresheet',
            'menu_transcript_admin',
            'menu_staff_users',
            'menu_staff_roles',
            'menu_lms_admin',
            'menu_library_admin',
            'menu_cbt_admin',
            'menu_hrm',
            'menu_student_attendance_admin',
            'menu_previous_admission_record',
            'menu_admin_clearance',
            'menu_graduated',
            'menu_notification_settings',
            'menu_feature_toggles',
            'menu_pages'
        ];
        foreach ($menuKeys as $mk) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO staff_menu_toggles (role_name, menu_key, enabled) VALUES ('Superadmin', ?, 1)");
                $st->execute([$mk]);
            } catch (Throwable $e) {}
        }
    }
}
if (!function_exists('portalBootstrapCurrentAdminSuperadmin')) {
    function portalBootstrapCurrentAdminSuperadmin(PDO $pdo): bool {
        if (!isAdmin()) return false;
        try { ensureStaffAclSchema($pdo); } catch (Throwable $e) {}

        $uid = portalCurrentAdminId();
        if ($uid <= 0) return false;

        try {
            $select = 'SELECT * FROM admins WHERE id = ? LIMIT 1';
            $st = $pdo->prepare($select);
            $st->execute([$uid]);
            $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$row) return false;

            if (!empty($row['is_superadmin']) || portalIsSuperadminRoleName((string)($row['role'] ?? ''))) {
                portalEnsureStaffRoleExists($pdo, 'Superadmin', 'Full system access');
                portalEnsureSuperadminAclRows($pdo);
                portalApplyAdminSessionFromRow($pdo, $row);
                $_SESSION['staff_role'] = 'Superadmin';
                $_SESSION['is_superadmin'] = 1;
                return true;
            }

            $usernameHint = strtolower(preg_replace('/[^a-z0-9]+/i', '', (string)($row['username'] ?? '')));
            $fullNameHint = strtolower(preg_replace('/[^a-z0-9]+/i', '', (string)($row['full_name'] ?? '')));
            $emailHint = strtolower((string)($row['email'] ?? ''));
            $localHint = $emailHint;
            if (strpos($localHint, '@') !== false) {
                $localHint = strstr($localHint, '@', true);
            }
            $currentPage = strtolower(trim((string)($_GET['page'] ?? '')));
            $superCount = 0;
            $firstAdminId = 0;
            try {
                $firstAdminId = (int)$pdo->query("SELECT id FROM admins ORDER BY id ASC LIMIT 1")->fetchColumn();
            } catch (Throwable $e) {}
            try {
                if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'is_superadmin')) {
                    $superCount = (int)$pdo->query("SELECT COUNT(*) FROM admins WHERE is_superadmin = 1 OR LOWER(TRIM(COALESCE(role,''))) IN ('superadmin','super admin','super-admin','super administrator','superadministrator','super_admin')")->fetchColumn();
                } else {
                    $superCount = (int)$pdo->query("SELECT COUNT(*) FROM admins WHERE LOWER(TRIM(COALESCE(role,''))) IN ('superadmin','super admin','super-admin','super administrator','superadministrator','super_admin')")->fetchColumn();
                }
            } catch (Throwable $e) {}

            $looksLikeSuperadmin = in_array($usernameHint, ['superadmin', 'superadministrator'], true)
                || in_array($fullNameHint, ['superadmin', 'superadministrator'], true)
                || strpos($emailHint, 'superadmin') !== false
                || in_array($localHint, ['superadmin', 'superadministrator'], true);

            $shouldPromote = $looksLikeSuperadmin
                || $uid === $firstAdminId
                || $superCount <= 0
                || ($currentPage === 'staff_roles' && $superCount <= 1);

            if (!$shouldPromote) {
                return false;
            }

            portalEnsureStaffRoleExists($pdo, 'Superadmin', 'Full system access');
            if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'is_superadmin')) {
                $up = $pdo->prepare("UPDATE admins SET role = 'Superadmin', is_superadmin = 1 WHERE id = ?");
            } else {
                $up = $pdo->prepare("UPDATE admins SET role = 'Superadmin' WHERE id = ?");
            }
            $up->execute([$uid]);

            portalEnsureSuperadminAclRows($pdo);
            try { portalSyncAdminSuperadminAssignments($pdo); } catch (Throwable $e) {}

            $st = $pdo->prepare("SELECT * FROM admins WHERE id = ? LIMIT 1");
            $st->execute([$uid]);
            $fresh = $st->fetch(PDO::FETCH_ASSOC) ?: $row;

            portalApplyAdminSessionFromRow($pdo, $fresh);
            $_SESSION['staff_role'] = 'Superadmin';
            $_SESSION['is_superadmin'] = 1;
            $_SESSION['superadmin'] = 1;
            return true;
        } catch (Throwable $e) {
            return false;
        }
    }
}
if (!function_exists('currentStaffRole')) {
    function currentStaffRole(PDO $pdo): string {
        if (!isAdmin()) return '';

        $uid = portalCurrentAdminId();

        // Session hints first, but do not let a stale cached role hide a DB-level Superadmin role.
        foreach (['staff_role', 'role', 'admin_role'] as $sessionKey) {
            $sessionRole = trim((string)($_SESSION[$sessionKey] ?? ''));
            if ($sessionRole !== '' && portalIsSuperadminRoleName($sessionRole)) {
                $_SESSION['staff_role'] = 'Superadmin';
                return 'Superadmin';
            }
        }
        foreach (['is_superadmin', 'superadmin', 'is_root_admin'] as $flagKey) {
            if (!empty($_SESSION[$flagKey])) {
                $_SESSION['staff_role'] = 'Superadmin';
                return 'Superadmin';
            }
        }

        $cached = trim((string)($_SESSION['staff_role'] ?? ''));
        $dbRole = '';
        if ($uid > 0) {
            try {
                $select = 'SELECT role, username, email, full_name';
                if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'is_superadmin')) {
                    $select .= ', is_superadmin';
                }
                $select .= ' FROM admins WHERE id = ? LIMIT 1';
                $st = $pdo->prepare($select);
                $st->execute([$uid]);
                $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                if (!empty($row)) {
                    $dbRole = trim((string)($row['role'] ?? ''));
                    if ((int)($row['is_superadmin'] ?? 0) === 1) {
                        $_SESSION['staff_role'] = 'Superadmin';
                        $_SESSION['is_superadmin'] = 1;
                        return 'Superadmin';
                    }
                    if ($dbRole !== '' && portalIsSuperadminRoleName($dbRole)) {
                        $_SESSION['staff_role'] = 'Superadmin';
                        $_SESSION['is_superadmin'] = 1;
                        return 'Superadmin';
                    }
                    $usernameHint = strtolower(preg_replace('/[^a-z0-9]+/i', '', (string)($row['username'] ?? '')));
                    $fullNameHint = strtolower(preg_replace('/[^a-z0-9]+/i', '', (string)($row['full_name'] ?? '')));
                    $emailHint = strtolower((string)($row['email'] ?? ''));
                    if ($usernameHint === 'superadmin' || $fullNameHint === 'superadmin' || strpos($emailHint, 'superadmin') !== false) {
                        $_SESSION['staff_role'] = 'Superadmin';
                        $_SESSION['is_superadmin'] = 1;
                        return 'Superadmin';
                    }
                }
            } catch (Throwable $e) {
                $dbRole = '';
            }
        }

        $role = $dbRole !== '' ? $dbRole : ($cached !== '' ? $cached : 'Admin');
        $role = portalNormalizeStaffRoleName($role);
        $_SESSION['staff_role'] = $role;
        return $role;
    }
}
if (!function_exists('isSuperadmin')) {
    function isSuperadmin(PDO $pdo): bool {
        if (!isAdmin()) return false;
        foreach (['is_superadmin', 'superadmin', 'is_root_admin'] as $flagKey) {
            if (!empty($_SESSION[$flagKey])) return true;
        }
        foreach (['staff_role', 'role', 'admin_role'] as $sessionKey) {
            if (portalIsSuperadminRoleName((string)($_SESSION[$sessionKey] ?? ''))) {
                $_SESSION['staff_role'] = 'Superadmin';
                $_SESSION['is_superadmin'] = 1;
                return true;
            }
        }

        $uid = portalCurrentAdminId();
        if ($uid > 0) {
            try {
                $select = 'SELECT role, username, email, full_name';
                if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'is_superadmin')) {
                    $select .= ', is_superadmin';
                }
                $select .= ' FROM admins WHERE id = ? LIMIT 1';
                $st = $pdo->prepare($select);
                $st->execute([$uid]);
                $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                if (!empty($row)) {
                    if ((int)($row['is_superadmin'] ?? 0) === 1 || portalIsSuperadminRoleName((string)($row['role'] ?? ''))) {
                        $_SESSION['staff_role'] = 'Superadmin';
                        $_SESSION['is_superadmin'] = 1;
                        return true;
                    }
                    $usernameHint = strtolower(preg_replace('/[^a-z0-9]+/i', '', (string)($row['username'] ?? '')));
                    $fullNameHint = strtolower(preg_replace('/[^a-z0-9]+/i', '', (string)($row['full_name'] ?? '')));
                    $emailHint = strtolower((string)($row['email'] ?? ''));
                    if ($usernameHint === 'superadmin' || $fullNameHint === 'superadmin' || strpos($emailHint, 'superadmin') !== false) {
                        $_SESSION['staff_role'] = 'Superadmin';
                        $_SESSION['is_superadmin'] = 1;
                        return true;
                    }
                }
            } catch (Throwable $e) {}
        }

        $r = strtolower(trim((string)currentStaffRole($pdo)));
        $is = portalIsSuperadminRoleName($r);
        if ($is) {
            $_SESSION['is_superadmin'] = 1;
        }
        return $is;
    }
}


if (!function_exists('portalRegistrationScopeSummary')) {
    function portalRegistrationScopeSummary(PDO $pdo, array $context): string {
        $parts = [];
        $sessionName = portalNormalizeSessionNameSafe((string)($context['session_name'] ?? ''));
        $semester = trim((string)($context['semester'] ?? ''));
        $facultyId = (int)($context['faculty_id'] ?? 0);
        $departmentId = (int)($context['department_id'] ?? 0);
        $programmeId = (int)($context['programme_id'] ?? 0);
        $level = trim((string)($context['level'] ?? ''));
        if ($sessionName !== '') $parts[] = $sessionName;
        if ($semester !== '') $parts[] = $semester . ' Semester';
        $lookupName = static function(string $table, int $id) use ($pdo): string {
            if ($id <= 0) return '';
            try {
                $stmt = $pdo->prepare("SELECT name FROM {$table} WHERE id = ? LIMIT 1");
                $stmt->execute([$id]);
                return trim((string)($stmt->fetchColumn() ?: ''));
            } catch (Throwable $e) {
                return '';
            }
        };
        $facultyName = $lookupName('faculties', $facultyId);
        $departmentName = $lookupName('departments', $departmentId);
        $programmeName = $lookupName('programmes', $programmeId);
        if ($facultyName !== '') $parts[] = $facultyName;
        if ($departmentName !== '') $parts[] = $departmentName;
        if ($programmeName !== '') $parts[] = $programmeName;
        if ($level !== '') $parts[] = 'Level ' . $level;
        return implode(' / ', $parts);
    }
}

if (!function_exists('portalCurrentAcademicSemesterName')) {
    function portalCurrentAcademicSemesterName(PDO $pdo): string {
        $semester = trim((string)portalSettingValueSafe($pdo, 'academic_semester', portalSettingValueSafe($pdo, 'current_semester', '')));
        if ($semester !== '') return portalNormalizeSemesterName($semester);
        try {
            $sessionName = trim((string)portalCurrentAcademicSessionName($pdo));
            if ($sessionName !== '') {
                $st = $pdo->prepare("SELECT semester FROM registration_settings WHERE COALESCE(session_name,'') = ? AND UPPER(TRIM(COALESCE(is_open,''))) IN ('YES','OPEN','1','TRUE') ORDER BY FIELD(semester, 'First Semester', 'Second Semester', 'First', 'Second'), id ASC LIMIT 1");
                $st->execute([$sessionName]);
                $semester = trim((string)($st->fetchColumn() ?: ''));
                if ($semester !== '') return portalNormalizeSemesterName($semester);
            }
        } catch (Throwable $e) {}
        return 'First Semester';
    }
}

if (!function_exists('portalNormalizeRegistrationSemesterAccess')) {
    function portalNormalizeRegistrationSemesterAccess($value, string $fallback = 'both'): string {
        $access = strtolower(trim((string)$value));
        $map = [
            'both' => 'both',
            'enable both semesters' => 'both',
            'all' => 'both',
            'first' => 'first',
            'first_only' => 'first',
            'first semester only' => 'first',
            'enable first semester only' => 'first',
            'second' => 'second',
            'second_only' => 'second',
            'second semester only' => 'second',
            'enable second semester only' => 'second',
            'none' => 'none',
            'disable both' => 'none',
            'disabled' => 'none',
        ];
        return $map[$access] ?? $fallback;
    }
}
if (!function_exists('portalEnsureAcademicSessionRegistrationControl')) {
    function portalEnsureAcademicSessionRegistrationControl(PDO $pdo): void {
        try {
            if (!portalTableExistsSafe($pdo, 'academic_sessions')) {
                portalSelfHealAcademicSessions($pdo);
            }
        } catch (Throwable $e) {}
        try {
            if (!portalColumnExistsSafe($pdo, 'academic_sessions', 'registration_semester_access')) {
                $pdo->exec("ALTER TABLE academic_sessions ADD COLUMN registration_semester_access VARCHAR(20) NOT NULL DEFAULT 'both' AFTER is_active");
            }
        } catch (Throwable $e) {}
        try {
            $pdo->exec("UPDATE academic_sessions
                SET registration_semester_access = CASE
                    WHEN LOWER(TRIM(COALESCE(registration_semester_access, ''))) IN ('both','first','second','none') THEN LOWER(TRIM(registration_semester_access))
                    ELSE 'both'
                END");
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalAcademicSessionRegistrationAccessOptions')) {
    function portalAcademicSessionRegistrationAccessOptions(): array {
        return [
            'both' => 'Enable Both Semesters',
            'first' => 'Enable First Semester Only',
            'second' => 'Enable Second Semester Only',
            'none' => 'Disable Both',
        ];
    }
}
if (!function_exists('portalAcademicSessionRegistrationAccessLabel')) {
    function portalAcademicSessionRegistrationAccessLabel(string $access): string {
        $options = portalAcademicSessionRegistrationAccessOptions();
        return $options[portalNormalizeRegistrationSemesterAccess($access)] ?? ($options['both'] ?? 'Enable Both Semesters');
    }
}
if (!function_exists('portalAcademicSessionRegistrationAccess')) {
    function portalAcademicSessionRegistrationAccess(PDO $pdo, string $sessionName = ''): string {
        portalEnsureAcademicSessionRegistrationControl($pdo);
        $sessionName = portalNormalizeSessionNameSafe($sessionName !== '' ? $sessionName : portalCurrentAcademicSessionName($pdo));
        if ($sessionName === '') return 'both';
        try {
            $st = $pdo->prepare("SELECT registration_semester_access FROM academic_sessions WHERE name = ? ORDER BY is_current DESC, id DESC LIMIT 1");
            $st->execute([$sessionName]);
            return portalNormalizeRegistrationSemesterAccess((string)($st->fetchColumn() ?: 'both'));
        } catch (Throwable $e) {
            return 'both';
        }
    }
}
if (!function_exists('portalAcademicSessionAllowedRegistrationSemesters')) {
    function portalAcademicSessionAllowedRegistrationSemesters(PDO $pdo, string $sessionName = ''): array {
        $access = portalAcademicSessionRegistrationAccess($pdo, $sessionName);
        return match ($access) {
            'first' => ['First'],
            'second' => ['Second'],
            'none' => [],
            default => ['First', 'Second'],
        };
    }
}
if (!function_exists('portalAcademicSessionAllowsSemester')) {
    function portalAcademicSessionAllowsSemester(PDO $pdo, string $sessionName, string $semester): bool {
        $semester = trim((string)$semester);
        return in_array($semester, portalAcademicSessionAllowedRegistrationSemesters($pdo, $sessionName), true);
    }
}
if (!function_exists('portalStudentResolvedCurrentLevel')) {
    function portalStudentResolvedCurrentLevel(PDO $pdo, array $student, string $sessionName = ''): string {
        $level = trim((string)($student['level'] ?? ''));
        if ($level !== '') return $level;
        $studentId = (int)($student['id'] ?? 0);
        if ($studentId > 0) {
            try {
                $sql = "SELECT level FROM course_registrations WHERE student_id = ?";
                $params = [$studentId];
                $sessionName = portalNormalizeSessionNameSafe($sessionName);
                if ($sessionName !== '') {
                    $sql .= " AND COALESCE(session_name,'') = ?";
                    $params[] = $sessionName;
                }
                $sql .= " AND COALESCE(TRIM(level),'') <> '' ORDER BY id DESC LIMIT 1";
                $st = $pdo->prepare($sql);
                $st->execute($params);
                $level = trim((string)($st->fetchColumn() ?: ''));
                if ($level !== '') return $level;
            } catch (Throwable $e) {}
        }
        $levels = portalAcademicLevelOptions($pdo);
        $fallback = trim((string)($levels[0] ?? '100'));
        return $fallback !== '' ? $fallback : '100';
    }
}
if (!function_exists('portalEnsureAcademicLevelsSchema')) {
    function portalEnsureAcademicLevelsSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS academic_levels (
                id INT AUTO_INCREMENT PRIMARY KEY,
                level_name VARCHAR(30) NOT NULL UNIQUE,
                sort_order INT NOT NULL DEFAULT 0,
                is_active TINYINT(1) NOT NULL DEFAULT 1,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
        $defaults = ['100','200','300','400','500','600','700'];
        $sort = 1;
        foreach ($defaults as $level) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO academic_levels (level_name, sort_order, is_active) VALUES (?, ?, 1)");
                $st->execute([$level, $sort]);
            } catch (Throwable $e) {}
            $sort++;
        }
    }
}
if (!function_exists('portalAcademicLevelOptions')) {
    function portalAcademicLevelOptions(PDO $pdo): array {
        portalEnsureAcademicLevelsSchema($pdo);
        $levels = [];
        $add = static function ($value) use (&$levels): void {
            $level = trim((string)$value);
            if ($level !== '') $levels[$level] = $level;
        };
        try {
            $rows = $pdo->query("SELECT level_name FROM academic_levels WHERE is_active = 1 ORDER BY sort_order ASC, level_name ASC")->fetchAll(PDO::FETCH_COLUMN) ?: [];
            foreach ($rows as $row) $add($row);
        } catch (Throwable $e) {}
        foreach ([
            ['students', 'level'],
            ['course_registrations', 'level'],
            ['registration_settings', 'level'],
            ['applications', 'level'],
            ['payment_transactions', 'level']
        ] as $pair) {
            [$table, $column] = $pair;
            try {
                if (!portalTableExistsSafe($pdo, $table) || !portalColumnExistsSafe($pdo, $table, $column)) continue;
                $st = $pdo->query("SELECT DISTINCT {$column} FROM {$table} WHERE COALESCE(TRIM({$column}),'') <> '' ORDER BY {$column} ASC");
                while ($st && ($val = $st->fetchColumn())) $add($val);
            } catch (Throwable $e) {}
        }
        if (!$levels) {
            foreach (['100','200','300','400','500','600','700'] as $level) $levels[$level] = $level;
        }
        return array_values($levels);
    }
}
if (!function_exists('portalAvailableAcademicLevels')) {
    function portalAvailableAcademicLevels(PDO $pdo): array {
        return portalAcademicLevelOptions($pdo);
    }
}

if (!function_exists('portalAcademicSessionOptions')) {
    function portalAcademicSessionOptions(PDO $pdo): array {
        $sessions = [];
        $add = static function ($value) use (&$sessions): void {
            $session = portalNormalizeSessionNameSafe((string)$value);
            if ($session !== '') {
                $sessions[$session] = $session;
            }
        };

        $add(portalCurrentAcademicSessionName($pdo));

        foreach ([
            ['academic_sessions', 'name'],
            ['registration_settings', 'session_name'],
            ['course_registrations', 'session_name'],
            ['students', 'session'],
            ['payment_transactions', 'session_name'],
        ] as $pair) {
            [$table, $column] = $pair;
            try {
                if (!portalTableExistsSafe($pdo, $table) || !portalColumnExistsSafe($pdo, $table, $column)) {
                    continue;
                }
                $stmt = $pdo->query("SELECT DISTINCT {$column} FROM {$table} WHERE COALESCE(TRIM({$column}), '') <> ''");
                while ($stmt && ($value = $stmt->fetchColumn()) !== false) {
                    $add($value);
                }
            } catch (Throwable $e) {}
        }

        $values = array_values($sessions);
        usort($values, static function (string $a, string $b): int {
            preg_match('/(20\d{2})/', $a, $ma);
            preg_match('/(20\d{2})/', $b, $mb);
            $ya = isset($ma[1]) ? (int)$ma[1] : 0;
            $yb = isset($mb[1]) ? (int)$mb[1] : 0;
            if ($ya !== $yb) {
                return $yb <=> $ya;
            }
            return strcasecmp($b, $a);
        });

        return $values;
    }
}

if (!function_exists('portalCourseRegistrationSessionColumn')) {
    function portalCourseRegistrationSessionColumn(PDO $pdo): string {
        if (portalColumnExistsSafe($pdo, 'course_registrations', 'session_name')) return 'session_name';
        if (portalColumnExistsSafe($pdo, 'course_registrations', 'session')) return 'session';
        return 'session_name';
    }
}
if (!function_exists('portalRegistrableCourseSessionColumn')) {
    function portalRegistrableCourseSessionColumn(PDO $pdo): string {
        if (portalColumnExistsSafe($pdo, 'registrable_courses', 'session_name')) return 'session_name';
        if (portalColumnExistsSafe($pdo, 'registrable_courses', 'session')) return 'session';
        return 'session_name';
    }
}
if (!function_exists('portalSyncCourseRegistrationItemLinks')) {
    function portalSyncCourseRegistrationItemLinks(PDO $pdo, array $filters = []): void {
        portalEnsureCourseRegistrationSyncSupport($pdo);
        $crSessionCol = portalCourseRegistrationSessionColumn($pdo);
        $rcSessionCol = portalRegistrableCourseSessionColumn($pdo);
        $crStatusExpr = portalCourseRegistrationStatusExpr($pdo, 'cr');

        $where = [
            "(cri.course_id IS NULL OR cri.course_id = 0)",
            "NULLIF(TRIM(COALESCE(cri.course_code, '')), '') IS NOT NULL",
            "{$crStatusExpr} <> 'cancelled'"
        ];
        $params = [];

        $session = trim((string)($filters['session'] ?? ''));
        $semester = trim((string)($filters['semester'] ?? ''));
        $facultyId = (int)($filters['faculty_id'] ?? 0);
        $departmentId = (int)($filters['department_id'] ?? 0);
        $programmeId = (int)($filters['programme_id'] ?? 0);
        $level = trim((string)($filters['level'] ?? ''));

        if ($session !== '') { $where[] = "COALESCE(cr.{$crSessionCol}, '') = ?"; $params[] = $session; }
        if ($semester !== '') { $where[] = "COALESCE(cr.semester, '') = ?"; $params[] = $semester; }
        if ($facultyId > 0) { $where[] = "COALESCE(cr.faculty_id, 0) = ?"; $params[] = $facultyId; }
        if ($departmentId > 0) { $where[] = "COALESCE(cr.department_id, 0) = ?"; $params[] = $departmentId; }
        if ($programmeId > 0) { $where[] = "COALESCE(cr.programme_id, 0) = ?"; $params[] = $programmeId; }
        if ($level !== '' && strtolower($level) !== 'all') { $where[] = "COALESCE(cr.level, '') = ?"; $params[] = $level; }

        $baseWhere = implode(' AND ', $where);

        $updates = [
            "UPDATE course_registration_items cri
                INNER JOIN course_registrations cr ON cr.id = cri.registration_id
                INNER JOIN registrable_courses rc
                    ON UPPER(TRIM(COALESCE(rc.course_code, ''))) = UPPER(TRIM(COALESCE(cri.course_code, '')))
                   AND COALESCE(rc.{$rcSessionCol}, '') = COALESCE(cr.{$crSessionCol}, '')
                   AND COALESCE(rc.semester, '') = COALESCE(cr.semester, '')
                   AND COALESCE(rc.programme_id, 0) = COALESCE(cr.programme_id, 0)
                   AND COALESCE(rc.level, '') = COALESCE(cr.level, '')
                SET cri.course_id = rc.id
                WHERE {$baseWhere}",
            "UPDATE course_registration_items cri
                INNER JOIN course_registrations cr ON cr.id = cri.registration_id
                INNER JOIN registrable_courses rc
                    ON UPPER(TRIM(COALESCE(rc.course_code, ''))) = UPPER(TRIM(COALESCE(cri.course_code, '')))
                   AND COALESCE(rc.{$rcSessionCol}, '') = COALESCE(cr.{$crSessionCol}, '')
                   AND COALESCE(rc.semester, '') = COALESCE(cr.semester, '')
                   AND COALESCE(rc.programme_id, 0) = 0
                   AND COALESCE(rc.department_id, 0) = COALESCE(cr.department_id, 0)
                   AND COALESCE(rc.level, '') = COALESCE(cr.level, '')
                SET cri.course_id = rc.id
                WHERE {$baseWhere}",
            "UPDATE course_registration_items cri
                INNER JOIN course_registrations cr ON cr.id = cri.registration_id
                INNER JOIN registrable_courses rc
                    ON UPPER(TRIM(COALESCE(rc.course_code, ''))) = UPPER(TRIM(COALESCE(cri.course_code, '')))
                   AND COALESCE(rc.{$rcSessionCol}, '') = COALESCE(cr.{$crSessionCol}, '')
                   AND COALESCE(rc.semester, '') = COALESCE(cr.semester, '')
                   AND COALESCE(rc.programme_id, 0) = 0
                   AND COALESCE(rc.department_id, 0) = 0
                   AND COALESCE(rc.faculty_id, 0) = COALESCE(cr.faculty_id, 0)
                   AND COALESCE(rc.level, '') = COALESCE(cr.level, '')
                SET cri.course_id = rc.id
                WHERE {$baseWhere}",
            "UPDATE course_registration_items cri
                INNER JOIN course_registrations cr ON cr.id = cri.registration_id
                INNER JOIN (
                    SELECT MIN(id) AS id, UPPER(TRIM(course_code)) AS code
                    FROM registrable_courses
                    WHERE NULLIF(TRIM(COALESCE(course_code, '')), '') IS NOT NULL
                    GROUP BY UPPER(TRIM(course_code))
                    HAVING COUNT(*) = 1
                ) uniq ON uniq.code = UPPER(TRIM(COALESCE(cri.course_code, '')))
                SET cri.course_id = uniq.id
                WHERE {$baseWhere}"
        ];

        foreach ($updates as $sql) {
            try {
                $st = $pdo->prepare($sql);
                $st->execute($params);
            } catch (Throwable $e) {}
        }
    }
}
if (!function_exists('portalFetchRegisteredCourseOptions')) {
    function portalFetchRegisteredCourseOptions(PDO $pdo, array $filters = [], int $lecturerId = 0, bool $respectLecturerAssignment = false): array {
        portalEnsureCourseRegistrationSyncSupport($pdo);
        $crSessionCol = portalCourseRegistrationSessionColumn($pdo);
        $rcSessionCol = portalRegistrableCourseSessionColumn($pdo);
        $crStatusExpr = portalCourseRegistrationStatusExpr($pdo, 'cr');

        portalSyncCourseRegistrationItemLinks($pdo, $filters);

        $where = [
            "NULLIF(TRIM(COALESCE(cri.course_code, '')), '') IS NOT NULL",
            "{$crStatusExpr} <> 'cancelled'"
        ];
        $params = [];

        $session = trim((string)($filters['session'] ?? ''));
        $semester = trim((string)($filters['semester'] ?? ''));
        $facultyId = (int)($filters['faculty_id'] ?? 0);
        $departmentId = (int)($filters['department_id'] ?? 0);
        $programmeId = (int)($filters['programme_id'] ?? 0);
        $level = trim((string)($filters['level'] ?? ''));

        if ($session !== '') { $where[] = "COALESCE(cr.{$crSessionCol}, '') = ?"; $params[] = $session; }
        if ($semester !== '') { $where[] = "COALESCE(cr.semester, '') = ?"; $params[] = $semester; }
        if ($facultyId > 0) { $where[] = "COALESCE(cr.faculty_id, 0) = ?"; $params[] = $facultyId; }
        if ($departmentId > 0) { $where[] = "COALESCE(cr.department_id, 0) = ?"; $params[] = $departmentId; }
        if ($programmeId > 0) { $where[] = "COALESCE(cr.programme_id, 0) = ?"; $params[] = $programmeId; }
        if ($level !== '' && strtolower($level) !== 'all') { $where[] = "COALESCE(cr.level, '') = ?"; $params[] = $level; }
        if ($respectLecturerAssignment && $lecturerId > 0 && portalColumnExistsSafe($pdo, 'registrable_courses', 'lecturer_staff_id')) {
            $where[] = "COALESCE(rc.lecturer_staff_id, 0) = ?";
            $params[] = $lecturerId;
        }

        $creditParts = [];
        if (portalColumnExistsSafe($pdo, 'registrable_courses', 'credit_unit')) { $creditParts[] = 'rc.credit_unit'; }
        if (portalColumnExistsSafe($pdo, 'registrable_courses', 'credit_load')) { $creditParts[] = 'rc.credit_load'; }
        $creditParts[] = 'cri.credit_unit';
        $creditExpr = 'COALESCE(' . implode(', ', $creditParts) . ', 0)';

        $sql = "SELECT
                    COALESCE(rc.id, cri.course_id) AS id,
                    COALESCE(NULLIF(TRIM(rc.course_code), ''), NULLIF(TRIM(cri.course_code), ''), CONCAT('COURSE-', COALESCE(cri.id, 0))) AS course_code,
                    COALESCE(NULLIF(TRIM(rc.course_title), ''), NULLIF(TRIM(cri.course_title), ''), 'Untitled Course') AS course_title,
                    {$creditExpr} AS credit_unit,
                    COUNT(DISTINCT cr.student_id) AS student_count
                FROM course_registration_items cri
                INNER JOIN course_registrations cr ON cr.id = cri.registration_id
                LEFT JOIN registrable_courses rc
                    ON rc.id = cri.course_id
                    OR (
                        UPPER(TRIM(COALESCE(rc.course_code, ''))) = UPPER(TRIM(COALESCE(cri.course_code, '')))
                        AND COALESCE(rc.{$rcSessionCol}, '') = COALESCE(cr.{$crSessionCol}, '')
                        AND COALESCE(rc.semester, '') = COALESCE(cr.semester, '')
                    )
                WHERE " . implode(' AND ', $where) . "
                  AND COALESCE(rc.id, cri.course_id, 0) > 0
                GROUP BY COALESCE(rc.id, cri.course_id), COALESCE(NULLIF(TRIM(rc.course_code), ''), NULLIF(TRIM(cri.course_code), ''), CONCAT('COURSE-', COALESCE(cri.id, 0))), COALESCE(NULLIF(TRIM(rc.course_title), ''), NULLIF(TRIM(cri.course_title), ''), 'Untitled Course'), {$creditExpr}
                ORDER BY course_code ASC, course_title ASC";
        try {
            $st = $pdo->prepare($sql);
            $st->execute($params);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            $deduped = [];
            foreach ($rows as $row) {
                $rowId = (int)($row['id'] ?? 0);
                if ($rowId <= 0) continue;
                if (!isset($deduped[$rowId])) {
                    $deduped[$rowId] = $row;
                    continue;
                }
                $deduped[$rowId]['student_count'] = (int)($deduped[$rowId]['student_count'] ?? 0) + (int)($row['student_count'] ?? 0);
            }
            return array_values($deduped);
        } catch (Throwable $e) {
            return [];
        }
    }
}
if (!function_exists('portalFetchRegisteredStudentsForCourseContext')) {
    function portalFetchRegisteredStudentsForCourseContext(PDO $pdo, int $courseId, string $sessionName, string $semester, array $filters = []): array {
        if ($courseId <= 0 || trim($sessionName) === '' || trim($semester) === '') return [];

        portalEnsureCourseRegistrationSyncSupport($pdo);
        portalSyncCourseRegistrationItemLinks($pdo, array_merge($filters, [
            'session' => $sessionName,
            'semester' => $semester,
        ]));

        $crSessionCol = portalCourseRegistrationSessionColumn($pdo);
        $firstExpr = portalStudentFirstNameSql($pdo, 's');
        $otherExpr = portalStudentOtherNameSql($pdo, 's');
        $studentFacultyIdExpr = portalStudentSelectableColumnSql($pdo, 'faculty_id', 's', '0');
        $studentDepartmentIdExpr = portalStudentSelectableColumnSql($pdo, 'department_id', 's', '0');
        $studentProgrammeIdExpr = portalStudentSelectableColumnSql($pdo, 'programme_id', 's', '0');
        $studentLevelExpr = portalStudentSelectableColumnSql($pdo, 'level', 's');
        $studentMatricExpr = portalStudentSelectableColumnSql($pdo, 'matric_no', 's');
        $studentRegNoExpr = portalStudentSelectableColumnSql($pdo, 'reg_no', 's');
        $studentRegistrationNoExpr = portalStudentSelectableColumnSql($pdo, 'registration_no', 's', $studentRegNoExpr);
        $studentAdmissionNoExpr = portalStudentSelectableColumnSql($pdo, 'admission_no', 's');
        $studentApplicationNoExpr = portalStudentSelectableColumnSql($pdo, 'application_no', 's', portalStudentSelectableColumnSql($pdo, 'applicant_no', 's'));
        $studentStoredFullNameExpr = portalStudentSelectableColumnSql($pdo, 'full_name', 's', portalStudentSelectableColumnSql($pdo, 'fullname', 's'));
        $studentLastNameExpr = portalStudentSelectableColumnSql($pdo, 'last_name', 's', portalStudentSelectableColumnSql($pdo, 'surname', 's'));
        $studentUsernameExpr = portalStudentSelectableColumnSql($pdo, 'username', 's');
        $studentEmailExpr = portalStudentSelectableColumnSql($pdo, 'email', 's');
        $studentSchoolNameExpr = portalStudentSelectableColumnSql($pdo, 'school_name', 's', portalStudentSelectableColumnSql($pdo, 'school', 's'));
        $studentSchoolExpr = portalStudentSelectableColumnSql($pdo, 'school', 's');
        $studentCourseOfStudyExpr = portalStudentSelectableColumnSql($pdo, 'course_of_study', 's', portalStudentSelectableColumnSql($pdo, 'course_in_view', 's'));
        $studentCourseInViewExpr = portalStudentSelectableColumnSql($pdo, 'course_in_view', 's');
        $studentProgrammeCacheExpr = portalStudentSelectableColumnSql($pdo, 'programme_name_cache', 's', portalStudentSelectableColumnSql($pdo, 'programme', 's'));
        $studentProgrammeExpr = portalStudentSelectableColumnSql($pdo, 'programme', 's');
        $crStatusExpr = portalCourseRegistrationStatusExpr($pdo, 'cr');

        $courseMeta = [];
        $courseCode = '';
        try {
            $st = $pdo->prepare("SELECT id, course_code, course_title FROM registrable_courses WHERE id = ? LIMIT 1");
            $st->execute([$courseId]);
            $courseMeta = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            $courseCode = strtoupper(trim((string)($courseMeta['course_code'] ?? '')));
        } catch (Throwable $e) {
            $courseMeta = [];
            $courseCode = '';
        }

        $facultyId = (int)($filters['faculty_id'] ?? 0);
        $departmentId = (int)($filters['department_id'] ?? 0);
        $programmeId = (int)($filters['programme_id'] ?? 0);
        $level = trim((string)($filters['level'] ?? ''));

        $where = [
            "COALESCE(cr.{$crSessionCol}, '') = ?",
            "COALESCE(cr.semester, '') = ?",
            "{$crStatusExpr} <> 'cancelled'"
        ];
        $params = [$sessionName, $semester];

        $courseWhere = ["COALESCE(cri.course_id, 0) = ?"];
        $courseParams = [$courseId];
        if ($courseCode !== '') {
            $courseWhere[] = "UPPER(TRIM(COALESCE(cri.course_code, ''))) = ?";
            $courseParams[] = $courseCode;
        }
        $where[] = '(' . implode(' OR ', $courseWhere) . ')';
        $params = array_merge($params, $courseParams);

        if ($facultyId > 0) {
            $where[] = "(COALESCE(cr.faculty_id, 0) = ? OR (COALESCE(cr.faculty_id, 0) = 0 AND COALESCE({$studentFacultyIdExpr}, 0) = ?))";
            $params[] = $facultyId;
            $params[] = $facultyId;
        }
        if ($departmentId > 0) {
            $where[] = "(COALESCE(cr.department_id, 0) = ? OR (COALESCE(cr.department_id, 0) = 0 AND COALESCE({$studentDepartmentIdExpr}, 0) = ?))";
            $params[] = $departmentId;
            $params[] = $departmentId;
        }
        if ($programmeId > 0) {
            $where[] = "(COALESCE(cr.programme_id, 0) = ? OR (COALESCE(cr.programme_id, 0) = 0 AND COALESCE({$studentProgrammeIdExpr}, 0) = ?))";
            $params[] = $programmeId;
            $params[] = $programmeId;
        }
        if ($level !== '' && strtolower($level) !== 'all') {
            $where[] = "COALESCE(NULLIF(TRIM(cr.level), ''), NULLIF(TRIM({$studentLevelExpr}), '')) = ?";
            $params[] = $level;
        }

        $nameExpr = "COALESCE(NULLIF(TRIM(CONCAT(COALESCE(s.surname,''), CASE WHEN COALESCE({$firstExpr},'')<>'' THEN CONCAT(', ', {$firstExpr}) ELSE '' END, CASE WHEN COALESCE({$otherExpr},'')<>'' THEN CONCAT(' ', {$otherExpr}) ELSE '' END)), ''), NULLIF(TRIM({$studentStoredFullNameExpr}), ''), NULLIF(TRIM(CONCAT(COALESCE({$studentLastNameExpr},''), ' ', COALESCE({$firstExpr},''))), ''), NULLIF(TRIM({$studentUsernameExpr}), ''), NULLIF(TRIM({$studentEmailExpr}), ''), CONCAT('Student #', s.id))";
        $matricExpr = "COALESCE(NULLIF(TRIM({$studentMatricExpr}), ''), NULLIF(TRIM({$studentRegNoExpr}), ''), NULLIF(TRIM({$studentRegistrationNoExpr}), ''), NULLIF(TRIM({$studentAdmissionNoExpr}), ''), NULLIF(TRIM({$studentUsernameExpr}), ''), CONCAT('STU-', s.id))";

        $sql = "
            SELECT DISTINCT
                s.*,
                s.id AS student_id,
                {$matricExpr} AS matric_no,
                {$nameExpr} AS full_name,
                COALESCE(NULLIF(TRIM({$studentApplicationNoExpr}), ''), '') AS application_no,
                COALESCE(NULLIF(TRIM({$studentAdmissionNoExpr}), ''), '') AS admission_no,
                COALESCE(NULLIF(TRIM({$studentRegistrationNoExpr}), ''), NULLIF(TRIM({$studentRegNoExpr}), ''), '') AS registration_no,
                COALESCE(NULLIF(TRIM({$studentSchoolNameExpr}), ''), NULLIF(TRIM({$studentSchoolExpr}), ''), NULLIF(TRIM(f.name), ''), NULLIF(TRIM(sf.name), ''), '—') AS faculty_name,
                COALESCE(NULLIF(TRIM({$studentCourseOfStudyExpr}), ''), NULLIF(TRIM({$studentCourseInViewExpr}), ''), NULLIF(TRIM(d.name), ''), NULLIF(TRIM(sd.name), ''), '—') AS department_name,
                COALESCE(NULLIF(TRIM({$studentProgrammeCacheExpr}), ''), NULLIF(TRIM(p.name), ''), NULLIF(TRIM(sp.name), ''), NULLIF(TRIM({$studentProgrammeExpr}), ''), '—') AS programme_name,
                COALESCE(NULLIF(TRIM(cr.level), ''), NULLIF(TRIM({$studentLevelExpr}), ''), '') AS academic_level,
                r.ca_score, r.exam_score, r.total_score, r.grade, r.status
            FROM course_registration_items cri
            INNER JOIN course_registrations cr ON cr.id = cri.registration_id
            INNER JOIN students s ON s.id = cr.student_id
            LEFT JOIN faculties f ON f.id = cr.faculty_id
            LEFT JOIN departments d ON d.id = cr.department_id
            LEFT JOIN programmes p ON p.id = cr.programme_id
            LEFT JOIN faculties sf ON sf.id = {$studentFacultyIdExpr}
            LEFT JOIN departments sd ON sd.id = {$studentDepartmentIdExpr}
            LEFT JOIN programmes sp ON sp.id = {$studentProgrammeIdExpr}
            LEFT JOIN results r ON r.course_id = ? AND r.student_id = s.id AND r.session = ? AND r.semester = ?
            WHERE " . implode(' AND ', $where) . "
            ORDER BY full_name ASC
        ";
        try {
            $st = $pdo->prepare($sql);
            $st->execute(array_merge([$courseId, $sessionName, $semester], $params));
            return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalEnsureImpersonationAuditSchema')) {
    function portalEnsureImpersonationAuditSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS impersonation_audit_log (
                id INT AUTO_INCREMENT PRIMARY KEY,
                superadmin_id INT NOT NULL,
                target_user_id INT NOT NULL DEFAULT 0,
                target_user_type VARCHAR(20) NOT NULL,
                action_type VARCHAR(20) NOT NULL DEFAULT 'start',
                ip_address VARCHAR(64) NULL,
                meta_json TEXT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                KEY idx_superadmin (superadmin_id),
                KEY idx_target (target_user_type, target_user_id),
                KEY idx_created (created_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalImpersonationAuditLog')) {
    function portalImpersonationAuditLog(PDO $pdo, int $superadminId, int $targetUserId, string $targetUserType, string $actionType = 'start', array $meta = []): void {
        if ($superadminId <= 0) return;
        portalEnsureImpersonationAuditSchema($pdo);
        try {
            $ip = (string)($_SERVER['REMOTE_ADDR'] ?? '');
            $st = $pdo->prepare("INSERT INTO impersonation_audit_log (superadmin_id, target_user_id, target_user_type, action_type, ip_address, meta_json) VALUES (?,?,?,?,?,?)");
            $st->execute([$superadminId, max(0, $targetUserId), $targetUserType, $actionType, $ip, $meta ? json_encode($meta) : null]);
        } catch (Throwable $e) {}
        try {
            portalAuthLogActivity($pdo, 'admin', $superadminId, 'superadmin_impersonation_' . $actionType, [
                'target_user_id' => $targetUserId,
                'target_user_type' => $targetUserType,
                'ip_address' => (string)($_SERVER['REMOTE_ADDR'] ?? ''),
                'meta' => $meta,
            ]);
        } catch (Throwable $e) {}
    }
}
if (!function_exists('portalStartSuperadminImpersonation')) {
    function portalStartSuperadminImpersonation(PDO $pdo, string $targetUserType, int $targetUserId): void {
        if (!isAdmin() || !isSuperadmin($pdo)) {
            flash('danger', 'Only Superadmin can use Access Account.');
            redirect('?page=admin_dashboard');
        }
        if ($targetUserId <= 0) {
            flash('danger', 'Invalid account selected for impersonation.');
            redirect('?page=admin_dashboard');
        }

        $targetUserType = strtolower(trim($targetUserType));
        $superadminId = (int)(($_SESSION['superadmin_impersonation_backup']['user_id'] ?? 0) ?: ($_SESSION['impersonator_admin_id'] ?? ($_SESSION['user_id'] ?? 0)));
        if ($superadminId <= 0) $superadminId = (int)($_SESSION['user_id'] ?? 0);

        if (!isset($_SESSION['superadmin_impersonation_backup']) || !is_array($_SESSION['superadmin_impersonation_backup'])) {
            $_SESSION['superadmin_impersonation_backup'] = [
                'user_id' => (int)($_SESSION['user_id'] ?? 0),
                'user_type' => (string)($_SESSION['user_type'] ?? 'admin'),
                'username' => (string)($_SESSION['username'] ?? ''),
                'full_name' => (string)($_SESSION['full_name'] ?? ''),
                'email' => (string)($_SESSION['email'] ?? ''),
                'staff_role' => (string)($_SESSION['staff_role'] ?? currentStaffRole($pdo)),
                'is_superadmin' => (int)($_SESSION['is_superadmin'] ?? 0),
                'login_time' => (string)($_SESSION['login_time'] ?? ''),
            ];
        }

        if ($targetUserType === 'student') {
            $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $st->execute([$targetUserId]);
            $row = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$row) {
                flash('danger', 'Student account not found.');
                redirect('?page=admin_students');
            }
            if (session_status() === PHP_SESSION_ACTIVE) {
                session_regenerate_id(true);
            }
            $_SESSION['impersonator_admin_id'] = $superadminId;
            $_SESSION['is_impersonating'] = true;
            $_SESSION['impersonation_target_user_id'] = (int)$row['id'];
            $_SESSION['impersonation_target_user_type'] = 'student';
            $_SESSION['user_id'] = (int)$row['id'];
            $_SESSION['admin_id'] = (int)$row['id'];
            $_SESSION['student_id'] = (int)$row['id'];
            $_SESSION['registration_no'] = (string)($row['registration_no'] ?? '');
            $_SESSION['email'] = (string)($row['email'] ?? '');
            $_SESSION['full_name'] = full_name($row);
            $_SESSION['user_type'] = 'student';
            $_SESSION['last_activity'] = time();
            unset($_SESSION['staff_role'], $_SESSION['username']);
            portalImpersonationAuditLog($pdo, $superadminId, (int)$row['id'], 'student', 'start', ['target_name' => full_name($row)]);
            redirect('?page=student_dashboard');
        }

        if (!in_array($targetUserType, ['admin','staff'], true)) {
            flash('danger', 'Unsupported impersonation target.');
            redirect('?page=admin_dashboard');
        }

        $st = $pdo->prepare("SELECT id, username, email, full_name, role FROM admins WHERE id = ? LIMIT 1");
        $st->execute([$targetUserId]);
        $row = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        if (!$row) {
            flash('danger', 'Admin/staff account not found.');
            redirect('?page=staff_users');
        }
        if ((int)$row['id'] === $superadminId) {
            flash('info', 'You are already using this Superadmin account.');
            redirect('?page=admin_dashboard');
        }

        $_SESSION['impersonator_admin_id'] = $superadminId;
        $_SESSION['is_impersonating'] = true;
        $_SESSION['impersonation_target_user_id'] = (int)$row['id'];
        $_SESSION['impersonation_target_user_type'] = $targetUserType;
        portalApplyAdminSessionFromRow($pdo, $row);
        portalImpersonationAuditLog($pdo, $superadminId, (int)$row['id'], $targetUserType, 'start', ['target_name' => (string)($row['full_name'] ?? '')]);
        redirect('?page=admin_dashboard');
    }
}
if (!function_exists('portalResolveStudentViewerId')) {
    function portalResolveStudentViewerId(PDO $pdo, bool $allowAdminQueryParam = true): int {
        if (isStudent()) {
            return (int)($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0);
        }

        if (isAdmin()) {
            if ($allowAdminQueryParam && isset($_GET['student_id']) && (int)$_GET['student_id'] > 0) {
                return (int)$_GET['student_id'];
            }
            if (!empty($_SESSION['is_impersonating']) && (string)($_SESSION['impersonation_target_user_type'] ?? '') === 'student') {
                return (int)($_SESSION['impersonation_target_user_id'] ?? 0);
            }
            if (isset($_SESSION['student_id']) && (int)$_SESSION['student_id'] > 0) {
                return (int)$_SESSION['student_id'];
            }
        }

        return (int)($_SESSION['user_id'] ?? 0);
    }
}


if (!function_exists('portalStudentOtherNameSql')) {
    function portalStudentOtherNameSql(PDO $pdo, string $alias = 's'): string {
        $prefix = trim($alias) !== '' ? trim($alias) . '.' : '';
        $hasOtherName = portalColumnExistsSafe($pdo, 'students', 'other_name');
        $hasOthername = portalColumnExistsSafe($pdo, 'students', 'othername');
        if ($hasOtherName && $hasOthername) {
            return "COALESCE({$prefix}other_name, {$prefix}othername)";
        }
        if ($hasOtherName) {
            return "{$prefix}other_name";
        }
        if ($hasOthername) {
            return "{$prefix}othername";
        }
        return "''";
    }
}


if (!function_exists('portalStudentSelectableColumnSql')) {
    function portalStudentSelectableColumnSql(PDO $pdo, string $column, string $alias = 's', string $fallbackSql = "''"): string {
        $prefix = trim($alias) !== '' ? trim($alias) . '.' : '';
        return portalColumnExistsSafe($pdo, 'students', $column) ? ($prefix . $column) : $fallbackSql;
    }
}

if (!function_exists('portalCourseRegistrationActionUrls')) {
    function portalCourseRegistrationActionUrls(PDO $pdo, array $row, string $sourcePage = ''): array {
        $registrationId = (int)($row['registration_id'] ?? $row['reg_id'] ?? $row['id'] ?? 0);
        if ($registrationId <= 0) {
            $studentId = (int)($row['student_id'] ?? 0);
            $sessionName = trim((string)($row['session_name'] ?? ''));
            $semester = trim((string)($row['semester'] ?? ''));
            $level = trim((string)($row['level'] ?? ''));
            if ($studentId > 0) {
                try {
                    $sql = "SELECT id FROM course_registrations WHERE student_id = ?";
                    $params = [$studentId];
                    if ($sessionName !== '') { $sql .= " AND COALESCE(session_name,'') = ?"; $params[] = $sessionName; }
                    if ($semester !== '') { $sql .= " AND COALESCE(semester,'') = ?"; $params[] = $semester; }
                    if ($level !== '') { $sql .= " AND COALESCE(level,'') = ?"; $params[] = $level; }
                    $sql .= " ORDER BY COALESCE(created_at, updated_at) DESC, id DESC LIMIT 1";
                    $stmt = $pdo->prepare($sql);
                    $stmt->execute($params);
                    $registrationId = (int)($stmt->fetchColumn() ?: 0);
                } catch (Throwable $e) {
                    $registrationId = 0;
                }
            }
        }

        if ($registrationId <= 0) {
            return [
                'registration_id' => 0,
                'view' => '',
                'download' => '',
                'print' => '',
            ];
        }

        $query = ['page=course_form', 'reg_id=' . $registrationId];
        if ($sourcePage !== '') {
            $query[] = 'source=' . rawurlencode($sourcePage);
        }
        $baseHref = '?' . implode('&', $query);

        return [
            'registration_id' => $registrationId,
            'view' => $baseHref,
            'download' => $baseHref . '&download=1',
            'print' => $baseHref . '&print=1',
        ];
    }
}

if (!function_exists('portalCourseRegistrationStudentSelectList')) {
    function portalCourseRegistrationStudentSelectList(PDO $pdo, string $alias = 's'): string {
        $firstNameSql = portalStudentFirstNameSql($pdo, $alias);
        $otherNameSql = portalStudentOtherNameSql($pdo, $alias);
        $columns = [
            "" . portalStudentSelectableColumnSql($pdo, 'registration_no', $alias) . " AS registration_no",
            "" . portalStudentSelectableColumnSql($pdo, 'application_no', $alias) . " AS application_no",
            "" . portalStudentSelectableColumnSql($pdo, 'admission_no', $alias) . " AS admission_no",
            "" . portalStudentSelectableColumnSql($pdo, 'matric_no', $alias) . " AS matric_no",
            "" . portalStudentSelectableColumnSql($pdo, 'title', $alias) . " AS title",
            "" . portalStudentSelectableColumnSql($pdo, 'surname', $alias) . " AS surname",
            "{$firstNameSql} AS first_name",
            "{$otherNameSql} AS other_name",
            "" . portalStudentSelectableColumnSql($pdo, 'maiden_name', $alias) . " AS maiden_name",
            "" . portalStudentSelectableColumnSql($pdo, 'date_of_birth', $alias) . " AS date_of_birth",
            "" . portalStudentSelectableColumnSql($pdo, 'marital_status', $alias) . " AS marital_status",
            "" . portalStudentSelectableColumnSql($pdo, 'sex', $alias) . " AS sex",
            "" . portalStudentSelectableColumnSql($pdo, 'nationality', $alias) . " AS nationality",
            "" . portalStudentSelectableColumnSql($pdo, 'state_of_origin', $alias) . " AS state_of_origin",
            "" . portalStudentSelectableColumnSql($pdo, 'lga', $alias) . " AS lga",
            "" . portalStudentSelectableColumnSql($pdo, 'permanent_address', $alias) . " AS permanent_address",
            "" . portalStudentSelectableColumnSql($pdo, 'email', $alias) . " AS email",
            "" . portalStudentSelectableColumnSql($pdo, 'mobile_phone', $alias, portalStudentSelectableColumnSql($pdo, 'phone', $alias)) . " AS mobile_phone",
            "" . portalStudentSelectableColumnSql($pdo, 'mode_of_study', $alias) . " AS mode_of_study",
            "" . portalStudentSelectableColumnSql($pdo, 'profile_image', $alias) . " AS profile_image",
            "" . portalStudentSelectableColumnSql($pdo, 'passport_photo', $alias) . " AS passport_photo",
            "" . portalStudentSelectableColumnSql($pdo, 'school_name', $alias, portalStudentSelectableColumnSql($pdo, 'school', $alias)) . " AS school_name",
            "" . portalStudentSelectableColumnSql($pdo, 'course_in_view', $alias, portalStudentSelectableColumnSql($pdo, 'course_of_study', $alias)) . " AS course_in_view",
            "" . portalStudentSelectableColumnSql($pdo, 'course_of_study', $alias, portalStudentSelectableColumnSql($pdo, 'department', $alias)) . " AS course_of_study",
            "" . portalStudentSelectableColumnSql($pdo, 'programme_name_cache', $alias, portalStudentSelectableColumnSql($pdo, 'programme', $alias)) . " AS programme_name_cache",
            "" . portalStudentSelectableColumnSql($pdo, 'faculty', $alias) . " AS faculty_text",
            "" . portalStudentSelectableColumnSql($pdo, 'department', $alias) . " AS department_text",
            "" . portalStudentSelectableColumnSql($pdo, 'programme', $alias) . " AS programme_text",
        ];
        return implode(', ', $columns);
    }
}

if (!function_exists('portalAdminCanManageClearance')) {
    function portalAdminCanManageClearance(PDO $pdo): bool {
        if (!isAdmin()) return false;
        if (function_exists('isSuperadmin') && isSuperadmin($pdo)) return true;
        if (function_exists('hasStaffPermission')) {
            return hasStaffPermission($pdo, 'clearance_manage');
        }
        return true;
    }
}

if (!function_exists('portalEnsureClearanceSupport')) {
    function portalEnsureClearanceSupport(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS clearance_records (
                id INT AUTO_INCREMENT PRIMARY KEY,
                student_id INT NOT NULL,
                academic_session VARCHAR(80) NOT NULL,
                semester VARCHAR(40) NOT NULL,
                faculty_id INT NULL DEFAULT NULL,
                department_id INT NULL DEFAULT NULL,
                programme_id INT NULL DEFAULT NULL,
                level VARCHAR(40) NULL DEFAULT NULL,
                clearance_status ENUM('Pending','Approved','Rejected') NOT NULL DEFAULT 'Pending',
                payment_status VARCHAR(60) NOT NULL DEFAULT 'Not Paid',
                remarks TEXT NULL,
                officer_name VARCHAR(160) NULL,
                officer_signature LONGTEXT NULL,
                receipt_no VARCHAR(80) NULL,
                approved_by INT NULL DEFAULT NULL,
                approved_at DATETIME NULL DEFAULT NULL,
                rejected_by INT NULL DEFAULT NULL,
                rejected_at DATETIME NULL DEFAULT NULL,
                updated_by INT NULL DEFAULT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_student_clearance_period (student_id, academic_session, semester),
                KEY idx_clearance_period (academic_session, semester),
                KEY idx_clearance_status (clearance_status),
                KEY idx_clearance_student (student_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS clearance_audit_logs (
                id INT AUTO_INCREMENT PRIMARY KEY,
                clearance_record_id INT NULL DEFAULT NULL,
                student_id INT NOT NULL,
                academic_session VARCHAR(80) NOT NULL,
                semester VARCHAR(40) NOT NULL,
                action_taken VARCHAR(80) NOT NULL,
                previous_status VARCHAR(40) NULL,
                new_status VARCHAR(40) NULL,
                remarks TEXT NULL,
                admin_id INT NULL DEFAULT NULL,
                ip_address VARCHAR(80) NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                KEY idx_clearance_audit_student (student_id),
                KEY idx_clearance_audit_period (academic_session, semester)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'clearance_records', 'public_verify_token')) { $pdo->exec("ALTER TABLE clearance_records ADD COLUMN public_verify_token VARCHAR(96) NULL AFTER receipt_no"); } } catch (Throwable $e) {}
        try { if (!portalColumnExistsSafe($pdo, 'clearance_records', 'public_verify_token_hash')) { $pdo->exec("ALTER TABLE clearance_records ADD COLUMN public_verify_token_hash CHAR(64) NULL AFTER public_verify_token"); } } catch (Throwable $e) {}
        try { if (portalTableExistsSafe($pdo, 'clearance_records') && !portalIndexExistsSafe($pdo, 'clearance_records', 'idx_clearance_public_verify_token_hash')) { $pdo->exec("CREATE INDEX idx_clearance_public_verify_token_hash ON clearance_records(public_verify_token_hash)"); } } catch (Throwable $e) {}
    }
}

if (!function_exists('portalClearanceEnsurePublicToken')) {
    function portalClearanceEnsurePublicToken(PDO $pdo, array $record): array {
        portalEnsureClearanceSupport($pdo);
        $recordId = (int)($record['id'] ?? 0);
        if ($recordId <= 0) return $record;
        if (trim((string)($record['public_verify_token'] ?? '')) !== '' && trim((string)($record['public_verify_token_hash'] ?? '')) !== '') return $record;
        try { $token = bin2hex(random_bytes(24)); } catch (Throwable $e) { $token = sha1('clr|' . $recordId . '|' . microtime(true) . '|' . mt_rand()); }
        $hash = hash('sha256', $token);
        try { $pdo->prepare("UPDATE clearance_records SET public_verify_token = ?, public_verify_token_hash = ? WHERE id = ? AND COALESCE(public_verify_token,'') = ''")->execute([$token, $hash, $recordId]); } catch (Throwable $e) {}
        try { $st = $pdo->prepare("SELECT * FROM clearance_records WHERE id = ? LIMIT 1"); $st->execute([$recordId]); return $st->fetch(PDO::FETCH_ASSOC) ?: array_merge($record, ['public_verify_token' => $token, 'public_verify_token_hash' => $hash]); } catch (Throwable $e) { $record['public_verify_token'] = $token; $record['public_verify_token_hash'] = $hash; return $record; }
    }
}

if (!function_exists('portalFindClearanceRecordByPublicToken')) {
    function portalFindClearanceRecordByPublicToken(PDO $pdo, string $token): array {
        portalEnsureClearanceSupport($pdo);
        $token = trim($token);
        if ($token === '') return [];
        $hash = hash('sha256', $token);
        try {
            $sql = "SELECT cr.*, s.*, COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.faculty),''), 'Not Set') AS faculty_name, COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.department),''), 'Not Set') AS department_name, COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme),''), 'Not Set') AS programme_name FROM clearance_records cr INNER JOIN students s ON s.id = cr.student_id LEFT JOIN faculties f ON f.id = s.faculty_id LEFT JOIN departments d ON d.id = s.department_id LEFT JOIN programmes p ON p.id = s.programme_id WHERE cr.public_verify_token_hash = ? LIMIT 1";
            $st = $pdo->prepare($sql);
            $st->execute([$hash]);
            return $st->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) { return []; }
    }
}

if (!function_exists('portalClearancePublicUrl')) {
    function portalClearancePublicUrl(PDO $pdo, array $record): string {
        $record = portalClearanceEnsurePublicToken($pdo, $record);
        $token = trim((string)($record['public_verify_token'] ?? ''));
        return $token !== '' ? appBaseUrl() . '/index.php?page=public_clearance_verify&token=' . urlencode($token) : '';
    }
}

if (!function_exists('portalNormalizeClearanceStatus')) {
    function portalNormalizeClearanceStatus(?string $value, string $default = 'Pending'): string {
        $s = strtolower(trim((string)$value));
        if ($s === 'approved') return 'Approved';
        if ($s === 'rejected') return 'Rejected';
        if ($s === 'not created' || $s === 'not_created') return 'Not Created';
        if ($s === 'pending') return 'Pending';
        return $default;
    }
}

if (!function_exists('portalClearanceStatusBadgeClass')) {
    function portalClearanceStatusBadgeClass(string $status): string {
        $status = portalNormalizeClearanceStatus($status, 'Pending');
        if ($status === 'Approved') return 'success';
        if ($status === 'Rejected') return 'danger';
        if ($status === 'Not Created') return 'secondary';
        return 'warning text-dark';
    }
}

if (!function_exists('portalClearanceClientIp')) {
    function portalClearanceClientIp(): string {
        foreach (['HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'] as $key) {
            if (!empty($_SERVER[$key])) {
                $value = (string)$_SERVER[$key];
                if ($key === 'HTTP_X_FORWARDED_FOR' && strpos($value, ',') !== false) {
                    $parts = array_map('trim', explode(',', $value));
                    $value = (string)($parts[0] ?? '');
                }
                if ($value !== '') return substr($value, 0, 80);
            }
        }
        return '';
    }
}

if (!function_exists('portalClearancePhoneColumns')) {
    function portalClearancePhoneColumns(PDO $pdo, string $alias = 's'): array {
        $prefix = trim($alias) !== '' ? trim($alias) . '.' : '';
        $out = [];
        foreach (['phone', 'phone_number', 'phone_no', 'mobile', 'mobile_number'] as $col) {
            if (portalColumnExistsSafe($pdo, 'students', $col)) {
                $out[] = $prefix . $col;
            }
        }
        return $out;
    }
}

if (!function_exists('portalClearanceStudentPhone')) {
    function portalClearanceStudentPhone(array $student): string {
        foreach (['phone', 'phone_number', 'phone_no', 'mobile', 'mobile_number'] as $col) {
            $value = trim((string)($student[$col] ?? ''));
            if ($value !== '') return $value;
        }
        return '';
    }
}


if (!function_exists('portalFilterClearanceTransactionsForPeriod')) {
    function portalFilterClearanceTransactionsForPeriod(array $transactions, string $session = '', string $semester = '', array $fallbackStudent = []): array {
        $base = portalFilterTransactionsForPeriod($transactions, $session, $semester, $fallbackStudent);
        $out = [];
        $seen = [];
        foreach ($base as $tx) {
            $txId = (int)($tx['id'] ?? 0);
            $key = $txId > 0 ? ('id:' . $txId) : md5(json_encode($tx));
            $seen[$key] = true;
            $out[] = $tx;
        }

        $targetSession = trim((string)$session);
        if ($targetSession === '') {
            $targetSession = trim((string)($fallbackStudent['session'] ?? $fallbackStudent['session_name'] ?? ''));
        }
        if ($targetSession === '') {
            $targetSession = trim((string)portalCurrentAcademicSessionName($GLOBALS['pdo'] ?? null));
        }

        foreach ($transactions as $tx) {
            $type = strtolower(trim((string)($tx['transaction_type'] ?? '')));
            if ($type !== 'hostel fee') continue;
            $txSession = trim((string)portalPaymentAcademicSession((array)$tx, $fallbackStudent));
            if ($targetSession !== '' && $txSession !== '' && strcasecmp($txSession, $targetSession) !== 0) continue;
            $txId = (int)($tx['id'] ?? 0);
            $key = $txId > 0 ? ('id:' . $txId) : md5(json_encode($tx));
            if (isset($seen[$key])) continue;
            $seen[$key] = true;
            $out[] = $tx;
        }
        usort($out, static function(array $a, array $b): int {
            $ad = strtotime((string)($a['payment_date'] ?? $a['created_at'] ?? '')) ?: 0;
            $bd = strtotime((string)($b['payment_date'] ?? $b['created_at'] ?? '')) ?: 0;
            if ($ad === $bd) return ((int)($a['id'] ?? 0)) <=> ((int)($b['id'] ?? 0));
            return $ad <=> $bd;
        });
        return $out;
    }
}

if (!function_exists('portalSynchronizeClearanceForStudent')) {
    function portalSynchronizeClearanceForStudent(PDO $pdo, $studentOrId, string $session = '', string $semester = ''): void {
        portalEnsureClearanceSupport($pdo);
        $student = is_array($studentOrId) ? $studentOrId : [];
        $studentId = (int)($student['id'] ?? $studentOrId ?? 0);
        if ($studentId <= 0) return;

        if (!$student) {
            $stmt = $pdo->prepare("SELECT s.*, COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.faculty),''), 'Not Set') AS faculty_name, COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.department),''), 'Not Set') AS department_name, COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme),''), 'Not Set') AS programme_name, p.mode_of_study_type AS programme_mode_of_study_type, p.mode_of_study_custom AS programme_mode_of_study_custom FROM students s LEFT JOIN faculties f ON f.id = s.faculty_id LEFT JOIN departments d ON d.id = s.department_id LEFT JOIN programmes p ON p.id = s.programme_id WHERE s.id = ? LIMIT 1");
            $stmt->execute([$studentId]);
            $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$student) return;
        }

        $targets = [];
        $push = static function(string $sess, string $sem) use (&$targets): void {
            $sess = trim($sess);
            $sem = portalNormalizeSemesterName($sem);
            if ($sess === '' || $sem === '') return;
            $targets[strtolower($sess . '|' . $sem)] = ['session' => $sess, 'semester' => $sem];
        };

        $baseSession = trim($session);
        if ($baseSession === '') $baseSession = trim((string)($student['session'] ?? $student['session_name'] ?? portalCurrentAcademicSessionName($pdo)));
        $baseSemester = trim($semester);
        if ($baseSemester === '') $baseSemester = trim((string)($student['semester'] ?? portalCurrentAcademicSemesterName($pdo)));

        if ($baseSession !== '') {
            if ($baseSemester !== '') {
                $push($baseSession, $baseSemester);
            } else {
                $push($baseSession, 'First Semester');
                $push($baseSession, 'Second Semester');
            }
        }

        $currentSession = trim((string)portalCurrentAcademicSessionName($pdo));
        if ($currentSession !== '') {
            $push($currentSession, portalCurrentAcademicSemesterName($pdo));
        }

        foreach ($targets as $target) {
            $snapshot = portalBuildClearanceSnapshot($pdo, $student, (string)$target['session'], (string)$target['semester']);
            portalUpsertClearanceRecord($pdo, $student, $snapshot, ['updated_by' => (int)($_SESSION['user_id'] ?? 0)]);
        }
    }
}

if (!function_exists('portalBuildClearanceSnapshot')) {
    function portalBuildClearanceSnapshot(PDO $pdo, array $student, string $session = '', string $semester = ''): array {
        $session = trim($session);
        if ($session === '') {
            $session = trim((string)($student['session'] ?? $student['session_name'] ?? portalCurrentAcademicSessionName($pdo)));
        }
        if ($session === '') {
            $session = trim((string)portalCurrentAcademicSessionName($pdo));
        }
        $semester = portalNormalizeSemesterName($semester !== '' ? $semester : ($student['semester'] ?? 'First Semester'));

        $paymentStmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE student_id = ? ORDER BY COALESCE(payment_date, created_at) DESC, id DESC");
        $paymentStmt->execute([(int)($student['id'] ?? 0)]);
        $allClearancePayments = $paymentStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        $clearancePayments = portalFilterClearanceTransactionsForPeriod($allClearancePayments, $session, $semester, $student);
        $paidItems = portalPeriodPaidItems($pdo, $student, $session, $semester);

        $totalAmountPaid = 0.0;
        $approvedPaymentCount = count($paidItems);
        $pendingPaymentCount = 0;
        $lastPaymentDate = '';
        foreach ($paidItems as $item) {
            $amount = (float)($item['amount'] ?? 0);
            if ($amount > 0) {
                $totalAmountPaid += $amount;
            }
            if ($lastPaymentDate === '' && !empty($item['payment_date'])) {
                $lastPaymentDate = (string)$item['payment_date'];
            }
        }
        foreach ($clearancePayments as $paymentRow) {
            $status = strtolower(trim((string)($paymentRow['status'] ?? 'Pending')));
            if ($status === 'pending' && strcasecmp((string)($paymentRow['transaction_type'] ?? ''), 'Wallet Funding') !== 0) {
                $pendingPaymentCount++;
            }
        }

        $schoolFeeStatus = portalSchoolFeeStatusForPeriod($pdo, $student, $session, $semester);
        $hostelStatus = portalHostelStatusForPeriod($pdo, $student, $session, $semester);
        $registrationStatus = payment_status_label((string)($student['registration_fee_status'] ?? 'Not Paid'));
        $acceptanceStatus = payment_status_label((string)($student['acceptance_fee_status'] ?? 'Not Paid'));

        $outstandingItems = [];
        if ($registrationStatus !== 'Paid') $outstandingItems[] = 'Registration Fee';
        if ($acceptanceStatus !== 'Paid') $outstandingItems[] = 'Acceptance Fee';
        if ($schoolFeeStatus !== 'Paid') $outstandingItems[] = 'School Fee for ' . $semester;
        if ($hostelStatus === 'Not Paid') $outstandingItems[] = 'Hostel Fee for ' . $semester;
        if (trim((string)($student['admission_no'] ?? '')) === '') $outstandingItems[] = 'Admission Number';
        if (trim((string)($student['matric_no'] ?? '')) === '') $outstandingItems[] = 'Matric Number';
        $clearanceReady = !$outstandingItems;

        $allRequiredPaid = $registrationStatus === 'Paid'
            && $acceptanceStatus === 'Paid'
            && $schoolFeeStatus === 'Paid'
            && $hostelStatus !== 'Not Paid';
        $hasSchoolFeePayment = false;
        foreach ($paidItems as $paidItemRow) { $lbl = strtolower(trim((string)($paidItemRow['label'] ?? ''))); if (strpos($lbl, 'school fee') !== false) { $hasSchoolFeePayment = true; break; } }
        $hasAnyPaymentSignal = !empty($paidItems)
            || $totalAmountPaid > 0
            || $registrationStatus === 'Paid'
            || $acceptanceStatus === 'Paid'
            || $schoolFeeStatus !== 'Not Paid'
            || $hostelStatus === 'Paid'
            || $hasSchoolFeePayment;
        $paymentStatus = $allRequiredPaid ? 'Paid' : ($hasAnyPaymentSignal ? 'Partially Paid' : 'Not Paid');

        return [
            'session' => $session,
            'semester' => $semester,
            'clearance_payments' => $clearancePayments,
            'paid_items' => $paidItems,
            'total_amount_paid' => $totalAmountPaid,
            'approved_payment_count' => $approvedPaymentCount,
            'pending_payment_count' => $pendingPaymentCount,
            'last_payment_date' => $lastPaymentDate,
            'school_fee_status' => $schoolFeeStatus,
            'hostel_status' => $hostelStatus,
            'registration_status' => $registrationStatus,
            'acceptance_status' => $acceptanceStatus,
            'outstanding_items' => $outstandingItems,
            'clearance_ready' => $clearanceReady,
            'payment_status' => $paymentStatus,
        ];
    }
}

if (!function_exists('portalClearanceReceiptNumber')) {
    function portalClearanceReceiptNumber(PDO $pdo, int $studentId, string $session, string $semester): string {
        $year = portalAcademicYearNumber($pdo, $session);
        $suffix = stripos($semester, 'second') !== false ? 'S2' : 'S1';
        return 'CLR-' . $year . '-' . str_pad((string)max(1, $studentId), 6, '0', STR_PAD_LEFT) . '-' . $suffix;
    }
}

if (!function_exists('portalFetchClearanceRecord')) {
    function portalFetchClearanceRecord(PDO $pdo, int $studentId, string $session, string $semester): array {
        portalEnsureClearanceSupport($pdo);
        try {
            $stmt = $pdo->prepare("SELECT * FROM clearance_records WHERE student_id = ? AND academic_session = ? AND semester = ? LIMIT 1");
            $stmt->execute([$studentId, $session, $semester]);
            return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalUpsertClearanceRecord')) {
    function portalUpsertClearanceRecord(PDO $pdo, array $student, array $snapshot, array $override = []): array {
        portalEnsureClearanceSupport($pdo);
        $studentId = (int)($student['id'] ?? 0);
        if ($studentId <= 0) return [];

        $session = trim((string)($snapshot['session'] ?? ''));
        $semester = portalNormalizeSemesterName((string)($snapshot['semester'] ?? 'First Semester'));
        $existing = portalFetchClearanceRecord($pdo, $studentId, $session, $semester);
        $previousStatus = (string)($existing['clearance_status'] ?? 'Pending');

        $clearanceStatus = portalNormalizeClearanceStatus((string)($override['clearance_status'] ?? ($existing['clearance_status'] ?? 'Pending')));
        $remarks = array_key_exists('remarks', $override) ? trim((string)$override['remarks']) : trim((string)($existing['remarks'] ?? ''));
        $officerName = array_key_exists('officer_name', $override) ? trim((string)$override['officer_name']) : trim((string)($existing['officer_name'] ?? ''));
        if ($officerName === '') {
            $officerName = 'Account Officer';
        }
        $officerSignature = array_key_exists('officer_signature', $override) ? trim((string)$override['officer_signature']) : trim((string)($existing['officer_signature'] ?? ''));
        $receiptNo = trim((string)($existing['receipt_no'] ?? ''));
        if ($receiptNo === '') {
            $receiptNo = portalClearanceReceiptNumber($pdo, $studentId, $session, $semester);
        }

        $updatedBy = (int)($override['updated_by'] ?? ($_SESSION['user_id'] ?? 0));
        $approvedBy = $existing['approved_by'] ?? null;
        $approvedAt = $existing['approved_at'] ?? null;
        $rejectedBy = $existing['rejected_by'] ?? null;
        $rejectedAt = $existing['rejected_at'] ?? null;

        if (array_key_exists('clearance_status', $override)) {
            if ($clearanceStatus === 'Approved') {
                $approvedBy = $updatedBy > 0 ? $updatedBy : ($approvedBy ?: null);
                $approvedAt = date('Y-m-d H:i:s');
                $rejectedBy = null;
                $rejectedAt = null;
            } elseif ($clearanceStatus === 'Rejected') {
                $rejectedBy = $updatedBy > 0 ? $updatedBy : ($rejectedBy ?: null);
                $rejectedAt = date('Y-m-d H:i:s');
            } else {
                $approvedBy = null;
                $approvedAt = null;
                $rejectedBy = null;
                $rejectedAt = null;
            }
        }

        $payload = [
            $studentId,
            $session,
            $semester,
            (int)($student['faculty_id'] ?? 0),
            (int)($student['department_id'] ?? 0),
            (int)($student['programme_id'] ?? 0),
            trim((string)($student['level'] ?? '')),
            $clearanceStatus,
            (string)($snapshot['payment_status'] ?? 'Not Paid'),
            $remarks !== '' ? $remarks : null,
            $officerName !== '' ? $officerName : null,
            $officerSignature !== '' ? $officerSignature : null,
            $receiptNo,
            $approvedBy,
            $approvedAt,
            $rejectedBy,
            $rejectedAt,
            $updatedBy > 0 ? $updatedBy : null,
        ];

        try {
            if ($existing) {
                $sql = "UPDATE clearance_records SET
                            faculty_id = ?,
                            department_id = ?,
                            programme_id = ?,
                            level = ?,
                            clearance_status = ?,
                            payment_status = ?,
                            remarks = ?,
                            officer_name = ?,
                            officer_signature = ?,
                            receipt_no = ?,
                            approved_by = ?,
                            approved_at = ?,
                            rejected_by = ?,
                            rejected_at = ?,
                            updated_by = ?
                        WHERE student_id = ? AND academic_session = ? AND semester = ?";
                $updatePayload = [
                    (int)($student['faculty_id'] ?? 0),
                    (int)($student['department_id'] ?? 0),
                    (int)($student['programme_id'] ?? 0),
                    trim((string)($student['level'] ?? '')),
                    $clearanceStatus,
                    (string)($snapshot['payment_status'] ?? 'Not Paid'),
                    $remarks !== '' ? $remarks : null,
                    $officerName !== '' ? $officerName : null,
                    $officerSignature !== '' ? $officerSignature : null,
                    $receiptNo,
                    $approvedBy,
                    $approvedAt,
                    $rejectedBy,
                    $rejectedAt,
                    $updatedBy > 0 ? $updatedBy : null,
                    $studentId,
                    $session,
                    $semester,
                ];
                $pdo->prepare($sql)->execute($updatePayload);
            } else {
                $sql = "INSERT INTO clearance_records
                            (student_id, academic_session, semester, faculty_id, department_id, programme_id, level, clearance_status, payment_status, remarks, officer_name, officer_signature, receipt_no, approved_by, approved_at, rejected_by, rejected_at, updated_by)
                        VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
                $pdo->prepare($sql)->execute($payload);
            }
        } catch (Throwable $e) {
            return $existing;
        }

        $record = portalFetchClearanceRecord($pdo, $studentId, $session, $semester);

        try {
            $shouldLog = !$existing
                || array_key_exists('clearance_status', $override)
                || array_key_exists('remarks', $override)
                || array_key_exists('officer_name', $override)
                || array_key_exists('officer_signature', $override);
            if ($shouldLog) {
                $actionTaken = (string)($override['audit_action'] ?? ($existing ? 'update' : 'create'));
                $pdo->prepare("INSERT INTO clearance_audit_logs (clearance_record_id, student_id, academic_session, semester, action_taken, previous_status, new_status, remarks, admin_id, ip_address) VALUES (?,?,?,?,?,?,?,?,?,?)")
                    ->execute([
                        (int)($record['id'] ?? 0) ?: null,
                        $studentId,
                        $session,
                        $semester,
                        $actionTaken,
                        $previousStatus !== '' ? $previousStatus : null,
                        $clearanceStatus,
                        $remarks !== '' ? $remarks : null,
                        $updatedBy > 0 ? $updatedBy : null,
                        portalClearanceClientIp() ?: null,
                    ]);
            }
        } catch (Throwable $e) {}

        return $record;
    }
}

if (!function_exists('portalFetchAdminClearanceRows')) {
    function portalFetchAdminClearanceRows(PDO $pdo, array $filters = [], string $search = ''): array {
        portalEnsureClearanceSupport($pdo);

        $session = trim((string)($filters['session'] ?? portalCurrentAcademicSessionName($pdo)));
        if ($session === '') {
            $sessionOptions = portalAvailableSessionOptions($pdo);
            $session = (string)($sessionOptions[0] ?? '');
        }
        $semester = portalNormalizeSemesterName((string)($filters['semester'] ?? 'First Semester'));
        $statusFilter = trim((string)($filters['clearance_status'] ?? ''));
        $facultyId = (int)($filters['faculty_id'] ?? 0);
        $departmentId = (int)($filters['department_id'] ?? 0);
        $programmeId = (int)($filters['programme_id'] ?? 0);
        $level = trim((string)($filters['level'] ?? ''));
        $modeOfStudy = trim((string)($filters['mode_of_study'] ?? ''));

        $firstNameSql = portalStudentFirstNameSql($pdo, 's');
        $otherNameSql = portalStudentOtherNameSql($pdo, 's');
        $nameExpr = "TRIM(CONCAT_WS(' ', COALESCE(s.surname,''), COALESCE({$firstNameSql},''), COALESCE({$otherNameSql},'')))";
        $nameExprAlt = "TRIM(CONCAT(COALESCE(s.surname,''), ', ', COALESCE({$firstNameSql},''), ' ', COALESCE({$otherNameSql},'')))";

        $sql = "SELECT s.*,
                       COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.faculty),''), 'Not Set') AS faculty_name,
                       COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.department),''), 'Not Set') AS department_name,
                       COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme),''), 'Not Set') AS programme_name,
                       cr.id AS clearance_record_id,
                       cr.clearance_status,
                       cr.payment_status AS clearance_payment_status,
                       cr.remarks AS clearance_remarks,
                       cr.receipt_no,
                       cr.academic_session AS clearance_session,
                       cr.semester AS clearance_semester,
                       cr.updated_at AS clearance_updated_at
                FROM students s
                LEFT JOIN faculties f ON f.id = s.faculty_id
                LEFT JOIN departments d ON d.id = s.department_id
                LEFT JOIN programmes p ON p.id = s.programme_id
                LEFT JOIN clearance_records cr ON cr.student_id = s.id AND cr.academic_session = ? AND cr.semester = ?
                WHERE 1=1";
        $params = [$session, $semester];

        if ($facultyId > 0) {
            $sql .= " AND s.faculty_id = ?";
            $params[] = $facultyId;
        }
        if ($departmentId > 0) {
            $sql .= " AND s.department_id = ?";
            $params[] = $departmentId;
        }
        if ($programmeId > 0) {
            $sql .= " AND s.programme_id = ?";
            $params[] = $programmeId;
        }
        if ($level !== '') {
            $sql .= " AND COALESCE(NULLIF(s.level,''), NULLIF(cr.level,''), '') = ?";
            $params[] = $level;
        }
        if ($modeOfStudy !== '') {
            $sql .= " AND COALESCE(NULLIF(TRIM(s.mode_of_study),''), CASE WHEN COALESCE(p.mode_of_study_type,'') = 'Others' AND COALESCE(TRIM(p.mode_of_study_custom),'') <> '' THEN TRIM(p.mode_of_study_custom) WHEN COALESCE(p.mode_of_study_type,'') IN ('Full-Time','Part-Time') THEN p.mode_of_study_type ELSE 'Full-Time' END) = ?";
            $params[] = $modeOfStudy;
        }
        if ($statusFilter !== '') {
            $normalizedFilter = portalNormalizeClearanceStatus($statusFilter, $statusFilter);
            if ($normalizedFilter === 'Not Created') {
                $sql .= " AND cr.id IS NULL";
            } else {
                $sql .= " AND COALESCE(cr.clearance_status, 'Pending') = ?";
                $params[] = $normalizedFilter;
            }
        }

        $search = trim($search);
        $orderParams = [];
        if ($search !== '') {
            $searchWhere = [];
            $searchLike = '%' . $search . '%';
            if (ctype_digit($search)) {
                $searchWhere[] = 's.id = ?';
                $params[] = (int)$search;
            }
            foreach (['application_no', 'admission_no', 'matric_no', 'registration_no', 'email'] as $col) {
                if (portalColumnExistsSafe($pdo, 'students', $col)) {
                    $searchWhere[] = "COALESCE(s.{$col},'') = ?";
                    $params[] = $search;
                    $searchWhere[] = "COALESCE(s.{$col},'') LIKE ?";
                    $params[] = $searchLike;
                    $orderParams[] = $search;
                }
            }
            foreach (portalClearancePhoneColumns($pdo, 's') as $phoneCol) {
                $searchWhere[] = "COALESCE({$phoneCol},'') = ?";
                $params[] = $search;
                $searchWhere[] = "COALESCE({$phoneCol},'') LIKE ?";
                $params[] = $searchLike;
                $orderParams[] = $search;
            }
            $searchWhere[] = "{$nameExpr} LIKE ?";
            $params[] = $searchLike;
            $searchWhere[] = "{$nameExprAlt} LIKE ?";
            $params[] = $searchLike;
            $searchWhere[] = "COALESCE(s.surname,'') LIKE ?";
            $params[] = $searchLike;
            $searchWhere[] = "COALESCE({$firstNameSql},'') LIKE ?";
            $params[] = $searchLike;
            $searchWhere[] = "COALESCE({$otherNameSql},'') LIKE ?";
            $params[] = $searchLike;
            $sql .= " AND (" . implode(' OR ', $searchWhere) . ")";
        }

        $sql .= " ORDER BY ";
        if ($search !== '') {
            $orderBy = [];
            if (ctype_digit($search)) {
                $orderBy[] = 'CASE WHEN s.id = ? THEN 0 ELSE 1 END';
                $orderParams = array_merge([(int)$search], $orderParams);
            }
            foreach (['application_no', 'admission_no', 'matric_no', 'registration_no', 'email'] as $col) {
                if (portalColumnExistsSafe($pdo, 'students', $col)) {
                    $orderBy[] = "CASE WHEN COALESCE(s.{$col},'') = ? THEN 0 ELSE 1 END";
                }
            }
            foreach (portalClearancePhoneColumns($pdo, 's') as $phoneCol) {
                $orderBy[] = "CASE WHEN COALESCE({$phoneCol},'') = ? THEN 0 ELSE 1 END";
            }
            $orderBy[] = "CASE WHEN {$nameExpr} = ? THEN 0 ELSE 1 END";
            $orderBy[] = "CASE WHEN {$nameExprAlt} = ? THEN 0 ELSE 1 END";
            $sql .= implode(', ', $orderBy) . ', ';
            $orderParams[] = $search;
            $orderParams[] = $search;
        }
        $sql .= "COALESCE(cr.updated_at, cr.created_at, s.updated_at, s.created_at) DESC, s.id DESC LIMIT 250";
        $params = array_merge($params, $orderParams);

        try {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }

        foreach ($rows as &$row) {
            $row['student_name'] = full_name($row);
            $status = trim((string)($row['clearance_status'] ?? ''));
            $row['clearance_status'] = $status !== '' ? portalNormalizeClearanceStatus($status) : 'Not Created';
            $paymentStatus = trim((string)($row['clearance_payment_status'] ?? ''));
            if ($paymentStatus === '') {
                $regPaid = payment_status_label((string)($row['registration_fee_status'] ?? 'Not Paid')) === 'Paid';
                $accPaid = payment_status_label((string)($row['acceptance_fee_status'] ?? 'Not Paid')) === 'Paid';
                $schPaid = payment_status_label((string)($row['school_fee_status'] ?? 'Not Paid')) === 'Paid';
                $paymentStatus = ($regPaid || $accPaid || $schPaid) ? 'Partially Paid' : 'Not Paid';
                if ($regPaid && $accPaid && $schPaid) $paymentStatus = 'Paid';
            }
            $row['clearance_payment_status'] = $paymentStatus;
        }
        unset($row);

        return $rows;
    }
}

if (!function_exists('portalSynchronizeNumberAliases')) {
    function portalSynchronizeNumberAliases(PDO $pdo, string $numberType): void {
        portalEnsureNumberingSchema($pdo);
        try {
            if ($numberType === 'application') {
                $pdo->exec("UPDATE students SET application_no = COALESCE(NULLIF(TRIM(application_no),''), NULLIF(TRIM(registration_no),'')) WHERE COALESCE(TRIM(application_no),'') = '' AND COALESCE(TRIM(registration_no),'') <> ''");
                $pdo->exec("UPDATE students SET registration_no = COALESCE(NULLIF(TRIM(registration_no),''), NULLIF(TRIM(application_no),'')) WHERE COALESCE(TRIM(registration_no),'') = '' AND COALESCE(TRIM(application_no),'') <> ''");
            } elseif ($numberType === 'registration') {
                $pdo->exec("UPDATE students SET registration_no = TRIM(registration_no) WHERE COALESCE(registration_no,'') <> ''");
            } elseif ($numberType === 'admission') {
                $pdo->exec("UPDATE students SET admission_no = TRIM(admission_no) WHERE COALESCE(admission_no,'') <> ''");
            } elseif ($numberType === 'matric') {
                $pdo->exec("UPDATE students SET matric_no = TRIM(matric_no) WHERE COALESCE(matric_no,'') <> ''");
            }
        } catch (Throwable $e) {}
    }
}
if (!function_exists('ensureStaffAclSchema')) {
    function ensureStaffAclSchema(PDO $pdo): void {
        try {
            // admins table: role + scope
            $pdo->exec("ALTER TABLE admins ADD COLUMN role VARCHAR(50) NOT NULL DEFAULT 'Admin' AFTER full_name");
        } catch (Throwable $e) { /* ignore */ }
        try { $pdo->exec("ALTER TABLE admins ADD COLUMN faculty_id INT NULL AFTER role"); } catch (Throwable $e) { /* ignore */ }
        try { $pdo->exec("ALTER TABLE admins ADD COLUMN department_id INT NULL AFTER faculty_id"); } catch (Throwable $e) { /* ignore */ }

        // roles list (optional)
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS staff_roles (
                id INT AUTO_INCREMENT PRIMARY KEY,
                role_name VARCHAR(50) UNIQUE NOT NULL,
                description VARCHAR(255) NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }

        // permissions per role
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS staff_permissions (
                id INT AUTO_INCREMENT PRIMARY KEY,
                role_name VARCHAR(50) NOT NULL,
                perm_key VARCHAR(80) NOT NULL,
                allowed TINYINT(1) NOT NULL DEFAULT 1,
                UNIQUE KEY uniq_role_perm (role_name, perm_key)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }

        // menu/module toggles per role
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS staff_menu_toggles (
                id INT AUTO_INCREMENT PRIMARY KEY,
                role_name VARCHAR(50) NOT NULL,
                menu_key VARCHAR(80) NOT NULL,
                enabled TINYINT(1) NOT NULL DEFAULT 1,
                UNIQUE KEY uniq_role_menu (role_name, menu_key)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }

        // seed base roles
        $seedRoles = [
            ['Superadmin','Full system access'],
            ['Admin','System administrator'],
            ['Dean','Faculty-level management'],
            ['HOD','Department-level management'],
            ['Lecturer','Teaching staff (results)'],
            ['Course Adviser','Advises registrations'],
            ['Account Department','Finance & billing'],
            ['Librarian','Library management'],
            ['HR Officer','Human Resource Management'],
        ];
        foreach ($seedRoles as $r) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO staff_roles (role_name, description) VALUES (?, ?)");
                $st->execute([$r[0], $r[1]]);
            } catch (Throwable $e) { /* ignore */ }
        }
        try { portalSyncAdminSuperadminAssignments($pdo); } catch (Throwable $e) { /* ignore */ }

        // seed default permissions
        $defaultPerms = [
            'staff_users_manage',
            'staff_roles_manage',
            'academics_manage',
            'registrations_manage',
            'results_compute',
            'results_approve',
            'results_publish',
            'scoresheet_manage',
            'finance_manage',
            'library_manage',
            'cbt_manage',
            'lms_manage',
            'hrm_manage',
            'attendance_manage'
        ];

        $roleDefaults = [
            'Superadmin' => $defaultPerms,
            'Admin' => ['staff_users_manage','staff_roles_manage','academics_manage','registrations_manage','results_compute','results_approve','results_publish','scoresheet_manage','finance_manage','clearance_manage','library_manage','cbt_manage','lms_manage','hrm_manage','attendance_manage'],
            'Dean' => ['academics_manage','registrations_manage','results_approve','attendance_manage'],
            'HOD' => ['academics_manage','registrations_manage','results_approve','attendance_manage'],
            'Lecturer' => ['results_compute','attendance_manage'],
            'Course Adviser' => ['registrations_manage','attendance_manage'],
            'Account Department' => ['finance_manage'],
            'Librarian' => ['library_manage'],
            'HR Officer' => ['hrm_manage'],
        ];

        foreach ($roleDefaults as $role => $perms) {
            foreach ($perms as $pk) {
                try {
                    $st = $pdo->prepare("INSERT IGNORE INTO staff_permissions (role_name, perm_key, allowed) VALUES (?, ?, 1)");
                    $st->execute([$role, $pk]);
                } catch (Throwable $e) { /* ignore */ }
            }
        }

        // seed default menu toggles as enabled (only for menus we explicitly gate)
        $menuKeys = [
            'menu_admin_dashboard',
            'menu_admin_students',
            'menu_admin_applications',
            'menu_admin_add_application',
            'menu_admin_payments',
            'menu_admin_wallets',
            'menu_admin_settings',
            'menu_reports',
            'menu_messages',
            'menu_notifications',
            'menu_structure_manager',
            'menu_sessions_manager',
            'menu_registrable_courses',
            'menu_numbering_settings',
            'menu_registrations_manager',
            'menu_yet_to_register',
            'menu_registration_settings',
            'menu_grading_result_settings',
            'menu_results_manager',
            'menu_results_approvals',
            'menu_download_scoresheet',
            'menu_transcript_admin',
            'menu_staff_users',
            'menu_staff_roles',
            'menu_lms_admin',
            'menu_library_admin',
            'menu_cbt_admin',
            'menu_hrm',
            'menu_student_attendance_admin',
            'menu_previous_admission_record',
            'menu_admin_clearance',
            'menu_graduated',
            'menu_notification_settings',
            'menu_feature_toggles',
            'menu_pages'
        ];
        foreach (['Superadmin','Admin','Dean','HOD','Lecturer','Course Adviser','Account Department','Librarian','HR Officer'] as $role) {
            foreach ($menuKeys as $mk) {
                try {
                    $st = $pdo->prepare("INSERT IGNORE INTO staff_menu_toggles (role_name, menu_key, enabled) VALUES (?, ?, 1)");
                    $st->execute([$role, $mk]);
                } catch (Throwable $e) { /* ignore */ }
            }
        }

        // ensure admin superadmin flag exists and stays in sync with role labels
        try {
            if (function_exists('portalColumnExistsSafe') && !portalColumnExistsSafe($pdo, 'admins', 'is_superadmin')) {
                $pdo->exec("ALTER TABLE admins ADD COLUMN is_superadmin TINYINT(1) NOT NULL DEFAULT 0 AFTER role");
            }
        } catch (Throwable $e) { /* ignore */ }
        try {
            if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'is_superadmin')) {
                $pdo->exec("UPDATE admins SET is_superadmin = 1 WHERE LOWER(TRIM(COALESCE(role, ''))) IN ('superadmin','super admin','super-admin','super administrator','superadministrator')");
            }
        } catch (Throwable $e) { /* ignore */ }

        // ensure at least one Superadmin exists (upgrade first admin if none)
        try {
            $cnt = 0;
            if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'is_superadmin')) {
                $cnt = (int)$pdo->query("SELECT COUNT(*) FROM admins WHERE is_superadmin = 1 OR LOWER(TRIM(COALESCE(role,''))) IN ('superadmin','super admin','super-admin','super administrator','superadministrator')")->fetchColumn();
            } else {
                $cnt = (int)$pdo->query("SELECT COUNT(*) FROM admins WHERE LOWER(TRIM(COALESCE(role,''))) IN ('superadmin','super admin','super-admin','super administrator','superadministrator')")->fetchColumn();
            }
            if ($cnt <= 0) {
                $first = (int)$pdo->query("SELECT id FROM admins ORDER BY id ASC LIMIT 1")->fetchColumn();
                if ($first > 0) {
                    if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'is_superadmin')) {
                        $st = $pdo->prepare("UPDATE admins SET role = 'Superadmin', is_superadmin = 1 WHERE id = ?");
                    } else {
                        $st = $pdo->prepare("UPDATE admins SET role = 'Superadmin' WHERE id = ?");
                    }
                    $st->execute([$first]);
                }
            }
        } catch (Throwable $e) { /* ignore */ }
        try { portalSyncAdminSuperadminAssignments($pdo); } catch (Throwable $e) { /* ignore */ }
    }
}
if (!function_exists('hasStaffPermission')) {
    function hasStaffPermission(PDO $pdo, string $permKey): bool {
        if (!isAdmin()) return false;
        try { ensureStaffAclSchema($pdo); } catch (Throwable $e) { /* ignore */ }
        if (isSuperadmin($pdo)) return true;

        try {
            portalSyncAdminSuperadminAssignments($pdo);
        } catch (Throwable $e) { /* ignore */ }

        if (isSuperadmin($pdo)) return true;

        if (in_array($permKey, ['staff_roles_manage','staff_users_manage'], true)) {
            try {
                if (portalBootstrapCurrentAdminSuperadmin($pdo)) {
                    return true;
                }
            } catch (Throwable $e) { /* ignore */ }
        }

        $role = currentStaffRole($pdo);
        if ($role === '') return false;

        try {
            $st = $pdo->prepare("SELECT allowed FROM staff_permissions WHERE role_name = ? AND perm_key = ? LIMIT 1");
            $st->execute([$role, $permKey]);
            $val = $st->fetchColumn();
            if ($val === false) return false;
            return (int)$val === 1;
        } catch (Throwable $e) {
            return false;
        }
    }
}
if (!function_exists('requireStaffPermission')) {
    function requireStaffPermission(PDO $pdo, string $permKey): void {
        if (!hasStaffPermission($pdo, $permKey)) {
            try {
                ensureStaffAclSchema($pdo);
                portalSyncAdminSuperadminAssignments($pdo);
                if (in_array($permKey, ['staff_roles_manage','staff_users_manage'], true)) {
                    portalBootstrapCurrentAdminSuperadmin($pdo);
                }
            } catch (Throwable $e) { /* ignore */ }
            if (hasStaffPermission($pdo, $permKey)) {
                return;
            }
            flash('danger', 'Access denied. Permission required: ' . $permKey);
            redirect('?page=admin_dashboard');
        }
    }
}
if (!function_exists('menuEnabled')) {
    function menuEnabled(PDO $pdo, string $menuKey): bool {
        if (!isAdmin()) return false;
        if (isSuperadmin($pdo)) return true;
        $role = currentStaffRole($pdo);
        if ($role === '') return false;
        try {
            $st = $pdo->prepare("SELECT enabled FROM staff_menu_toggles WHERE role_name = ? AND menu_key = ? LIMIT 1");
            $st->execute([$role, $menuKey]);
            $val = $st->fetchColumn();
            if ($val === false) return true; // default visible
            return (int)$val === 1;
        } catch (Throwable $e) {
            return true;
        }
    }


// =========================
// Messaging & Infrastructure (Self-healing)
// =========================
if (!function_exists('ensureMessagingInfrastructure')) {
    function ensureMessagingInfrastructure(PDO $pdo): void {
        // Notification settings (email/sms + providers)
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS notification_settings (
                id INT AUTO_INCREMENT PRIMARY KEY,
                email_enabled TINYINT(1) NOT NULL DEFAULT 0,
                sms_enabled TINYINT(1) NOT NULL DEFAULT 0,
                from_email VARCHAR(120) NULL,
                from_name VARCHAR(120) NULL,
                smtp_host VARCHAR(120) NULL,
                smtp_port INT NULL,
                smtp_user VARCHAR(120) NULL,
                smtp_pass VARCHAR(255) NULL,
                smtp_secure VARCHAR(10) NULL,
                sms_provider VARCHAR(40) NULL,
                sms_api_url VARCHAR(255) NULL,
                sms_api_key VARCHAR(255) NULL,
                sms_sender_id VARCHAR(40) NULL,
                sms_test_number VARCHAR(30) NULL,
                sms_timeout_seconds INT NULL,
                sms_retry_attempts INT NULL,
                sms_sender_number VARCHAR(40) NULL,
                bulksmsnigeria_base_url VARCHAR(255) NULL,
                bulksmsnigeria_sandbox_url VARCHAR(255) NULL,
                bulksmsnigeria_api_token VARCHAR(255) NULL,
                bulksmsnigeria_sender_id VARCHAR(40) NULL,
                bulksmsnigeria_environment VARCHAR(20) NULL,
                termii_base_url VARCHAR(255) NULL,
                termii_api_key VARCHAR(255) NULL,
                termii_sender_id VARCHAR(40) NULL,
                termii_channel VARCHAR(30) NULL,
                termii_message_type VARCHAR(20) NULL,
                twilio_account_sid VARCHAR(80) NULL,
                twilio_api_key VARCHAR(80) NULL,
                twilio_api_key_secret VARCHAR(255) NULL,
                twilio_auth_token VARCHAR(255) NULL,
                twilio_from_number VARCHAR(40) NULL,
                twilio_messaging_service_sid VARCHAR(80) NULL,
                twilio_base_url VARCHAR(255) NULL,
                vonage_api_key VARCHAR(120) NULL,
                vonage_api_secret VARCHAR(255) NULL,
                vonage_sender_id VARCHAR(40) NULL,
                vonage_base_url VARCHAR(255) NULL,
                infobip_base_url VARCHAR(255) NULL,
                infobip_api_key VARCHAR(255) NULL,
                infobip_sender VARCHAR(40) NULL,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }

        $notificationCols = [
            'sms_test_number' => "ALTER TABLE notification_settings ADD COLUMN sms_test_number VARCHAR(30) NULL AFTER sms_sender_id",
            'sms_timeout_seconds' => "ALTER TABLE notification_settings ADD COLUMN sms_timeout_seconds INT NULL AFTER sms_test_number",
            'sms_retry_attempts' => "ALTER TABLE notification_settings ADD COLUMN sms_retry_attempts INT NULL AFTER sms_timeout_seconds",
            'sms_sender_number' => "ALTER TABLE notification_settings ADD COLUMN sms_sender_number VARCHAR(40) NULL AFTER sms_retry_attempts",
            'bulksmsnigeria_base_url' => "ALTER TABLE notification_settings ADD COLUMN bulksmsnigeria_base_url VARCHAR(255) NULL AFTER sms_sender_number",
            'bulksmsnigeria_sandbox_url' => "ALTER TABLE notification_settings ADD COLUMN bulksmsnigeria_sandbox_url VARCHAR(255) NULL AFTER bulksmsnigeria_base_url",
            'bulksmsnigeria_api_token' => "ALTER TABLE notification_settings ADD COLUMN bulksmsnigeria_api_token VARCHAR(255) NULL AFTER bulksmsnigeria_sandbox_url",
            'bulksmsnigeria_sender_id' => "ALTER TABLE notification_settings ADD COLUMN bulksmsnigeria_sender_id VARCHAR(40) NULL AFTER bulksmsnigeria_api_token",
            'bulksmsnigeria_environment' => "ALTER TABLE notification_settings ADD COLUMN bulksmsnigeria_environment VARCHAR(20) NULL AFTER bulksmsnigeria_sender_id",
            'termii_base_url' => "ALTER TABLE notification_settings ADD COLUMN termii_base_url VARCHAR(255) NULL AFTER bulksmsnigeria_environment",
            'termii_api_key' => "ALTER TABLE notification_settings ADD COLUMN termii_api_key VARCHAR(255) NULL AFTER termii_base_url",
            'termii_sender_id' => "ALTER TABLE notification_settings ADD COLUMN termii_sender_id VARCHAR(40) NULL AFTER termii_api_key",
            'termii_channel' => "ALTER TABLE notification_settings ADD COLUMN termii_channel VARCHAR(30) NULL AFTER termii_sender_id",
            'termii_message_type' => "ALTER TABLE notification_settings ADD COLUMN termii_message_type VARCHAR(20) NULL AFTER termii_channel",
            'twilio_account_sid' => "ALTER TABLE notification_settings ADD COLUMN twilio_account_sid VARCHAR(80) NULL AFTER termii_message_type",
            'twilio_api_key' => "ALTER TABLE notification_settings ADD COLUMN twilio_api_key VARCHAR(80) NULL AFTER twilio_account_sid",
            'twilio_api_key_secret' => "ALTER TABLE notification_settings ADD COLUMN twilio_api_key_secret VARCHAR(255) NULL AFTER twilio_api_key",
            'twilio_auth_token' => "ALTER TABLE notification_settings ADD COLUMN twilio_auth_token VARCHAR(255) NULL AFTER twilio_api_key_secret",
            'twilio_from_number' => "ALTER TABLE notification_settings ADD COLUMN twilio_from_number VARCHAR(40) NULL AFTER twilio_auth_token",
            'twilio_messaging_service_sid' => "ALTER TABLE notification_settings ADD COLUMN twilio_messaging_service_sid VARCHAR(80) NULL AFTER twilio_from_number",
            'twilio_base_url' => "ALTER TABLE notification_settings ADD COLUMN twilio_base_url VARCHAR(255) NULL AFTER twilio_messaging_service_sid",
            'vonage_api_key' => "ALTER TABLE notification_settings ADD COLUMN vonage_api_key VARCHAR(120) NULL AFTER twilio_base_url",
            'vonage_api_secret' => "ALTER TABLE notification_settings ADD COLUMN vonage_api_secret VARCHAR(255) NULL AFTER vonage_api_key",
            'vonage_sender_id' => "ALTER TABLE notification_settings ADD COLUMN vonage_sender_id VARCHAR(40) NULL AFTER vonage_api_secret",
            'vonage_base_url' => "ALTER TABLE notification_settings ADD COLUMN vonage_base_url VARCHAR(255) NULL AFTER vonage_sender_id",
            'infobip_base_url' => "ALTER TABLE notification_settings ADD COLUMN infobip_base_url VARCHAR(255) NULL AFTER vonage_base_url",
            'infobip_api_key' => "ALTER TABLE notification_settings ADD COLUMN infobip_api_key VARCHAR(255) NULL AFTER infobip_base_url",
            'infobip_sender' => "ALTER TABLE notification_settings ADD COLUMN infobip_sender VARCHAR(40) NULL AFTER infobip_api_key",
        ];
        foreach ($notificationCols as $column => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, 'notification_settings', $column)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) { /* ignore */ }
        }

        // Ensure single row exists
        try {
            $cnt = (int)$pdo->query("SELECT COUNT(*) FROM notification_settings")->fetchColumn();
            if ($cnt <= 0) {
                $pdo->exec("INSERT INTO notification_settings (email_enabled,sms_enabled,sms_timeout_seconds,sms_retry_attempts) VALUES (0,0,12,0)");
            }
        } catch (Throwable $e) { /* ignore */ }

        try {
            $pdo->exec("UPDATE notification_settings SET
                sms_timeout_seconds = CASE WHEN sms_timeout_seconds IS NULL OR sms_timeout_seconds <= 0 THEN 12 ELSE sms_timeout_seconds END,
                sms_retry_attempts = CASE WHEN sms_retry_attempts IS NULL OR sms_retry_attempts < 0 THEN 0 ELSE sms_retry_attempts END,
                bulksmsnigeria_base_url = COALESCE(NULLIF(TRIM(bulksmsnigeria_base_url), ''), 'https://www.bulksmsnigeria.com/api/v2'),
                bulksmsnigeria_sandbox_url = COALESCE(NULLIF(TRIM(bulksmsnigeria_sandbox_url), ''), 'https://www.bulksmsnigeria.com/api/sandbox/v2'),
                bulksmsnigeria_environment = COALESCE(NULLIF(TRIM(bulksmsnigeria_environment), ''), 'production'),
                termii_base_url = COALESCE(NULLIF(TRIM(termii_base_url), ''), 'https://api.ng.termii.com'),
                termii_channel = COALESCE(NULLIF(TRIM(termii_channel), ''), 'dnd'),
                termii_message_type = COALESCE(NULLIF(TRIM(termii_message_type), ''), 'plain'),
                twilio_base_url = COALESCE(NULLIF(TRIM(twilio_base_url), ''), 'https://api.twilio.com'),
                vonage_base_url = COALESCE(NULLIF(TRIM(vonage_base_url), ''), 'https://rest.nexmo.com'),
                infobip_base_url = COALESCE(NULLIF(TRIM(infobip_base_url), ''), 'https://api.infobip.com')
            ");
        } catch (Throwable $e) { /* ignore */ }

        // SMS logs
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS sms_logs (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_type VARCHAR(20) NULL,
                user_id INT NOT NULL DEFAULT 0,
                recipient_number VARCHAR(30) NOT NULL,
                provider_name VARCHAR(40) NOT NULL,
                event_type VARCHAR(120) NOT NULL DEFAULT 'general',
                context VARCHAR(190) NULL,
                request_time DATETIME NOT NULL,
                send_status VARCHAR(30) NOT NULL DEFAULT 'Failed',
                response_code VARCHAR(60) NULL,
                response_id VARCHAR(120) NULL,
                error_message TEXT NULL,
                meta_json LONGTEXT NULL,
                sent_at DATETIME NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                KEY idx_sms_user (user_type, user_id),
                KEY idx_sms_recipient (recipient_number),
                KEY idx_sms_event (event_type),
                KEY idx_sms_context (context),
                KEY idx_sms_created (created_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS sms_templates (
                id INT AUTO_INCREMENT PRIMARY KEY,
                template_key VARCHAR(80) NOT NULL UNIQUE,
                template_name VARCHAR(120) NOT NULL,
                message_text TEXT NOT NULL,
                is_active TINYINT(1) NOT NULL DEFAULT 1,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }

        try { portalEnsureNotificationTemplateSchema($pdo, 'email'); } catch (Throwable $e) { /* ignore */ }
        try { portalEnsureNotificationTemplateSchema($pdo, 'sms'); } catch (Throwable $e) { /* ignore */ }

        try { portalSynchronizeCompactSmsTemplates($pdo); } catch (Throwable $e) { /* ignore */ }

        // Student menu/module toggles (for feature toggles that affect students)
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS student_menu_toggles (
                id INT AUTO_INCREMENT PRIMARY KEY,
                menu_key VARCHAR(80) NOT NULL,
                enabled TINYINT(1) NOT NULL DEFAULT 1,
                UNIQUE KEY uniq_student_menu (menu_key)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }

        // Seed common student menus/modules as enabled by default
        $studentMenuKeys = [
            'menu_student_dashboard',
            'menu_student_application_form',
            'menu_student_course_registration',
            'menu_student_admission_letter',
            'menu_student_id_card',
            'menu_student_payments',
            'menu_student_wallet',
            'menu_student_registration_fee',
            'menu_student_acceptance_fee',
            'menu_student_hostel_fee',
            'menu_student_school_fee',
            'menu_student_payment_history',
            'menu_student_results',
            'menu_student_transcript',
            'menu_student_library',
            'menu_student_lms',
            'menu_student_cbt',
            'menu_student_messages',
            'menu_student_notifications',
            'menu_student_profile',
            'menu_student_clearance_form',
            'menu_student_attendance'
        ];
        foreach ($studentMenuKeys as $mk) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO student_menu_toggles (menu_key, enabled) VALUES (?, 1)");
                $st->execute([$mk]);
            } catch (Throwable $e) { /* ignore */ }
        }
    }
}
try { ensureMessagingInfrastructure($pdo); } catch (Throwable $e) { /* ignore */ }

if (!function_exists('getNotificationSettings')) {
    function getNotificationSettings(PDO $pdo): array {
        try {
            $row = $pdo->query("SELECT * FROM notification_settings ORDER BY id ASC LIMIT 1")->fetch(PDO::FETCH_ASSOC);
            return $row ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalSmsProviderOptions')) {
    function portalSmsProviderOptions(): array {
        return [
            'bulksmsnigeria' => 'BulkSMSNigeria',
            'termii' => 'Termii',
            'twilio' => 'Twilio',
            'vonage' => 'Vonage',
            'infobip' => 'Infobip',
        ];
    }
}

if (!function_exists('portalNormalizeSmsProvider')) {
    function portalNormalizeSmsProvider(string $provider): string {
        $provider = strtolower(trim($provider));
        $provider = str_replace([' ', '-', '_'], '', $provider);
        $map = [
            'bulksmsnigeria' => 'bulksmsnigeria',
            'bulksms' => 'bulksmsnigeria',
            'termii' => 'termii',
            'twilio' => 'twilio',
            'vonage' => 'vonage',
            'nexmo' => 'vonage',
            'infobip' => 'infobip',
        ];
        return $map[$provider] ?? '';
    }
}

if (!function_exists('portalSmsSettingsDefaults')) {
    function portalSmsSettingsDefaults(): array {
        return [
            'sms_enabled' => 0,
            'sms_provider' => 'termii',
            'sms_api_url' => '',
            'sms_api_key' => '',
            'sms_sender_id' => '',
            'sms_sender_number' => '',
            'sms_test_number' => '',
            'sms_timeout_seconds' => 12,
            'sms_retry_attempts' => 0,
            'bulksmsnigeria_base_url' => 'https://www.bulksmsnigeria.com/api/v2',
            'bulksmsnigeria_sandbox_url' => 'https://www.bulksmsnigeria.com/api/sandbox/v2',
            'bulksmsnigeria_api_token' => '',
            'bulksmsnigeria_sender_id' => '',
            'bulksmsnigeria_environment' => 'production',
            'termii_base_url' => 'https://api.ng.termii.com',
            'termii_api_key' => '',
            'termii_sender_id' => '',
            'termii_channel' => 'dnd',
            'termii_message_type' => 'plain',
            'twilio_account_sid' => '',
            'twilio_api_key' => '',
            'twilio_api_key_secret' => '',
            'twilio_auth_token' => '',
            'twilio_from_number' => '',
            'twilio_messaging_service_sid' => '',
            'twilio_base_url' => 'https://api.twilio.com',
            'vonage_api_key' => '',
            'vonage_api_secret' => '',
            'vonage_sender_id' => '',
            'vonage_base_url' => 'https://rest.nexmo.com',
            'infobip_base_url' => 'https://api.infobip.com',
            'infobip_api_key' => '',
            'infobip_sender' => '',
        ];
    }
}

if (!function_exists('portalSmsSettingsMerged')) {
    function portalSmsSettingsMerged(array $settings): array {
        return array_replace(portalSmsSettingsDefaults(), $settings);
    }
}

if (!function_exists('portalSmsCleanText')) {
    function portalSmsCleanText($value, int $maxLen = 255): string {
        $value = trim((string)$value);
        $value = preg_replace('/\s+/u', ' ', $value) ?: $value;
        if ($maxLen > 0 && function_exists('mb_substr')) {
            $value = mb_substr($value, 0, $maxLen, 'UTF-8');
        } elseif ($maxLen > 0) {
            $value = substr($value, 0, $maxLen);
        }
        return trim($value);
    }
}

if (!function_exists('portalSmsSinglePageLimit')) {
    function portalSmsSinglePageLimit(): int {
        return 150;
    }
}

if (!function_exists('portalSmsCharLength')) {
    function portalSmsCharLength(string $value): int {
        return function_exists('mb_strlen') ? mb_strlen($value, 'UTF-8') : strlen($value);
    }
}

if (!function_exists('portalSmsSubstrSafe')) {
    function portalSmsSubstrSafe(string $value, int $start, ?int $length = null): string {
        if (function_exists('mb_substr')) {
            return $length === null ? (string)mb_substr($value, $start, null, 'UTF-8') : (string)mb_substr($value, $start, $length, 'UTF-8');
        }
        return $length === null ? (string)substr($value, $start) : (string)substr($value, $start, $length);
    }
}

if (!function_exists('portalSmsTrimToLimit')) {
    function portalSmsTrimToLimit(string $value, int $limit): string {
        $value = trim($value);
        if ($limit <= 0 || portalSmsCharLength($value) <= $limit) {
            return $value;
        }
        $slice = trim(portalSmsSubstrSafe($value, 0, $limit));
        $spacePos = function_exists('mb_strrpos') ? mb_strrpos($slice, ' ', 0, 'UTF-8') : strrpos($slice, ' ');
        if ($spacePos !== false && $spacePos > (int)($limit * 0.55)) {
            $slice = trim(portalSmsSubstrSafe($slice, 0, (int)$spacePos));
        }
        $slice = rtrim($slice, " ,;:-");
        return trim($slice);
    }
}

if (!function_exists('portalSmsCommonShorteners')) {
    function portalSmsCommonShorteners(): array {
        return [
            '/\bYour wallet was credited with\b/iu' => 'Wallet CR ',
            '/\bYour wallet was debited with\b/iu' => 'Wallet DR ',
            '/\bWallet credited\b/iu' => 'Wallet CR',
            '/\bWallet debited\b/iu' => 'Wallet DR',
            '/\bWallet updated\b/iu' => 'Wallet update',
            '/\bPayment receipt\b/iu' => 'Payment',
            '/\bPayment successful(?:ly)?\b/iu' => 'Payment OK',
            '/\bPayment has been received successfully\b/iu' => 'Payment OK',
            '/\bApplication has been submitted successfully\b/iu' => 'Application submitted',
            '/\bYour application has been submitted successfully\b/iu' => 'Application submitted',
            '/\bApplication status has changed\b/iu' => 'Status update',
            '/\bAdmission has been granted\b/iu' => 'Admission offered',
            '/\bAdmission offer has been accepted successfully\b/iu' => 'Admission accepted',
            '/\bCourse form has been submitted successfully\b/iu' => 'Course form submitted',
            '/\bLogin notification\b/iu' => 'Login alert',
            '/\bPrevious balance\b/iu' => 'Prev',
            '/\bNew balance\b/iu' => 'Bal',
            '/\bCurrent balance\b/iu' => 'Bal',
            '/\bTotal balance remaining\b/iu' => 'Bal',
            '/\bOutstanding balance\b/iu' => 'Outstd',
            '/\bAmount paid\b/iu' => 'Amt',
            '/\bAmount\b/iu' => 'Amt',
            '/\bPayment item\b/iu' => 'Fee',
            '/\bPayment reference\b/iu' => 'Ref',
            '/\bReference\b/iu' => 'Ref',
            '/\bRelated reference\b/iu' => 'Ref',
            '/\bTransaction reference\b/iu' => 'Ref',
            '/\bTransaction type\b/iu' => 'Type',
            '/\bDate\/Time\b/iu' => 'Date',
            '/\bDate and Time\b/iu' => 'Date',
            '/\bNarration\b/iu' => 'Narr',
            '/\bRemarks?\b/iu' => 'Note',
            '/\bDescription\b/iu' => 'Info',
            '/\bTemporary password\b/iu' => 'Pwd',
            '/\bLogin URL\b/iu' => 'Login',
            '/\bApplication Number\b/iu' => 'App No',
            '/\bAdmission Number\b/iu' => 'Adm No',
            '/\bCourse of Study\b/iu' => 'Course',
            '/\bSemester\b/iu' => 'Sem',
            '/\bSession\b/iu' => 'Sess',
            '/\bValid for\b/iu' => 'Valid',
            '/\bminutes\b/iu' => 'mins',
            '/\bminute\b/iu' => 'min',
            '/\bKindly\b/iu' => '',
            '/\bPlease\b/iu' => '',
            '/\bRegards\b/iu' => '',
            '/\bThank you\b/iu' => '',
            '/\bimmediately\b/iu' => 'now',
            '/\bcontinue from your dashboard\b/iu' => 'Login to portal',
            '/\blogin to continue\b/iu' => 'Login',
            '/\blogin to portal\b/iu' => 'Login',
            '/\bportal account\b/iu' => 'account',
            '/\s*\|\s*/u' => '. ',
            '/\s*;\s*/u' => '. ',
        ];
    }
}

if (!function_exists('portalSmsApplyShorteners')) {
    function portalSmsApplyShorteners(string $message): string {
        foreach (portalSmsCommonShorteners() as $pattern => $replacement) {
            $message = preg_replace($pattern, $replacement, $message) ?: $message;
        }
        $message = preg_replace('/^([A-Za-z][A-Za-z0-9 &\/_-]{2,40})\s*-\s*([A-Za-z][A-Za-z0-9 &\/_-]{2,40})\s*:\s*\1\s*/u', '$2: $1 ', $message) ?: $message;
        $message = preg_replace('/\bHonttech\b/i', 'HontTech', $message) ?: $message;
        $message = preg_replace('/\bNaira\b/iu', 'NGN', $message) ?: $message;
        $message = str_replace(['₦', '€', '£'], ['NGN ', 'EUR ', 'GBP '], $message);
        $message = preg_replace('/\?(\d)/u', 'NGN $1', $message) ?: $message;
        $message = preg_replace('/\s+/u', ' ', $message) ?: $message;
        $message = preg_replace('/\s*([:;,.])\s*/u', '$1 ', $message) ?: $message;
        $message = preg_replace('/([:;,.]){2,}/u', '$1', $message) ?: $message;
        $message = preg_replace('/(\b[A-Za-z][A-Za-z0-9 ]{1,24}:)\s+\1/u', '$1 ', $message) ?: $message;
        return trim($message, " \t\n\r\0\x0B.,;");
    }
}

if (!function_exists('portalSmsSectionPriority')) {
    function portalSmsSectionPriority(string $section): int {
        $s = strtolower(trim($section));
        if ($s === '') return -1000;
        $score = 300;
        if (preg_match('/\botp\b|\bcode\b/u', $s)) $score += 1000;
        if (preg_match('/\b(wallet|payment|application|admission|course form|registration|clearance|result|login|account)\b/u', $s)) $score += 800;
        if (preg_match('/\b(success|successful|submitted|approved|accepted|declined|rejected|credited|debited|updated|offered|alert)\b/u', $s)) $score += 500;
        if (preg_match('/\b(ngn|amt|amount|fee|bal|balance|outstd|otp)\b/u', $s) || preg_match('/\b\d[\d,\.]*\b/u', $s)) $score += 420;
        if (preg_match('/\b(ref|reference|txn|receipt)\b/u', $s)) $score += 260;
        if (preg_match('/\b(date|time|sess|session|sem|semester|status)\b/u', $s)) $score += 220;
        if (preg_match('/\b(narr|note|info|remark|regards|thanks?)\b/u', $s)) $score -= 320;
        if (preg_match('/\blogin\b/u', $s) && preg_match('/\bportal\b/u', $s)) $score -= 60;
        return $score;
    }
}

if (!function_exists('portalSmsJoinSections')) {
    function portalSmsJoinSections(string $prefix, array $sections): string {
        $cleanSections = [];
        foreach ($sections as $section) {
            $section = trim((string)$section);
            if ($section !== '') {
                $cleanSections[] = rtrim($section, " .");
            }
        }
        $body = implode('. ', $cleanSections);
        $body = trim($body);
        if ($prefix !== '') {
            $body = trim($prefix) . ($body !== '' ? ' ' . $body : '');
        }
        $body = preg_replace('/\s+/u', ' ', $body) ?: $body;
        return trim($body, " \t\n\r\0\x0B.");
    }
}

if (!function_exists('portalSmsCompressSectionsToLimit')) {
    function portalSmsCompressSectionsToLimit(string $message, int $limit): string {
        $message = trim($message);
        if ($message === '' || portalSmsCharLength($message) <= $limit) {
            return $message;
        }

        $prefix = '';
        $body = $message;
        if (preg_match('/^\s*([^:.!?]{1,28}:)\s*(.+)$/u', $message, $m)) {
            $prefix = trim((string)$m[1]);
            $body = trim((string)$m[2]);
        }

        $body = preg_replace('/\s*(?:\r\n|\r|\n)+\s*/u', '. ', $body) ?: $body;
        $body = preg_replace('/\s*;\s*/u', '. ', $body) ?: $body;
        $sections = preg_split('/\s*(?:\.(?=\s|$))\s*/u', $body, -1, PREG_SPLIT_NO_EMPTY) ?: [$body];
        $sections = array_values(array_filter(array_map(static function($section) {
            return trim((string)$section);
        }, $sections), static function($section) {
            return $section !== '';
        }));

        if (!$sections) {
            $sections = [$body];
        }

        $current = portalSmsJoinSections($prefix, $sections);
        while (count($sections) > 1 && portalSmsCharLength($current) > $limit) {
            $dropIndex = null;
            $dropPriority = PHP_INT_MAX;
            $dropLength = -1;
            foreach ($sections as $i => $section) {
                $priority = portalSmsSectionPriority($section);
                $length = portalSmsCharLength($section);
                if ($dropIndex === null || $priority < $dropPriority || ($priority === $dropPriority && $length > $dropLength)) {
                    $dropIndex = $i;
                    $dropPriority = $priority;
                    $dropLength = $length;
                }
            }
            if ($dropIndex === null) {
                break;
            }
            array_splice($sections, (int)$dropIndex, 1);
            $current = portalSmsJoinSections($prefix, $sections);
        }

        if (portalSmsCharLength($current) <= $limit) {
            return $current;
        }

        $aggressive = [
            '/\bhas been\b/iu' => '',
            '/\bwas\b/iu' => '',
            '/\byour\b/iu' => '',
            '/\ba new\b/iu' => 'new',
            '/\bfor the\b/iu' => 'for',
            '/\bfor your\b/iu' => 'for',
            '/\bthe exact\b/iu' => '',
            '/\bdo not\b/iu' => "don't",
            '/\bhas not\b/iu' => "not",
            '/\s+/u' => ' ',
        ];
        foreach ($aggressive as $pattern => $replacement) {
            $current = preg_replace($pattern, $replacement, $current) ?: $current;
        }
        $current = trim($current, " \t\n\r\0\x0B.,;");
        if (portalSmsCharLength($current) <= $limit) {
            return $current;
        }

        return portalSmsTrimToLimit($current, $limit);
    }
}

if (!function_exists('portalSmsNormalizeTemplateForStorage')) {
    function portalSmsNormalizeTemplateForStorage(PDO $pdo, string $message, string $templateKey = 'general'): string {
        $message = html_entity_decode((string)$message, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $message = preg_replace('/<\s*br\s*\/?>/iu', ' ', $message) ?: $message;
        $message = strip_tags($message);
        $message = str_replace(["\r\n", "\r", "\n"], ' ', $message);
        $message = portalSmsApplyShorteners($message);
        $message = preg_replace('/\bDear\s+[^,:\n]{0,60},\s*/iu', '', $message) ?: $message;
        $message = preg_replace('/\bHello\s+[^,:\n]{0,60},\s*/iu', '', $message) ?: $message;
        $message = preg_replace('/\bHi\s+[^,:\n]{0,60},\s*/iu', '', $message) ?: $message;

        $portalName = trim((string)portalNotificationMailSubjectPrefix($pdo));
        if ($portalName === '') {
            $portalName = 'HontTech';
        }
        $hasPortal = (stripos($message, $portalName) !== false) || (stripos($message, '{{portal_name}}') !== false);
        if (!$hasPortal && trim($message) !== '') {
            $message = '{{portal_name}}: ' . ltrim($message, ': ');
        }

        $message = portalSmsApplyShorteners($message);
        $message = portalSmsCompressSectionsToLimit($message, portalSmsSinglePageLimit());
        $message = trim($message);
        return $message !== '' ? $message : '{{portal_name}}: {{message}}';
    }
}

if (!function_exists('portalSmsEnforceSinglePage')) {
    function portalSmsEnforceSinglePage(PDO $pdo, string $message, string $eventType = 'general', string $context = ''): string {
        $raw = (string)$message;
        $eventType = trim($eventType) !== '' ? trim($eventType) : portalSmsEventTypeFromContext($context);
        $eventType = portalNormalizeNotificationCommandKey($eventType);

        if (in_array($eventType, ['login_otp', 'reset_otp'], true)) {
            $portalName = trim((string)portalNotificationMailSubjectPrefix($pdo));
            if ($portalName === '') {
                $portalName = 'HontTech';
            }
            $code = '';
            if (preg_match('/\b(\d{4,10})\b/u', $raw, $m)) {
                $code = (string)$m[1];
            }
            $mins = '';
            if (preg_match('/\b(\d{1,3})\s*(?:mins?|minutes?)\b/iu', $raw, $m)) {
                $mins = (string)$m[1];
            }
            if ($code !== '') {
                $otpMsg = $portalName . ': OTP ' . $code . ($mins !== '' ? '. Valid ' . $mins . ' mins.' : '.');
                return portalSmsCompressSectionsToLimit(portalSmsApplyShorteners($otpMsg), portalSmsSinglePageLimit());
            }
        }

        $normalized = portalSmsNormalizeTemplateForStorage($pdo, $raw, $eventType);
        return portalSmsCompressSectionsToLimit(portalSmsApplyShorteners($normalized), portalSmsSinglePageLimit());
    }
}

if (!function_exists('portalCompactSmsTemplateDefaults')) {
    function portalCompactSmsTemplateDefaults(): array {
        return [
            'application_submitted' => ['Application Submitted', '{{portal_name}}: Application submitted. {{message}}'],
            'admission_given' => ['Admission Update', '{{portal_name}}: Admission update. {{message}}'],
            'admission_offer_accepted' => ['Admission Accepted', '{{portal_name}}: Admission accepted. {{message}}'],
            'application_status_update' => ['Application Status Update', '{{portal_name}}: Status update. {{message}}'],
            'course_form_submitted' => ['Course Form Submitted', '{{portal_name}}: Course form submitted. {{message}}'],
            'payment_success' => ['Payment Successful', '{{portal_name}}: Payment update. {{message}}'],
            'wallet_credited' => ['Wallet Credited', '{{portal_name}}: Wallet CR. {{message}}'],
            'wallet_debited' => ['Wallet Debited', '{{portal_name}}: Wallet DR. {{message}}'],
            'wallet_updated' => ['Wallet Updated', '{{portal_name}}: Wallet update. {{message}}'],
            'login_notification' => ['Login Notification', '{{portal_name}}: Login alert. {{message}}'],
            'login_otp' => ['Login OTP', '{{portal_name}}: {{message}}'],
            'reset_otp' => ['Reset OTP', '{{portal_name}}: {{message}}'],
            'staff_account_created' => ['Staff Account Created', '{{portal_name}}: Account update. {{message}}'],
            'assigned_payment' => ['Assigned Payment', '{{portal_name}}: Payment due. {{message}}'],
            'admission_letter' => ['Admission Letter', '{{portal_name}}: Admission update. {{message}}'],
            'id_card_generated' => ['ID Card Generated', '{{portal_name}}: ID card update. {{message}}'],
            'test_sms' => ['Test SMS', '{{portal_name}}: {{message}}'],
            'general' => ['General SMS', '{{portal_name}}: {{message}}'],
        ];
    }
}

if (!function_exists('portalSynchronizeCompactSmsTemplates')) {
    function portalSynchronizeCompactSmsTemplates(PDO $pdo): void {
        portalEnsureNotificationTemplateSchema($pdo, 'sms');
        $defaults = portalCompactSmsTemplateDefaults();

        foreach ($defaults as $commandKey => $meta) {
            $normalizedKey = portalNormalizeNotificationCommandKey($commandKey);
            $templateName = trim((string)($meta[0] ?? 'SMS Template'));
            $messageText = portalSmsNormalizeTemplateForStorage($pdo, (string)($meta[1] ?? '{{portal_name}}: {{message}}'), $normalizedKey);
            $candidates = portalNotificationCommandCandidates($normalizedKey);
            if (!$candidates) {
                $candidates = [$normalizedKey];
            }
            try {
                $placeholders = implode(',', array_fill(0, count($candidates), '?'));
                $sql = "SELECT id, is_active, is_enabled, status, template_name
                        FROM sms_templates
                        WHERE command_key IN ({$placeholders})
                           OR event_key IN ({$placeholders})
                           OR template_key IN ({$placeholders})
                        ORDER BY id ASC LIMIT 1";
                $params = array_merge($candidates, $candidates, $candidates);
                $st = $pdo->prepare($sql);
                $st->execute($params);
                $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) {
                $row = [];
            }

            $enabled = isset($row['id']) ? (portalTemplateRowEnabled($row) ? 1 : 0) : 1;
            $status = $enabled === 1 ? 'enabled' : 'disabled';

            try {
                if (!empty($row['id'])) {
                    $st = $pdo->prepare("UPDATE sms_templates
                        SET template_key = ?, command_key = ?, event_key = ?, template_name = ?, message_text = ?, body = ?, content = ?, is_active = ?, is_enabled = ?, status = ?
                        WHERE id = ?");
                    $st->execute([$normalizedKey, $normalizedKey, $normalizedKey, $templateName, $messageText, $messageText, $messageText, $enabled, $enabled, $status, (int)$row['id']]);
                } else {
                    $st = $pdo->prepare("INSERT INTO sms_templates (template_key, command_key, event_key, template_name, message_text, body, content, is_active, is_enabled, status)
                        VALUES (?, ?, ?, ?, ?, ?, ?, 1, 1, 'enabled')");
                    $st->execute([$normalizedKey, $normalizedKey, $normalizedKey, $templateName, $messageText, $messageText, $messageText]);
                }
            } catch (Throwable $e) {
            }
        }

        try {
            $rows = $pdo->query("SELECT * FROM sms_templates ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
            $st = $pdo->prepare("UPDATE sms_templates
                SET command_key = ?, event_key = ?, template_key = ?, message_text = ?, body = ?, content = ?, template_name = ?, is_active = ?, is_enabled = ?, status = ?
                WHERE id = ?");
            $catalog = portalNotificationCommandCatalog();
            foreach ($rows as $row) {
                $id = (int)($row['id'] ?? 0);
                if ($id <= 0) {
                    continue;
                }
                $commandKey = portalTemplateRowCommandKey($row);
                if ($commandKey === '') {
                    $commandKey = 'general';
                }
                $templateName = trim((string)($row['template_name'] ?? ''));
                if ($templateName === '' && isset($catalog[$commandKey])) {
                    $templateName = $catalog[$commandKey];
                }
                if ($templateName === '') {
                    $templateName = 'SMS Template';
                }
                $messageText = trim((string)($row['message_text'] ?? $row['body'] ?? $row['content'] ?? ''));
                $messageText = portalSmsNormalizeTemplateForStorage($pdo, $messageText !== '' ? $messageText : '{{portal_name}}: {{message}}', $commandKey);
                $enabled = portalTemplateRowEnabled($row) ? 1 : 0;
                $status = $enabled === 1 ? 'enabled' : 'disabled';
                $st->execute([$commandKey, $commandKey, $commandKey, $messageText, $messageText, $messageText, $templateName, $enabled, $enabled, $status, $id]);
            }
        } catch (Throwable $e) {
        }
    }
}

if (!function_exists('portalSmsSecretPostValue')) {
    function portalSmsSecretPostValue(array $post, array $current, string $key): string {
        $incoming = trim((string)($post[$key] ?? ''));
        if ($incoming === '') {
            return trim((string)($current[$key] ?? ''));
        }
        return $incoming;
    }
}

if (!function_exists('portalSmsNormalizePhone')) {
    function portalSmsNormalizePhone(string $phone): string {
        $phone = trim($phone);
        if ($phone === '') return '';
        $hasPlus = str_starts_with($phone, '+');
        $digits = preg_replace('/\D+/', '', $phone);
        if ($digits === '') return '';
        if (str_starts_with($digits, '00')) {
            $digits = substr($digits, 2);
        }
        if (strlen($digits) === 11 && str_starts_with($digits, '0')) {
            $digits = '234' . substr($digits, 1);
        }
        if (strlen($digits) < 8 || strlen($digits) > 15) {
            return '';
        }
        return $hasPlus ? '+' . $digits : $digits;
    }
}

if (!function_exists('portalSmsFormatPhoneForProvider')) {
    function portalSmsFormatPhoneForProvider(string $phone, string $provider): string {
        $normalized = portalSmsNormalizePhone($phone);
        if ($normalized === '') return '';
        $digits = ltrim($normalized, '+');
        return portalNormalizeSmsProvider($provider) === 'twilio' ? '+' . $digits : $digits;
    }
}

if (!function_exists('portalSmsValidateSettings')) {
    function portalSmsValidateSettings(array $settings, bool $onlyIfEnabled = true): array {
        $settings = portalSmsSettingsMerged($settings);
        $errors = [];
        $enabled = (int)($settings['sms_enabled'] ?? 0) === 1;
        if ($onlyIfEnabled && !$enabled) {
            return $errors;
        }
        $provider = portalNormalizeSmsProvider((string)($settings['sms_provider'] ?? ''));
        if ($provider === '') {
            return ['Please select a valid active SMS provider.'];
        }
        switch ($provider) {
            case 'bulksmsnigeria':
                if (trim((string)$settings['bulksmsnigeria_api_token']) === '') $errors[] = 'BulkSMSNigeria API Token is required.';
                if (trim((string)$settings['bulksmsnigeria_sender_id']) === '') $errors[] = 'BulkSMSNigeria Sender ID / From is required.';
                $env = strtolower(trim((string)$settings['bulksmsnigeria_environment']));
                if (!in_array($env, ['production', 'sandbox'], true)) $errors[] = 'BulkSMSNigeria environment must be Production or Sandbox.';
                if ($env === 'sandbox' && trim((string)$settings['bulksmsnigeria_sandbox_url']) === '') $errors[] = 'BulkSMSNigeria Sandbox URL is required when Sandbox is selected.';
                if ($env !== 'sandbox' && trim((string)$settings['bulksmsnigeria_base_url']) === '') $errors[] = 'BulkSMSNigeria Base URL is required.';
                break;
            case 'termii':
                if (trim((string)$settings['termii_base_url']) === '') $errors[] = 'Termii Base URL is required.';
                if (trim((string)$settings['termii_api_key']) === '') $errors[] = 'Termii API Key is required.';
                if (trim((string)$settings['termii_sender_id']) === '') $errors[] = 'Termii Sender ID is required.';
                if (trim((string)$settings['termii_channel']) === '') $errors[] = 'Termii Channel / Route is required.';
                if (trim((string)$settings['termii_message_type']) === '') $errors[] = 'Termii Message Type is required.';
                break;
            case 'twilio':
                if (trim((string)$settings['twilio_base_url']) === '') $errors[] = 'Twilio Base URL is required.';
                if (trim((string)$settings['twilio_account_sid']) === '') $errors[] = 'Twilio Account SID is required.';
                $hasApiKeyPair = trim((string)$settings['twilio_api_key']) !== '' && trim((string)$settings['twilio_api_key_secret']) !== '';
                $hasAuthToken = trim((string)$settings['twilio_auth_token']) !== '';
                if (!$hasApiKeyPair && !$hasAuthToken) $errors[] = 'Twilio requires either API Key + API Key Secret or Auth Token fallback.';
                if (trim((string)$settings['twilio_from_number']) === '' && trim((string)$settings['twilio_messaging_service_sid']) === '') {
                    $errors[] = 'Twilio requires either a From Number or a Messaging Service SID.';
                }
                break;
            case 'vonage':
                if (trim((string)$settings['vonage_base_url']) === '') $errors[] = 'Vonage Base URL is required.';
                if (trim((string)$settings['vonage_api_key']) === '') $errors[] = 'Vonage API Key is required.';
                if (trim((string)$settings['vonage_api_secret']) === '') $errors[] = 'Vonage API Secret is required.';
                if (trim((string)$settings['vonage_sender_id']) === '') $errors[] = 'Vonage Sender ID / From is required.';
                break;
            case 'infobip':
                if (trim((string)$settings['infobip_base_url']) === '') $errors[] = 'Infobip Base URL is required.';
                if (trim((string)$settings['infobip_api_key']) === '') $errors[] = 'Infobip API Key is required.';
                if (trim((string)$settings['infobip_sender']) === '') $errors[] = 'Infobip Sender is required.';
                break;
        }
        $timeout = (int)($settings['sms_timeout_seconds'] ?? 0);
        $retry = (int)($settings['sms_retry_attempts'] ?? 0);
        if ($timeout < 5 || $timeout > 60) $errors[] = 'SMS timeout must be between 5 and 60 seconds.';
        if ($retry < 0 || $retry > 5) $errors[] = 'SMS retry attempts must be between 0 and 5.';
        return $errors;
    }
}

if (!function_exists('portalSmsEventTypeFromContext')) {
    function portalSmsEventTypeFromContext(string $context): string {
        $ctx = strtolower(trim($context));
        if (str_starts_with($ctx, 'receipt:')) return 'payment_receipt';
        if (str_starts_with($ctx, 'application_status_update:')) return 'application_status_change';
        if (str_starts_with($ctx, 'application_submission:')) return 'application_submission';
        if (str_starts_with($ctx, 'admission_granted:')) return 'admission_granted';
        if (str_starts_with($ctx, 'course_form_first_submit:')) return 'course_form_submitted';
        if (str_starts_with($ctx, 'login_alert:')) return 'login_alert';
        if (str_starts_with($ctx, 'login_otp')) return 'login_otp';
        if (str_starts_with($ctx, 'reset_otp')) return 'reset_otp';
        if (str_starts_with($ctx, 'wallet_credit')) return 'wallet_credit';
        if (str_starts_with($ctx, 'wallet_debit')) return 'wallet_debit';
        if (str_starts_with($ctx, 'wallet_update')) return 'wallet_update';
        if (str_starts_with($ctx, 'staff_account_created:')) return 'staff_account_created';
        if ($ctx === 'admission_offer_accepted') return 'admission_offer_accepted';
        return $ctx !== '' ? $ctx : 'general';
    }
}

if (!function_exists('portalSmsStripHtml')) {
    function portalSmsStripHtml(string $subject, string $bodyHtml): string {
        $body = preg_replace('/<\s*br\s*\/?>/i', "\n", $bodyHtml);
        $body = preg_replace('/<\/(p|div|li|h\d)>/i', "\n", (string)$body);
        $body = html_entity_decode(strip_tags((string)$body), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $body = preg_replace('/[ \t]+/u', ' ', $body) ?: $body;
        $body = preg_replace('/\n{2,}/u', "\n", $body) ?: $body;
        $body = trim($body);
        $subject = trim($subject);
        $message = $subject !== '' ? ($subject . ': ' . $body) : $body;
        return trim($message);
    }
}

if (!function_exists('portalSmsSafeErrorMessage')) {
    function portalSmsSafeErrorMessage(string $message): string {
        $message = trim($message);
        if ($message === '') return 'SMS request failed.';
        $message = preg_replace('/(Bearer\s+)[A-Za-z0-9._\-]+/i', '$1[REDACTED]', $message) ?: $message;
        $message = preg_replace('/([A-Za-z_\-]*token[A-Za-z_\-]*\s*[:=]\s*)([^\s,;]+)/i', '$1[REDACTED]', $message) ?: $message;
        $message = preg_replace('/([A-Za-z_\-]*secret[A-Za-z_\-]*\s*[:=]\s*)([^\s,;]+)/i', '$1[REDACTED]', $message) ?: $message;
        return portalSmsCleanText($message, 500);
    }
}

if (!function_exists('portalSmsHttpRequest')) {
    function portalSmsHttpRequest(string $method, string $url, array $headers = [], $payload = null, int $timeout = 12, bool $asForm = false, int $retryAttempts = 0): array {
        $attempts = max(1, $retryAttempts + 1);
        $last = ['ok' => false, 'http_code' => 0, 'body' => '', 'json' => null, 'error' => 'SMS request not sent.'];
        for ($i = 0; $i < $attempts; $i++) {
            $ch = curl_init($url);
            $opts = [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_CUSTOMREQUEST => strtoupper($method),
                CURLOPT_TIMEOUT => max(5, min(60, $timeout)),
                CURLOPT_CONNECTTIMEOUT => max(5, min(30, $timeout)),
                CURLOPT_HTTPHEADER => $headers,
            ];
            if ($payload !== null) {
                if ($asForm && is_array($payload)) {
                    $opts[CURLOPT_POSTFIELDS] = http_build_query($payload);
                } elseif (is_array($payload)) {
                    $opts[CURLOPT_POSTFIELDS] = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
                } else {
                    $opts[CURLOPT_POSTFIELDS] = (string)$payload;
                }
            }
            curl_setopt_array($ch, $opts);
            $body = curl_exec($ch);
            $curlError = curl_error($ch);
            $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            $decoded = null;
            if (is_string($body) && trim($body) !== '') {
                $decoded = json_decode($body, true);
                if (!is_array($decoded)) $decoded = null;
            }
            $last = [
                'ok' => ($curlError === '' && $httpCode >= 200 && $httpCode < 300),
                'http_code' => $httpCode,
                'body' => is_string($body) ? $body : '',
                'json' => $decoded,
                'error' => $curlError,
            ];
            if ($last['ok']) {
                return $last;
            }
        }
        return $last;
    }
}

if (!function_exists('logSmsSend')) {
    function logSmsSend(PDO $pdo, string $userType, int $userId, string $recipientNumber, string $providerName, string $eventType, string $context, bool $success, string $responseCode = '', string $responseId = '', string $errorMessage = '', array $meta = []): void {
        try {
            ensureMessagingInfrastructure($pdo);
            $stmt = $pdo->prepare("INSERT INTO sms_logs (user_type, user_id, recipient_number, provider_name, event_type, context, request_time, send_status, response_code, response_id, error_message, meta_json, sent_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)");
            $stmt->execute([
                $userType !== '' ? $userType : null,
                max(0, $userId),
                $recipientNumber,
                $providerName !== '' ? $providerName : 'unknown',
                $eventType !== '' ? $eventType : 'general',
                $context !== '' ? $context : null,
                date('Y-m-d H:i:s'),
                $success ? 'Sent' : 'Failed',
                $responseCode !== '' ? $responseCode : null,
                $responseId !== '' ? $responseId : null,
                $errorMessage !== '' ? portalSmsSafeErrorMessage($errorMessage) : null,
                !empty($meta) ? json_encode($meta, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : null,
                $success ? date('Y-m-d H:i:s') : null,
            ]);
        } catch (Throwable $e) { /* ignore */ }
    }
}

if (!function_exists('smsAlreadySent')) {
    function smsAlreadySent(PDO $pdo, string $userType, int $userId, string $context, int $withinSeconds = 86400): bool {
        if ($context === '') return false;
        try {
            ensureMessagingInfrastructure($pdo);
            $stmt = $pdo->prepare("SELECT id FROM sms_logs WHERE COALESCE(user_type,'') = ? AND user_id = ? AND context = ? AND send_status = 'Sent' AND created_at >= (NOW() - INTERVAL ? SECOND) LIMIT 1");
            $stmt->execute([$userType, max(0, $userId), $context, max(1, $withinSeconds)]);
            return (bool)$stmt->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('portalResolveNotificationPhone')) {
    function portalResolveNotificationPhone(PDO $pdo, string $userType, int $userId, array $contextRow = []): string {
        foreach (['mobile_phone', 'phone', 'phone_number', 'phone_no', 'mobile', 'mobile_number', 'residential_phone'] as $col) {
            if (!empty($contextRow[$col])) {
                return trim((string)$contextRow[$col]);
            }
        }
        if ($userId <= 0) return '';
        try {
            if ($userType === 'student') {
                $cols = [];
                foreach (['mobile_phone', 'phone', 'phone_number', 'phone_no', 'mobile', 'mobile_number', 'residential_phone'] as $col) {
                    if (portalColumnExistsSafe($pdo, 'students', $col)) $cols[] = $col;
                }
                if (!$cols) return '';
                $sql = "SELECT " . implode(', ', array_map(static fn($c) => "COALESCE($c,'') AS $c", $cols)) . " FROM students WHERE id = ? LIMIT 1";
                $st = $pdo->prepare($sql);
                $st->execute([$userId]);
                $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                foreach ($cols as $col) {
                    if (!empty($row[$col])) return trim((string)$row[$col]);
                }
            }
            if ($userType === 'admin') {
                if (portalTableExistsSafe($pdo, 'hr_staff') && portalColumnExistsSafe($pdo, 'hr_staff', 'phone') && portalColumnExistsSafe($pdo, 'hr_staff', 'admin_id')) {
                    $st = $pdo->prepare("SELECT phone FROM hr_staff WHERE admin_id = ? AND COALESCE(phone,'') <> '' ORDER BY id ASC LIMIT 1");
                    $st->execute([$userId]);
                    $phone = trim((string)($st->fetchColumn() ?: ''));
                    if ($phone !== '') return $phone;
                }
            }
        } catch (Throwable $e) {}
        return '';
    }
}

if (!function_exists('portalSendSmsViaProvider')) {
    function portalSendSmsViaProvider(array $settings, string $phone, string $message): array {
        $settings = portalSmsSettingsMerged($settings);
        $provider = portalNormalizeSmsProvider((string)($settings['sms_provider'] ?? ''));
        $timeout = max(5, min(60, (int)($settings['sms_timeout_seconds'] ?? 12)));
        $retryAttempts = max(0, min(5, (int)($settings['sms_retry_attempts'] ?? 0)));
        if ($provider === '') {
            return ['ok' => false, 'provider' => 'unknown', 'http_code' => 0, 'response_code' => '', 'response_id' => '', 'error' => 'No active SMS provider selected.', 'meta' => []];
        }

        switch ($provider) {
            case 'bulksmsnigeria':
                $env = strtolower(trim((string)$settings['bulksmsnigeria_environment']));
                $baseUrl = trim((string)($env === 'sandbox' ? $settings['bulksmsnigeria_sandbox_url'] : $settings['bulksmsnigeria_base_url']));
                $url = rtrim($baseUrl, '/') . '/sms';
                $payload = [
                    'from' => trim((string)$settings['bulksmsnigeria_sender_id']),
                    'to' => portalSmsFormatPhoneForProvider($phone, $provider),
                    'body' => $message,
                ];
                $resp = portalSmsHttpRequest('POST', $url, [
                    'Authorization: Bearer ' . trim((string)$settings['bulksmsnigeria_api_token']),
                    'Content-Type: application/json',
                    'Accept: application/json',
                ], $payload, $timeout, false, $retryAttempts);
                $json = is_array($resp['json'] ?? null) ? $resp['json'] : [];
                $status = strtolower(trim((string)($json['status'] ?? '')));
                $successFlag = $json['success'] ?? null;
                $messageId = trim((string)($json['data']['message_id'] ?? $json['data']['id'] ?? $json['message_id'] ?? $json['id'] ?? ''));
                $ok = false;
                if (!empty($resp['ok'])) {
                    if ($successFlag !== null) {
                        $ok = filter_var($successFlag, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE);
                        if ($ok === null) {
                            $ok = in_array(strtolower(trim((string)$successFlag)), ['1', 'true', 'yes', 'ok', 'success'], true);
                        }
                    }
                    if (!$ok && $status !== '') {
                        $ok = in_array($status, ['success', 'ok', 'queued', 'accepted'], true);
                    }
                    if (!$ok && $messageId !== '') {
                        $ok = true;
                    }
                    if (!$ok && empty($json) && (int)($resp['http_code'] ?? 0) >= 200 && (int)($resp['http_code'] ?? 0) < 300) {
                        $ok = true;
                    }
                }
                return [
                    'ok' => $ok,
                    'provider' => 'BulkSMSNigeria',
                    'http_code' => (int)($resp['http_code'] ?? 0),
                    'response_code' => $status !== '' ? $status : (!empty($successFlag) ? 'success' : (string)($resp['http_code'] ?? '')),
                    'response_id' => $messageId,
                    'error' => $ok ? '' : (string)($json['message'] ?? $json['error'] ?? $resp['error'] ?? 'BulkSMSNigeria request failed.'),
                    'meta' => ['environment' => $env !== '' ? $env : 'production'],
                ];

            case 'termii':
                $url = rtrim(trim((string)$settings['termii_base_url']), '/') . '/api/sms/send';
                $payload = [
                    'api_key' => trim((string)$settings['termii_api_key']),
                    'to' => portalSmsFormatPhoneForProvider($phone, $provider),
                    'from' => trim((string)$settings['termii_sender_id']),
                    'sms' => $message,
                    'channel' => trim((string)$settings['termii_channel']),
                    'type' => trim((string)$settings['termii_message_type']),
                ];
                $resp = portalSmsHttpRequest('POST', $url, ['Content-Type: application/json', 'Accept: application/json'], $payload, $timeout, false, $retryAttempts);
                $json = is_array($resp['json'] ?? null) ? $resp['json'] : [];
                $code = strtolower(trim((string)($json['code'] ?? '')));
                $ok = ($resp['ok'] && $code === 'ok' && trim((string)($json['message_id'] ?? $json['message_id_str'] ?? '')) !== '');
                return [
                    'ok' => $ok,
                    'provider' => 'Termii',
                    'http_code' => (int)($resp['http_code'] ?? 0),
                    'response_code' => (string)($json['code'] ?? $resp['http_code'] ?? ''),
                    'response_id' => (string)($json['message_id'] ?? $json['message_id_str'] ?? ''),
                    'error' => $ok ? '' : (string)($json['message'] ?? $resp['error'] ?? 'Termii request failed.'),
                    'meta' => ['channel' => trim((string)$settings['termii_channel']), 'type' => trim((string)$settings['termii_message_type'])],
                ];

            case 'twilio':
                $baseUrl = rtrim(trim((string)$settings['twilio_base_url']), '/');
                $accountSid = trim((string)$settings['twilio_account_sid']);
                $url = $baseUrl . '/2010-04-01/Accounts/' . rawurlencode($accountSid) . '/Messages.json';
                $username = trim((string)$settings['twilio_api_key']);
                $password = trim((string)$settings['twilio_api_key_secret']);
                if ($username === '' || $password === '') {
                    $username = $accountSid;
                    $password = trim((string)$settings['twilio_auth_token']);
                }
                $payload = [
                    'To' => portalSmsFormatPhoneForProvider($phone, $provider),
                    'Body' => $message,
                ];
                $from = trim((string)$settings['twilio_from_number']);
                $messagingServiceSid = trim((string)$settings['twilio_messaging_service_sid']);
                if ($messagingServiceSid !== '') {
                    $payload['MessagingServiceSid'] = $messagingServiceSid;
                } elseif ($from !== '') {
                    $payload['From'] = $from;
                }
                $resp = portalSmsHttpRequest('POST', $url, [
                    'Authorization: Basic ' . base64_encode($username . ':' . $password),
                    'Content-Type: application/x-www-form-urlencoded',
                    'Accept: application/json',
                ], $payload, $timeout, true, $retryAttempts);
                $json = is_array($resp['json'] ?? null) ? $resp['json'] : [];
                $status = strtolower(trim((string)($json['status'] ?? '')));
                $ok = ($resp['ok'] && trim((string)($json['sid'] ?? '')) !== '' && !in_array($status, ['failed', 'undelivered', 'canceled'], true));
                return [
                    'ok' => $ok,
                    'provider' => 'Twilio',
                    'http_code' => (int)($resp['http_code'] ?? 0),
                    'response_code' => (string)($json['error_code'] ?? $resp['http_code'] ?? ''),
                    'response_id' => (string)($json['sid'] ?? ''),
                    'error' => $ok ? '' : (string)($json['message'] ?? $json['error_message'] ?? $resp['error'] ?? 'Twilio request failed.'),
                    'meta' => ['status' => $status !== '' ? $status : 'unknown'],
                ];

            case 'vonage':
                $baseUrl = rtrim(trim((string)$settings['vonage_base_url']), '/');
                $url = $baseUrl . '/sms/json';
                $payload = [
                    'from' => trim((string)$settings['vonage_sender_id']),
                    'to' => portalSmsFormatPhoneForProvider($phone, $provider),
                    'text' => $message,
                    'api_key' => trim((string)$settings['vonage_api_key']),
                    'api_secret' => trim((string)$settings['vonage_api_secret']),
                ];
                $resp = portalSmsHttpRequest('POST', $url, ['Content-Type: application/x-www-form-urlencoded', 'Accept: application/json'], $payload, $timeout, true, $retryAttempts);
                $json = is_array($resp['json'] ?? null) ? $resp['json'] : [];
                $msg0 = (isset($json['messages'][0]) && is_array($json['messages'][0])) ? $json['messages'][0] : [];
                $status = (string)($msg0['status'] ?? '');
                $ok = ($resp['ok'] && $status === '0');
                return [
                    'ok' => $ok,
                    'provider' => 'Vonage',
                    'http_code' => (int)($resp['http_code'] ?? 0),
                    'response_code' => $status !== '' ? $status : (string)($resp['http_code'] ?? ''),
                    'response_id' => (string)($msg0['message-id'] ?? $msg0['messageId'] ?? ''),
                    'error' => $ok ? '' : (string)($msg0['error-text'] ?? $resp['error'] ?? 'Vonage request failed.'),
                    'meta' => [],
                ];

            case 'infobip':
                $baseUrl = rtrim(trim((string)$settings['infobip_base_url']), '/');
                $url = $baseUrl . '/sms/3/messages';
                $payload = [
                    'messages' => [[
                        'sender' => trim((string)$settings['infobip_sender']),
                        'destinations' => [['to' => portalSmsFormatPhoneForProvider($phone, $provider)]],
                        'content' => ['text' => $message],
                    ]],
                ];
                $resp = portalSmsHttpRequest('POST', $url, [
                    'Authorization: App ' . trim((string)$settings['infobip_api_key']),
                    'Content-Type: application/json',
                    'Accept: application/json',
                ], $payload, $timeout, false, $retryAttempts);
                $json = is_array($resp['json'] ?? null) ? $resp['json'] : [];
                $msg0 = (isset($json['messages'][0]) && is_array($json['messages'][0])) ? $json['messages'][0] : [];
                $statusName = strtoupper(trim((string)($msg0['status']['name'] ?? '')));
                $groupName = strtoupper(trim((string)($msg0['status']['groupName'] ?? '')));
                $ok = ($resp['ok'] && trim((string)($msg0['messageId'] ?? '')) !== '' && !in_array($groupName, ['REJECTED', 'UNDELIVERABLE'], true));
                return [
                    'ok' => $ok,
                    'provider' => 'Infobip',
                    'http_code' => (int)($resp['http_code'] ?? 0),
                    'response_code' => (string)($msg0['status']['id'] ?? $resp['http_code'] ?? ''),
                    'response_id' => (string)($msg0['messageId'] ?? ''),
                    'error' => $ok ? '' : (string)($msg0['status']['description'] ?? $resp['error'] ?? 'Infobip request failed.'),
                    'meta' => ['status' => $statusName !== '' ? $statusName : $groupName],
                ];
        }

        return ['ok' => false, 'provider' => 'unknown', 'http_code' => 0, 'response_code' => '', 'response_id' => '', 'error' => 'Unsupported SMS provider.', 'meta' => []];
    }
}

if (!function_exists('sendSmsNotification')) {
    function sendSmsNotification(PDO $pdo, string $toPhone, string $message, string $eventType = 'general', string $context = '', string $userType = '', int $userId = 0): bool {
        $GLOBALS['portal_last_sms_error'] = '';
        $settings = portalSmsSettingsMerged(getNotificationSettings($pdo));
        $provider = portalNormalizeSmsProvider((string)($settings['sms_provider'] ?? ''));
        $eventType = trim($eventType) !== '' ? trim($eventType) : portalSmsEventTypeFromContext($context);
        $eventType = portalNormalizeNotificationCommandKey($eventType);
        $formatted = $provider !== '' ? portalSmsFormatPhoneForProvider($toPhone, $provider) : portalSmsNormalizePhone($toPhone);
        $templateDecision = portalResolveNotificationTemplateDecision($pdo, 'sms', $eventType);
        if (($templateDecision['status'] ?? '') !== 'enabled') {
            logSmsSend($pdo, $userType, $userId, $formatted !== '' ? $formatted : trim($toPhone), $provider !== '' ? ucfirst($provider) : 'unknown', $eventType, $context, false, '', '', (string)($templateDecision['reason'] ?? 'SMS template unavailable.'), ['template_status' => (string)($templateDecision['status'] ?? 'missing')]);
            $GLOBALS['portal_last_sms_error'] = (string)($templateDecision['reason'] ?? 'SMS template unavailable.');
            return false;
        }
        if ((int)($settings['sms_enabled'] ?? 0) !== 1) {
            logSmsSend($pdo, $userType, $userId, $formatted !== '' ? $formatted : trim($toPhone), $provider !== '' ? ucfirst($provider) : 'unknown', $eventType, $context, false, '', '', 'SMS notifications are disabled.');
            $GLOBALS['portal_last_sms_error'] = 'SMS notifications are disabled.';
            return false;
        }
        $validationErrors = portalSmsValidateSettings($settings, true);
        if (!empty($validationErrors)) {
            logSmsSend($pdo, $userType, $userId, $formatted !== '' ? $formatted : trim($toPhone), $provider !== '' ? ucfirst($provider) : 'unknown', $eventType, $context, false, '', '', implode(' ', $validationErrors));
            $GLOBALS['portal_last_sms_error'] = implode(' ', $validationErrors);
            return false;
        }
        if ($formatted === '') {
            logSmsSend($pdo, $userType, $userId, trim($toPhone), $provider !== '' ? ucfirst($provider) : 'unknown', $eventType, $context, false, '', '', 'Recipient phone number is missing or invalid.');
            $GLOBALS['portal_last_sms_error'] = 'Recipient phone number is missing or invalid.';
            return false;
        }
        $message = portalSmsEnforceSinglePage($pdo, trim($message), $eventType, $context);
        if ($message === '') {
            logSmsSend($pdo, $userType, $userId, $formatted, $provider !== '' ? ucfirst($provider) : 'unknown', $eventType, $context, false, '', '', 'SMS message body is empty.');
            $GLOBALS['portal_last_sms_error'] = 'SMS message body is empty.';
            return false;
        }
        $result = portalSendSmsViaProvider($settings, $formatted, $message);
        logSmsSend(
            $pdo,
            $userType,
            $userId,
            $formatted,
            (string)($result['provider'] ?? ($provider !== '' ? ucfirst($provider) : 'unknown')),
            $eventType,
            $context,
            !empty($result['ok']),
            (string)($result['response_code'] ?? ($result['http_code'] ?? '')),
            (string)($result['response_id'] ?? ''),
            (string)($result['error'] ?? ''),
            array_merge(['http_code' => (int)($result['http_code'] ?? 0), 'template_status' => 'enabled'], (array)($result['meta'] ?? []))
        );
        $GLOBALS['portal_last_sms_error'] = !empty($result['ok']) ? '' : (string)($result['error'] ?? 'Unable to send SMS.');
        return !empty($result['ok']);
    }
}

if (!function_exists('addNotification')) {
    function addNotification(PDO $pdo, string $userType, int $userId, string $title, string $message): void {
        // Try to insert into existing notifications table if present
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS notifications (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_type VARCHAR(20) NOT NULL,
                user_id INT NOT NULL,
                title VARCHAR(200) NOT NULL,
                message TEXT NOT NULL,
                is_read TINYINT(1) NOT NULL DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) { /* ignore */ }

        try {
            $st = $pdo->prepare("INSERT INTO notifications (user_type,user_id,title,message,is_read) VALUES (?,?,?,?,0)");
            $st->execute([$userType, $userId, $title, $message]);
        } catch (Throwable $e) { /* ignore */ }
    }
}

if (!function_exists('sendEmailNotification')) {
    function sendEmailNotification(PDO $pdo, string $toEmail, string $subject, string $body): bool {
        $s = getNotificationSettings($pdo);
        if (empty($s) || (int)($s['email_enabled'] ?? 0) !== 1) return false;

        $fromEmail = $s['from_email'] ?? '';
        $fromName  = $s['from_name'] ?? '';
        $headers = "MIME-Version: 1.0\r\nContent-type:text/html;charset=UTF-8\r\n";
        if ($fromEmail) {
            $from = $fromName ? "{$fromName} <{$fromEmail}>" : $fromEmail;
            $headers .= "From: {$from}\r\n";
        }

        // Lightweight default: PHP mail(). (SMTP fields are stored for future upgrade)
        try {
            return @mail($toEmail, $subject, $body, $headers);
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('studentMenuEnabled')) {
    function studentMenuEnabled(PDO $pdo, string $menuKey): bool {
        if (!isLoggedIn() || isAdmin()) return false;
        try {
            $st = $pdo->prepare("SELECT enabled FROM student_menu_toggles WHERE menu_key = ? LIMIT 1");
            $st->execute([$menuKey]);
            $v = $st->fetchColumn();
            if ($v === false) return true; // default visible
            return (int)$v === 1;
        } catch (Throwable $e) {
            return true;
        }
    }
}

if (!function_exists('featureToggleCatalogStaff')) {
    function featureToggleCatalogStaff(): array {
        return [
            'menu_staff_users' => 'Staff Users',
            'menu_staff_roles' => 'Roles & Permissions',
            'menu_grading_result_settings' => 'Grading & Result Settings',
            'menu_results_manager' => 'Manage Results',
            'menu_results_approvals' => 'Result Approvals',
            'menu_download_scoresheet' => 'Download Scoresheet',
            'menu_transcript_admin' => 'Transcript',
            'menu_numbering_settings' => 'Admission / Matric Numbering',
            'menu_notification_settings' => 'Email/SMS Settings',
            'menu_feature_toggles' => 'Feature Toggles',
            'menu_pages' => 'Pages',
            'menu_student_attendance_admin' => 'Student Attendance',
            'menu_previous_admission_record' => 'Previous Admission Record',
            'menu_admin_clearance' => 'Admin Clearance',
            'menu_academic_levels' => 'Academic Levels',
            'menu_academic_levels' => 'Academic Levels',
            'menu_graduated' => 'Graduated',
        ];
    }
}

if (!function_exists('featureToggleCatalogStudent')) {
    function featureToggleCatalogStudent(): array {
        return [
            'menu_student_dashboard' => 'Dashboard',
            'menu_student_application_form' => 'Application Form',
            'menu_student_course_registration' => 'Course Registration',
            'menu_student_admission_letter' => 'Admission Letter',
            'menu_student_id_card' => 'Student ID Card',
            'menu_student_payments' => 'Payments Overview',
            'menu_student_wallet' => 'Wallet',
            'menu_student_registration_fee' => 'Registration Fee',
            'menu_student_acceptance_fee' => 'Acceptance Fee',
            'menu_student_hostel_fee' => 'Hostel Fee',
            'menu_student_school_fee' => 'School Fee',
            'menu_student_payment_history' => 'Payment History',
            'menu_student_results' => 'Results / CGPA',
            'menu_student_transcript' => 'Transcripts',
            'menu_student_library' => 'Library',
            'menu_student_lms' => 'LMS',
            'menu_student_cbt' => 'CBT',
            'menu_student_messages' => 'Messages',
            'menu_student_notifications' => 'Notifications',
            'menu_student_profile' => 'Profile',
            'menu_student_clearance_form' => 'Clearance Form',
            'menu_student_attendance' => 'Attendance',
        ];
    }
}



// ------------------------------
// Module Self-Healing Schemas (LMS / Library / CBT)
// ------------------------------
if (!function_exists('tableHasColumn')) {
    function tableHasColumn(PDO $pdo, string $table, string $column): bool {
        try {
            $st = $pdo->query("SHOW COLUMNS FROM `{$table}` LIKE " . $pdo->quote($column));
            return (bool)$st->fetch();
        } catch (Throwable $e) {
            return false;
        }
    }
}


if (!function_exists('portalSearchBarSettingKey')) {
    function portalSearchBarSettingKey(): string {
        return 'portal_search_bar_enabled';
    }
}

if (!function_exists('portalSearchBarEnabled')) {
    function portalSearchBarEnabled(PDO $pdo): bool {
        return strtolower(trim((string)getSystemSettingValue($pdo, portalSearchBarSettingKey(), 'enabled'))) === 'enabled';
    }
}

if (!function_exists('portalSearchPushEntry')) {
    function portalSearchPushEntry(array &$bag, string $label, string $url, string $kind = 'Feature/Page', array $keywords = [], array $aliases = [], string $parent = ''): void {
        $label = trim($label);
        $url = trim($url);
        $kind = trim($kind) !== '' ? trim($kind) : 'Feature/Page';
        $parent = trim($parent);
        if ($label === '' || $url === '') return;
        $dedupeKey = strtolower($kind . '|' . $label . '|' . $url);
        if (isset($bag[$dedupeKey])) return;
        $allTerms = array_values(array_unique(array_filter(array_map(static function($v): string {
            return trim((string)$v);
        }, array_merge([$label, $parent], $keywords, $aliases)))));
        $bag[$dedupeKey] = [
            'label' => $label,
            'url' => $url,
            'kind' => $kind,
            'parent' => $parent,
            'keywords' => implode(' ', $allTerms),
            'aliases' => implode(' ', array_values(array_unique(array_filter(array_map('trim', $aliases))))),
        ];
    }
}

if (!function_exists('portalBuildSearchEntriesForCurrentUser')) {
    function portalBuildSearchEntriesForCurrentUser(PDO $pdo): array {
        if (!isLoggedIn()) return [];
        $entries = [];
        $push = static function(array &$bag, string $label, string $url, string $kind = 'Feature/Page', array $keywords = [], array $aliases = [], string $parent = ''): void {
            portalSearchPushEntry($bag, $label, $url, $kind, $keywords, $aliases, $parent);
        };

        if (isAdmin()) {
            $push($entries, 'Dashboard', '?page=admin_dashboard', 'Main Menu', ['home','overview','admin home']);

            $push($entries, 'Applications', '?page=admin_applications', 'Main Menu', ['admissions','applications','application manager']);
            $push($entries, 'Add Application', '?page=admin_add_application', 'Sub Menu', ['new application','create application','manual application'], ['apply','new applicant'], 'Applications');

            $push($entries, 'Students', '?page=admin_students', 'Main Menu', ['student records','manage students','student management']);
            $push($entries, 'Staff Users', '?page=staff_users', 'Feature/Page', ['staff','users','accounts'], ['staff portal users','staff accounts'], 'Settings');
            $push($entries, 'Staff Roles', '?page=staff_roles', 'Feature/Page', ['roles','permissions','access control'], ['role manager','staff permissions'], 'Settings');

            $push($entries, 'Faculty/Dept/Programme', '?page=admin_structure_manager', 'Main Menu', ['academics','structure','departments','programmes','faculty']);
            $push($entries, 'Academic Session/Promotion Manager', '?page=admin_sessions_manager', 'Sub Menu', ['session','promotion','academic calendar'], ['session manager','promotion manager'], 'Academics');
            $push($entries, 'Registrable Courses', '?page=admin_courses_manager', 'Sub Menu', ['courses','course manager','course registration'], ['courses'], 'Academics');
            $push($entries, 'Registered Students', '?page=admin_registrations_manager', 'Sub Menu', ['registrations','registered students','course registrations'], ['registered'], 'Academics');
            $push($entries, 'Yet To Register Students', '?page=admin_yet_to_register', 'Sub Menu', ['pending registration','not registered','yet to register'], ['unregistered'], 'Academics');
            $push($entries, 'Registration Settings', '?page=admin_registration_settings', 'Sub Menu', ['registration config','registration settings'], ['registration'], 'Academics');
            if (menuEnabled($pdo, 'menu_numbering_settings')) {
                $push($entries, 'Admission / Matric Numbering', '?page=admin_numbering_settings', 'Sub Menu', ['numbering','admission number','matric number'], ['numbering settings'], 'Academics');
                $push($entries, 'Change Department/Matric No.', '?page=admin_matric_verifier', 'Feature/Page', ['verify matric','matric check','change matric number','change department'], ['verifier'], 'Academics');
            }

            $push($entries, 'Fees Manager', '?page=admin_fees_manager', 'Main Menu', ['payments','fees','fee setup','billing']);
            $push($entries, 'General Fee Setup', '?page=admin_fees_manager&fees_tab=general_setup', 'Sub Menu', ['fees setup','general fees'], ['fee setup'], 'Fees Manager');
            $push($entries, 'School Fees', '?page=admin_fees_manager&fees_tab=school_fees&fee_type=school', 'Sub Menu', ['school fee','tuition'], ['school fees','tuition fees'], 'Fees Manager');
            $push($entries, 'Acceptance Fees', '?page=admin_fees_manager&fees_tab=acceptance_fees&fee_type=acceptance', 'Sub Menu', ['acceptance fee'], ['acceptance'], 'Fees Manager');
            $push($entries, 'Registration Fees', '?page=admin_fees_manager&fees_tab=registration_fees&fee_type=registration', 'Sub Menu', ['registration fee'], ['registration payment'], 'Fees Manager');
            $push($entries, 'Hostel Fees', '?page=admin_fees_manager&fees_tab=hostel_fees&fee_type=hostel', 'Sub Menu', ['hostel fee','accommodation fee'], ['hostel'], 'Fees Manager');
            $push($entries, 'Assigned / Custom Fees', '?page=admin_fees_manager&fees_tab=assigned_custom_fees&fee_type=custom', 'Sub Menu', ['assigned fees','custom fees'], ['custom fee'], 'Fees Manager');
            $push($entries, 'Late Fee', '?page=admin_fees_manager&fees_tab=late_fee', 'Sub Menu', ['late fees','penalty'], ['penalty'], 'Fees Manager');
            $push($entries, 'Wallet Settings', '?page=admin_fees_manager&fees_tab=wallet_settings', 'Sub Menu', ['wallet config','wallet'], ['wallet'], 'Fees Manager');
            $push($entries, 'Receipts / Payment Notifications', '?page=admin_fees_manager&fees_tab=receipts_notifications', 'Sub Menu', ['receipts','payment notifications'], ['receipt settings'], 'Fees Manager');
            $push($entries, 'Fee Scope / Filters', '?page=admin_fees_manager&fees_tab=fee_scope_filters', 'Sub Menu', ['fee scope','filters'], ['scope filters'], 'Fees Manager');
            $push($entries, 'Payment Gateway', '?page=admin_fees_manager&fees_tab=payment_gateways', 'Sub Menu', ['gateway','paystack','flutterwave','monnify'], ['payment gateways'], 'Fees Manager');
            $push($entries, 'Manage Payments', '?page=admin_payments', 'Feature/Page', ['payments','transactions','payment records'], ['payment list'], 'Fees Manager');
            $push($entries, 'Wallet Ledger', '?page=admin_wallet', 'Feature/Page', ['wallet ledger','wallet history','wallet transactions'], ['wallet'], 'Fees Manager');

            if (menuEnabled($pdo, 'menu_admin_clearance') && portalAdminCanManageClearance($pdo)) {
                $push($entries, 'Admin Clearance', '?page=admin_clearance', 'Main Menu', ['clearance','semester clearance','clearance management']);
                $push($entries, 'Clearance Verification', '?page=admin_clearance', 'Feature/Page', ['verify clearance','clearance receipt'], ['clearance verify'], 'Admin Clearance');
            }

            if (menuEnabled($pdo, 'menu_grading_result_settings')) {
                $push($entries, 'Grading & Result Settings', '?page=grading_result_settings', 'Main Menu', ['grade settings','result settings','grading']);
                $push($entries, 'CA Settings', '?page=grading_result_settings#grading_ca', 'Sub Menu', ['continuous assessment','ca'], ['min ca','minimum ca','maximum ca'], 'Grading & Result Settings');
                $push($entries, 'EXAM Settings', '?page=grading_result_settings#grading_exam', 'Sub Menu', ['exam'], ['maximum exam'], 'Grading & Result Settings');
                $push($entries, 'GRADE Settings', '?page=grading_result_settings#grading_grade', 'Sub Menu', ['grade scale','pass mark'], ['grades'], 'Grading & Result Settings');
                $push($entries, 'CGP Settings', '?page=grading_result_settings#grading_cgp', 'Sub Menu', ['cgp'], ['grade point'], 'Grading & Result Settings');
                $push($entries, 'CGPA Settings', '?page=grading_result_settings#grading_cgpa', 'Sub Menu', ['cgpa','classification'], ['class of degree'], 'Grading & Result Settings');
            }
            if (menuEnabled($pdo, 'menu_results_manager')) {
                $push($entries, 'Manage Results', '?page=results_manager', 'Main Menu', ['results','input result','result manager']);
                $push($entries, 'Input Results', '?page=results_manager', 'Sub Menu', ['result entry','input scores'], ['enter result'], 'Manage Results');
            }
            if (menuEnabled($pdo, 'menu_results_approvals')) $push($entries, 'Result Approvals', '?page=results_approvals', 'Sub Menu', ['approve results'], ['approvals'], 'Manage Results');
            if (menuEnabled($pdo, 'menu_download_scoresheet')) $push($entries, 'Download Scoresheet', '?page=download_scoresheet', 'Sub Menu', ['scoresheet','download result','print scoresheet'], ['scoresheet'], 'Manage Results');
            if (menuEnabled($pdo, 'menu_transcript_admin')) {
                $push($entries, 'Transcript', '?page=admin_transcript', 'Main Menu', ['transcript']);
                $push($entries, 'Transcript Tab', '?page=admin_transcript&tab=transcript', 'Sub Menu', ['transcript setup'], ['transcript'], 'Transcript');
                $push($entries, 'Rules & Aggregation', '?page=admin_transcript&tab=rules', 'Sub Menu', ['aggregation','session summary','semester aggregation'], ['rules'], 'Transcript');
                $push($entries, 'Display Fields', '?page=admin_transcript&tab=display', 'Sub Menu', ['visible fields','transcript fields'], ['display'], 'Transcript');
                $push($entries, 'Approval / Signature', '?page=admin_transcript&tab=approval', 'Sub Menu', ['signature','approval'], ['approvals'], 'Transcript');
            }

            if (menuEnabled($pdo, 'menu_notification_settings')) {
                $push($entries, 'Notification Settings', '?page=notification_settings', 'Main Menu', ['notifications','email','sms']);
                $push($entries, 'Email Templates', '?page=notification_settings&tab=templates', 'Sub Menu', ['email templates','mail templates'], ['templates'], 'Notification Settings');
                $push($entries, 'SMS Templates', '?page=notification_settings&tab=sms_templates', 'Sub Menu', ['sms templates','text messages'], ['sms'], 'Notification Settings');
                $push($entries, 'Email / SMS Settings', '?page=admin_settings', 'Feature/Page', ['mail setup','sms settings','communication settings'], ['email settings','sms settings'], 'Notification Settings');
            }

            if (menuEnabled($pdo, 'menu_pages')) {
                $push($entries, 'Pages', '?page=pages', 'Main Menu', ['page manager','page settings']);
                $push($entries, 'Transcript Page', '?page=pages&tab=transcript', 'Sub Menu', ['transcript page'], ['transcript'], 'Pages');
            }
            if (menuEnabled($pdo, 'menu_admin_settings')) {
                $push($entries, 'Settings', '?page=admin_settings', 'Main Menu', ['portal settings','configuration']);
                $push($entries, 'Theme/Branding', '?page=theme_branding', 'Main Menu', ['branding','theme colors','portal colors','theme branding'], ['color settings','theme settings','branding settings'], 'Settings');
                $push($entries, 'Feature Toggles', '?page=feature_toggles', 'Sub Menu', ['menus','feature switches'], ['feature toggle'], 'Settings');
                $push($entries, 'Portal Search Bar', '?page=admin_settings#portal-search-bar-control', 'Feature/Page', ['search bar','search'], ['portal search'], 'Settings');
                $push($entries, 'Email Settings', '?page=admin_settings#email-settings', 'Feature/Page', ['email config','smtp','mail settings'], ['mail settings'], 'Settings');
                $push($entries, 'SMS Settings', '?page=admin_settings#sms-settings', 'Feature/Page', ['sms config','text message settings'], ['sms gateway'], 'Settings');
            }
        } else {
            $push($entries, 'Dashboard', '?page=student_dashboard', 'Main Menu', ['home','student home']);
            if (studentMenuEnabled($pdo, 'menu_student_application_form')) $push($entries, 'Application Form', '?page=application_form', 'Main Menu', ['application','form']);
            if (studentMenuEnabled($pdo, 'menu_student_course_registration')) $push($entries, 'Course Registration', '?page=student_course_registration', 'Main Menu', ['register courses','course form']);
            if (studentMenuEnabled($pdo, 'menu_student_admission_letter')) $push($entries, 'Admission Letter', '?page=admission_letter', 'Main Menu', ['offer letter']);
            if (studentMenuEnabled($pdo, 'menu_student_id_card')) $push($entries, 'Student ID Card', '?page=student_id_card', 'Main Menu', ['id card']);
            if (studentMenuEnabled($pdo, 'menu_student_payments')) {
                $push($entries, 'Payment', '?page=payment', 'Main Menu', ['fees','payments']);
                if (studentMenuEnabled($pdo, 'menu_student_registration_fee')) $push($entries, 'Registration Fee', '?page=payment&tab=registration', 'Sub Menu', ['registration payment'], ['registration fee'], 'Payment');
                if (studentMenuEnabled($pdo, 'menu_student_acceptance_fee')) $push($entries, 'Acceptance Fee', '?page=payment&tab=acceptance', 'Sub Menu', ['acceptance payment'], ['acceptance fee'], 'Payment');
                if (studentMenuEnabled($pdo, 'menu_student_hostel_fee')) $push($entries, 'Hostel Fee', '?page=payment&tab=hostel', 'Sub Menu', ['hostel payment','accommodation'], ['hostel'], 'Payment');
                if (studentMenuEnabled($pdo, 'menu_student_school_fee')) {
                    $push($entries, 'School Fee', '?page=payment&tab=school', 'Sub Menu', ['tuition','school fees'], ['school fee'], 'Payment');
                    $push($entries, 'School Fee Installments', '?page=payment&tab=school', 'Feature/Page', ['installment','school fee installment'], ['school installment'], 'Payment');
                }
            }
            if (studentMenuEnabled($pdo, 'menu_student_wallet')) {
                $push($entries, 'Wallet', '?page=wallet', 'Main Menu', ['wallet balance','fund wallet']);
                $push($entries, 'Fund Wallet', '?page=wallet', 'Feature/Page', ['top up wallet','wallet funding'], ['wallet topup'], 'Wallet');
            }
            if (studentMenuEnabled($pdo, 'menu_student_payment_history')) $push($entries, 'Payment History', '?page=payment_history', 'Main Menu', ['transactions','payment records','receipts']);
            if (studentMenuEnabled($pdo, 'menu_student_clearance_form')) $push($entries, 'Clearance Form', '?page=clearance_form', 'Main Menu', ['clearance','clearance receipt','semester clearance']);
            if (studentMenuEnabled($pdo, 'menu_student_results')) $push($entries, 'Results / CGPA', '?page=student_results', 'Main Menu', ['results','cgpa','grades']);
            if (studentMenuEnabled($pdo, 'menu_student_attendance')) $push($entries, 'Attendance', '?page=student_attendance', 'Main Menu', ['attendance']);
            if (studentMenuEnabled($pdo, 'menu_student_lms')) $push($entries, 'LMS', '?page=student_lms', 'Main Menu', ['learning','course materials']);
            if (studentMenuEnabled($pdo, 'menu_student_library')) $push($entries, 'Library', '?page=student_library', 'Main Menu', ['books','library']);
            if (studentMenuEnabled($pdo, 'menu_student_cbt')) $push($entries, 'CBT', '?page=student_cbt', 'Main Menu', ['exam','test','cbt']);
            if (studentMenuEnabled($pdo, 'menu_student_messages')) $push($entries, 'Messages', '?page=messages', 'Main Menu', ['chat','messages']);
            if (studentMenuEnabled($pdo, 'menu_student_notifications')) $push($entries, 'Notifications', '?page=notifications', 'Main Menu', ['alerts']);
            if (studentMenuEnabled($pdo, 'menu_student_profile')) $push($entries, 'Profile', '?page=profile', 'Main Menu', ['account','bio']);
            if (studentMenuEnabled($pdo, 'menu_student_transcript')) $push($entries, 'Transcripts', '?page=student_transcript', 'Main Menu', ['transcript','academic transcript']);
        }
        return array_values($entries);
    }
}

if (!function_exists('portalRenderSidebarSearch')) {
    function portalRenderSidebarSearch(PDO $pdo): void {
        if (!isLoggedIn() || !portalSearchBarEnabled($pdo)) return;
        $entries = portalBuildSearchEntriesForCurrentUser($pdo);
        if (!$entries) return;
        $entriesJson = htmlspecialchars(json_encode($entries, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
        ?>
        <div class="portal-search-wrap no-print" data-portal-search-root data-portal-search-items="<?php echo $entriesJson; ?>">
            <div class="portal-search-input-group">
                <i class="bi bi-search portal-search-icon"></i>
                <input type="search" class="portal-search-input" placeholder="Search menus, sub menus, and features" autocomplete="off" aria-label="Portal search">
            </div>
            <div class="portal-search-results" hidden></div>
        </div>
        <?php
    }
}

if (!function_exists('moduleAttachmentRules')) {
    function moduleAttachmentRules(): array {
        return [
            'doc'  => 3 * 1024 * 1024,
            'docx' => 3 * 1024 * 1024,
            'xls'  => 3 * 1024 * 1024,
            'xlsx' => 3 * 1024 * 1024,
            'csv'  => 3 * 1024 * 1024,
            'pdf'  => 3 * 1024 * 1024,
            'png'  => 3 * 1024 * 1024,
            'jpg'  => 3 * 1024 * 1024,
            'jpeg' => 3 * 1024 * 1024,
            'mp4'  => 5 * 1024 * 1024,
        ];
    }
}

if (!function_exists('moduleAttachmentAcceptAttr')) {
    function moduleAttachmentAcceptAttr(): string {
        return '.doc,.docx,.xls,.xlsx,.csv,.pdf,.png,.jpg,.jpeg,.mp4';
    }
}

if (!function_exists('moduleAttachmentHelpText')) {
    function moduleAttachmentHelpText(): string {
        return 'Allowed: DOC, DOCX, XLS, XLSX, CSV, PDF, PNG, JPG, JPEG up to 3MB each, and MP4 up to 5MB.';
    }
}

if (!function_exists('moduleSaveUpload')) {
    function moduleSaveUpload(array $file, string $subDir, string $prefix, int $recordId = 0): ?array {
        if (empty($file['name']) || empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
            return null;
        }

        $rules = moduleAttachmentRules();
        $orig = basename((string)($file['name'] ?? ''));
        $ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
        if ($orig === '' || !isset($rules[$ext])) {
            throw new RuntimeException('Invalid file type. ' . moduleAttachmentHelpText());
        }

        $size = (int)($file['size'] ?? 0);
        $maxBytes = (int)$rules[$ext];
        if ($size <= 0 || $size > $maxBytes) {
            $limitMb = ($ext === 'mp4') ? '5MB' : '3MB';
            throw new RuntimeException(strtoupper($ext) . ' file must not exceed ' . $limitMb . '.');
        }

        $allowedMimeByExt = [
            'doc'  => ['application/msword', 'application/octet-stream'],
            'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/octet-stream'],
            'xls'  => ['application/vnd.ms-excel', 'application/octet-stream'],
            'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip', 'application/octet-stream'],
            'csv'  => ['text/csv', 'text/plain', 'application/csv', 'application/vnd.ms-excel'],
            'pdf'  => ['application/pdf', 'application/x-pdf'],
            'png'  => ['image/png'],
            'jpg'  => ['image/jpeg'],
            'jpeg' => ['image/jpeg'],
            'mp4'  => ['video/mp4', 'application/mp4'],
        ];
        $mime = '';
        try {
            if (function_exists('finfo_open')) {
                $fi = finfo_open(FILEINFO_MIME_TYPE);
                if ($fi) {
                    $mime = (string)finfo_file($fi, (string)$file['tmp_name']);
                    finfo_close($fi);
                }
            }
        } catch (Throwable $e) { $mime = ''; }
        if ($mime !== '' && isset($allowedMimeByExt[$ext]) && !in_array($mime, $allowedMimeByExt[$ext], true)) {
            throw new RuntimeException('Invalid file content. The uploaded file does not match its extension.');
        }
        if (in_array($ext, ['jpg','jpeg','png'], true) && function_exists('getimagesize') && @getimagesize((string)$file['tmp_name']) === false) {
            throw new RuntimeException('Invalid image file.');
        }

        $baseDir = __DIR__ . '/storage/' . trim($subDir, '/');
        if (!is_dir($baseDir)) {
            @mkdir($baseDir, 0775, true);
        }
        honttechHardenWritableDirectory($baseDir, false);
        if (!is_dir($baseDir) || !is_writable($baseDir)) {
            throw new RuntimeException('Unable to create upload directory.');
        }

        $safeExt = preg_replace('/[^a-z0-9]/', '', $ext);
        if ($safeExt === '') {
            $safeExt = 'bin';
        }

        $fileName = trim($prefix, '_') . '_' . ($recordId > 0 ? $recordId : 'tmp') . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $safeExt;
        $dest = $baseDir . '/' . $fileName;
        if (!@move_uploaded_file($file['tmp_name'], $dest)) {
            throw new RuntimeException('Failed to upload file.');
        }

        return [
            'file_path' => 'storage/' . trim($subDir, '/') . '/' . $fileName,
            'original_name' => $orig,
            'mime_type' => $mime !== '' ? $mime : (string)($file['type'] ?? ''),
            'file_size' => $size,
        ];
    }
}

if (!function_exists('moduleDeleteStoredFile')) {
    function moduleDeleteStoredFile(?string $relativePath): void {
        $relativePath = trim((string)$relativePath);
        if ($relativePath === '') {
            return;
        }
        $full = __DIR__ . '/' . ltrim($relativePath, '/');
        if (is_file($full)) {
            @unlink($full);
        }
    }
}

if (!function_exists('moduleNormalizeCsvHeader')) {
    function moduleNormalizeCsvHeader(string $value): string {
        $value = strtolower(trim($value));
        $value = preg_replace('/[^a-z0-9]+/', '_', $value);
        return trim((string)$value, '_');
    }
}

if (!function_exists('moduleUploadedCsvRows')) {
    function moduleUploadedCsvRows(array $file): array {
        if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
            throw new RuntimeException('Please choose a CSV file to import.');
        }
        $ext = strtolower(pathinfo((string)($file['name'] ?? ''), PATHINFO_EXTENSION));
        if ($ext !== 'csv') {
            throw new RuntimeException('Only CSV import files are allowed.');
        }
        $fh = fopen($file['tmp_name'], 'r');
        if (!$fh) {
            throw new RuntimeException('Unable to read uploaded CSV file.');
        }

        $headers = fgetcsv($fh);
        if (!is_array($headers) || !$headers) {
            fclose($fh);
            throw new RuntimeException('CSV file is empty.');
        }

        $headers = array_map(fn($h) => moduleNormalizeCsvHeader((string)$h), $headers);
        $rows = [];
        while (($row = fgetcsv($fh)) !== false) {
            if ($row === [null] || $row === false) {
                continue;
            }
            $assoc = [];
            foreach ($headers as $idx => $header) {
                if ($header === '') {
                    continue;
                }
                $assoc[$header] = trim((string)($row[$idx] ?? ''));
            }
            if ($assoc) {
                $rows[] = $assoc;
            }
        }
        fclose($fh);
        return $rows;
    }
}


if (!function_exists('moduleCsvDownload')) {
    function moduleCsvDownload(string $filename, array $headers, array $rows): void {
        while (ob_get_level() > 0) {
            @ob_end_clean();
        }
        if (!headers_sent()) {
            header('Content-Type: text/csv; charset=utf-8');
            header('Content-Disposition: attachment; filename="' . $filename . '"');
            header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
            header('Pragma: no-cache');
            header('Expires: 0');
        }
        $out = fopen('php://output', 'w');
        if ($out === false) {
            exit;
        }
        fwrite($out, "ï»¿");
        fputcsv($out, $headers);
        foreach ($rows as $row) {
            if (!is_array($row)) {
                fputcsv($out, [(string)$row]);
                continue;
            }
            $isList = array_keys($row) === range(0, count($row) - 1);
            if ($isList) {
                fputcsv($out, array_values($row));
                continue;
            }
            $line = [];
            foreach ($headers as $header) {
                $line[] = $row[$header] ?? '';
            }
            fputcsv($out, $line);
        }
        fclose($out);
        exit;
    }
}

if (!function_exists('moduleCsvInt')) {
    function moduleCsvInt(array $row, string $key, int $default = 0): int {
        return isset($row[$key]) && $row[$key] !== '' ? (int)$row[$key] : $default;
    }
}

if (!function_exists('moduleCsvFloat')) {
    function moduleCsvFloat(array $row, string $key, float $default = 0): float {
        return isset($row[$key]) && $row[$key] !== '' ? (float)$row[$key] : $default;
    }
}

if (!function_exists('moduleCsvText')) {
    function moduleCsvText(array $row, string $key, string $default = ''): string {
        return trim((string)($row[$key] ?? $default));
    }
}


if (!function_exists('ensureLmsSchema')) {
    function ensureLmsSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS lms_materials (
                id INT AUTO_INCREMENT PRIMARY KEY,
                title VARCHAR(255) NOT NULL,
                description TEXT NULL,
                file_path VARCHAR(500) NULL,
                course_id INT NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                level VARCHAR(10) NULL,
                semester VARCHAR(20) NULL,
                session_name VARCHAR(20) NULL,
                created_by INT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS lms_assignments (
                id INT AUTO_INCREMENT PRIMARY KEY,
                title VARCHAR(255) NOT NULL,
                instructions TEXT NULL,
                due_date DATE NULL,
                course_id INT NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                level VARCHAR(10) NULL,
                semester VARCHAR(20) NULL,
                session_name VARCHAR(20) NULL,
                created_by INT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS lms_submissions (
                id INT AUTO_INCREMENT PRIMARY KEY,
                assignment_id INT NOT NULL,
                student_id INT NOT NULL,
                submission_text TEXT NULL,
                file_path VARCHAR(500) NULL,
                score DECIMAL(5,2) NULL,
                remarks VARCHAR(255) NULL,
                submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_assign_student (assignment_id, student_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        // Self-healing: add file upload + visibility columns (non-breaking for existing installs)
        try {
            if (!portalColumnExists($pdo, 'lms_materials', 'file_path')) { $pdo->exec("ALTER TABLE lms_materials ADD COLUMN file_path VARCHAR(500) NULL"); }
            if (!portalColumnExists($pdo, 'lms_materials', 'original_name')) { $pdo->exec("ALTER TABLE lms_materials ADD COLUMN original_name VARCHAR(255) NULL"); }
            if (!portalColumnExists($pdo, 'lms_materials', 'mime_type')) { $pdo->exec("ALTER TABLE lms_materials ADD COLUMN mime_type VARCHAR(120) NULL"); }
            if (!portalColumnExists($pdo, 'lms_materials', 'file_size')) { $pdo->exec("ALTER TABLE lms_materials ADD COLUMN file_size INT NULL"); }
            if (!portalColumnExists($pdo, 'lms_materials', 'course_code')) { $pdo->exec("ALTER TABLE lms_materials ADD COLUMN course_code VARCHAR(32) NULL"); }
        } catch (Throwable $e) {}

        try {
            if (!portalColumnExists($pdo, 'lms_assignments', 'attachment_path')) { $pdo->exec("ALTER TABLE lms_assignments ADD COLUMN attachment_path VARCHAR(500) NULL"); }
            if (!portalColumnExists($pdo, 'lms_assignments', 'attachment_name')) { $pdo->exec("ALTER TABLE lms_assignments ADD COLUMN attachment_name VARCHAR(255) NULL"); }
            if (!portalColumnExists($pdo, 'lms_assignments', 'attachment_mime')) { $pdo->exec("ALTER TABLE lms_assignments ADD COLUMN attachment_mime VARCHAR(120) NULL"); }
            if (!portalColumnExists($pdo, 'lms_assignments', 'attachment_size')) { $pdo->exec("ALTER TABLE lms_assignments ADD COLUMN attachment_size INT NULL"); }
        } catch (Throwable $e) {}

    }
}

if (!function_exists('ensureLibrarySchema')) {
    function ensureLibrarySchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS library_books (
                id INT AUTO_INCREMENT PRIMARY KEY,
                isbn VARCHAR(40) NULL,
                title VARCHAR(255) NOT NULL,
                author VARCHAR(255) NULL,
                category VARCHAR(120) NULL,
                copies_total INT NOT NULL DEFAULT 1,
                copies_available INT NOT NULL DEFAULT 1,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                UNIQUE KEY uniq_isbn (isbn)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS library_loans (
                id INT AUTO_INCREMENT PRIMARY KEY,
                book_id INT NOT NULL,
                student_id INT NOT NULL,
                status VARCHAR(20) NOT NULL DEFAULT 'requested',
                requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                issued_at DATETIME NULL,
                due_at DATETIME NULL,
                returned_at DATETIME NULL,
                approved_by INT NULL,
                UNIQUE KEY uniq_active_loan (book_id, student_id, status)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        // Self-healing: overdue/fines support
        try {
            if (!portalColumnExists($pdo, 'library_loans', 'fine_amount')) { $pdo->exec("ALTER TABLE library_loans ADD COLUMN fine_amount DECIMAL(10,2) NULL"); }
        } catch (Throwable $e) {}

        try {
            if (!portalColumnExists($pdo, 'library_books', 'file_path')) { $pdo->exec("ALTER TABLE library_books ADD COLUMN file_path VARCHAR(500) NULL"); }
            if (!portalColumnExists($pdo, 'library_books', 'original_name')) { $pdo->exec("ALTER TABLE library_books ADD COLUMN original_name VARCHAR(255) NULL"); }
            if (!portalColumnExists($pdo, 'library_books', 'mime_type')) { $pdo->exec("ALTER TABLE library_books ADD COLUMN mime_type VARCHAR(120) NULL"); }
            if (!portalColumnExists($pdo, 'library_books', 'file_size')) { $pdo->exec("ALTER TABLE library_books ADD COLUMN file_size INT NULL"); }
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS library_settings (
                id INT AUTO_INCREMENT PRIMARY KEY,
                fine_per_day DECIMAL(10,2) NOT NULL DEFAULT 0,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
            // seed single row
            $cnt = (int)$pdo->query("SELECT COUNT(*) FROM library_settings")->fetchColumn();
            if ($cnt === 0) { $pdo->exec("INSERT INTO library_settings (fine_per_day) VALUES (0)"); }
        } catch (Throwable $e) {}

    }
}

if (!function_exists('ensureCbtSchema')) {
    function ensureCbtSchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_tests (
                id INT AUTO_INCREMENT PRIMARY KEY,
                title VARCHAR(255) NOT NULL,
                description TEXT NULL,
                course_code VARCHAR(50) NULL,
                duration_minutes INT NOT NULL DEFAULT 30,
                start_at DATETIME NULL,
                end_at DATETIME NULL,
                course_id INT NULL,
                faculty_id INT NULL,
                department_id INT NULL,
                programme_id INT NULL,
                level VARCHAR(10) NULL,
                semester VARCHAR(20) NULL,
                session_name VARCHAR(20) NULL,
                status VARCHAR(30) NOT NULL DEFAULT 'active',
                pass_mark INT NOT NULL DEFAULT 0,
                randomize_questions TINYINT(1) NOT NULL DEFAULT 0,
                published TINYINT(1) NOT NULL DEFAULT 0,
                results_published TINYINT(1) NOT NULL DEFAULT 0,
                question_per_page INT NOT NULL DEFAULT 1,
                negative_mark_per_wrong DECIMAL(6,2) NOT NULL DEFAULT 0,
                created_by INT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            if (!portalColumnExists($pdo, 'cbt_tests', 'course_code')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN course_code VARCHAR(50) NULL AFTER description"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'status')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN status VARCHAR(30) NOT NULL DEFAULT 'active'"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'pass_mark')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN pass_mark INT NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'randomize_questions')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN randomize_questions TINYINT(1) NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'published')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN published TINYINT(1) NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'results_published')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN results_published TINYINT(1) NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'question_per_page')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN question_per_page INT NOT NULL DEFAULT 1"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'negative_mark_per_wrong')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN negative_mark_per_wrong DECIMAL(6,2) NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'level')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN level VARCHAR(10) NULL"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'semester')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN semester VARCHAR(20) NULL"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'session_name')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN session_name VARCHAR(32) NULL"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'attachment_path')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN attachment_path VARCHAR(500) NULL"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'attachment_name')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN attachment_name VARCHAR(255) NULL"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'attachment_mime')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN attachment_mime VARCHAR(120) NULL"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'attachment_size')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN attachment_size INT NULL"); }
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_questions (
                id INT AUTO_INCREMENT PRIMARY KEY,
                test_id INT NOT NULL,
                question TEXT NOT NULL,
                option_a TEXT NULL,
                option_b TEXT NULL,
                option_c TEXT NULL,
                option_d TEXT NULL,
                correct_option CHAR(1) NULL,
                marks INT NOT NULL DEFAULT 1,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_cbt_q_test (test_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_attempts (
                id INT AUTO_INCREMENT PRIMARY KEY,
                test_id INT NOT NULL,
                student_id INT NOT NULL,
                score DECIMAL(6,2) NULL,
                answers_json LONGTEXT NULL,
                question_order_json LONGTEXT NULL,
                status VARCHAR(20) NOT NULL DEFAULT 'started',
                started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                submitted_at DATETIME NULL,
                UNIQUE KEY uniq_test_student (test_id, student_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            if (!portalColumnExists($pdo, 'cbt_attempts', 'answers_json')) { $pdo->exec("ALTER TABLE cbt_attempts ADD COLUMN answers_json LONGTEXT NULL"); }
            if (!portalColumnExists($pdo, 'cbt_attempts', 'question_order_json')) { $pdo->exec("ALTER TABLE cbt_attempts ADD COLUMN question_order_json LONGTEXT NULL"); }
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalCbtCurrentAdminId')) {
    function portalCbtCurrentAdminId(): int {
        return (int)($_SESSION['admin_id'] ?? ($_SESSION['user_id'] ?? 0));
    }
}

if (!function_exists('portalCbtEnsureColumn')) {
    function portalCbtEnsureColumn(PDO $pdo, string $table, string $column, string $sql): void {
        try {
            if (!portalColumnExistsSafe($pdo, $table, $column)) {
                $pdo->exec($sql);
            }
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalCbtEnsureIndex')) {
    function portalCbtEnsureIndex(PDO $pdo, string $table, string $indexName, string $sql): void {
        try {
            if (!portalIndexExistsSafe($pdo, $table, $indexName)) {
                $pdo->exec($sql);
            }
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalCbtEnsureEnterpriseSchema')) {
    function portalCbtEnsureEnterpriseSchema(PDO $pdo): void {
        ensureCbtSchema($pdo);
        try { if (function_exists('ensureStaffAclSchema')) { ensureStaffAclSchema($pdo); } } catch (Throwable $e) {}

        foreach ([
            ['Examiner', 'CBT examiner and manual grading access'],
            ['Instructor', 'CBT instructor and question bank access'],
        ] as $roleSeed) {
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO staff_roles (role_name, description) VALUES (?, ?)");
                $st->execute([$roleSeed[0], $roleSeed[1]]);
            } catch (Throwable $e) {}
        }
        foreach ([
            'Examiner' => ['cbt_manage', 'results_compute', 'results_approve'],
            'Instructor' => ['cbt_manage'],
        ] as $roleName => $permKeys) {
            foreach ($permKeys as $permKey) {
                try {
                    $st = $pdo->prepare("INSERT IGNORE INTO staff_permissions (role_name, perm_key, allowed) VALUES (?, ?, 1)");
                    $st->execute([$roleName, $permKey]);
                } catch (Throwable $e) {}
            }
            try {
                $st = $pdo->prepare("INSERT IGNORE INTO staff_menu_toggles (role_name, menu_key, enabled) VALUES (?, 'menu_cbt_admin', 1)");
                $st->execute([$roleName]);
            } catch (Throwable $e) {}
        }

        portalCbtEnsureColumn($pdo, 'cbt_tests', 'exam_code', "ALTER TABLE cbt_tests ADD COLUMN exam_code VARCHAR(80) NULL AFTER title");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'subject_name', "ALTER TABLE cbt_tests ADD COLUMN subject_name VARCHAR(180) NULL AFTER course_code");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'class_group', "ALTER TABLE cbt_tests ADD COLUMN class_group VARCHAR(120) NULL AFTER subject_name");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'instructions', "ALTER TABLE cbt_tests ADD COLUMN instructions LONGTEXT NULL AFTER description");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'total_marks', "ALTER TABLE cbt_tests ADD COLUMN total_marks DECIMAL(10,2) NOT NULL DEFAULT 0 AFTER pass_mark");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'late_entry_minutes', "ALTER TABLE cbt_tests ADD COLUMN late_entry_minutes INT NOT NULL DEFAULT 0 AFTER end_at");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'attempt_limit', "ALTER TABLE cbt_tests ADD COLUMN attempt_limit INT NOT NULL DEFAULT 1 AFTER late_entry_minutes");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'instant_result_enabled', "ALTER TABLE cbt_tests ADD COLUMN instant_result_enabled TINYINT(1) NOT NULL DEFAULT 1 AFTER attempt_limit");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'answer_review_enabled', "ALTER TABLE cbt_tests ADD COLUMN answer_review_enabled TINYINT(1) NOT NULL DEFAULT 1 AFTER instant_result_enabled");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'randomize_options', "ALTER TABLE cbt_tests ADD COLUMN randomize_options TINYINT(1) NOT NULL DEFAULT 0 AFTER randomize_questions");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'negative_marking_enabled', "ALTER TABLE cbt_tests ADD COLUMN negative_marking_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER negative_mark_per_wrong");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'mark_per_question', "ALTER TABLE cbt_tests ADD COLUMN mark_per_question DECIMAL(10,2) NOT NULL DEFAULT 1 AFTER negative_marking_enabled");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'browser_tolerance_count', "ALTER TABLE cbt_tests ADD COLUMN browser_tolerance_count INT NOT NULL DEFAULT 3 AFTER mark_per_question");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'auto_submit_on_timeout', "ALTER TABLE cbt_tests ADD COLUMN auto_submit_on_timeout TINYINT(1) NOT NULL DEFAULT 1 AFTER browser_tolerance_count");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'sectional_enabled', "ALTER TABLE cbt_tests ADD COLUMN sectional_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER auto_submit_on_timeout");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'calculator_enabled', "ALTER TABLE cbt_tests ADD COLUMN calculator_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER sectional_enabled");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'fullscreen_required', "ALTER TABLE cbt_tests ADD COLUMN fullscreen_required TINYINT(1) NOT NULL DEFAULT 0 AFTER calculator_enabled");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'anti_copy_enabled', "ALTER TABLE cbt_tests ADD COLUMN anti_copy_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER fullscreen_required");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'anti_switch_enabled', "ALTER TABLE cbt_tests ADD COLUMN anti_switch_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER anti_copy_enabled");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'exam_mode', "ALTER TABLE cbt_tests ADD COLUMN exam_mode VARCHAR(20) NOT NULL DEFAULT 'draft' AFTER anti_switch_enabled");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'updated_by', "ALTER TABLE cbt_tests ADD COLUMN updated_by INT NULL AFTER created_by");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'approved_by', "ALTER TABLE cbt_tests ADD COLUMN approved_by INT NULL AFTER updated_by");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'updated_at', "ALTER TABLE cbt_tests ADD COLUMN updated_at DATETIME NULL AFTER created_at");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'approved_at', "ALTER TABLE cbt_tests ADD COLUMN approved_at DATETIME NULL AFTER updated_at");
        portalCbtEnsureColumn($pdo, 'cbt_tests', 'archived_at', "ALTER TABLE cbt_tests ADD COLUMN archived_at DATETIME NULL AFTER approved_at");
        portalCbtEnsureIndex($pdo, 'cbt_tests', 'idx_cbt_tests_scope', "CREATE INDEX idx_cbt_tests_scope ON cbt_tests(status, published, faculty_id, department_id, programme_id, level, session_name, semester)");
        portalCbtEnsureIndex($pdo, 'cbt_tests', 'idx_cbt_tests_window', "CREATE INDEX idx_cbt_tests_window ON cbt_tests(start_at, end_at, exam_mode)");

        portalCbtEnsureColumn($pdo, 'cbt_questions', 'question_type', "ALTER TABLE cbt_questions ADD COLUMN question_type VARCHAR(40) NOT NULL DEFAULT 'single_choice' AFTER test_id");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'section_name', "ALTER TABLE cbt_questions ADD COLUMN section_name VARCHAR(180) NULL AFTER question_type");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'subject_name', "ALTER TABLE cbt_questions ADD COLUMN subject_name VARCHAR(180) NULL AFTER section_name");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'difficulty_level', "ALTER TABLE cbt_questions ADD COLUMN difficulty_level VARCHAR(20) NOT NULL DEFAULT 'medium' AFTER subject_name");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'topic_tag', "ALTER TABLE cbt_questions ADD COLUMN topic_tag VARCHAR(255) NULL AFTER difficulty_level");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'explanation', "ALTER TABLE cbt_questions ADD COLUMN explanation LONGTEXT NULL AFTER correct_option");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'question_image_path', "ALTER TABLE cbt_questions ADD COLUMN question_image_path VARCHAR(500) NULL AFTER explanation");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'question_image_name', "ALTER TABLE cbt_questions ADD COLUMN question_image_name VARCHAR(255) NULL AFTER question_image_path");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'question_image_mime', "ALTER TABLE cbt_questions ADD COLUMN question_image_mime VARCHAR(120) NULL AFTER question_image_name");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'question_image_size', "ALTER TABLE cbt_questions ADD COLUMN question_image_size INT NULL AFTER question_image_mime");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'media_path', "ALTER TABLE cbt_questions ADD COLUMN media_path VARCHAR(500) NULL AFTER question_image_size");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'media_name', "ALTER TABLE cbt_questions ADD COLUMN media_name VARCHAR(255) NULL AFTER media_path");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'media_mime', "ALTER TABLE cbt_questions ADD COLUMN media_mime VARCHAR(120) NULL AFTER media_name");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'media_size', "ALTER TABLE cbt_questions ADD COLUMN media_size INT NULL AFTER media_mime");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'options_json', "ALTER TABLE cbt_questions ADD COLUMN options_json LONGTEXT NULL AFTER option_d");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'correct_answer_text', "ALTER TABLE cbt_questions ADD COLUMN correct_answer_text LONGTEXT NULL AFTER options_json");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'status', "ALTER TABLE cbt_questions ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active' AFTER marks");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'created_by', "ALTER TABLE cbt_questions ADD COLUMN created_by INT NULL AFTER status");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'updated_by', "ALTER TABLE cbt_questions ADD COLUMN updated_by INT NULL AFTER created_by");
        portalCbtEnsureColumn($pdo, 'cbt_questions', 'updated_at', "ALTER TABLE cbt_questions ADD COLUMN updated_at DATETIME NULL AFTER created_at");
        portalCbtEnsureIndex($pdo, 'cbt_questions', 'idx_cbt_q_type', "CREATE INDEX idx_cbt_q_type ON cbt_questions(test_id, question_type, difficulty_level, status)");

        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'attempt_number', "ALTER TABLE cbt_attempts ADD COLUMN attempt_number INT NOT NULL DEFAULT 1 AFTER student_id");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'objective_score', "ALTER TABLE cbt_attempts ADD COLUMN objective_score DECIMAL(10,2) NULL AFTER score");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'theory_score', "ALTER TABLE cbt_attempts ADD COLUMN theory_score DECIMAL(10,2) NULL AFTER objective_score");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'max_score', "ALTER TABLE cbt_attempts ADD COLUMN max_score DECIMAL(10,2) NULL AFTER theory_score");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'percentage_score', "ALTER TABLE cbt_attempts ADD COLUMN percentage_score DECIMAL(8,2) NULL AFTER max_score");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'grade_label', "ALTER TABLE cbt_attempts ADD COLUMN grade_label VARCHAR(20) NULL AFTER percentage_score");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'pass_fail_status', "ALTER TABLE cbt_attempts ADD COLUMN pass_fail_status VARCHAR(20) NULL AFTER grade_label");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'grading_status', "ALTER TABLE cbt_attempts ADD COLUMN grading_status VARCHAR(40) NOT NULL DEFAULT 'pending' AFTER pass_fail_status");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'result_status', "ALTER TABLE cbt_attempts ADD COLUMN result_status VARCHAR(40) NOT NULL DEFAULT 'pending' AFTER grading_status");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'remaining_seconds', "ALTER TABLE cbt_attempts ADD COLUMN remaining_seconds INT NULL AFTER question_order_json");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'current_question_no', "ALTER TABLE cbt_attempts ADD COLUMN current_question_no INT NOT NULL DEFAULT 1 AFTER remaining_seconds");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'started_ip', "ALTER TABLE cbt_attempts ADD COLUMN started_ip VARCHAR(120) NULL AFTER current_question_no");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'started_browser', "ALTER TABLE cbt_attempts ADD COLUMN started_browser VARCHAR(255) NULL AFTER started_ip");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'device_signature', "ALTER TABLE cbt_attempts ADD COLUMN device_signature VARCHAR(255) NULL AFTER started_browser");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'tolerance_used', "ALTER TABLE cbt_attempts ADD COLUMN tolerance_used INT NOT NULL DEFAULT 0 AFTER device_signature");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'incident_count', "ALTER TABLE cbt_attempts ADD COLUMN incident_count INT NOT NULL DEFAULT 0 AFTER tolerance_used");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'incidents_json', "ALTER TABLE cbt_attempts ADD COLUMN incidents_json LONGTEXT NULL AFTER incident_count");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'start_snapshot_json', "ALTER TABLE cbt_attempts ADD COLUMN start_snapshot_json LONGTEXT NULL AFTER incidents_json");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'review_json', "ALTER TABLE cbt_attempts ADD COLUMN review_json LONGTEXT NULL AFTER start_snapshot_json");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'submitted_hash', "ALTER TABLE cbt_attempts ADD COLUMN submitted_hash VARCHAR(120) NULL AFTER review_json");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'reviewed_by', "ALTER TABLE cbt_attempts ADD COLUMN reviewed_by INT NULL AFTER submitted_hash");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'reviewed_at', "ALTER TABLE cbt_attempts ADD COLUMN reviewed_at DATETIME NULL AFTER reviewed_by");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'last_seen_at', "ALTER TABLE cbt_attempts ADD COLUMN last_seen_at DATETIME NULL AFTER reviewed_at");
        portalCbtEnsureColumn($pdo, 'cbt_attempts', 'updated_at', "ALTER TABLE cbt_attempts ADD COLUMN updated_at DATETIME NULL AFTER last_seen_at");
        portalCbtEnsureIndex($pdo, 'cbt_attempts', 'idx_cbt_attempts_status', "CREATE INDEX idx_cbt_attempts_status ON cbt_attempts(test_id, student_id, status, grading_status, result_status)");

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_assignments (
                id INT AUTO_INCREMENT PRIMARY KEY,
                test_id INT NOT NULL,
                scope_type VARCHAR(40) NOT NULL DEFAULT 'all',
                scope_value VARCHAR(190) NULL,
                student_id INT NULL,
                is_whitelist TINYINT(1) NOT NULL DEFAULT 0,
                is_blacklist TINYINT(1) NOT NULL DEFAULT 0,
                can_retake TINYINT(1) NOT NULL DEFAULT 0,
                created_by INT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_cbt_assignments_test (test_id),
                INDEX idx_cbt_assignments_lookup (scope_type, scope_value, student_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_student_access (
                id INT AUTO_INCREMENT PRIMARY KEY,
                student_id INT NOT NULL UNIQUE,
                access_status VARCHAR(20) NOT NULL DEFAULT 'active',
                login_allowed TINYINT(1) NOT NULL DEFAULT 1,
                note VARCHAR(255) NULL,
                deleted_at DATETIME NULL,
                updated_by INT NULL,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_cbt_student_access_status (access_status)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_activity_logs (
                id INT AUTO_INCREMENT PRIMARY KEY,
                actor_user_type VARCHAR(20) NOT NULL DEFAULT 'admin',
                actor_id INT NULL,
                entity_type VARCHAR(40) NOT NULL,
                entity_id INT NULL,
                action_key VARCHAR(80) NOT NULL,
                log_message VARCHAR(255) NULL,
                extra_json LONGTEXT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_cbt_activity_entity (entity_type, entity_id),
                INDEX idx_cbt_activity_actor (actor_user_type, actor_id),
                INDEX idx_cbt_activity_action (action_key)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_incidents (
                id INT AUTO_INCREMENT PRIMARY KEY,
                attempt_id INT NOT NULL,
                test_id INT NOT NULL,
                student_id INT NOT NULL,
                incident_type VARCHAR(60) NOT NULL,
                incident_details VARCHAR(255) NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_cbt_incident_attempt (attempt_id),
                INDEX idx_cbt_incident_test (test_id),
                INDEX idx_cbt_incident_student (student_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_manual_scores (
                id INT AUTO_INCREMENT PRIMARY KEY,
                attempt_id INT NOT NULL,
                question_id INT NOT NULL,
                marks_awarded DECIMAL(10,2) NOT NULL DEFAULT 0,
                marker_comment LONGTEXT NULL,
                graded_by INT NULL,
                graded_at DATETIME NULL,
                UNIQUE KEY uniq_cbt_manual_score (attempt_id, question_id),
                INDEX idx_cbt_manual_attempt (attempt_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_attempt_archives (
                id INT AUTO_INCREMENT PRIMARY KEY,
                test_id INT NOT NULL,
                student_id INT NOT NULL,
                attempt_number INT NOT NULL DEFAULT 1,
                archived_from_attempt_id INT NOT NULL,
                snapshot_json LONGTEXT NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_cbt_archive_lookup (test_id, student_id, attempt_number)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_settings (
                id INT AUTO_INCREMENT PRIMARY KEY,
                site_name VARCHAR(180) NOT NULL DEFAULT 'HontTech CBT Manager',
                logo_path VARCHAR(500) NULL,
                timezone_name VARCHAR(80) NOT NULL DEFAULT 'Africa/Lagos',
                default_instructions LONGTEXT NULL,
                default_browser_tolerance INT NOT NULL DEFAULT 3,
                default_exam_ui VARCHAR(20) NOT NULL DEFAULT 'modern',
                email_notifications_enabled TINYINT(1) NOT NULL DEFAULT 0,
                sms_placeholder_enabled TINYINT(1) NOT NULL DEFAULT 1,
                email_template_assignment LONGTEXT NULL,
                email_template_result LONGTEXT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
            $count = (int)$pdo->query("SELECT COUNT(*) FROM cbt_settings")->fetchColumn();
            if ($count === 0) {
                $pdo->exec("INSERT INTO cbt_settings (default_instructions, email_template_assignment, email_template_result) VALUES ('Read all instructions carefully before starting the exam. Do not refresh the page during a live CBT session.', 'You have a new CBT assignment waiting in your student portal.', 'Your CBT result has been published in your portal.')");
            }
        } catch (Throwable $e) {}

        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_grade_configs (
                id INT AUTO_INCREMENT PRIMARY KEY,
                minimum_percent DECIMAL(6,2) NOT NULL,
                grade_label VARCHAR(20) NOT NULL,
                remark_text VARCHAR(80) NOT NULL,
                sort_order INT NOT NULL DEFAULT 0,
                UNIQUE KEY uniq_cbt_grade_minimum (minimum_percent)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
            $count = (int)$pdo->query("SELECT COUNT(*) FROM cbt_grade_configs")->fetchColumn();
            if ($count === 0) {
                $seed = [
                    [70, 'A', 'Excellent', 1],
                    [60, 'B', 'Very Good', 2],
                    [50, 'C', 'Good', 3],
                    [45, 'D', 'Fair', 4],
                    [40, 'E', 'Pass', 5],
                    [0, 'F', 'Fail', 6],
                ];
                $st = $pdo->prepare("INSERT IGNORE INTO cbt_grade_configs (minimum_percent, grade_label, remark_text, sort_order) VALUES (?,?,?,?)");
                foreach ($seed as $row) { $st->execute($row); }
            }
        } catch (Throwable $e) {}

        try {
            $pdo->exec("UPDATE cbt_tests SET exam_mode = CASE WHEN published = 1 THEN 'live' ELSE COALESCE(NULLIF(exam_mode,''), 'draft') END WHERE COALESCE(exam_mode,'') = ''");
            $pdo->exec("UPDATE cbt_tests SET total_marks = (SELECT COALESCE(SUM(q.marks),0) FROM cbt_questions q WHERE q.test_id = cbt_tests.id) WHERE COALESCE(total_marks,0) <= 0");
            $pdo->exec("UPDATE cbt_attempts SET attempt_number = CASE WHEN attempt_number IS NULL OR attempt_number <= 0 THEN 1 ELSE attempt_number END");
            $pdo->exec("UPDATE cbt_attempts SET grading_status = CASE WHEN grading_status IS NULL OR grading_status = '' THEN 'pending' ELSE grading_status END");
            $pdo->exec("UPDATE cbt_attempts SET result_status = CASE WHEN result_status IS NULL OR result_status = '' THEN 'pending' ELSE result_status END");
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalCbtSettings')) {
    function portalCbtSettings(PDO $pdo): array {
        portalCbtEnsureEnterpriseSchema($pdo);
        try {
            $row = $pdo->query("SELECT * FROM cbt_settings ORDER BY id ASC LIMIT 1")->fetch(PDO::FETCH_ASSOC);
            return is_array($row) ? $row : [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalCbtCsrfToken')) {
    function portalCbtCsrfToken(): string {
        if (empty($_SESSION['cbt_csrf_token'])) {
            try {
                $_SESSION['cbt_csrf_token'] = bin2hex(random_bytes(32));
            } catch (Throwable $e) {
                $_SESSION['cbt_csrf_token'] = hash('sha256', uniqid('cbt', true) . microtime(true) . mt_rand());
            }
        }
        return (string)$_SESSION['cbt_csrf_token'];
    }
}

if (!function_exists('portalCbtVerifyCsrf')) {
    function portalCbtVerifyCsrf(): bool {
        $posted = (string)($_POST['cbt_csrf_token'] ?? $_GET['cbt_csrf_token'] ?? $_SERVER['HTTP_X_CBT_CSRF'] ?? '');
        $session = (string)($_SESSION['cbt_csrf_token'] ?? '');
        return $posted !== '' && $session !== '' && hash_equals($session, $posted);
    }
}

if (!function_exists('portalCbtJsonDecode')) {
    function portalCbtJsonDecode($value, $default = []) {
        if (is_array($value)) return $value;
        $raw = trim((string)$value);
        if ($raw === '') return $default;
        try {
            $decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
            return is_array($decoded) ? $decoded : $default;
        } catch (Throwable $e) {
            return $default;
        }
    }
}

if (!function_exists('portalCbtJsonResponse')) {
    function portalCbtJsonResponse(array $payload): void {
        if (!headers_sent()) {
            header('Content-Type: application/json; charset=utf-8');
        }
        echo json_encode($payload);
        exit;
    }
}

if (!function_exists('portalCbtClientIp')) {
    function portalCbtClientIp(): string {
        foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'] as $key) {
            $value = trim((string)($_SERVER[$key] ?? ''));
            if ($value === '') continue;
            if (str_contains($value, ',')) {
                $value = trim((string)explode(',', $value)[0]);
            }
            if ($value !== '') return substr($value, 0, 120);
        }
        return 'unknown';
    }
}

if (!function_exists('portalCbtBrowserName')) {
    function portalCbtBrowserName(): string {
        return substr(trim((string)($_SERVER['HTTP_USER_AGENT'] ?? 'Unknown Browser')), 0, 250);
    }
}

if (!function_exists('portalCbtDeviceSignature')) {
    function portalCbtDeviceSignature(): string {
        return hash('sha256', portalCbtClientIp() . '|' . portalCbtBrowserName() . '|' . session_id());
    }
}

if (!function_exists('portalCbtLogActivity')) {
    function portalCbtLogActivity(PDO $pdo, string $entityType, int $entityId, string $actionKey, string $message = '', array $extra = [], string $actorUserType = 'admin', ?int $actorId = null): void {
        portalCbtEnsureEnterpriseSchema($pdo);
        try {
            $st = $pdo->prepare("INSERT INTO cbt_activity_logs (actor_user_type, actor_id, entity_type, entity_id, action_key, log_message, extra_json) VALUES (?, ?, ?, ?, ?, ?, ?)");
            $st->execute([
                $actorUserType,
                $actorId,
                $entityType,
                $entityId > 0 ? $entityId : null,
                $actionKey,
                $message !== '' ? $message : null,
                $extra ? json_encode($extra) : null,
            ]);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalCbtAppendIncident')) {
    function portalCbtAppendIncident(PDO $pdo, int $attemptId, int $testId, int $studentId, string $incidentType, string $details = ''): void {
        portalCbtEnsureEnterpriseSchema($pdo);
        if ($attemptId <= 0 || $testId <= 0 || $studentId <= 0 || trim($incidentType) === '') return;
        try {
            $st = $pdo->prepare("INSERT INTO cbt_incidents (attempt_id, test_id, student_id, incident_type, incident_details) VALUES (?,?,?,?,?)");
            $st->execute([$attemptId, $testId, $studentId, $incidentType, $details !== '' ? $details : null]);
        } catch (Throwable $e) {}
        try {
            $row = $pdo->prepare("SELECT incidents_json, incident_count FROM cbt_attempts WHERE id = ? LIMIT 1");
            $row->execute([$attemptId]);
            $attempt = $row->fetch(PDO::FETCH_ASSOC) ?: [];
            $incidents = portalCbtJsonDecode($attempt['incidents_json'] ?? '[]', []);
            $incidents[] = ['type' => $incidentType, 'details' => $details, 'at' => date('Y-m-d H:i:s')];
            $count = max((int)($attempt['incident_count'] ?? 0), count($incidents));
            $pdo->prepare("UPDATE cbt_attempts SET incidents_json = ?, incident_count = ?, tolerance_used = tolerance_used + 1, updated_at = NOW() WHERE id = ?")
                ->execute([json_encode($incidents), $count, $attemptId]);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalCbtGradeFromPercent')) {
    function portalCbtGradeFromPercent(PDO $pdo, float $percent): array {
        portalCbtEnsureEnterpriseSchema($pdo);
        try {
            $st = $pdo->prepare("SELECT grade_label, remark_text FROM cbt_grade_configs WHERE minimum_percent <= ? ORDER BY minimum_percent DESC, sort_order ASC LIMIT 1");
            $st->execute([$percent]);
            $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if ($row) return $row;
        } catch (Throwable $e) {}
        return $percent >= 50 ? ['grade_label' => 'P', 'remark_text' => 'Pass'] : ['grade_label' => 'F', 'remark_text' => 'Fail'];
    }
}

if (!function_exists('portalCbtQuestionType')) {
    function portalCbtQuestionType($value): string {
        $allowed = ['single_choice','multiple_choice','true_false','short_answer','essay','fill_blank'];
        $type = strtolower(trim((string)$value));
        return in_array($type, $allowed, true) ? $type : 'single_choice';
    }
}

if (!function_exists('portalCbtQuestionOptions')) {
    function portalCbtQuestionOptions(array $question): array {
        $type = portalCbtQuestionType($question['question_type'] ?? '');
        $options = portalCbtJsonDecode($question['options_json'] ?? '[]', []);
        $normalized = [];
        if ($type === 'true_false') {
            return ['A' => 'True', 'B' => 'False'];
        }
        if (!$options) {
            foreach (['A' => 'option_a', 'B' => 'option_b', 'C' => 'option_c', 'D' => 'option_d'] as $label => $field) {
                $value = trim((string)($question[$field] ?? ''));
                if ($value !== '') $normalized[$label] = $value;
            }
            return $normalized;
        }
        foreach ($options as $key => $value) {
            if (is_array($value)) {
                $label = trim((string)($value['label'] ?? $key));
                $textValue = trim((string)($value['text'] ?? $value['value'] ?? ''));
                if ($textValue !== '') $normalized[$label] = $textValue;
            } else {
                $textValue = trim((string)$value);
                if ($textValue !== '') $normalized[trim((string)$key)] = $textValue;
            }
        }
        return $normalized;
    }
}

if (!function_exists('portalCbtQuestionOrder')) {
    function portalCbtQuestionOrder(PDO $pdo, int $testId, bool $randomize = false): array {
        $questions = [];
        try {
            $st = $pdo->prepare("SELECT id FROM cbt_questions WHERE test_id = ? AND COALESCE(status,'active') <> 'inactive' ORDER BY id ASC");
            $st->execute([$testId]);
            $questions = array_map('intval', array_column($st->fetchAll(PDO::FETCH_ASSOC) ?: [], 'id'));
        } catch (Throwable $e) {
            $questions = [];
        }
        if ($randomize && count($questions) > 1) {
            shuffle($questions);
        }
        return $questions;
    }
}

if (!function_exists('portalCbtFetchQuestionsByIds')) {
    function portalCbtFetchQuestionsByIds(PDO $pdo, array $ids): array {
        $ids = array_values(array_filter(array_map('intval', $ids)));
        if (!$ids) return [];
        $placeholders = implode(',', array_fill(0, count($ids), '?'));
        try {
            $st = $pdo->prepare("SELECT * FROM cbt_questions WHERE id IN ($placeholders)");
            $st->execute($ids);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $rows = [];
        }
        $map = [];
        foreach ($rows as $row) { $map[(int)$row['id']] = $row; }
        $ordered = [];
        foreach ($ids as $id) {
            if (isset($map[$id])) $ordered[] = $map[$id];
        }
        return $ordered;
    }
}

if (!function_exists('portalCbtSyncTestTotals')) {
    function portalCbtSyncTestTotals(PDO $pdo, int $testId): void {
        if ($testId <= 0) return;
        try {
            $st = $pdo->prepare("SELECT COALESCE(SUM(marks),0) FROM cbt_questions WHERE test_id = ? AND COALESCE(status,'active') <> 'inactive'");
            $st->execute([$testId]);
            $sum = (float)($st->fetchColumn() ?: 0);
            $pdo->prepare("UPDATE cbt_tests SET total_marks = ?, updated_at = NOW() WHERE id = ?")->execute([$sum, $testId]);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalCbtStudentAccessRow')) {
    function portalCbtStudentAccessRow(PDO $pdo, int $studentId): array {
        portalCbtEnsureEnterpriseSchema($pdo);
        if ($studentId <= 0) return [];
        try {
            $st = $pdo->prepare("SELECT * FROM cbt_student_access WHERE student_id = ? LIMIT 1");
            $st->execute([$studentId]);
            return $st->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalCbtUpsertStudentAccess')) {
    function portalCbtUpsertStudentAccess(PDO $pdo, int $studentId, string $status, string $note = '', ?int $updatedBy = null): void {
        portalCbtEnsureEnterpriseSchema($pdo);
        if ($studentId <= 0) return;
        $status = in_array($status, ['active','suspended','deleted'], true) ? $status : 'active';
        try {
            $st = $pdo->prepare("INSERT INTO cbt_student_access (student_id, access_status, login_allowed, note, deleted_at, updated_by) VALUES (?, ?, ?, ?, ?, ?)
                ON DUPLICATE KEY UPDATE access_status = VALUES(access_status), login_allowed = VALUES(login_allowed), note = VALUES(note), deleted_at = VALUES(deleted_at), updated_by = VALUES(updated_by)");
            $st->execute([
                $studentId,
                $status,
                $status === 'active' ? 1 : 0,
                $note !== '' ? $note : null,
                $status === 'deleted' ? date('Y-m-d H:i:s') : null,
                $updatedBy,
            ]);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalCbtAssignmentMatchesStudent')) {
    function portalCbtAssignmentMatchesStudent(array $assignment, array $student): bool {
        $scopeType = strtolower(trim((string)($assignment['scope_type'] ?? 'all')));
        $scopeValue = trim((string)($assignment['scope_value'] ?? ''));
        $studentId = (int)($student['id'] ?? 0);
        $classGroup = trim((string)($student['class_group'] ?? ($student['group_name'] ?? '')));
        switch ($scopeType) {
            case 'all':
                return true;
            case 'student':
                return ((int)($assignment['student_id'] ?? 0) > 0 && (int)($assignment['student_id'] ?? 0) === $studentId)
                    || ($scopeValue !== '' && (int)$scopeValue === $studentId);
            case 'faculty':
                return (int)($student['faculty_id'] ?? 0) > 0 && (int)$scopeValue === (int)($student['faculty_id'] ?? 0);
            case 'department':
                return (int)($student['department_id'] ?? 0) > 0 && (int)$scopeValue === (int)($student['department_id'] ?? 0);
            case 'programme':
                return (int)($student['programme_id'] ?? 0) > 0 && (int)$scopeValue === (int)($student['programme_id'] ?? 0);
            case 'level':
                return strcasecmp($scopeValue, (string)($student['level'] ?? '')) === 0;
            case 'session':
                return strcasecmp($scopeValue, (string)($student['session'] ?? '')) === 0;
            case 'class_group':
                return $scopeValue !== '' && strcasecmp($scopeValue, $classGroup) === 0;
        }
        return false;
    }
}

if (!function_exists('portalCbtEligibility')) {
    function portalCbtEligibility(PDO $pdo, array $test, array $student, ?array $attempt = null): array {
        portalCbtEnsureEnterpriseSchema($pdo);
        $now = time();
        $startAt = trim((string)($test['start_at'] ?? ''));
        $endAt = trim((string)($test['end_at'] ?? ''));
        $startTs = $startAt !== '' ? strtotime($startAt) : false;
        $endTs = $endAt !== '' ? strtotime($endAt) : false;
        $lateEntry = max(0, (int)($test['late_entry_minutes'] ?? 0));
        $attemptLimit = max(1, (int)($test['attempt_limit'] ?? 1));
        $accessRow = portalCbtStudentAccessRow($pdo, (int)($student['id'] ?? 0));
        $currentAttemptNumber = max(1, (int)($attempt['attempt_number'] ?? 1));
        $canRetake = false;
        $blockedReason = '';
        $whitelistExists = false;
        $whitelistMatched = false;
        $blacklistMatched = false;

        if ((int)($test['published'] ?? 0) !== 1 || strtolower(trim((string)($test['exam_mode'] ?? 'draft'))) !== 'live') {
            $blockedReason = 'This exam is still in draft mode or not yet published.';
        } elseif (strtolower(trim((string)($test['status'] ?? 'active'))) !== 'active') {
            $blockedReason = 'This exam is currently inactive.';
        } elseif (!empty($accessRow) && strtolower(trim((string)($accessRow['access_status'] ?? 'active'))) !== 'active') {
            $blockedReason = 'Your CBT access has been restricted by the administrator.';
        }

        try {
            $st = $pdo->prepare("SELECT * FROM cbt_assignments WHERE test_id = ? ORDER BY id ASC");
            $st->execute([(int)($test['id'] ?? 0)]);
            $assignments = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $assignments = [];
        }

        foreach ($assignments as $assignment) {
            $isWhitelist = (int)($assignment['is_whitelist'] ?? 0) === 1;
            $isBlacklist = (int)($assignment['is_blacklist'] ?? 0) === 1;
            if ($isWhitelist) $whitelistExists = true;
            if (portalCbtAssignmentMatchesStudent($assignment, $student)) {
                if ($isWhitelist) $whitelistMatched = true;
                if ($isBlacklist) $blacklistMatched = true;
                if ((int)($assignment['can_retake'] ?? 0) === 1) $canRetake = true;
            }
        }
        if ($blacklistMatched) {
            $blockedReason = 'You have been excluded from this specific exam.';
        } elseif ($whitelistExists && !$whitelistMatched) {
            $blockedReason = 'You are not part of the approved assignment list for this exam.';
        }

        if ($blockedReason === '' && $startTs && $now < $startTs) {
            $blockedReason = 'This exam has not opened yet.';
        }
        if ($blockedReason === '' && $endTs && $now > $endTs) {
            $blockedReason = 'The exam window has closed.';
        }
        if ($blockedReason === '' && $attempt && !empty($attempt['submitted_at']) && $currentAttemptNumber >= $attemptLimit && !$canRetake) {
            $blockedReason = 'You have exhausted the allowed number of attempts for this exam.';
        }
        if ($blockedReason === '' && $attempt && empty($attempt['submitted_at']) && $startTs && ($lateEntry > 0) && ($now > ($startTs + ($lateEntry * 60)))) {
            $blockedReason = 'Late entry tolerance has been exceeded for this exam.';
        }

        return [
            'allowed' => $blockedReason === '',
            'blocked_reason' => $blockedReason,
            'can_retake' => $canRetake,
            'attempt_limit' => $attemptLimit,
            'attempt_number' => $currentAttemptNumber,
            'is_open' => !($startTs && $now < $startTs) && !($endTs && $now > $endTs),
            'window_text' => ($startAt !== '' || $endAt !== '') ? trim(($startAt !== '' ? date('d M Y h:i A', $startTs) : 'Open') . ' - ' . ($endAt !== '' ? date('d M Y h:i A', $endTs) : 'No close time')) : 'Always available',
        ];
    }
}

if (!function_exists('portalCbtAttemptRow')) {
    function portalCbtAttemptRow(PDO $pdo, int $testId, int $studentId): ?array {
        try {
            $st = $pdo->prepare("SELECT * FROM cbt_attempts WHERE test_id = ? AND student_id = ? LIMIT 1");
            $st->execute([$testId, $studentId]);
            $row = $st->fetch(PDO::FETCH_ASSOC);
            return is_array($row) ? $row : null;
        } catch (Throwable $e) {
            return null;
        }
    }
}

if (!function_exists('portalCbtArchiveAttemptSnapshot')) {
    function portalCbtArchiveAttemptSnapshot(PDO $pdo, array $attempt): void {
        if (!(int)($attempt['id'] ?? 0)) return;
        portalCbtEnsureEnterpriseSchema($pdo);
        try {
            $st = $pdo->prepare("INSERT INTO cbt_attempt_archives (test_id, student_id, attempt_number, archived_from_attempt_id, snapshot_json) VALUES (?, ?, ?, ?, ?)");
            $st->execute([
                (int)($attempt['test_id'] ?? 0),
                (int)($attempt['student_id'] ?? 0),
                max(1, (int)($attempt['attempt_number'] ?? 1)),
                (int)($attempt['id'] ?? 0),
                json_encode($attempt),
            ]);
        } catch (Throwable $e) {}
    }
}

if (!function_exists('portalCbtStartOrResumeAttempt')) {
    function portalCbtStartOrResumeAttempt(PDO $pdo, array $test, array $student): array {
        portalCbtEnsureEnterpriseSchema($pdo);
        $testId = (int)($test['id'] ?? 0);
        $studentId = (int)($student['id'] ?? 0);
        $existing = portalCbtAttemptRow($pdo, $testId, $studentId);
        $randomize = !empty($test['randomize_questions']);
        $questionOrder = portalCbtQuestionOrder($pdo, $testId, $randomize);
        $durationSeconds = max(60, (int)($test['duration_minutes'] ?? 30) * 60);
        $snapshot = ['question_order' => $questionOrder, 'randomize_questions' => $randomize, 'randomize_options' => !empty($test['randomize_options']) ? 1 : 0];

        if ($existing && empty($existing['submitted_at'])) {
            return $existing;
        }

        if ($existing && !empty($existing['submitted_at'])) {
            portalCbtArchiveAttemptSnapshot($pdo, $existing);
            $attemptNumber = max(1, (int)($existing['attempt_number'] ?? 1)) + 1;
            $pdo->prepare("UPDATE cbt_attempts SET attempt_number = ?, score = NULL, objective_score = NULL, theory_score = NULL, max_score = NULL, percentage_score = NULL, grade_label = NULL, pass_fail_status = NULL, grading_status = 'pending', result_status = 'pending', answers_json = ?, question_order_json = ?, remaining_seconds = ?, current_question_no = 1, status = 'started', started_at = CURRENT_TIMESTAMP, submitted_at = NULL, incidents_json = ?, incident_count = 0, tolerance_used = 0, started_ip = ?, started_browser = ?, device_signature = ?, start_snapshot_json = ?, submitted_hash = NULL, reviewed_by = NULL, reviewed_at = NULL, last_seen_at = NOW(), updated_at = NOW() WHERE id = ?")
                ->execute([
                    $attemptNumber,
                    json_encode(new stdClass()),
                    json_encode($questionOrder),
                    $durationSeconds,
                    json_encode([]),
                    portalCbtClientIp(),
                    portalCbtBrowserName(),
                    portalCbtDeviceSignature(),
                    json_encode($snapshot),
                    (int)$existing['id'],
                ]);
            $existing = portalCbtAttemptRow($pdo, $testId, $studentId);
            return $existing ?: [];
        }

        $pdo->prepare("INSERT INTO cbt_attempts (test_id, student_id, attempt_number, answers_json, question_order_json, remaining_seconds, current_question_no, status, started_ip, started_browser, device_signature, incidents_json, start_snapshot_json, last_seen_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")
            ->execute([
                $testId,
                $studentId,
                1,
                json_encode(new stdClass()),
                json_encode($questionOrder),
                $durationSeconds,
                1,
                'started',
                portalCbtClientIp(),
                portalCbtBrowserName(),
                portalCbtDeviceSignature(),
                json_encode([]),
                json_encode($snapshot),
                date('Y-m-d H:i:s'),
                date('Y-m-d H:i:s'),
            ]);
        $attempt = portalCbtAttemptRow($pdo, $testId, $studentId);
        return $attempt ?: [];
    }
}

if (!function_exists('portalCbtNormalizeSubmittedAnswer')) {
    function portalCbtNormalizeSubmittedAnswer(string $questionType, $rawValue) {
        $questionType = portalCbtQuestionType($questionType);
        if ($questionType === 'multiple_choice') {
            if (is_array($rawValue)) {
                $values = array_values(array_unique(array_filter(array_map(static fn($v) => trim((string)$v), $rawValue), static fn($v) => $v !== '')));
                sort($values);
                return $values;
            }
            $decoded = portalCbtJsonDecode($rawValue, []);
            if (is_array($decoded) && $decoded) {
                $values = array_values(array_unique(array_filter(array_map(static fn($v) => trim((string)$v), $decoded), static fn($v) => $v !== '')));
                sort($values);
                return $values;
            }
            $rawString = trim((string)$rawValue);
            if ($rawString === '') return [];
            $values = array_values(array_unique(array_filter(array_map('trim', explode(',', $rawString)), static fn($v) => $v !== '')));
            sort($values);
            return $values;
        }
        return trim((string)$rawValue);
    }
}

if (!function_exists('portalCbtSaveAttemptProgress')) {
    function portalCbtSaveAttemptProgress(PDO $pdo, array $attempt, array $question, $answerValue, bool $flagged = false, int $currentQuestionNo = 1, ?int $remainingSeconds = null): array {
        $attemptId = (int)($attempt['id'] ?? 0);
        if ($attemptId <= 0) return [];
        $answers = portalCbtJsonDecode($attempt['answers_json'] ?? '{}', []);
        $qid = (int)($question['id'] ?? 0);
        if ($qid <= 0) return [];
        $normalizedAnswer = portalCbtNormalizeSubmittedAnswer((string)($question['question_type'] ?? 'single_choice'), $answerValue);
        $answers[(string)$qid] = [
            'selected' => $normalizedAnswer,
            'visited' => 1,
            'flagged' => $flagged ? 1 : 0,
            'updated_at' => date('Y-m-d H:i:s'),
        ];
        $remainingSeconds = $remainingSeconds !== null ? max(0, $remainingSeconds) : (int)($attempt['remaining_seconds'] ?? 0);
        try {
            $pdo->prepare("UPDATE cbt_attempts SET answers_json = ?, current_question_no = ?, remaining_seconds = ?, last_seen_at = NOW(), updated_at = NOW() WHERE id = ? AND submitted_at IS NULL")
                ->execute([json_encode($answers), max(1, $currentQuestionNo), $remainingSeconds, $attemptId]);
        } catch (Throwable $e) {}
        $fresh = portalCbtAttemptRow($pdo, (int)($attempt['test_id'] ?? 0), (int)($attempt['student_id'] ?? 0));
        return $fresh ?: [];
    }
}

if (!function_exists('portalCbtQuestionCorrectAnswerNormalized')) {
    function portalCbtQuestionCorrectAnswerNormalized(array $question) {
        $type = portalCbtQuestionType($question['question_type'] ?? '');
        if ($type === 'multiple_choice') {
            $raw = portalCbtJsonDecode($question['correct_answer_text'] ?? '[]', []);
            if (!$raw) {
                $text = trim((string)($question['correct_option'] ?? ''));
                $raw = $text !== '' ? explode(',', $text) : [];
            }
            $raw = array_values(array_unique(array_filter(array_map(static fn($v) => strtoupper(trim((string)$v)), (array)$raw), static fn($v) => $v !== '')));
            sort($raw);
            return $raw;
        }
        if ($type === 'true_false') {
            $raw = strtoupper(trim((string)($question['correct_option'] ?? 'A')));
            if (!in_array($raw, ['A','B'], true)) $raw = strtoupper(trim((string)($question['correct_answer_text'] ?? 'A')));
            return $raw;
        }
        if (in_array($type, ['short_answer','fill_blank'], true)) {
            return strtolower(trim((string)($question['correct_answer_text'] ?? '')));
        }
        if ($type === 'essay') {
            return null;
        }
        $raw = strtoupper(trim((string)($question['correct_option'] ?? '')));
        return $raw !== '' ? $raw : strtoupper(trim((string)($question['correct_answer_text'] ?? '')));
    }
}

if (!function_exists('portalCbtAutoScoreAttempt')) {
    function portalCbtAutoScoreAttempt(PDO $pdo, int $attemptId): array {
        portalCbtEnsureEnterpriseSchema($pdo);
        $attemptInfo = [];
        try {
            $st = $pdo->prepare("SELECT a.*, t.title AS test_title, t.pass_mark, t.results_published, t.answer_review_enabled, t.instant_result_enabled, t.negative_mark_per_wrong, t.negative_marking_enabled FROM cbt_attempts a INNER JOIN cbt_tests t ON t.id = a.test_id WHERE a.id = ? LIMIT 1");
            $st->execute([$attemptId]);
            $attemptInfo = $st->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $attemptInfo = [];
        }
        if (!$attemptInfo) return [];

        $questionOrder = portalCbtJsonDecode($attemptInfo['question_order_json'] ?? '[]', []);
        $questions = portalCbtFetchQuestionsByIds($pdo, $questionOrder);
        $questionMap = [];
        foreach ($questions as $question) { $questionMap[(int)$question['id']] = $question; }
        $answers = portalCbtJsonDecode($attemptInfo['answers_json'] ?? '{}', []);

        $objectiveScore = 0.0;
        $theoryScore = 0.0;
        $maxScore = 0.0;
        $hasTheory = false;
        $breakdown = [];
        foreach ($questionOrder as $qidRaw) {
            $qid = (int)$qidRaw;
            if (!isset($questionMap[$qid])) continue;
            $question = $questionMap[$qid];
            $type = portalCbtQuestionType($question['question_type'] ?? '');
            $marks = (float)($question['marks'] ?? 1);
            $maxScore += $marks;
            $submitted = $answers[(string)$qid]['selected'] ?? '';
            $normalizedSubmitted = portalCbtNormalizeSubmittedAnswer($type, $submitted);
            $isCorrect = false;
            $earned = 0.0;

            if ($type === 'essay') {
                $hasTheory = true;
                $earned = 0.0;
            } elseif ($type === 'multiple_choice') {
                $correct = portalCbtQuestionCorrectAnswerNormalized($question);
                $isCorrect = $normalizedSubmitted === $correct;
                $earned = $isCorrect ? $marks : 0.0;
            } elseif ($type === 'true_false' || $type === 'single_choice') {
                $correct = portalCbtQuestionCorrectAnswerNormalized($question);
                $submittedLabel = strtoupper(trim((string)$normalizedSubmitted));
                $isCorrect = $submittedLabel !== '' && $submittedLabel === $correct;
                $earned = $isCorrect ? $marks : 0.0;
            } elseif (in_array($type, ['short_answer','fill_blank'], true)) {
                $correct = portalCbtQuestionCorrectAnswerNormalized($question);
                $submittedText = strtolower(trim((string)$normalizedSubmitted));
                $isCorrect = $submittedText !== '' && $correct !== '' && $submittedText === $correct;
                $earned = $isCorrect ? $marks : 0.0;
            }

            if (!$isCorrect && !$hasTheory && !empty($attemptInfo['negative_marking_enabled']) && (float)($attemptInfo['negative_mark_per_wrong'] ?? 0) > 0 && $normalizedSubmitted !== '' && !(is_array($normalizedSubmitted) && !$normalizedSubmitted)) {
                $earned -= (float)$attemptInfo['negative_mark_per_wrong'];
            }

            if ($type !== 'essay') $objectiveScore += $earned;
            $breakdown[$qid] = [
                'question_type' => $type,
                'marks' => $marks,
                'earned' => $earned,
                'correct' => $isCorrect ? 1 : 0,
                'submitted' => $normalizedSubmitted,
                'correct_answer' => portalCbtQuestionCorrectAnswerNormalized($question),
            ];
        }

        $manualRows = [];
        try {
            $st = $pdo->prepare("SELECT question_id, marks_awarded, marker_comment FROM cbt_manual_scores WHERE attempt_id = ?");
            $st->execute([$attemptId]);
            $manualRows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $manualRows = [];
        }
        foreach ($manualRows as $manualRow) {
            $theoryScore += (float)($manualRow['marks_awarded'] ?? 0);
            $breakdown[(int)($manualRow['question_id'] ?? 0)]['manual_marks'] = (float)($manualRow['marks_awarded'] ?? 0);
        }

        $totalScore = $objectiveScore + $theoryScore;
        $percent = $maxScore > 0 ? round(($totalScore / $maxScore) * 100, 2) : 0.0;
        $grade = portalCbtGradeFromPercent($pdo, $percent);
        $passMark = (float)($attemptInfo['pass_mark'] ?? 0);
        $passFail = $percent >= $passMark ? 'passed' : 'failed';
        $gradingStatus = $hasTheory ? 'awaiting_manual_grading' : 'graded';
        if ($hasTheory && $manualRows) {
            $gradingStatus = 'graded';
        }
        $resultStatus = $gradingStatus === 'graded' ? (((int)($attemptInfo['results_published'] ?? 0) === 1) ? 'published' : 'approved') : 'awaiting_manual_grading';

        try {
            $pdo->prepare("UPDATE cbt_attempts SET score = ?, objective_score = ?, theory_score = ?, max_score = ?, percentage_score = ?, grade_label = ?, pass_fail_status = ?, grading_status = ?, result_status = ?, review_json = ?, updated_at = NOW() WHERE id = ?")
                ->execute([
                    $totalScore,
                    $objectiveScore,
                    $theoryScore,
                    $maxScore,
                    $percent,
                    (string)($grade['grade_label'] ?? ''),
                    $passFail,
                    $gradingStatus,
                    $resultStatus,
                    json_encode($breakdown),
                    $attemptId,
                ]);
        } catch (Throwable $e) {}

        return [
            'score' => $totalScore,
            'objective_score' => $objectiveScore,
            'theory_score' => $theoryScore,
            'max_score' => $maxScore,
            'percentage_score' => $percent,
            'grade_label' => (string)($grade['grade_label'] ?? ''),
            'pass_fail_status' => $passFail,
            'grading_status' => $gradingStatus,
            'result_status' => $resultStatus,
            'breakdown' => $breakdown,
        ];
    }
}

if (!function_exists('portalCbtFinalizeAttempt')) {
    function portalCbtFinalizeAttempt(PDO $pdo, int $attemptId): array {
        portalCbtEnsureEnterpriseSchema($pdo);
        $attempt = [];
        try {
            $st = $pdo->prepare("SELECT * FROM cbt_attempts WHERE id = ? LIMIT 1");
            $st->execute([$attemptId]);
            $attempt = $st->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            $attempt = [];
        }
        if (!$attempt || !empty($attempt['submitted_at'])) {
            return $attempt;
        }
        $hash = sha1('cbt-submit|' . $attemptId . '|' . microtime(true) . '|' . mt_rand());
        try {
            $pdo->prepare("UPDATE cbt_attempts SET status = 'submitted', submitted_at = NOW(), submitted_hash = ?, updated_at = NOW() WHERE id = ? AND submitted_at IS NULL")
                ->execute([$hash, $attemptId]);
        } catch (Throwable $e) {}
        $scoreRow = portalCbtAutoScoreAttempt($pdo, $attemptId);
        try {
            $st = $pdo->prepare("SELECT * FROM cbt_attempts WHERE id = ? LIMIT 1");
            $st->execute([$attemptId]);
            $attempt = $st->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {}
        return $attempt ?: $scoreRow;
    }
}


if (!function_exists('portalCbtAttemptContext')) {
    function portalCbtAttemptContext(PDO $pdo, int $attemptId): array {
        if ($attemptId <= 0) return [];
        try {
            $sql = "SELECT a.*, t.title AS test_title, t.exam_code, t.course_code, t.subject_name, t.pass_mark, t.results_published, t.answer_review_enabled, t.instant_result_enabled, t.instructions, t.duration_minutes,
                           s.email, s.phone, s.matric_no, s.registration_no, s.admission_no, s.department, s.programme, s.level, s.session,
                           CONCAT_WS(' ', NULLIF(TRIM(s.surname),''), NULLIF(TRIM(s.first_name),''), NULLIF(TRIM(s.other_name),'')) AS student_name
                    FROM cbt_attempts a
                    INNER JOIN cbt_tests t ON t.id = a.test_id
                    INNER JOIN students s ON s.id = a.student_id
                    WHERE a.id = ?
                    LIMIT 1";
            $st = $pdo->prepare($sql);
            $st->execute([$attemptId]);
            return $st->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalCbtNormalizeAnswerText')) {
    function portalCbtNormalizeAnswerText($value): string {
        if (is_array($value)) {
            $parts = array_map(static fn($v) => trim((string)$v), $value);
            $parts = array_values(array_filter($parts, static fn($v) => $v !== ''));
            sort($parts);
            return implode(', ', $parts);
        }
        return trim((string)$value);
    }
}

if (!function_exists('portalCbtAnswerReviewRows')) {
    function portalCbtAnswerReviewRows(PDO $pdo, array $attempt): array {
        $questionIds = portalCbtJsonDecode($attempt['question_order_json'] ?? '[]', []);
        $questions = portalCbtFetchQuestionsByIds($pdo, $questionIds);
        $answers = portalCbtJsonDecode($attempt['answers_json'] ?? '{}', []);
        $manualScores = [];
        try {
            $st = $pdo->prepare("SELECT * FROM cbt_manual_scores WHERE attempt_id = ?");
            $st->execute([(int)($attempt['id'] ?? 0)]);
            foreach (($st->fetchAll(PDO::FETCH_ASSOC) ?: []) as $row) {
                $manualScores[(int)($row['question_id'] ?? 0)] = $row;
            }
        } catch (Throwable $e) {}
        $rows = [];
        foreach ($questions as $index => $question) {
            $qid = (int)($question['id'] ?? 0);
            $type = portalCbtQuestionType($question['question_type'] ?? 'single_choice');
            $answerRow = (array)($answers[(string)$qid] ?? []);
            $selected = $answerRow['selected'] ?? '';
            $selectedText = portalCbtNormalizeAnswerText($selected);
            $correctOption = trim((string)($question['correct_option'] ?? ''));
            $correctAnswerText = trim((string)($question['correct_answer_text'] ?? ''));
            $correctDisplay = $correctAnswerText !== '' ? $correctAnswerText : $correctOption;
            $isCorrect = null;
            if ($type === 'single_choice' || $type === 'true_false') {
                $isCorrect = strtoupper($selectedText) === strtoupper($correctDisplay);
            } elseif ($type === 'multiple_choice') {
                $picked = array_values(array_filter(array_map('trim', explode(',', strtoupper($selectedText)))));
                $expected = array_values(array_filter(array_map('trim', explode(',', strtoupper($correctDisplay)))));
                sort($picked); sort($expected);
                $isCorrect = $picked === $expected;
            } elseif ($type === 'short_answer' || $type === 'fill_blank') {
                $isCorrect = strtolower($selectedText) === strtolower($correctDisplay);
            }
            $rows[] = [
                'sn' => $index + 1,
                'question' => $question,
                'type' => $type,
                'selected_text' => $selectedText,
                'correct_text' => $correctDisplay,
                'is_correct' => $isCorrect,
                'manual_score' => $manualScores[$qid] ?? [],
            ];
        }
        return $rows;
    }
}

if (!function_exists('portalCbtRenderResultSlipPrintable')) {
    function portalCbtRenderResultSlipPrintable(PDO $pdo, array $attempt, bool $autoPrint = false): void {
        $studentName = trim((string)($attempt['student_name'] ?? 'Student'));
        $siteName = 'HontTech CBT Manager';
        try { $settings = portalCbtSettings($pdo); if (!empty($settings['site_name'])) $siteName = (string)$settings['site_name']; } catch (Throwable $e) {}
        echo '<style>.cbt-print-wrap{max-width:980px;margin:0 auto;background:#fff}.cbt-print-card{border:1px solid #dbe7ff;border-radius:18px;padding:24px;box-shadow:0 10px 28px rgba(13,110,253,.08)}.cbt-print-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.cbt-print-kpi{border:1px solid #dbe7ff;border-radius:14px;padding:14px;background:#f7fbff}.cbt-print-title{font-size:1.5rem;font-weight:700;color:#0d3b8c}.cbt-print-sub{color:#5c6b82}.cbt-print-label{font-size:.8rem;color:#5c6b82;text-transform:uppercase;letter-spacing:.04em}.cbt-print-value{font-weight:600}.no-print{display:block}@media print{.no-print{display:none !important}body{background:#fff}}</style>';
        echo '<div class="container py-3 cbt-print-wrap">';
        echo '<div class="d-flex justify-content-between align-items-center mb-3 no-print"><div><a href="javascript:history.back()" class="btn btn-outline-secondary btn-sm">Back</a></div><button onclick="window.print()" class="btn btn-primary btn-sm">Print Result Slip</button></div>';
        echo '<div class="cbt-print-card">';
        echo '<div class="d-flex justify-content-between align-items-start flex-wrap gap-3 mb-4"><div><div class="cbt-print-title">' . e($siteName) . '</div><div class="cbt-print-sub">CBT Result Slip</div></div><div class="text-end"><div class="cbt-print-label">Exam</div><div class="cbt-print-value">' . e((string)($attempt['test_title'] ?? '')) . '</div><div class="small text-muted">' . e((string)($attempt['exam_code'] ?? '')) . ' • ' . e((string)($attempt['course_code'] ?? '')) . '</div></div></div>';
        echo '<div class="cbt-print-grid mb-4">';
        foreach ([
            ['Student Name', $studentName],
            ['Matric / Reg No.', (string)($attempt['matric_no'] ?? ($attempt['registration_no'] ?? $attempt['admission_no'] ?? '—'))],
            ['Department / Programme', trim((string)($attempt['department'] ?? '')) . ' / ' . trim((string)($attempt['programme'] ?? ''))],
            ['Level / Session', trim((string)($attempt['level'] ?? '')) . ' / ' . trim((string)($attempt['session'] ?? ''))],
            ['Score', (string)($attempt['score'] ?? '0') . ' / ' . (string)($attempt['max_score'] ?? '0')],
            ['Percentage', number_format((float)($attempt['percentage_score'] ?? 0), 1) . '%'],
            ['Grade', (string)($attempt['grade_label'] ?? '—')],
            ['Decision', ucfirst((string)($attempt['pass_fail_status'] ?? 'pending'))],
            ['Grading Status', ucfirst(str_replace('_',' ', (string)($attempt['grading_status'] ?? 'pending')))],
            ['Result Status', ucfirst(str_replace('_',' ', (string)($attempt['result_status'] ?? 'pending')))],
            ['Started At', !empty($attempt['started_at']) ? date('d M Y h:i A', strtotime((string)$attempt['started_at'])) : '—'],
            ['Submitted At', !empty($attempt['submitted_at']) ? date('d M Y h:i A', strtotime((string)$attempt['submitted_at'])) : '—'],
        ] as $row) {
            echo '<div class="cbt-print-kpi"><div class="cbt-print-label">' . e($row[0]) . '</div><div class="cbt-print-value">' . e($row[1]) . '</div></div>';
        }
        echo '</div>';
        echo '<div class="small text-muted">This slip was generated from the HontTech CBT module. Result visibility obeys the current exam publication settings.</div>';
        echo '</div></div>';
        if ($autoPrint) echo '<script>window.addEventListener("load",function(){window.print();});</script>';
    }
}

if (!function_exists('portalCbtRenderCertificatePrintable')) {
    function portalCbtRenderCertificatePrintable(PDO $pdo, array $attempt, bool $autoPrint = false): void {
        $studentName = trim((string)($attempt['student_name'] ?? 'Student'));
        $siteName = 'HontTech CBT Manager';
        try { $settings = portalCbtSettings($pdo); if (!empty($settings['site_name'])) $siteName = (string)$settings['site_name']; } catch (Throwable $e) {}
        echo '<style>.cbt-cert-wrap{max-width:1100px;margin:0 auto;background:#fff}.cbt-cert-card{border:8px solid #0d6efd;border-radius:24px;padding:40px;background:linear-gradient(180deg,#f7fbff 0%,#ffffff 100%)}.cbt-cert-title{font-size:2rem;font-weight:800;color:#0d3b8c;letter-spacing:.08em;text-transform:uppercase}.cbt-cert-name{font-size:2.3rem;font-weight:700;color:#0a2f6a;margin:18px 0}.cbt-cert-line{font-size:1.1rem;color:#42526b}.no-print{display:block}@media print{.no-print{display:none !important}body{background:#fff}}</style>';
        echo '<div class="container py-3 cbt-cert-wrap">';
        echo '<div class="d-flex justify-content-between align-items-center mb-3 no-print"><a href="javascript:history.back()" class="btn btn-outline-secondary btn-sm">Back</a><button onclick="window.print()" class="btn btn-primary btn-sm">Print Certificate</button></div>';
        echo '<div class="cbt-cert-card text-center">';
        echo '<div class="cbt-cert-title">Certificate of Achievement</div>';
        echo '<div class="cbt-cert-line mt-2">' . e($siteName) . '</div>';
        echo '<div class="cbt-cert-line mt-4">This is to certify that</div>';
        echo '<div class="cbt-cert-name">' . e($studentName) . '</div>';
        echo '<div class="cbt-cert-line">successfully passed the CBT assessment titled <strong>' . e((string)($attempt['test_title'] ?? '')) . '</strong>.</div>';
        echo '<div class="cbt-cert-line mt-2">Score: <strong>' . e((string)($attempt['score'] ?? '0')) . '/' . e((string)($attempt['max_score'] ?? '0')) . '</strong> &nbsp; | &nbsp; Percentage: <strong>' . e(number_format((float)($attempt['percentage_score'] ?? 0), 1) . '%') . '</strong> &nbsp; | &nbsp; Grade: <strong>' . e((string)($attempt['grade_label'] ?? '')) . '</strong></div>';
        echo '<div class="cbt-cert-line mt-5">Issued on ' . e(date('d M Y')) . '</div>';
        echo '<div class="row mt-5"><div class="col-6 text-start"><div style="border-top:1px solid #8aa7df;max-width:280px"></div><div class="small mt-2 text-muted">Exam Office</div></div><div class="col-6 text-end"><div style="border-top:1px solid #8aa7df;max-width:280px;margin-left:auto"></div><div class="small mt-2 text-muted">Authorized Signatory</div></div></div>';
        echo '</div></div>';
        if ($autoPrint) echo '<script>window.addEventListener("load",function(){window.print();});</script>';
    }
}

if (!function_exists('portalCbtRenderAnswerReviewPage')) {
    function portalCbtRenderAnswerReviewPage(PDO $pdo, array $attempt, bool $showBack = true): void {
        $rows = portalCbtAnswerReviewRows($pdo, $attempt);
        echo '<style>.cbt-review-card{border:1px solid #dbe7ff;border-radius:18px;background:#fff;box-shadow:0 10px 28px rgba(13,110,253,.08)}.cbt-review-soft{border:1px solid #dbe7ff;border-radius:14px;background:#f7fbff}</style>';
        echo '<div class="container-fluid py-3">';
        if ($showBack) echo '<div class="mb-3"><a href="javascript:history.back()" class="btn btn-outline-secondary btn-sm">Back</a></div>';
        echo '<div class="cbt-review-card p-3 p-lg-4">';
        echo '<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3"><div><h4 class="mb-1">Answer Review</h4><div class="text-muted small">' . e((string)($attempt['test_title'] ?? '')) . ' • ' . e((string)($attempt['student_name'] ?? 'Student')) . '</div></div><span class="badge text-bg-' . portalCbtStatusBadgeClass((string)($attempt['result_status'] ?? 'pending')) . '">' . e(ucfirst(str_replace('_',' ', (string)($attempt['result_status'] ?? 'pending')))) . '</span></div>';
        foreach ($rows as $row) {
            $question = (array)$row['question'];
            $manual = (array)$row['manual_score'];
            echo '<div class="cbt-review-soft p-3 mb-3">';
            echo '<div class="d-flex justify-content-between align-items-start gap-2 mb-2"><div><div class="fw-semibold">Q' . (int)$row['sn'] . ' • ' . e(ucwords(str_replace('_',' ', (string)$row['type']))) . '</div><div class="small text-muted">' . e((string)($question['subject_name'] ?? '')) . (($question['section_name'] ?? '') !== '' ? ' • ' . e((string)$question['section_name']) : '') . '</div></div><span class="badge text-bg-light border">' . e((string)($question['marks'] ?? '1')) . ' mark(s)</span></div>';
            echo '<div class="mb-2 richtext-output">' . portalRenderSafeRichText((string)($question['question'] ?? '')) . '</div>';
            if (($row['type'] ?? '') !== 'essay') {
                $options = portalCbtQuestionOptions($question);
                if ($options) {
                    echo '<div class="small mb-2">';
                    foreach ($options as $label => $value) {
                        echo '<div><strong>' . e((string)$label) . '.</strong> ' . e((string)$value) . '</div>';
                    }
                    echo '</div>';
                }
            }
            echo '<div class="row g-2">';
            echo '<div class="col-md-4"><div class="border rounded p-2 h-100"><div class="small text-muted">Submitted Answer</div><div class="fw-semibold">' . nl2br(e((string)($row['selected_text'] !== '' ? $row['selected_text'] : 'No answer'))) . '</div></div></div>';
            echo '<div class="col-md-4"><div class="border rounded p-2 h-100"><div class="small text-muted">Correct Answer</div><div class="fw-semibold">' . nl2br(e((string)($row['correct_text'] !== '' ? $row['correct_text'] : 'Manual review'))) . '</div></div></div>';
            $statusHtml = '<span class="badge text-bg-secondary">Reviewed</span>';
            if ($row['is_correct'] === true) $statusHtml = '<span class="badge text-bg-success">Correct</span>';
            if ($row['is_correct'] === false) $statusHtml = '<span class="badge text-bg-danger">Incorrect</span>';
            if (($row['type'] ?? '') === 'essay') $statusHtml = '<span class="badge text-bg-warning">Essay</span>';
            echo '<div class="col-md-4"><div class="border rounded p-2 h-100"><div class="small text-muted">Evaluation</div><div class="fw-semibold">' . $statusHtml . '</div>';
            if ($manual) { echo '<div class="small mt-2">Manual Score: <strong>' . e((string)($manual['marks_awarded'] ?? '0')) . '</strong></div>'; if (!empty($manual['marker_comment'])) echo '<div class="small text-muted">' . e((string)$manual['marker_comment']) . '</div>'; }
            echo '</div></div>';
            echo '</div>';
            if (!empty($question['explanation'])) { echo '<div class="mt-2"><div class="small text-muted mb-1">Explanation / Solution</div><div class="richtext-output">' . portalRenderSafeRichText((string)$question['explanation']) . '</div></div>'; }
        }
        echo '</div></div>';
    }
}

if (!function_exists('portalCbtReportBundle')) {
    function portalCbtReportBundle(PDO $pdo, array $filters = []): array {
        portalCbtEnsureEnterpriseSchema($pdo);
        $where = ['1=1']; $params = [];
        $testId = (int)($filters['test_id'] ?? 0);
        $status = strtolower(trim((string)($filters['status'] ?? 'all')));
        $dateFrom = trim((string)($filters['date_from'] ?? ''));
        $dateTo = trim((string)($filters['date_to'] ?? ''));
        if ($testId > 0) { $where[] = 'a.test_id = ?'; $params[] = $testId; }
        if ($status !== '' && $status !== 'all') {
            if ($status === 'published') $where[] = "a.result_status = 'published'";
            elseif ($status === 'withheld') $where[] = "a.result_status = 'withheld'";
            elseif ($status === 'graded') $where[] = "a.grading_status = 'graded'";
            elseif ($status === 'pending') $where[] = "a.grading_status <> 'graded'";
            elseif ($status === 'submitted') $where[] = "a.status = 'submitted'";
            elseif ($status === 'started') $where[] = "a.status = 'started'";
        }
        if ($dateFrom !== '') { $where[] = 'DATE(COALESCE(a.submitted_at, a.started_at)) >= ?'; $params[] = $dateFrom; }
        if ($dateTo !== '') { $where[] = 'DATE(COALESCE(a.submitted_at, a.started_at)) <= ?'; $params[] = $dateTo; }
        $rows = [];
        try {
            $sql = "SELECT a.*, t.title AS test_title, t.exam_code, t.course_code, t.pass_mark, t.results_published,
                           CONCAT_WS(' ', NULLIF(TRIM(s.surname),''), NULLIF(TRIM(s.first_name),''), NULLIF(TRIM(s.other_name),'')) AS student_name,
                           s.matric_no, s.registration_no, s.admission_no, s.department, s.programme, s.level, s.session
                    FROM cbt_attempts a
                    INNER JOIN cbt_tests t ON t.id = a.test_id
                    INNER JOIN students s ON s.id = a.student_id
                    WHERE " . implode(' AND ', $where) . "
                    ORDER BY COALESCE(a.submitted_at, a.started_at) DESC, a.id DESC
                    LIMIT 1000";
            $st = $pdo->prepare($sql);
            $st->execute($params);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) { $rows = []; }
        $submitted = array_values(array_filter($rows, static fn($r) => !empty($r['submitted_at'])));
        $graded = array_values(array_filter($rows, static fn($r) => strtolower(trim((string)($r['grading_status'] ?? ''))) === 'graded'));
        $passed = array_values(array_filter($graded, static fn($r) => strtolower(trim((string)($r['pass_fail_status'] ?? ''))) === 'passed'));
        $failed = array_values(array_filter($graded, static fn($r) => strtolower(trim((string)($r['pass_fail_status'] ?? ''))) === 'failed'));
        $perc = array_map(static fn($r) => (float)($r['percentage_score'] ?? 0), $graded);
        return ['rows' => $rows, 'summary' => [
            'total_attempts' => count($rows), 'submitted' => count($submitted), 'graded' => count($graded), 'passed' => count($passed), 'failed' => count($failed),
            'average_percent' => $perc ? round(array_sum($perc)/count($perc),1) : 0, 'highest_percent' => $perc ? round(max($perc),1) : 0, 'lowest_percent' => $perc ? round(min($perc),1) : 0,
            'attendance_rate' => $rows ? round((count($submitted)/count($rows))*100,1) : 0,
        ]];
    }
}

if (!function_exists('portalCbtSendExamAssignmentNotifications')) {
    function portalCbtSendExamAssignmentNotifications(PDO $pdo, int $testId): void {
        if ($testId <= 0) return;
        $settings = portalCbtSettings($pdo);
        try { $st = $pdo->prepare("SELECT * FROM cbt_tests WHERE id = ? LIMIT 1"); $st->execute([$testId]); $test = $st->fetch(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $test = []; }
        if (!$test) return;
        try { $students = $pdo->query("SELECT * FROM students ORDER BY id DESC LIMIT 1000")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $students = []; }
        foreach ($students as $studentRow) {
            $studentId = (int)($studentRow['id'] ?? 0);
            if ($studentId <= 0) continue;
            $eligibility = portalCbtEligibility($pdo, $test, $studentRow, portalCbtAttemptRow($pdo, $testId, $studentId));
            if (empty($eligibility['allowed'])) continue;
            try { if (function_exists('createNotification')) createNotification($pdo, 'student', $studentId, 'New CBT assignment', 'A CBT exam has been assigned to you: ' . (string)($test['title'] ?? 'Exam') . '.', '?page=student_cbt&tab=available&selected_test=' . $testId); } catch (Throwable $e) {}
            if (!empty($settings['email_notifications_enabled']) && !empty($studentRow['email']) && function_exists('sendLoggedEmail')) {
                try {
                    $subject = (string)($settings['site_name'] ?? 'HontTech CBT Manager') . ': New CBT Assignment';
                    $body = '<p>Dear ' . e(portalCbtAdminStudentName($studentRow)) . ',</p><p>' . portalRenderSafeRichText((string)($settings['email_template_assignment'] ?? 'You have a new CBT assignment waiting in your student portal.')) . '</p><p><strong>Exam:</strong> ' . e((string)($test['title'] ?? '')) . '</p><p><a href="' . e(appBaseUrl() . '/index.php?page=student_cbt&tab=available&selected_test=' . $testId) . '">Open CBT Dashboard</a></p>';
                    sendLoggedEmail($pdo, 'student', $studentId, (string)$studentRow['email'], 'cbt_assignment', $subject, $body);
                } catch (Throwable $e) {}
            }
        }
    }
}

if (!function_exists('portalCbtSendResultPublishedNotifications')) {
    function portalCbtSendResultPublishedNotifications(PDO $pdo, int $testId, int $attemptId = 0): void {
        if ($testId <= 0) return;
        $settings = portalCbtSettings($pdo);
        try {
            $sql = "SELECT a.id, a.student_id, a.test_id, a.score, a.max_score, a.percentage_score, a.grade_label, a.result_status, t.title AS test_title, s.email,
                           CONCAT_WS(' ', NULLIF(TRIM(s.surname),''), NULLIF(TRIM(s.first_name),''), NULLIF(TRIM(s.other_name),'')) AS student_name
                    FROM cbt_attempts a
                    INNER JOIN cbt_tests t ON t.id = a.test_id
                    INNER JOIN students s ON s.id = a.student_id
                    WHERE a.test_id = ? AND a.grading_status = 'graded'" . ($attemptId > 0 ? " AND a.id = ?" : "");
            $st = $pdo->prepare($sql);
            $params = [$testId]; if ($attemptId > 0) $params[] = $attemptId;
            $st->execute($params); $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) { $rows = []; }
        foreach ($rows as $row) {
            try { $pdo->prepare("UPDATE cbt_attempts SET result_status = 'published', updated_at = NOW() WHERE id = ?")->execute([(int)($row['id'] ?? 0)]); } catch (Throwable $e) {}
            try { if (function_exists('createNotification')) createNotification($pdo, 'student', (int)($row['student_id'] ?? 0), 'CBT result published', 'Your CBT result is now available for ' . (string)($row['test_title'] ?? 'the exam') . '.', '?page=student_cbt&tab=history'); } catch (Throwable $e) {}
            if (!empty($settings['email_notifications_enabled']) && !empty($row['email']) && function_exists('sendLoggedEmail')) {
                try {
                    $subject = (string)($settings['site_name'] ?? 'HontTech CBT Manager') . ': CBT Result Published';
                    $body = '<p>Dear ' . e((string)($row['student_name'] ?? 'Student')) . ',</p><p>' . portalRenderSafeRichText((string)($settings['email_template_result'] ?? 'Your CBT result has been published in your portal.')) . '</p><p><strong>Exam:</strong> ' . e((string)($row['test_title'] ?? '')) . '<br><strong>Score:</strong> ' . e((string)($row['score'] ?? '0')) . '/' . e((string)($row['max_score'] ?? '0')) . ' (' . e(number_format((float)($row['percentage_score'] ?? 0), 1)) . '%)</p><p><a href="' . e(appBaseUrl() . '/index.php?page=student_cbt&tab=history') . '">Open My Results</a></p>';
                    sendLoggedEmail($pdo, 'student', (int)($row['student_id'] ?? 0), (string)$row['email'], 'cbt_result_published', $subject, $body);
                } catch (Throwable $e) {}
            }
        }
    }
}

if (!function_exists('portalCbtAdminStudentName')) {
    function portalCbtAdminStudentName(array $row): string {
        if (function_exists('studentDisplayFullName')) {
            $name = trim((string)studentDisplayFullName($row));
            if ($name !== '') return $name;
        }
        if (function_exists('full_name')) {
            $name = trim((string)full_name($row));
            if ($name !== '') return $name;
        }
        return trim((string)($row['full_name'] ?? 'Student'));
    }
}

if (!function_exists('portalCbtStatusBadgeClass')) {
    function portalCbtStatusBadgeClass(string $status): string {
        $status = strtolower(trim($status));
        return match ($status) {
            'active', 'published', 'open', 'passed', 'eligible', 'graded', 'live' => 'success',
            'pending', 'awaiting_manual_grading', 'upcoming', 'draft', 'approved' => 'warning',
            'failed', 'withheld', 'closed', 'suspended', 'inactive', 'deleted', 'blocked' => 'danger',
            default => 'secondary',
        };
    }
}


if (!function_exists('featureEnabledForCurrentUser')) {
    function featureEnabledForCurrentUser(PDO $pdo, string $menuKey): bool {
        if (!isLoggedIn()) return false;
        if (isAdmin()) return menuEnabled($pdo, $menuKey);
        return studentMenuEnabled($pdo, $menuKey);

        // Self-healing: expand CBT engine fields (non-breaking)
        try {
            if (!portalColumnExists($pdo, 'cbt_tests', 'pass_mark')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN pass_mark INT NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'randomize_questions')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN randomize_questions TINYINT(1) NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'published')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN published TINYINT(1) NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'level')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN level VARCHAR(10) NULL"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'semester')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN semester VARCHAR(20) NULL"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'session_name')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN session_name VARCHAR(32) NULL"); }
        } catch (Throwable $e) {}

        try {
            // Questions table: supports MCQ (A-D)
            $pdo->exec("CREATE TABLE IF NOT EXISTS cbt_questions (
                id INT AUTO_INCREMENT PRIMARY KEY,
                test_id INT NOT NULL,
                question_text TEXT NOT NULL,
                option_a TEXT NULL,
                option_b TEXT NULL,
                option_c TEXT NULL,
                option_d TEXT NULL,
                correct_option CHAR(1) NULL,
                marks INT NOT NULL DEFAULT 1,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX (test_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        try {
            if (!portalColumnExists($pdo, 'cbt_attempts', 'answers_json')) { $pdo->exec("ALTER TABLE cbt_attempts ADD COLUMN answers_json LONGTEXT NULL"); }
            if (!portalColumnExists($pdo, 'cbt_attempts', 'score')) { $pdo->exec("ALTER TABLE cbt_attempts ADD COLUMN score INT NULL"); }
            if (!portalColumnExists($pdo, 'cbt_attempts', 'submitted_at')) { $pdo->exec("ALTER TABLE cbt_attempts ADD COLUMN submitted_at DATETIME NULL"); }
            if (!portalColumnExists($pdo, 'cbt_attempts', 'status')) { $pdo->exec("ALTER TABLE cbt_attempts ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'started'"); }
        } catch (Throwable $e) {}

    }
}

}
/**
 * =============================
 * Name formatting helpers
 * =============================
 */
if (!function_exists('studentFullName')) {
    function studentFullName(array $student): string {
        $surname = trim((string)($student['surname'] ?? ''));
        $first   = trim((string)($student['first_name'] ?? ($student['firstname'] ?? '')));
        $other   = trim((string)($student['other_name'] ?? ($student['othername'] ?? '')));
        $name = $surname;
        if ($first !== '') $name .= ($name !== '' ? ', ' : '') . $first;
        if ($other !== '') $name .= ' ' . $other;
        return trim($name);
    }
}
if (!function_exists('studentFullNameUpper')) {
    function studentFullNameUpper(array $student): string {
        return strtoupper(studentFullName($student));
    }
}
if (!function_exists('studentDisplayFullName')) {
    function studentDisplayFullName(array $student): string {
        $parts = [];
        foreach (['surname', 'first_name', 'other_name'] as $field) {
            $value = trim((string)($student[$field] ?? ''));
            if ($value !== '') {
                $parts[] = $value;
            }
        }
        if (!$parts) {
            $fallback = trim((string)studentFullName($student));
            return $fallback !== '' ? str_replace(',', '', $fallback) : 'Student';
        }
        return trim(preg_replace('/\s+/', ' ', implode(' ', $parts)));
    }
}
if (!function_exists('studentAdminSingleSelectLabel')) {
    function studentAdminSingleSelectLabel(array $student): string {
        $name = trim((string)studentDisplayFullName($student));
        if ($name === '') {
            $name = 'Student';
        }
        $matric = trim((string)($student['matric_no'] ?? ''));
        if ($matric !== '') {
            return $name . ' - ' . $matric;
        }
        $applicationNo = trim((string)($student['application_no'] ?? ($student['registration_no'] ?? '')));
        if ($applicationNo !== '') {
            return $name . ' - ' . $applicationNo;
        }
        return $name;
    }
}


if (!function_exists('studentIdentityPhotoPath')) {
    function studentIdentityPhotoPath(array $student): string {
        $candidates = [
            trim((string)($student['passport_photo'] ?? '')),
            trim((string)($student['profile_image'] ?? '')),
        ];
        foreach ($candidates as $path) {
            if ($path !== '') {
                return $path;
            }
        }
        return '';
    }
}

if (!function_exists('portalNormalizeMatricLookup')) {
    function portalNormalizeMatricLookup(string $value): string {
        $value = trim($value);
        if ($value === '') return '';
        $value = preg_replace('/\s+/', '', $value) ?? $value;
        return strtolower($value);
    }
}

if (!function_exists('portalAdminFindStudentByIdentifier')) {
    function portalAdminFindStudentByIdentifier(PDO $pdo, string $identifier): ?array {
        $normalized = portalNormalizeMatricLookup($identifier);
        if ($normalized === '') return null;
        $baseSql = "SELECT s.*,
                           COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.school_name),''), NULLIF(TRIM(s.school),''), NULLIF(TRIM(s.faculty),''), 'Not Set') AS faculty_name,
                           COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.course_of_study),''), NULLIF(TRIM(s.course_in_view),''), NULLIF(TRIM(s.department),''), 'Not Set') AS department_name,
                           COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme_name_cache),''), NULLIF(TRIM(s.programme),''), 'Not Set') AS programme_name
                    FROM students s
                    LEFT JOIN faculties f ON f.id = s.faculty_id
                    LEFT JOIN departments d ON d.id = s.department_id
                    LEFT JOIN programmes p ON p.id = s.programme_id
                    WHERE %s = ?
                    LIMIT 1";
        $matchExpressions = [
            "LOWER(REPLACE(TRIM(COALESCE(s.matric_no,'')), ' ', ''))",
            "LOWER(REPLACE(TRIM(COALESCE(s.admission_no,'')), ' ', ''))",
        ];
        foreach ($matchExpressions as $expr) {
            $stmt = $pdo->prepare(sprintf($baseSql, $expr));
            $stmt->execute([$normalized]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($row) {
                return $row;
            }
        }
        return null;
    }
}

if (!function_exists('portalAdminFindStudentByMatric')) {
    function portalAdminFindStudentByMatric(PDO $pdo, string $matricNumber): ?array {
        return portalAdminFindStudentByIdentifier($pdo, $matricNumber);
    }
}


if (!function_exists('portalNormalizeMatricForSave')) {
    function portalNormalizeMatricForSave(string $value): string {
        $value = trim($value);
        if ($value === '') {
            return '';
        }
        $value = preg_replace('/\s+/', '', $value) ?? $value;
        return trim($value);
    }
}

if (!function_exists('portalMatricExistsForOtherStudent')) {
    function portalMatricExistsForOtherStudent(PDO $pdo, string $matricNumber, int $excludeStudentId = 0): bool {
        $normalized = portalNormalizeMatricLookup($matricNumber);
        if ($normalized === '') {
            return false;
        }
        $sql = "SELECT id FROM students WHERE LOWER(REPLACE(TRIM(COALESCE(matric_no,'')), ' ', '')) = ?";
        $params = [$normalized];
        if ($excludeStudentId > 0) {
            $sql .= " AND id <> ?";
            $params[] = $excludeStudentId;
        }
        $sql .= " LIMIT 1";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return (bool)$stmt->fetchColumn();
    }
}

if (!function_exists('portalSyncStudentMatricReferences')) {
    function portalSyncStudentMatricReferences(PDO $pdo, int $studentId, string $oldMatric, string $newMatric): array {
        $studentId = (int)$studentId;
        $oldMatric = portalNormalizeMatricForSave($oldMatric);
        $newMatric = portalNormalizeMatricForSave($newMatric);
        $touched = [];
        if ($studentId <= 0 || $newMatric === '') {
            return $touched;
        }

        try {
            $dbName = (string)$pdo->query('SELECT DATABASE()')->fetchColumn();
            if ($dbName === '') {
                return $touched;
            }
            $stmt = $pdo->prepare("SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = ? AND column_name IN ('matric_no','matric_number','matriculation') ORDER BY table_name, column_name");
            $stmt->execute([$dbName]);
            $tableColumns = [];
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $table = (string)($row['table_name'] ?? '');
                $column = (string)($row['column_name'] ?? '');
                if ($table === '' || $column === '' || $table === 'students' || $table === 'matric_number_sequences') {
                    continue;
                }
                $tableColumns[$table][] = $column;
            }

            foreach ($tableColumns as $table => $columns) {
                $uniqueCols = array_values(array_unique(array_filter($columns)));
                if (!$uniqueCols) {
                    continue;
                }
                $hasStudentId = function_exists('portalColumnExistsSafe') ? portalColumnExistsSafe($pdo, $table, 'student_id') : false;
                $matchedThisTable = false;
                foreach ($uniqueCols as $col) {
                    try {
                        $quotedTable = '`' . str_replace('`', '``', $table) . '`';
                        $quotedCol = '`' . str_replace('`', '``', $col) . '`';
                        if ($hasStudentId) {
                            $sql = "UPDATE {$quotedTable} SET {$quotedCol} = ? WHERE student_id = ?";
                            $up = $pdo->prepare($sql);
                            $up->execute([$newMatric, $studentId]);
                            if ($up->rowCount() > 0) {
                                $matchedThisTable = true;
                            }
                        } elseif ($oldMatric !== '') {
                            $sql = "UPDATE {$quotedTable} SET {$quotedCol} = ? WHERE LOWER(REPLACE(TRIM(COALESCE({$quotedCol},'')), ' ', '')) = ?";
                            $up = $pdo->prepare($sql);
                            $up->execute([$newMatric, portalNormalizeMatricLookup($oldMatric)]);
                            if ($up->rowCount() > 0) {
                                $matchedThisTable = true;
                            }
                        }
                    } catch (Throwable $e) {
                    }
                }
                if ($matchedThisTable) {
                    $touched[] = $table;
                }
            }
        } catch (Throwable $e) {
            return $touched;
        }

        return array_values(array_unique($touched));
    }
}


if (!function_exists('portalNormalizeStudentAcademicSyncPayload')) {
    function portalNormalizeStudentAcademicSyncPayload(array $payload): array {
        $school = trim((string)($payload['school_name'] ?? $payload['school'] ?? $payload['faculty'] ?? ''));
        $department = trim((string)($payload['course_of_study'] ?? $payload['course_in_view'] ?? $payload['department'] ?? ''));
        $programme = trim((string)($payload['programme_name_cache'] ?? $payload['program'] ?? $payload['programme'] ?? ''));
        $modeOfStudy = trim((string)($payload['mode_of_study'] ?? ''));
        $level = trim((string)($payload['level'] ?? $payload['current_level'] ?? ''));
        return [
            'faculty_id' => (int)($payload['faculty_id'] ?? 0),
            'department_id' => (int)($payload['department_id'] ?? 0),
            'programme_id' => (int)($payload['programme_id'] ?? 0),
            'school_name' => $school,
            'school' => $school,
            'faculty' => $school,
            'department' => $department,
            'course_of_study' => $department,
            'course_in_view' => $department,
            'programme' => $programme,
            'program' => $programme,
            'programme_name_cache' => $programme,
            'mode_of_study' => $modeOfStudy,
            'level' => $level,
            'current_level' => $level,
        ];
    }
}

if (!function_exists('portalNormalizeLooseIdentifierLookup')) {
    function portalNormalizeLooseIdentifierLookup(string $value): string {
        $value = trim($value);
        if ($value === '') {
            return '';
        }
        $value = preg_replace('/\s+/', '', $value) ?? $value;
        return strtolower(trim($value));
    }
}

if (!function_exists('portalSyncStudentAcademicReferences')) {
    function portalSyncStudentAcademicReferences(PDO $pdo, int $studentId, array $studentIdentity, array $payload): array {
        $studentId = (int)$studentId;
        if ($studentId <= 0) {
            return [];
        }

        $payload = portalNormalizeStudentAcademicSyncPayload($payload);
        $dbName = '';
        try {
            $dbName = (string)($pdo->query('SELECT DATABASE()')->fetchColumn() ?: '');
        } catch (Throwable $e) {
            $dbName = '';
        }
        if ($dbName === '') {
            return [];
        }

        $syncableColumns = [
            'faculty_id','department_id','programme_id',
            'school_name','school','faculty',
            'department','course_of_study','course_in_view',
            'programme','program','programme_name_cache',
            'mode_of_study','level','current_level'
        ];
        $identifierColumns = ['student_id','matric_no','registration_no','admission_no','application_no'];
        $allColumns = array_values(array_unique(array_merge($syncableColumns, $identifierColumns)));
        $placeholders = implode(',', array_fill(0, count($allColumns), '?'));
        $tableColumns = [];

        try {
            $st = $pdo->prepare("SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = ? AND column_name IN ($placeholders) ORDER BY table_name, ordinal_position");
            $st->execute(array_merge([$dbName], $allColumns));
            while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
                $table = (string)($row['table_name'] ?? '');
                $column = (string)($row['column_name'] ?? '');
                if ($table === '' || $column === '') {
                    continue;
                }
                $tableColumns[$table][] = $column;
            }
        } catch (Throwable $e) {
            return [];
        }

        $ignoreTables = [
            'students','faculties','departments','programmes','academic_sessions','system_settings','notification_settings',
            'email_templates','sms_templates','registration_page_scopes','registrable_courses','matric_number_sequences',
            'notifications','messages','message_recipients','email_logs','sms_logs','wallets','wallet_transactions'
        ];

        $identityMap = [
            'matric_no' => portalNormalizeLooseIdentifierLookup((string)($studentIdentity['matric_no'] ?? '')),
            'registration_no' => portalNormalizeLooseIdentifierLookup((string)($studentIdentity['registration_no'] ?? '')),
            'admission_no' => portalNormalizeLooseIdentifierLookup((string)($studentIdentity['admission_no'] ?? '')),
            'application_no' => portalNormalizeLooseIdentifierLookup((string)($studentIdentity['application_no'] ?? '')),
        ];

        $touched = [];
        foreach ($tableColumns as $table => $columns) {
            if (in_array($table, $ignoreTables, true)) {
                continue;
            }
            $columns = array_values(array_unique(array_filter($columns)));
            $setClauses = [];
            $params = [];
            foreach ($payload as $column => $value) {
                if (!in_array($column, $columns, true)) {
                    continue;
                }
                $setClauses[] = '`' . str_replace('`', '``', $column) . '` = ?';
                $params[] = $value;
            }
            if (!$setClauses) {
                continue;
            }

            $whereClauses = [];
            $whereParams = [];
            if (in_array('student_id', $columns, true)) {
                $whereClauses[] = '`student_id` = ?';
                $whereParams[] = $studentId;
            } else {
                foreach ($identityMap as $column => $normalizedValue) {
                    if ($normalizedValue === '' || !in_array($column, $columns, true)) {
                        continue;
                    }
                    $quoted = '`' . str_replace('`', '``', $column) . '`';
                    $whereClauses[] = "LOWER(REPLACE(TRIM(COALESCE({$quoted},'')), ' ', '')) = ?";
                    $whereParams[] = $normalizedValue;
                }
            }
            if (!$whereClauses) {
                continue;
            }

            try {
                $quotedTable = '`' . str_replace('`', '``', $table) . '`';
                $sql = "UPDATE {$quotedTable} SET " . implode(', ', $setClauses) . " WHERE " . implode(' OR ', $whereClauses);
                $up = $pdo->prepare($sql);
                $up->execute(array_merge($params, $whereParams));
                if ($up->rowCount() > 0) {
                    $touched[] = $table;
                }
            } catch (Throwable $e) {
            }
        }

        return array_values(array_unique($touched));
    }
}


if (!function_exists('portalResolveAdminDisplayName')) {
    function portalResolveAdminDisplayName(PDO $pdo, int $adminId, string $fallback = 'System'): string {
        $adminId = (int)$adminId;
        if ($adminId <= 0) return $fallback;
        try {
            $st = $pdo->prepare("SELECT COALESCE(NULLIF(TRIM(full_name), ''), NULLIF(TRIM(username), ''), NULLIF(TRIM(email), ''), CONCAT('Admin #', id)) FROM admins WHERE id = ? LIMIT 1");
            $st->execute([$adminId]);
            $name = trim((string)($st->fetchColumn() ?: ''));
            return $name !== '' ? $name : $fallback;
        } catch (Throwable $e) {
            return $fallback;
        }
    }
}

if (!function_exists('portalEnsureStudentVerifierChangeHistorySchema')) {
    function portalEnsureStudentVerifierChangeHistorySchema(PDO $pdo): void {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS student_verifier_change_history (
                id INT AUTO_INCREMENT PRIMARY KEY,
                student_id INT NOT NULL,
                matric_no VARCHAR(120) NULL,
                admission_no VARCHAR(120) NULL,
                student_full_name VARCHAR(255) NULL,
                action_type VARCHAR(80) NOT NULL DEFAULT 'academic_details_updated',
                old_matric_no VARCHAR(120) NULL,
                new_matric_no VARCHAR(120) NULL,
                old_faculty VARCHAR(255) NULL,
                new_faculty VARCHAR(255) NULL,
                old_department VARCHAR(255) NULL,
                new_department VARCHAR(255) NULL,
                old_programme VARCHAR(255) NULL,
                new_programme VARCHAR(255) NULL,
                old_mode_of_study VARCHAR(120) NULL,
                new_mode_of_study VARCHAR(120) NULL,
                old_level VARCHAR(50) NULL,
                new_level VARCHAR(50) NULL,
                changed_by_admin_id INT NULL,
                changed_by_name VARCHAR(255) NULL,
                source_page VARCHAR(120) NULL,
                remark VARCHAR(255) NULL,
                changed_fields_json TEXT NULL,
                change_date DATE NULL,
                change_time TIME NULL,
                change_year VARCHAR(4) NULL,
                created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                KEY idx_svch_student (student_id),
                KEY idx_svch_action (action_type),
                KEY idx_svch_created (created_at),
                KEY idx_svch_matric (matric_no),
                KEY idx_svch_admission (admission_no),
                KEY idx_svch_admin (changed_by_admin_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (Throwable $e) {}

        $columns = [
            'student_id' => "ALTER TABLE student_verifier_change_history ADD COLUMN student_id INT NOT NULL AFTER id",
            'matric_no' => "ALTER TABLE student_verifier_change_history ADD COLUMN matric_no VARCHAR(120) NULL AFTER student_id",
            'admission_no' => "ALTER TABLE student_verifier_change_history ADD COLUMN admission_no VARCHAR(120) NULL AFTER matric_no",
            'student_full_name' => "ALTER TABLE student_verifier_change_history ADD COLUMN student_full_name VARCHAR(255) NULL AFTER admission_no",
            'action_type' => "ALTER TABLE student_verifier_change_history ADD COLUMN action_type VARCHAR(80) NOT NULL DEFAULT 'academic_details_updated' AFTER student_full_name",
            'old_matric_no' => "ALTER TABLE student_verifier_change_history ADD COLUMN old_matric_no VARCHAR(120) NULL AFTER action_type",
            'new_matric_no' => "ALTER TABLE student_verifier_change_history ADD COLUMN new_matric_no VARCHAR(120) NULL AFTER old_matric_no",
            'old_faculty' => "ALTER TABLE student_verifier_change_history ADD COLUMN old_faculty VARCHAR(255) NULL AFTER new_matric_no",
            'new_faculty' => "ALTER TABLE student_verifier_change_history ADD COLUMN new_faculty VARCHAR(255) NULL AFTER old_faculty",
            'old_department' => "ALTER TABLE student_verifier_change_history ADD COLUMN old_department VARCHAR(255) NULL AFTER new_faculty",
            'new_department' => "ALTER TABLE student_verifier_change_history ADD COLUMN new_department VARCHAR(255) NULL AFTER old_department",
            'old_programme' => "ALTER TABLE student_verifier_change_history ADD COLUMN old_programme VARCHAR(255) NULL AFTER new_department",
            'new_programme' => "ALTER TABLE student_verifier_change_history ADD COLUMN new_programme VARCHAR(255) NULL AFTER old_programme",
            'old_mode_of_study' => "ALTER TABLE student_verifier_change_history ADD COLUMN old_mode_of_study VARCHAR(120) NULL AFTER new_programme",
            'new_mode_of_study' => "ALTER TABLE student_verifier_change_history ADD COLUMN new_mode_of_study VARCHAR(120) NULL AFTER old_mode_of_study",
            'old_level' => "ALTER TABLE student_verifier_change_history ADD COLUMN old_level VARCHAR(50) NULL AFTER new_mode_of_study",
            'new_level' => "ALTER TABLE student_verifier_change_history ADD COLUMN new_level VARCHAR(50) NULL AFTER old_level",
            'changed_by_admin_id' => "ALTER TABLE student_verifier_change_history ADD COLUMN changed_by_admin_id INT NULL AFTER new_level",
            'changed_by_name' => "ALTER TABLE student_verifier_change_history ADD COLUMN changed_by_name VARCHAR(255) NULL AFTER changed_by_admin_id",
            'source_page' => "ALTER TABLE student_verifier_change_history ADD COLUMN source_page VARCHAR(120) NULL AFTER changed_by_name",
            'remark' => "ALTER TABLE student_verifier_change_history ADD COLUMN remark VARCHAR(255) NULL AFTER source_page",
            'changed_fields_json' => "ALTER TABLE student_verifier_change_history ADD COLUMN changed_fields_json TEXT NULL AFTER remark",
            'change_date' => "ALTER TABLE student_verifier_change_history ADD COLUMN change_date DATE NULL AFTER changed_fields_json",
            'change_time' => "ALTER TABLE student_verifier_change_history ADD COLUMN change_time TIME NULL AFTER change_date",
            'change_year' => "ALTER TABLE student_verifier_change_history ADD COLUMN change_year VARCHAR(4) NULL AFTER change_time",
            'created_at' => "ALTER TABLE student_verifier_change_history ADD COLUMN created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER change_year",
        ];
        foreach ($columns as $column => $sql) {
            try {
                if (!portalColumnExistsSafe($pdo, 'student_verifier_change_history', $column)) {
                    $pdo->exec($sql);
                }
            } catch (Throwable $e) {}
        }

        $indexes = [
            "ALTER TABLE student_verifier_change_history ADD INDEX idx_svch_student (student_id)",
            "ALTER TABLE student_verifier_change_history ADD INDEX idx_svch_action (action_type)",
            "ALTER TABLE student_verifier_change_history ADD INDEX idx_svch_created (created_at)",
            "ALTER TABLE student_verifier_change_history ADD INDEX idx_svch_matric (matric_no)",
            "ALTER TABLE student_verifier_change_history ADD INDEX idx_svch_admission (admission_no)",
            "ALTER TABLE student_verifier_change_history ADD INDEX idx_svch_admin (changed_by_admin_id)"
        ];
        foreach ($indexes as $sql) {
            try { $pdo->exec($sql); } catch (Throwable $e) {}
        }
    }
}

if (!function_exists('portalResolveStudentVerifierActionType')) {
    function portalResolveStudentVerifierActionType(array $changedFlags): string {
        $normalized = [];
        foreach ($changedFlags as $key => $flag) {
            if ($flag) $normalized[] = (string)$key;
        }
        if (in_array('matric_no', $normalized, true)) {
            return 'matric_number_changed';
        }
        if (count($normalized) === 1) {
            return match ($normalized[0]) {
                'faculty' => 'faculty_changed',
                'department' => 'department_changed',
                'programme' => 'programme_changed',
                'mode_of_study' => 'mode_of_study_changed',
                'level' => 'level_changed',
                default => 'academic_details_updated',
            };
        }
        return 'academic_details_updated';
    }
}

if (!function_exists('portalLogStudentVerifierChange')) {
    function portalLogStudentVerifierChange(PDO $pdo, array $payload): void {
        $studentId = (int)($payload['student_id'] ?? 0);
        if ($studentId <= 0) return;
        portalEnsureStudentVerifierChangeHistorySchema($pdo);

        $changedFlags = (array)($payload['changed_fields'] ?? []);
        $actionType = trim((string)($payload['action_type'] ?? ''));
        if ($actionType === '') {
            $actionType = portalResolveStudentVerifierActionType($changedFlags);
        }
        $adminId = (int)($payload['changed_by_admin_id'] ?? 0);
        $changedByName = trim((string)($payload['changed_by_name'] ?? ''));
        if ($changedByName === '') {
            $changedByName = portalResolveAdminDisplayName($pdo, $adminId, 'System');
        }
        $jsonChangedFields = null;
        try {
            $jsonChangedFields = $changedFlags ? json_encode($changedFlags, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
        } catch (Throwable $e) {
            $jsonChangedFields = null;
        }

        $st = $pdo->prepare("INSERT INTO student_verifier_change_history (
            student_id, matric_no, admission_no, student_full_name, action_type,
            old_matric_no, new_matric_no,
            old_faculty, new_faculty,
            old_department, new_department,
            old_programme, new_programme,
            old_mode_of_study, new_mode_of_study,
            old_level, new_level,
            changed_by_admin_id, changed_by_name, source_page, remark, changed_fields_json,
            change_date, change_time, change_year, created_at
        ) VALUES (
            ?, ?, ?, ?, ?,
            ?, ?,
            ?, ?,
            ?, ?,
            ?, ?,
            ?, ?,
            ?, ?,
            ?, ?, ?, ?, ?,
            CURDATE(), CURTIME(), DATE_FORMAT(NOW(), '%Y'), NOW()
        )");
        $st->execute([
            $studentId,
            trim((string)($payload['matric_no'] ?? '')),
            trim((string)($payload['admission_no'] ?? '')),
            trim((string)($payload['student_full_name'] ?? '')),
            $actionType,
            trim((string)($payload['old_matric_no'] ?? '')),
            trim((string)($payload['new_matric_no'] ?? '')),
            trim((string)($payload['old_faculty'] ?? '')),
            trim((string)($payload['new_faculty'] ?? '')),
            trim((string)($payload['old_department'] ?? '')),
            trim((string)($payload['new_department'] ?? '')),
            trim((string)($payload['old_programme'] ?? '')),
            trim((string)($payload['new_programme'] ?? '')),
            trim((string)($payload['old_mode_of_study'] ?? '')),
            trim((string)($payload['new_mode_of_study'] ?? '')),
            trim((string)($payload['old_level'] ?? '')),
            trim((string)($payload['new_level'] ?? '')),
            $adminId > 0 ? $adminId : null,
            $changedByName,
            trim((string)($payload['source_page'] ?? 'admin_matric_verifier')),
            trim((string)($payload['remark'] ?? '')) ?: null,
            $jsonChangedFields,
        ]);
    }
}

if (!function_exists('portalFetchStudentVerifierChangeHistory')) {
    function portalFetchStudentVerifierChangeHistory(PDO $pdo, int $studentId): array {
        $studentId = (int)$studentId;
        if ($studentId <= 0) return [];
        portalEnsureStudentVerifierChangeHistorySchema($pdo);
        try {
            $st = $pdo->prepare("SELECT * FROM student_verifier_change_history WHERE student_id = ? ORDER BY created_at DESC, id DESC");
            $st->execute([$studentId]);
            return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}

if (!function_exists('portalUpdateStudentMatricNumber')) {
    function portalUpdateStudentMatricNumber(PDO $pdo, int $studentId, string $newMatricNumber, int $adminId = 0): array {
        $studentId = (int)$studentId;
        if ($studentId <= 0) {
            throw new RuntimeException('Invalid student selected for matric number update.');
        }

        $newMatricNumber = portalNormalizeMatricForSave($newMatricNumber);
        if ($newMatricNumber === '') {
            throw new RuntimeException('Please enter the new matric number.');
        }
        if (strlen($newMatricNumber) > 120) {
            throw new RuntimeException('The matric number is too long.');
        }

        $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
        $st->execute([$studentId]);
        $student = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        if (!$student) {
            throw new RuntimeException('Student record not found.');
        }

        $oldMatricNumber = trim((string)($student['matric_no'] ?? ''));
        if (portalNormalizeMatricLookup($oldMatricNumber) === portalNormalizeMatricLookup($newMatricNumber)) {
            throw new RuntimeException('The new matric number is the same as the current one.');
        }
        if (portalMatricExistsForOtherStudent($pdo, $newMatricNumber, $studentId)) {
            throw new RuntimeException('That matric number already exists for another student.');
        }

        $startedTxn = false;
        if (!$pdo->inTransaction()) {
            $pdo->beginTransaction();
            $startedTxn = true;
        }

        try {
            portalEnsureStudentVerifierChangeHistorySchema($pdo);
            $pdo->prepare("UPDATE students SET matric_no = ? WHERE id = ?")->execute([$newMatricNumber, $studentId]);
            if (function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'students', 'matriculation')) {
                try {
                    $pdo->prepare("UPDATE students SET matriculation = ? WHERE id = ?")->execute([$newMatricNumber, $studentId]);
                } catch (Throwable $e) {
                }
            }
            $touchedTables = portalSyncStudentMatricReferences($pdo, $studentId, $oldMatricNumber, $newMatricNumber);
            if ($startedTxn) {
                $pdo->commit();
            }

            if (function_exists('portalAuthLogActivity')) {
                portalAuthLogActivity($pdo, 'admin', max(0, $adminId), 'student_matric_number_changed', [
                    'student_id' => $studentId,
                    'student_name' => studentDisplayFullName($student),
                    'old_matric_no' => $oldMatricNumber,
                    'new_matric_no' => $newMatricNumber,
                    'touched_tables' => $touchedTables,
                ]);
            }
            try {
                portalLogStudentVerifierChange($pdo, [
                    'student_id' => $studentId,
                    'matric_no' => $newMatricNumber,
                    'admission_no' => (string)($student['admission_no'] ?? ''),
                    'student_full_name' => studentDisplayFullName($student),
                    'action_type' => 'matric_number_changed',
                    'old_matric_no' => $oldMatricNumber,
                    'new_matric_no' => $newMatricNumber,
                    'changed_by_admin_id' => max(0, $adminId),
                    'source_page' => 'admin_matric_verifier',
                    'remark' => 'Matric number updated from verifier page.',
                    'changed_fields' => ['matric_no' => true],
                ]);
            } catch (Throwable $e) {}

            $updated = portalAdminFindStudentByMatric($pdo, $newMatricNumber);
            if (!$updated) {
                $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $st->execute([$studentId]);
                $updated = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            }

            return [
                'old_matric_no' => $oldMatricNumber,
                'new_matric_no' => $newMatricNumber,
                'student' => $updated,
                'touched_tables' => $touchedTables,
            ];
        } catch (Throwable $e) {
            if ($startedTxn && $pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }
    }
}


if (!function_exists('portalStudentIdentityDisplay')) {
    function portalStudentIdentityDisplay(array $student): array {
        $choices = [
            'Matric Number' => trim((string)($student['matric_no'] ?? '')),
            'Student ID' => trim((string)($student['student_id'] ?? '')),
            'Admission Number' => trim((string)($student['admission_no'] ?? '')),
            'Registration Number' => trim((string)($student['registration_no'] ?? '')),
            'Application Number' => trim((string)($student['application_no'] ?? '')),
        ];
        foreach ($choices as $label => $value) {
            if ($value !== '') {
                return ['label' => $label, 'value' => $value];
            }
        }
        return ['label' => 'Student ID', 'value' => (string)($student['id'] ?? '')];
    }
}

if (!function_exists('studentIdentityPreviewPayload')) {
    function studentIdentityPreviewPayload(PDO $pdo, string $identity): ?array {
        $identity = trim($identity);
        if ($identity === '' || !portalStudentIdentityLooksMeaningful($identity)) return null;

        $student = portalFindStudentByIdentity($pdo, $identity, [
            'id',
            'registration_no',
            'application_no',
            'admission_no',
            'matric_no',
            'matriculation',
            'student_id',
            'email',
            'mobile_phone',
            'phone',
            'surname',
            'first_name',
            'other_name',
            'passport_photo',
            'profile_image',
        ]);
        if (!$student) return null;

        $identityDisplay = portalStudentIdentityDisplay($student);

        return [
            'found' => true,
            'fullname' => studentFullName($student),
            'matric_number' => (string)($identityDisplay['value'] ?? ''),
            'identifier_label' => (string)($identityDisplay['label'] ?? 'Student ID'),
            'identifier_value' => (string)($identityDisplay['value'] ?? ''),
            'photo' => studentIdentityPhotoPath($student),
            'student_id' => (string)($student['id'] ?? ''),
        ];
    }
}

/**
 * Officer decision helper (signature used on ID card + admission letter)
 */
if (!function_exists('getOfficerDecision')) {
    function getOfficerDecision(PDO $pdo, int $studentId): ?array {
        try {
            $stmt = $pdo->prepare("SELECT qualified, officer_name, officer_signature FROM admin_decisions WHERE student_id = ? LIMIT 1");
            $stmt->execute([$studentId]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            return $row ?: null;
        } catch (Throwable $e) {
            return null;
        }
    }
}


/**
 * =============================
 * Messaging + Notifications helpers
 * =============================
 */

if (!function_exists('currentUserRole')) {
function currentUserRole() {
    return $_SESSION['user_type'] ?? '';
}
}

if (!function_exists('currentUserId')) {
function currentUserId() {
    if (isAdmin()) return $_SESSION['user_id'] ?? 0;
    if (isStudent()) return $_SESSION['user_id'] ?? ($_SESSION['student_id'] ?? 0);
    return 0;
}
}

if (!function_exists('cleanupOldNotifications')) {
function cleanupOldNotifications($pdo) {
    // Delete archived notifications older than 1 month
    try {
        $pdo->exec("DELETE FROM notifications
                   WHERE archived = 1
                     AND archived_at IS NOT NULL
                     AND archived_at < DATE_SUB(NOW(), INTERVAL 1 MONTH)");
    } catch (Exception $e) {
        // Silent fail
    }
}
}


if (!function_exists('createNotification')) {
function createNotification($pdo, $role, $userId, $title, $body = '', $link = '') {
    try {
        $stmt = $pdo->prepare("INSERT INTO notifications (user_role, user_id, title, body, link, is_read, created_at)
                               VALUES (?, ?, ?, ?, ?, 0, NOW())");
        $stmt->execute([$role, $userId, $title, $body, $link]);
    } catch (Exception $e) {
        // Silent fail
    }
}
}

if (!function_exists('getUnreadNotificationCount')) {
function getUnreadNotificationCount($pdo, $role, $userId) {
    try {
        $stmt = $pdo->prepare("SELECT COUNT(*) FROM notifications WHERE user_role = ? AND user_id = ? AND is_read = 0");
        $stmt->execute([$role, $userId]);
        return (int)$stmt->fetchColumn();
    } catch (Exception $e) {
        return 0;
    }
}
}

if (!function_exists('getUnreadMessageCount')) {
function getUnreadMessageCount($pdo, $role, $userId) {
    try {
        $stmt = $pdo->prepare("SELECT COUNT(*)
                               FROM message_recipients
                               WHERE recipient_role = ? AND recipient_id = ? AND is_read = 0");
        $stmt->execute([$role, $userId]);
        return (int)$stmt->fetchColumn();
    } catch (Exception $e) {
        return 0;
    }
}
}
function markNotificationReadAndArchive($pdo, $notificationId, $role, $userId) {
    try {
        $stmt = $pdo->prepare("UPDATE notifications
                               SET is_read = 1, read_at = NOW(), archived = 1, archived_at = NOW()
                               WHERE id = ? AND user_role = ? AND user_id = ?");
        $stmt->execute([$notificationId, $role, $userId]);
    } catch (Exception $e) {
        // Silent fail
    }
}

/**
 * Payment date helper:
 * - Prevents deprecated strtotime(null)
 * - If payment_date is empty, auto-generates NOW() and persists into DB (payment_transactions).
 */
if (!function_exists('getPaymentDateValue')) {
function getPaymentDateValue($pdo, $transaction) {
    $paymentDate = $transaction['payment_date'] ?? '';

    if (empty($paymentDate)) {
        $fallback = $transaction['approval_date'] ?? ($transaction['created_at'] ?? date('Y-m-d H:i:s'));
        $paymentDate = !empty($fallback) ? $fallback : date('Y-m-d H:i:s');

        // Persist into DB where possible
        if (!empty($transaction['reference'])) {
            try {
                $stmt = $pdo->prepare("UPDATE payment_transactions
                                       SET payment_date = COALESCE(payment_date, ?)
                                       WHERE reference = ?");
                $stmt->execute([$paymentDate, $transaction['reference']]);
            } catch (Exception $e) {
                // Silent fail
            }
        }
    }

    return $paymentDate;
}
}


if (!function_exists('formatPaymentDate')) {
function formatPaymentDate($pdo, $transaction) {
    $val = getPaymentDateValue($pdo, $transaction);
    $ts = strtotime((string)$val);
    if ($ts === false) {
        $ts = time();
    }
    return date('d M Y, g:i A', $ts);
}
}


/**
 * =============================
 * Core Notification Functions
 * (declared unconditionally so they are ALWAYS available)
 * =============================
 */
if (!function_exists('createNotification')) {
function createNotification(PDO $pdo, string $role, int $userId, string $title, string $body = '', string $link = ''): void {
    if ($userId <= 0 || ($role !== 'admin' && $role !== 'student')) return;
    try {
        $stmt = $pdo->prepare("
            INSERT INTO notifications (user_role, user_id, title, body, link, is_read, read_at, archived, archived_at, created_at)
            VALUES (?, ?, ?, ?, ?, 0, NULL, 0, NULL, NOW())
        ");
        $stmt->execute([$role, $userId, $title, $body, $link]);
    } catch (Throwable $e) {
        // fail silently
    }
}
}


if (!function_exists('getUnreadNotificationCount')) {
function getUnreadNotificationCount(PDO $pdo, string $role, int $userId): int {
    if ($userId <= 0 || ($role !== 'admin' && $role !== 'student')) return 0;
    try {
        $stmt = $pdo->prepare("
            SELECT COUNT(*)
            FROM notifications
            WHERE user_role = ?
              AND user_id = ?
              AND is_read = 0
              AND archived = 0
        ");
        $stmt->execute([$role, $userId]);
        return (int)$stmt->fetchColumn();
    } catch (Throwable $e) {
        return 0;
    }
}
}


function redirect($url) {
    $url = trim((string)$url);
    $url = str_replace(["\r", "\n", "%0d", "%0a", "%0D", "%0A"], '', $url);
    if ($url === '') {
        $url = '?page=dashboard';
    }

    // Prevent open-redirect abuse through user-controlled return/next parameters.
    if (preg_match('#^//#', $url)) {
        $url = '?page=dashboard';
    } elseif (preg_match('#^https?://#i', $url)) {
        $targetHost = strtolower((string)(parse_url($url, PHP_URL_HOST) ?: ''));
        $currentHost = strtolower((string)($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? ''));
        $currentHost = preg_replace('/:\d+$/', '', $currentHost) ?: $currentHost;
        if ($targetHost === '' || $currentHost === '' || $targetHost !== $currentHost) {
            $url = '?page=dashboard';
        }
    } elseif (!preg_match('#^(\?|/|[A-Za-z0-9_./&=%+\-:#]+$)#', $url)) {
        $url = '?page=dashboard';
    }

    // Clear any output buffer
    if (ob_get_level()) {
        ob_end_clean();
    }

    header('Location: ' . $url, true, 302);
    exit;
}


// =============================
// Module import/export + attachment enhancement handlers
// =============================
$currentPageEnh = trim((string)($_GET['page'] ?? $_POST['page'] ?? ''));
$currentActEnh = trim((string)($_POST['act'] ?? $_GET['act'] ?? ''));
$currentTabEnh = trim((string)($_GET['tab'] ?? $_POST['tab'] ?? ''));
$currentDatasetEnh = trim((string)($_GET['dataset'] ?? $_POST['dataset'] ?? ''));
$currentModuleExport = trim((string)($_GET['module_export'] ?? ''));
$currentDownload = trim((string)($_GET['download'] ?? ''));

if ($currentPageEnh === 'lms_admin' && isAdmin()) {
    ensureLmsSchema($pdo);

    if ($currentDownload === 'sample_csv' && $currentDatasetEnh === 'materials') {
        moduleCsvDownload('lms_materials_sample.csv',
            ['title','description','course_code','faculty_id','department_id','programme_id','level','semester','session_name'],
            [[
                'title' => 'Intro to Algebra',
                'description' => 'Week 1 note',
                'course_code' => 'MTH101',
                'faculty_id' => '0',
                'department_id' => '0',
                'programme_id' => '0',
                'level' => '100',
                'semester' => 'First',
                'session_name' => '2026/2027',
            ]]
        );
    }

    if ($currentDownload === 'sample_csv' && $currentDatasetEnh === 'assignments') {
        moduleCsvDownload('lms_assignments_sample.csv',
            ['title','instructions','due_date','faculty_id','department_id','programme_id','level','semester','session_name'],
            [[
                'title' => 'Assignment 1',
                'instructions' => 'Answer all questions and submit before due date',
                'due_date' => '2026-10-15',
                'faculty_id' => '0',
                'department_id' => '0',
                'programme_id' => '0',
                'level' => '100',
                'semester' => 'First',
                'session_name' => '2026/2027',
            ]]
        );
    }

    if ($currentModuleExport === 'csv' && $currentDatasetEnh === 'materials') {
        $rows = [];
        try {
            $rows = $pdo->query("SELECT title, description, course_code, faculty_id, department_id, programme_id, level, semester, session_name FROM lms_materials ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {}
        moduleCsvDownload('lms_materials_export.csv', ['title','description','course_code','faculty_id','department_id','programme_id','level','semester','session_name'], $rows);
    }

    if ($currentModuleExport === 'csv' && $currentDatasetEnh === 'assignments') {
        $rows = [];
        try {
            $rows = $pdo->query("SELECT title, instructions, due_date, faculty_id, department_id, programme_id, level, semester, session_name FROM lms_assignments ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {}
        moduleCsvDownload('lms_assignments_export.csv', ['title','instructions','due_date','faculty_id','department_id','programme_id','level','semester','session_name'], $rows);
    }

    if ($currentActEnh === 'import_lms_materials_csv' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $rows = moduleUploadedCsvRows($_FILES['csv_file'] ?? []);
            $count = 0;
            $st = $pdo->prepare("INSERT INTO lms_materials (title, description, course_code, faculty_id, department_id, programme_id, level, semester, session_name, created_by) VALUES (?,?,?,?,?,?,?,?,?,?)");
            foreach ($rows as $row) {
                $title = moduleCsvText($row, 'title');
                if ($title === '') { continue; }
                $st->execute([
                    $title,
                    moduleCsvText($row, 'description'),
                    moduleCsvText($row, 'course_code'),
                    moduleCsvInt($row, 'faculty_id'),
                    moduleCsvInt($row, 'department_id'),
                    moduleCsvInt($row, 'programme_id'),
                    moduleCsvText($row, 'level'),
                    moduleCsvText($row, 'semester'),
                    moduleCsvText($row, 'session_name'),
                    (int)($_SESSION['admin_id'] ?? 0),
                ]);
                $count++;
            }
            flash('success', $count . ' LMS material record(s) imported successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Failed to import LMS materials CSV.');
        }
        redirect('?page=lms_admin&tab=materials');
    }

    if ($currentActEnh === 'import_lms_assignments_csv' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $rows = moduleUploadedCsvRows($_FILES['csv_file'] ?? []);
            $count = 0;
            $st = $pdo->prepare("INSERT INTO lms_assignments (title, instructions, due_date, faculty_id, department_id, programme_id, level, semester, session_name, created_by) VALUES (?,?,?,?,?,?,?,?,?,?)");
            foreach ($rows as $row) {
                $title = moduleCsvText($row, 'title');
                if ($title === '') { continue; }
                $dueDate = moduleCsvText($row, 'due_date');
                $st->execute([
                    $title,
                    moduleCsvText($row, 'instructions'),
                    $dueDate !== '' ? $dueDate : null,
                    moduleCsvInt($row, 'faculty_id'),
                    moduleCsvInt($row, 'department_id'),
                    moduleCsvInt($row, 'programme_id'),
                    moduleCsvText($row, 'level'),
                    moduleCsvText($row, 'semester'),
                    moduleCsvText($row, 'session_name'),
                    (int)($_SESSION['admin_id'] ?? 0),
                ]);
                $count++;
            }
            flash('success', $count . ' LMS assignment record(s) imported successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Failed to import LMS assignments CSV.');
        }
        redirect('?page=lms_admin&tab=assignments');
    }

    if ($currentActEnh === 'save_lms_material' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $id = (int)($_POST['id'] ?? 0);
        $title = trim((string)($_POST['title'] ?? ''));
        $desc = trim((string)($_POST['description'] ?? ''));
        $level = trim((string)($_POST['level'] ?? ''));
        $semester = trim((string)($_POST['semester'] ?? ''));
        $sessionName = trim((string)($_POST['session_name'] ?? ''));
        $facultyId = (int)($_POST['faculty_id'] ?? 0);
        $departmentId = (int)($_POST['department_id'] ?? 0);
        $programmeId = (int)($_POST['programme_id'] ?? 0);
        $courseCode = trim((string)($_POST['course_code'] ?? ''));

        if ($title === '') {
            flash('danger', 'Title is required.');
            redirect('?page=lms_admin&tab=materials');
        }

        try {
            if ($id > 0) {
                $pdo->prepare("UPDATE lms_materials SET title=?, description=?, course_code=?, faculty_id=?, department_id=?, programme_id=?, level=?, semester=?, session_name=? WHERE id=?")
                    ->execute([$title, $desc, $courseCode, $facultyId, $departmentId, $programmeId, $level, $semester, $sessionName, $id]);
                $materialId = $id;
            } else {
                $pdo->prepare("INSERT INTO lms_materials (title, description, course_code, faculty_id, department_id, programme_id, level, semester, session_name, created_by) VALUES (?,?,?,?,?,?,?,?,?,?)")
                    ->execute([$title, $desc, $courseCode, $facultyId, $departmentId, $programmeId, $level, $semester, $sessionName, (int)($_SESSION['admin_id'] ?? 0)]);
                $materialId = (int)$pdo->lastInsertId();
            }

            $upload = moduleSaveUpload($_FILES['material_file'] ?? [], 'lms_materials', 'mat', $materialId);
            if ($upload) {
                try {
                    $oldPath = '';
                    $stOld = $pdo->prepare("SELECT file_path FROM lms_materials WHERE id=? LIMIT 1");
                    $stOld->execute([$materialId]);
                    $oldPath = (string)($stOld->fetchColumn() ?? '');
                    if ($oldPath !== '' && $oldPath !== $upload['file_path']) {
                        moduleDeleteStoredFile($oldPath);
                    }
                } catch (Throwable $e) {}
                $pdo->prepare("UPDATE lms_materials SET file_path=?, original_name=?, mime_type=?, file_size=? WHERE id=?")
                    ->execute([$upload['file_path'], $upload['original_name'], $upload['mime_type'], $upload['file_size'], $materialId]);
            }

            flash('success', 'Material saved successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Failed to save material.');
        }
        redirect('?page=lms_admin&tab=materials');
    }

    if ($currentActEnh === 'save_lms_assignment' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $id = (int)($_POST['id'] ?? 0);
        $title = trim((string)($_POST['title'] ?? ''));
        $instructions = trim((string)($_POST['instructions'] ?? ''));
        $dueDate = trim((string)($_POST['due_date'] ?? ''));
        $level = trim((string)($_POST['level'] ?? ''));
        $semester = trim((string)($_POST['semester'] ?? ''));
        $sessionName = trim((string)($_POST['session_name'] ?? ''));
        $facultyId = (int)($_POST['faculty_id'] ?? 0);
        $departmentId = (int)($_POST['department_id'] ?? 0);
        $programmeId = (int)($_POST['programme_id'] ?? 0);

        if ($title === '') {
            flash('danger', 'Assignment title is required.');
            redirect('?page=lms_admin&tab=assignments');
        }

        try {
            if ($id > 0) {
                $pdo->prepare("UPDATE lms_assignments SET title=?, instructions=?, due_date=?, faculty_id=?, department_id=?, programme_id=?, level=?, semester=?, session_name=? WHERE id=?")
                    ->execute([$title, $instructions, $dueDate ?: null, $facultyId, $departmentId, $programmeId, $level, $semester, $sessionName, $id]);
                $assignmentId = $id;
            } else {
                $pdo->prepare("INSERT INTO lms_assignments (title, instructions, due_date, faculty_id, department_id, programme_id, level, semester, session_name, created_by) VALUES (?,?,?,?,?,?,?,?,?,?)")
                    ->execute([$title, $instructions, $dueDate ?: null, $facultyId, $departmentId, $programmeId, $level, $semester, $sessionName, (int)($_SESSION['admin_id'] ?? 0)]);
                $assignmentId = (int)$pdo->lastInsertId();
            }

            $upload = moduleSaveUpload($_FILES['assignment_file'] ?? [], 'lms_assignments', 'asg', $assignmentId);
            if ($upload) {
                try {
                    $oldPath = '';
                    $stOld = $pdo->prepare("SELECT attachment_path FROM lms_assignments WHERE id=? LIMIT 1");
                    $stOld->execute([$assignmentId]);
                    $oldPath = (string)($stOld->fetchColumn() ?? '');
                    if ($oldPath !== '' && $oldPath !== $upload['file_path']) {
                        moduleDeleteStoredFile($oldPath);
                    }
                } catch (Throwable $e) {}
                $pdo->prepare("UPDATE lms_assignments SET attachment_path=?, attachment_name=?, attachment_mime=?, attachment_size=? WHERE id=?")
                    ->execute([$upload['file_path'], $upload['original_name'], $upload['mime_type'], $upload['file_size'], $assignmentId]);
            }

            flash('success', 'Assignment saved successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Failed to save assignment.');
        }
        redirect('?page=lms_admin&tab=assignments');
    }
}

if ($currentPageEnh === 'library_admin' && isAdmin()) {
    ensureLibrarySchema($pdo);

    if ($currentDownload === 'sample_csv' && $currentDatasetEnh === 'books') {
        moduleCsvDownload('library_books_sample.csv',
            ['isbn','title','author','category','copies_total','copies_available'],
            [[
                'isbn' => '9781234567890',
                'title' => 'General Chemistry',
                'author' => 'A. Writer',
                'category' => 'Science',
                'copies_total' => '5',
                'copies_available' => '5',
            ]]
        );
    }

    if ($currentModuleExport === 'csv' && $currentDatasetEnh === 'books') {
        $rows = [];
        try {
            $rows = $pdo->query("SELECT isbn, title, author, category, copies_total, copies_available FROM library_books ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {}
        moduleCsvDownload('library_books_export.csv', ['isbn','title','author','category','copies_total','copies_available'], $rows);
    }

    if ($currentActEnh === 'import_library_books_csv' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $rows = moduleUploadedCsvRows($_FILES['csv_file'] ?? []);
            $count = 0;
            foreach ($rows as $row) {
                $title = moduleCsvText($row, 'title');
                if ($title === '') { continue; }
                $isbn = moduleCsvText($row, 'isbn');
                $author = moduleCsvText($row, 'author');
                $category = moduleCsvText($row, 'category');
                $copiesTotal = max(1, moduleCsvInt($row, 'copies_total', 1));
                $copiesAvailable = max(0, moduleCsvInt($row, 'copies_available', $copiesTotal));

                if ($isbn !== '') {
                    $pdo->prepare("INSERT INTO library_books (isbn, title, author, category, copies_total, copies_available) VALUES (?,?,?,?,?,?)
                        ON DUPLICATE KEY UPDATE title=VALUES(title), author=VALUES(author), category=VALUES(category), copies_total=VALUES(copies_total), copies_available=VALUES(copies_available)")
                        ->execute([$isbn, $title, $author, $category, $copiesTotal, $copiesAvailable]);
                } else {
                    $pdo->prepare("INSERT INTO library_books (isbn, title, author, category, copies_total, copies_available) VALUES (?,?,?,?,?,?)")
                        ->execute([null, $title, $author, $category, $copiesTotal, $copiesAvailable]);
                }
                $count++;
            }
            flash('success', $count . ' library book record(s) imported successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Failed to import library books CSV.');
        }
        redirect('?page=library_admin&tab=inventory');
    }

    if ($currentActEnh === 'save_book' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $id = (int)($_POST['id'] ?? 0);
        $isbn = trim((string)($_POST['isbn'] ?? ''));
        $title = trim((string)($_POST['title'] ?? ''));
        $author = trim((string)($_POST['author'] ?? ''));
        $category = trim((string)($_POST['category'] ?? ''));
        $copiesTotal = max(1, (int)($_POST['copies_total'] ?? 1));

        if ($title === '') {
            flash('danger', 'Book title is required.');
            redirect('?page=library_admin&tab=inventory');
        }

        try {
            if ($id > 0) {
                $pdo->prepare("UPDATE library_books SET isbn=?, title=?, author=?, category=?, copies_total=?, copies_available=LEAST(copies_available, ?) WHERE id=?")
                    ->execute([$isbn ?: null, $title, $author, $category, $copiesTotal, $copiesTotal, $id]);
                $bookId = $id;
            } else {
                $pdo->prepare("INSERT INTO library_books (isbn, title, author, category, copies_total, copies_available) VALUES (?,?,?,?,?,?)")
                    ->execute([$isbn ?: null, $title, $author, $category, $copiesTotal, $copiesTotal]);
                $bookId = (int)$pdo->lastInsertId();
            }

            $upload = moduleSaveUpload($_FILES['book_file'] ?? [], 'library_books', 'book', $bookId);
            if ($upload) {
                try {
                    $oldPath = '';
                    $stOld = $pdo->prepare("SELECT file_path FROM library_books WHERE id=? LIMIT 1");
                    $stOld->execute([$bookId]);
                    $oldPath = (string)($stOld->fetchColumn() ?? '');
                    if ($oldPath !== '' && $oldPath !== $upload['file_path']) {
                        moduleDeleteStoredFile($oldPath);
                    }
                } catch (Throwable $e) {}
                $pdo->prepare("UPDATE library_books SET file_path=?, original_name=?, mime_type=?, file_size=? WHERE id=?")
                    ->execute([$upload['file_path'], $upload['original_name'], $upload['mime_type'], $upload['file_size'], $bookId]);
            }

            flash('success', 'Book saved successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Failed to save book.');
        }
        redirect('?page=library_admin&tab=inventory');
    }
}

if ($currentPageEnh === 'cbt_admin' && isAdmin()) {
    ensureCbtSchema($pdo);

    if ($currentDownload === 'sample_csv' && $currentDatasetEnh === 'tests') {
        moduleCsvDownload('cbt_tests_sample.csv',
            ['title','description','course_code','duration_minutes','start_at','end_at','session_name','semester','level','status','pass_mark','question_per_page','negative_mark_per_wrong','randomize_questions','published','results_published'],
            [[
                'title' => 'GST CBT 1',
                'description' => 'Mid-semester practice test',
                'course_code' => 'GST101',
                'duration_minutes' => '30',
                'start_at' => '2026-10-01 09:00:00',
                'end_at' => '2026-10-01 10:00:00',
                'session_name' => '2026/2027',
                'semester' => 'First',
                'level' => '100',
                'status' => 'active',
                'pass_mark' => '40',
                'question_per_page' => '1',
                'negative_mark_per_wrong' => '0',
                'randomize_questions' => '1',
                'published' => '1',
                'results_published' => '1',
            ]]
        );
    }

    if ($currentDownload === 'sample_csv' && $currentDatasetEnh === 'questions') {
        moduleCsvDownload('cbt_questions_sample.csv',
            ['test_id','question','option_a','option_b','option_c','option_d','correct_option','marks'],
            [[
                'test_id' => '1',
                'question' => 'What is 2 + 2?',
                'option_a' => '3',
                'option_b' => '4',
                'option_c' => '5',
                'option_d' => '6',
                'correct_option' => 'B',
                'marks' => '1',
            ]]
        );
    }

    if ($currentModuleExport === 'csv' && $currentDatasetEnh === 'tests') {
        $rows = [];
        try {
            $rows = $pdo->query("SELECT title, description, course_code, duration_minutes, start_at, end_at, session_name, semester, level, status, pass_mark, question_per_page, negative_mark_per_wrong, randomize_questions, published, results_published FROM cbt_tests ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {}
        moduleCsvDownload('cbt_tests_export.csv', ['title','description','course_code','duration_minutes','start_at','end_at','session_name','semester','level','status','pass_mark','question_per_page','negative_mark_per_wrong','randomize_questions','published','results_published'], $rows);
    }

    if ($currentModuleExport === 'csv' && $currentDatasetEnh === 'questions') {
        $rows = [];
        $testId = (int)($_GET['test_id'] ?? 0);
        try {
            if ($testId > 0) {
                $st = $pdo->prepare("SELECT test_id, question, option_a, option_b, option_c, option_d, correct_option, marks FROM cbt_questions WHERE test_id=? ORDER BY id DESC");
                $st->execute([$testId]);
                $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            }
        } catch (Throwable $e) {}
        moduleCsvDownload('cbt_questions_export.csv', ['test_id','question','option_a','option_b','option_c','option_d','correct_option','marks'], $rows);
    }

    if ($currentActEnh === 'import_cbt_tests_csv' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $rows = moduleUploadedCsvRows($_FILES['csv_file'] ?? []);
            $count = 0;
            $st = $pdo->prepare("INSERT INTO cbt_tests (title, description, course_code, duration_minutes, start_at, end_at, session_name, semester, level, status, pass_mark, question_per_page, negative_mark_per_wrong, randomize_questions, published, results_published, created_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
            foreach ($rows as $row) {
                $title = moduleCsvText($row, 'title');
                if ($title === '') { continue; }
                $status = moduleCsvText($row, 'status', 'active');
                if (!in_array($status, ['active','inactive'], true)) { $status = 'active'; }
                $st->execute([
                    $title,
                    moduleCsvText($row, 'description'),
                    moduleCsvText($row, 'course_code'),
                    max(1, moduleCsvInt($row, 'duration_minutes', 30)),
                    moduleCsvText($row, 'start_at') !== '' ? moduleCsvText($row, 'start_at') : null,
                    moduleCsvText($row, 'end_at') !== '' ? moduleCsvText($row, 'end_at') : null,
                    moduleCsvText($row, 'session_name'),
                    moduleCsvText($row, 'semester'),
                    moduleCsvText($row, 'level'),
                    $status,
                    max(0, moduleCsvInt($row, 'pass_mark', 0)),
                    max(1, moduleCsvInt($row, 'question_per_page', 1)),
                    max(0, moduleCsvFloat($row, 'negative_mark_per_wrong', 0)),
                    moduleCsvInt($row, 'randomize_questions', 0) ? 1 : 0,
                    moduleCsvInt($row, 'published', 0) ? 1 : 0,
                    moduleCsvInt($row, 'results_published', 0) ? 1 : 0,
                    (int)($_SESSION['admin_id'] ?? 0),
                ]);
                $count++;
            }
            flash('success', $count . ' CBT test record(s) imported successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Failed to import CBT tests CSV.');
        }
        redirect('?page=cbt_admin&tab=tests');
    }

    if ($currentActEnh === 'import_cbt_questions_csv' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $defaultTestId = (int)($_POST['test_id'] ?? 0);
        try {
            $rows = moduleUploadedCsvRows($_FILES['csv_file'] ?? []);
            $count = 0;
            $st = $pdo->prepare("INSERT INTO cbt_questions (test_id, question, option_a, option_b, option_c, option_d, correct_option, marks) VALUES (?,?,?,?,?,?,?,?,?)");
            foreach ($rows as $row) {
                $testId = moduleCsvInt($row, 'test_id', $defaultTestId);
                $question = moduleCsvText($row, 'question');
                if ($testId <= 0 || $question === '') { continue; }
                $correct = strtoupper(moduleCsvText($row, 'correct_option', 'A'));
                if (!in_array($correct, ['A','B','C','D'], true)) { $correct = 'A'; }
                $st->execute([
                    $testId,
                    $question,
                    moduleCsvText($row, 'option_a'),
                    moduleCsvText($row, 'option_b'),
                    moduleCsvText($row, 'option_c'),
                    moduleCsvText($row, 'option_d'),
                    $correct,
                    max(1, moduleCsvInt($row, 'marks', 1)),
                ]);
                $count++;
            }
            flash('success', $count . ' CBT question record(s) imported successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Failed to import CBT questions CSV.');
        }
        $suffix = $defaultTestId > 0 ? '&test_id=' . $defaultTestId : '';
        redirect('?page=cbt_admin&tab=questions' . $suffix);
    }

    if ($currentActEnh === 'save_test' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $id = (int)($_POST['id'] ?? 0);
        $title = trim((string)($_POST['title'] ?? ''));
        $desc = trim((string)($_POST['description'] ?? ''));
        $courseCode = trim((string)($_POST['course_code'] ?? ''));
        $duration = max(1, (int)($_POST['duration_minutes'] ?? 30));
        $startAt = trim((string)($_POST['start_at'] ?? ''));
        $endAt = trim((string)($_POST['end_at'] ?? ''));
        $sessionName = trim((string)($_POST['session_name'] ?? ''));
        $semester = trim((string)($_POST['semester'] ?? ''));
        $level = trim((string)($_POST['level'] ?? ''));
        $status = trim((string)($_POST['status'] ?? 'active'));
        if (!in_array($status, ['active','inactive'], true)) { $status = 'active'; }
        $passMark = max(0, (int)($_POST['pass_mark'] ?? 0));
        $questionPerPage = max(1, (int)($_POST['question_per_page'] ?? 1));
        $negativeMark = max(0, (float)($_POST['negative_mark_per_wrong'] ?? 0));
        $randomizeQuestions = isset($_POST['randomize_questions']) ? 1 : 0;
        $published = isset($_POST['published']) ? 1 : 0;
        $resultsPublished = isset($_POST['results_published']) ? 1 : 0;

        if ($title === '') {
            flash('danger', 'Test title is required.');
            redirect('?page=cbt_admin&tab=tests');
        }

        try {
            if ($id > 0) {
                $pdo->prepare("UPDATE cbt_tests SET title=?, description=?, course_code=?, duration_minutes=?, start_at=?, end_at=?, session_name=?, semester=?, level=?, status=?, pass_mark=?, question_per_page=?, negative_mark_per_wrong=?, randomize_questions=?, published=?, results_published=? WHERE id=?")
                    ->execute([$title, $desc, $courseCode, $duration, $startAt ?: null, $endAt ?: null, $sessionName, $semester, $level, $status, $passMark, $questionPerPage, $negativeMark, $randomizeQuestions, $published, $resultsPublished, $id]);
                $testId = $id;
            } else {
                $pdo->prepare("INSERT INTO cbt_tests (title, description, course_code, duration_minutes, start_at, end_at, session_name, semester, level, status, pass_mark, question_per_page, negative_mark_per_wrong, randomize_questions, published, results_published, created_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")
                    ->execute([$title, $desc, $courseCode, $duration, $startAt ?: null, $endAt ?: null, $sessionName, $semester, $level, $status, $passMark, $questionPerPage, $negativeMark, $randomizeQuestions, $published, $resultsPublished, (int)($_SESSION['admin_id'] ?? 0)]);
                $testId = (int)$pdo->lastInsertId();
            }

            $upload = moduleSaveUpload($_FILES['test_file'] ?? [], 'cbt_tests', 'test', $testId);
            if ($upload) {
                try {
                    $oldPath = '';
                    $stOld = $pdo->prepare("SELECT attachment_path FROM cbt_tests WHERE id=? LIMIT 1");
                    $stOld->execute([$testId]);
                    $oldPath = (string)($stOld->fetchColumn() ?? '');
                    if ($oldPath !== '' && $oldPath !== $upload['file_path']) {
                        moduleDeleteStoredFile($oldPath);
                    }
                } catch (Throwable $e) {}
                $pdo->prepare("UPDATE cbt_tests SET attachment_path=?, attachment_name=?, attachment_mime=?, attachment_size=? WHERE id=?")
                    ->execute([$upload['file_path'], $upload['original_name'], $upload['mime_type'], $upload['file_size'], $testId]);
            }

            flash('success', 'Test saved successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Failed to save test.');
        }
        redirect('?page=cbt_admin&tab=tests');
    }
}



// =============================
// Messaging + Offer Decision handlers
// =============================

// Student: accept/reject admission offer
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['offer_decision']) && isStudent()) {
    $studentId = (int)($_SESSION['user_id'] ?? 0);
    $decisionRaw = $_POST['offer_decision'] ?? '';
    $decision = ($decisionRaw === 'accept') ? 'Accepted' : 'Rejected';

    if ($studentId > 0) {
        $stmt = $pdo->prepare("UPDATE students SET offer_status = ?, offer_decision_date = NOW() WHERE id = ?");
        $stmt->execute([$decision, $studentId]);

        $generatedAdmissionNo = '';
        $generatedMatricNo = '';
        if ($decision === 'Accepted') {
            try {
                $numberProgress = ensureStudentNumberProgression($pdo, $studentId, [
                    'allow_admission_generation' => true,
                    'admission_trigger' => 'admission_offer_accepted',
                ]);
                $generatedAdmissionNo = trim((string)($numberProgress['admission_no'] ?? ''));
            } catch (Throwable $e) {
                $generatedAdmissionNo = '';
            }
        }

        // Notify all admins
        $admins = $pdo->query("SELECT id FROM admins")->fetchAll(PDO::FETCH_ASSOC);
        foreach ($admins as $a) {
            createNotification(
                $pdo,
                'admin',
                (int)$a['id'],
                "Admission offer " . strtolower($decision),
                "Student has " . strtolower($decision) . " the admission offer.",
                "?page=admin_students"
            );
        }


        // Notify student (in-app + email)
        createNotification(
            $pdo,
            'student',
            (int)$studentId,
            "Admission Offer " . $decision,
            "You have " . strtolower($decision) . " the admission offer." . ($generatedAdmissionNo !== '' ? " Your admission number is " . $generatedAdmissionNo . "." : ''),
            "?page=student_dashboard"
        );

        // Email confirmation
        try {
            $stmtE = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $stmtE->execute([$studentId]);
            $st = $stmtE->fetch(PDO::FETCH_ASSOC);
            if ($st && !empty($st['email'])) {
                if ($decision === 'Accepted') {
                    portalSendStudentAdmissionOfferAcceptedEmail($pdo, $st);
                } else {
                    $subj = "Admission Offer " . $decision . " - " . portalNotificationMailSubjectPrefix($pdo);
                    $bod = portalWrapNotificationEmail($pdo, "Admission Offer " . $decision,
                        '<p>Dear ' . htmlspecialchars(studentFullName($st), ENT_QUOTES, 'UTF-8') . ',</p>'
                        . '<p>Your admission offer decision has been recorded as <strong>' . htmlspecialchars($decision, ENT_QUOTES, 'UTF-8') . '</strong>.</p>'
                    );
                    sendLoggedEmail($pdo, 'student', (int)$st['id'], (string)$st['email'], 'admission_offer_' . strtolower($decision), $subj, $bod);
                }
            }
        } catch (Throwable $e) {}
$_SESSION['decision_updated'] = true;
        if ($decision === 'Accepted') {
            redirect('?page=admission_letter');
        }
        redirect('?page=student_dashboard');
    }
}

// Send message (admin <-> student, admin broadcast)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['send_message']) && isset($_SESSION['user_type'])) {
    $senderRole = currentUserRole();
    $senderId = currentUserId();
    $subject = trim($_POST['subject'] ?? '');
    $body = trim($_POST['body'] ?? '');

    if ($senderRole && $senderId > 0 && $subject !== '' && $body !== '') {
        try {
            $pdo->beginTransaction();

            $stmt = $pdo->prepare("INSERT INTO messages (sender_role, sender_id, subject, body, created_at) VALUES (?,?,?,?,NOW())");
            $stmt->execute([$senderRole, $senderId, $subject, $body]);
            $messageId = (int)$pdo->lastInsertId();

            if ($senderRole === 'admin') {
                $recipientMode = $_POST['recipient_mode'] ?? 'student';

                if ($recipientMode === 'all_students') {
                    $students = $pdo->query("SELECT id FROM students")->fetchAll(PDO::FETCH_ASSOC);
                    foreach ($students as $s) {
                        $rid = (int)$s['id'];
                        $stmt = $pdo->prepare("INSERT INTO message_recipients (message_id, recipient_role, recipient_id, created_at) VALUES (?,?,?,NOW())");
                        $stmt->execute([$messageId, 'student', $rid]);

                        createNotification($pdo, 'student', $rid, "New message: ".$subject, "You have a new message from Admin.", "?page=view_message&id=".$messageId);
                    }
                } else {
                    $studentId = (int)($_POST['student_id'] ?? 0);
                    if ($studentId > 0) {
                        $stmt = $pdo->prepare("INSERT INTO message_recipients (message_id, recipient_role, recipient_id, created_at) VALUES (?,?,?,NOW())");
                        $stmt->execute([$messageId, 'student', $studentId]);

                        createNotification($pdo, 'student', $studentId, "New message: ".$subject, "You have a new message from Admin.", "?page=view_message&id=".$messageId);
                    }
                }
            } else if ($senderRole === 'student') {
                // Student -> all admins
                $admins = $pdo->query("SELECT id FROM admins")->fetchAll(PDO::FETCH_ASSOC);
                foreach ($admins as $a) {
                    $rid = (int)$a['id'];
                    $stmt = $pdo->prepare("INSERT INTO message_recipients (message_id, recipient_role, recipient_id, created_at) VALUES (?,?,?,NOW())");
                    $stmt->execute([$messageId, 'admin', $rid]);

                    createNotification($pdo, 'admin', $rid, "New message: ".$subject, "You have a new message from a student.", "?page=view_message&id=".$messageId);
                }
            }

            $pdo->commit();
            $_SESSION['message_sent'] = true;
            redirect('?page=messages');
        } catch (Exception $e) {
            $pdo->rollBack();
            $_SESSION['upload_error'] = "Unable to send message. Please try again.";
            redirect('?page=messages');
        }
    } else {
        $_SESSION['upload_error'] = "Subject and message are required.";
        redirect('?page=messages&view=compose');
    }
}


function handleFileUpload($file, $documentType = '') {
    if (!isset($file) || !is_array($file) || (int)($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
        return ['success' => false, 'message' => 'No valid file was uploaded.'];
    }

    $size = (int)($file['size'] ?? 0);
    if ($size <= 0 || $size > MAX_FILE_SIZE) {
        return ['success' => false, 'message' => 'File is too large or empty. Maximum size is ' . (MAX_FILE_SIZE / 1024 / 1024) . 'MB.'];
    }

    $originalName = (string)($file['name'] ?? '');
    $fileInfo = pathinfo($originalName);
    $extension = strtolower((string)($fileInfo['extension'] ?? ''));
    $extension = preg_replace('/[^a-z0-9]/', '', $extension) ?: '';

    if ($extension === '' || !in_array($extension, ALLOWED_FILE_TYPES, true)) {
        return ['success' => false, 'message' => 'Invalid file type. Allowed types: ' . implode(', ', ALLOWED_FILE_TYPES)];
    }

    $allowedMimeByExt = [
        'jpg' => ['image/jpeg'],
        'jpeg' => ['image/jpeg'],
        'png' => ['image/png'],
        'pdf' => ['application/pdf', 'application/x-pdf'],
        'doc' => ['application/msword', 'application/octet-stream'],
        'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip', 'application/octet-stream'],
    ];
    $mime = '';
    try {
        if (function_exists('finfo_open')) {
            $fi = finfo_open(FILEINFO_MIME_TYPE);
            if ($fi) {
                $mime = (string)finfo_file($fi, (string)$file['tmp_name']);
                finfo_close($fi);
            }
        }
    } catch (Throwable $e) { $mime = ''; }
    if ($mime !== '' && isset($allowedMimeByExt[$extension]) && !in_array($mime, $allowedMimeByExt[$extension], true)) {
        return ['success' => false, 'message' => 'Invalid file content. The uploaded file does not match its extension.'];
    }
    if (in_array($extension, ['jpg','jpeg','png'], true) && function_exists('getimagesize') && @getimagesize((string)$file['tmp_name']) === false) {
        return ['success' => false, 'message' => 'Invalid image file.'];
    }

    $uploadDir = rtrim((string)UPLOAD_DIR, "\\/") . DIRECTORY_SEPARATOR;
    if (!is_dir($uploadDir)) { @mkdir($uploadDir, 0775, true); }
    honttechHardenWritableDirectory($uploadDir, false);
    if (!is_dir($uploadDir) || !is_writable($uploadDir)) {
        return ['success' => false, 'message' => 'Upload directory is not writable.'];
    }

    $random = honttechSecureToken(16);
    $filename = $random . '.' . $extension;
    $filepath = $uploadDir . $filename;

    if (move_uploaded_file($file['tmp_name'], $filepath)) {
        return [
            'success' => true,
            'filepath' => $filepath,
            'filename' => $filename,
            'document_type' => $documentType
        ];
    }
    return ['success' => false, 'message' => 'Failed to move uploaded file.'];
}

// Get system assets (logos, favicon) - Modified to handle separate logos
function getSystemAsset($assetType) {
    global $pdo;

    $stmt = $pdo->prepare("SELECT file_path FROM system_assets WHERE asset_type = ? ORDER BY created_at DESC LIMIT 1");
    $stmt->execute([$assetType]);
    $asset = $stmt->fetch(PDO::FETCH_ASSOC);

    return $asset ? $asset['file_path'] : null;
}


// -------------------------
// PWA endpoints (manifest + service worker)
// -------------------------
if (isset($_GET['pwa'])) {
    $pwa = (string)($_GET['pwa'] ?? '');

    if ($pwa === 'manifest') {
        $portalTitle = (string)(getSystemSettingValue($pdo, 'portal_title', 'HontTech') ?? 'HontTech');
        $shortName = function_exists('mb_substr') ? mb_substr($portalTitle, 0, 12) : substr($portalTitle, 0, 12);
        $icon = getSystemAsset('favicon') ?: (getSystemAsset('left_logo') ?: (getSystemAsset('right_logo') ?: null));
        if (!$icon) $icon = 'favicon.ico';
        $iconPath = (string)(parse_url((string)$icon, PHP_URL_PATH) ?: $icon);
        $iconExt = strtolower((string)pathinfo($iconPath, PATHINFO_EXTENSION));
        $iconTypeMap = [
            'png' => 'image/png',
            'jpg' => 'image/jpeg',
            'jpeg' => 'image/jpeg',
            'webp' => 'image/webp',
            'svg' => 'image/svg+xml',
            'ico' => 'image/x-icon'
        ];
        $iconType = $iconTypeMap[$iconExt] ?? 'image/png';

        $manifest = [
            "id" => "./",
            "name" => $portalTitle,
            "short_name" => $shortName,
            "start_url" => "./",
            "scope" => "./",
            "display" => "standalone",
            "display_override" => ["standalone", "minimal-ui", "browser"],
            "orientation" => "portrait-primary",
            "background_color" => (string)(getSystemSettingValue($pdo,'theme_color_4','#ffffff') ?? '#ffffff'),
            "theme_color" => (string)(getSystemSettingValue($pdo,'theme_color_1','#003366') ?? '#003366'),
            "description" => "HontTech Portal",
            "lang" => "en",
            "prefer_related_applications" => false,
            "icons" => [
                ["src" => $icon, "sizes" => "180x180", "type" => $iconType, "purpose" => "any maskable"],
                ["src" => $icon, "sizes" => "192x192", "type" => $iconType, "purpose" => "any maskable"],
                ["src" => $icon, "sizes" => "512x512", "type" => $iconType, "purpose" => "any maskable"]
            ],
            "shortcuts" => [
                ["name" => "Dashboard", "short_name" => "Dashboard", "url" => "?page=dashboard"],
                ["name" => "Payments", "short_name" => "Payments", "url" => "?page=payment"],
                ["name" => "Clearance", "short_name" => "Clearance", "url" => "?page=clearance_form"]
            ]
        ];

        header('Content-Type: application/manifest+json; charset=UTF-8');
        echo json_encode($manifest, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        exit;
    }

    if ($pwa === 'sw') {
        header('Content-Type: application/javascript; charset=UTF-8');
        echo "const CACHE_NAME='honttech-pwa-v2';\n";
        echo "const STATIC_ALLOW=/\\.(?:css|js|png|jpg|jpeg|svg|gif|webp|ico|woff2?)$/i;\n";
        echo "self.addEventListener('install',e=>{self.skipWaiting();e.waitUntil(caches.open(CACHE_NAME).then(c=>c.addAll(['./','?pwa=manifest'])).catch(()=>{}));});\n";
        echo "self.addEventListener('activate',e=>{e.waitUntil((async()=>{const keys=await caches.keys();await Promise.all(keys.map(k=>k!==CACHE_NAME?caches.delete(k):Promise.resolve()));await self.clients.claim();})());});\n";
        echo "self.addEventListener('fetch',e=>{const r=e.request;if(r.method!=='GET')return;const u=new URL(r.url);if(u.origin!==location.origin)return;const isNav=r.mode==='navigate'||(r.headers.get('accept')||'').includes('text/html');\n";
        echo "if(STATIC_ALLOW.test(u.pathname)){e.respondWith((async()=>{const c=await caches.match(r);if(c)return c;const f=await fetch(r);caches.open(CACHE_NAME).then(cc=>cc.put(r,f.clone())).catch(()=>{});return f;})());return;}\n";
        echo "if(isNav){e.respondWith((async()=>{try{return await fetch(r);}catch(err){const c=await caches.match('./');return c||new Response('Offline',{status:200,headers:{'Content-Type':'text/plain'}});}})());return;}\n";
        echo "e.respondWith((async()=>{try{return await fetch(r);}catch(err){const c=await caches.match(r);return c||new Response('',{status:504});}})());});\n";
        exit;
    }
}

// AJAX: student identity preview
if (isset($_GET['ajax']) && $_GET['ajax'] === 'student_identity_preview') {
    header('Content-Type: application/json; charset=UTF-8');
    header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');

    $identity = trim((string)($_GET['identity'] ?? ''));
    $userType = (($_GET['user_type'] ?? 'student') === 'admin') ? 'admin' : 'student';

    if ($userType !== 'student' || $identity === '' || !portalStudentIdentityLooksMeaningful($identity)) {
        echo json_encode(['found' => false], JSON_UNESCAPED_SLASHES);
        exit;
    }

    try {
        $payload = studentIdentityPreviewPayload($pdo, $identity);
        if ($payload) {
            echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        } else {
            echo json_encode(['found' => false], JSON_UNESCAPED_SLASHES);
        }
    } catch (Throwable $e) {
        echo json_encode(['found' => false], JSON_UNESCAPED_SLASHES);
    }
    exit;
}



// Save lazy-loader / resume-progress state from client beacons
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_resume_progress') {
    header('Content-Type: application/json');
    $token = trim((string)($_POST['resume_state_token'] ?? ''));
    if (!portalResumeProgressTokenValid($token)) {
        http_response_code(403);
        echo json_encode(['ok' => false, 'message' => 'Invalid resume token.']);
        exit;
    }
    if (!portalLazyLoaderEnabled($pdo)) {
        echo json_encode(['ok' => true, 'message' => 'Lazy loader is disabled.']);
        exit;
    }
    $currentUrl = trim((string)($_POST['current_url'] ?? ''));
    $currentThemeMode = portalThemeNormalizeMode((string)($_POST['current_theme_mode'] ?? ''), '');
    $extra = [];
    if ($currentThemeMode !== '') {
        $_SESSION['portal_theme_mode'] = $currentThemeMode;
        $extra['theme_mode'] = $currentThemeMode;
    }
    portalResumeTrackClientUrl($pdo, $currentUrl, $extra);
    echo json_encode(['ok' => true]);
    exit;
}

// Save user theme preference safely
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_theme_preference') {
    header('Content-Type: application/json');
    $token = trim((string)($_POST['theme_preference_token'] ?? ''));
    if (!portalThemePreferenceTokenValid($token)) {
        http_response_code(403);
        echo json_encode(['ok' => false, 'message' => 'Invalid theme token.']);
        exit;
    }
    if (!portalThemeFeatureEnabled($pdo)) {
        echo json_encode(['ok' => false, 'message' => 'Theme switcher is disabled.']);
        exit;
    }
    $actor = portalResumeCurrentActor();
    if (($actor['user_id'] ?? 0) <= 0 || ($actor['user_type'] ?? '') === '') {
        http_response_code(403);
        echo json_encode(['ok' => false, 'message' => 'You must be logged in to change theme mode.']);
        exit;
    }
    $mode = portalThemeNormalizeMode((string)($_POST['theme_mode'] ?? ''), 'light');
    portalThemeSavePreference($pdo, (string)$actor['user_type'], (int)$actor['user_id'], $mode);
    $currentUrl = trim((string)($_POST['current_url'] ?? ''));
    if ($currentUrl !== '' && portalLazyLoaderEnabled($pdo)) {
        portalResumeTrackClientUrl($pdo, $currentUrl, ['theme_mode' => $mode]);
    }
    echo json_encode(['ok' => true, 'mode' => $mode]);
    exit;
}

// Handle login
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'login') {
    $username = trim((string)($_POST['username'] ?? ''));
    $password = (string)($_POST['password'] ?? '');
    $userType = ($_POST['user_type'] ?? '') === 'admin' ? 'admin' : 'student';
    $remember = isset($_POST['remember_me']) && (string)($_POST['remember_me']) === '1';

    if ($userType === 'admin') {
        $stmt = $pdo->prepare("SELECT * FROM admins WHERE username = ?");
        $stmt->execute([$username]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user && password_verify($password, (string)$user['password'])) {
            session_regenerate_id(true);
            portalApplyAdminSessionFromRow($pdo, $user);
            portalThemeInitializeForAuthenticatedUser($pdo, 'admin', (int)$user['id']);
            portalAuthLogActivity($pdo, 'admin', (int)$user['id'], 'login_password', []);
            portalAuthCreateNotification($pdo, 'admin', (int)$user['id'], 'Login Successful', 'You signed into the admin portal successfully.');
            portalAuthRespond(true, 'Login successful.', portalResumeResolveLoginRedirect($pdo, 'admin', (int)$user['id'], '?page=admin_dashboard'));
        } else {
            $loginError = "Invalid username or password";
            if (portalAuthWantsJson()) {
                portalAuthJson(['ok' => false, 'message' => $loginError]);
            }
        }
    } else {
        $user = portalFindStudentByIdentity($pdo, $username, [
            'id',
            'registration_no',
            'surname',
            'first_name',
            'other_name',
            'password',
            'account_status',
            'locked_until',
            'failed_login_attempts',
            'email',
            'mobile_phone',
            'phone'
        ]);

        if (!$user) {
            $loginError = "No account was found with the details entered.";
            if (portalAuthWantsJson()) {
                portalAuthJson(['ok' => false, 'message' => $loginError]);
            }
        } else {
            $accountStatus = portalNormalizeStudentAccountStatus((string)($user['account_status'] ?? 'Active'), 'Active');
            $passwordHash = (string)($user['password'] ?? '');
            $passwordOk = ($passwordHash !== '') && password_verify($password, $passwordHash);

            if (!$passwordOk) {
                $attempts = (int)($user['failed_login_attempts'] ?? 0) + 1;
                $lockUntil = null;
                $alreadyLocked = !empty($user['locked_until']) && strtotime((string)$user['locked_until']) > time();
                $loginError = "Incorrect password.";

                if ($alreadyLocked) {
                    $lockUntil = (string)$user['locked_until'];
                } elseif ($attempts >= 5) {
                    $attempts = 0;
                    $lockUntil = date('Y-m-d H:i:s', time() + 1800);
                    $loginError = "Incorrect password. Your account is temporarily locked. Please try again later.";
                }

                try {
                    if ($lockUntil) {
                        $pdo->prepare("UPDATE students SET failed_login_attempts = ?, locked_until = ? WHERE id = ?")
                            ->execute([$attempts, $lockUntil, (int)$user['id']]);
                    } else {
                        $pdo->prepare("UPDATE students SET failed_login_attempts = ? WHERE id = ?")
                            ->execute([$attempts, (int)$user['id']]);
                    }
                    portalAuthLogActivity($pdo, 'student', (int)$user['id'], 'login_failed', ['identifier' => $username, 'reason' => 'incorrect_password']);
                } catch (Throwable $e) {}

                if (portalAuthWantsJson()) {
                    portalAuthJson(['ok' => false, 'message' => $loginError]);
                }
            }

            if (!empty($user['locked_until']) && strtotime((string)$user['locked_until']) > time()) {
                $loginError = "Your account is temporarily locked. Please try again later.";
                if (portalAuthWantsJson()) {
                    portalAuthJson(['ok' => false, 'message' => $loginError]);
                }
            } elseif ($accountStatus !== 'Active') {
                $loginError = "Your account has been " . strtolower($accountStatus) . ". Please contact the administration.";
                if (portalAuthWantsJson()) {
                    portalAuthJson(['ok' => false, 'message' => $loginError]);
                }
            } else {
                portalAuthStudentLogin($pdo, $user, 'password', $remember);
                portalThemeInitializeForAuthenticatedUser($pdo, 'student', (int)$user['id']);
                portalAuthRespond(true, 'Login successful.', portalResumeResolveLoginRedirect($pdo, 'student', (int)$user['id'], '?page=student_dashboard'));
            }
        }
    }
}


// Handle OTP login - request OTP
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'request_login_otp') {
    $userType = (($_POST['user_type'] ?? 'student') === 'admin') ? 'admin' : 'student';
    $identity = trim((string)($_POST['identity'] ?? ''));
    $remember = isset($_POST['remember_me']) && (string)($_POST['remember_me']) === '1';

    if ($userType === 'admin') {
        // keep existing admin OTP behaviour
        $generic = "If the account exists and has a valid email, an OTP has been sent.";
        if ($identity !== '') {
            try {
                $stmt = $pdo->prepare("SELECT id, email, full_name, username, password FROM admins WHERE username = ? OR email = ? LIMIT 1");
                $stmt->execute([$identity, $identity]);
                $u = $stmt->fetch(PDO::FETCH_ASSOC);
                if ($u && !empty($u['email'])) {
                    $otp = createLoginOtp($pdo, 'admin', (int)$u['id'], (string)$u['email']);
                    $_SESSION['otp_pending'] = ['user_type' => 'admin', 'user_id' => (int)$u['id'], 'remember' => $remember ? 1 : 0];
                    $name = htmlspecialchars((string)($u['full_name'] ?? 'Admin'), ENT_QUOTES, 'UTF-8');
                    $subject = APP_NAME . " - Login OTP";
                    $body = "<p>Hello {$name},</p><p>Your login OTP is:</p><h2 style='letter-spacing:2px'>{$otp}</h2><p>This code expires in 10 minutes.</p>";
                    portalAuthSendEmail($pdo, (string)$u['email'], $subject, $body);
                }
            } catch (Throwable $e) {}
        }
        portalAuthRespond(true, $generic, '?page=admin_login&mode=otp');
    }

    $generic = "If the student record exists and has a valid email, an OTP has been sent.";
    if ($identity !== '') {
        try {
            $u = portalFindStudentByIdentity($pdo, $identity);
            if ($u && !empty($u['email']) && filter_var((string)$u['email'], FILTER_VALIDATE_EMAIL)) {
                if (!in_array((string)($u['account_status'] ?? ''), ['Suspended', 'Blocked'], true)) {
                    $issued = portalAuthIssueStudentOtp($pdo, $u, 'login');
                    if ($issued['ok']) {
                        $subject = APP_NAME . " - Login OTP";
                        $name = htmlspecialchars(portalAuthStudentName($u), ENT_QUOTES, 'UTF-8');
                        $body = "<p>Hello {$name},</p><p>Your login OTP is:</p><h2 style='letter-spacing:2px'>" . htmlspecialchars((string)$issued['otp']) . "</h2><p>This code expires in 10 minutes. If you did not request it, please ignore this email.</p>";
                        $sent = portalAuthSendEmail($pdo, (string)$u['email'], $subject, $body);
                        $smsSent = false;
                        $otpMessage = APP_NAME . ' login OTP: ' . (string)$issued['otp'] . '. It expires in 10 minutes. If you did not request it, ignore this message.';
                        $otpPhone = portalResolveNotificationPhone($pdo, 'student', (int)$u['id'], $u);
                        if ($otpPhone !== '') {
                            try { $smsSent = sendLoggedSms($pdo, 'student', (int)$u['id'], $otpPhone, 'login_otp:' . (int)$u['id'], $otpMessage, 'login_otp'); } catch (Throwable $e) {}
                        }
                        if ($sent || $smsSent) {
                            $_SESSION['otp_pending'] = ['user_type' => 'student', 'user_id' => (int)$u['id'], 'remember' => $remember ? 1 : 0];
                            portalAuthLogActivity($pdo, 'student', (int)$u['id'], 'login_otp_sent', []);
                            portalAuthCreateNotification($pdo, 'student', (int)$u['id'], 'Login OTP Sent', $smsSent ? 'A login OTP has been sent to your email and active SMS channel.' : 'A login OTP has been sent to your email.');
                            portalAuthRespond(true, $smsSent ? 'OTP sent successfully. Check your email and SMS.' : 'OTP sent successfully. Check your email.', '?page=login&mode=otp');
                        }
                        try {
                            $pdo->prepare("UPDATE students SET otp_last_sent_at = NULL, otp_blocked_until = NULL WHERE id = ?")->execute([(int)$u['id']]);
                        } catch (Throwable $e) {}
                        portalAuthRespond(false, portalAuthOtpDeliveryFailureMessage('Unable to send OTP right now. Please try again later.'), '?page=login&mode=otp');
                    } else {
                        portalAuthRespond(false, (string)$issued['message'], '?page=login&mode=otp');
                    }
                }
            }
        } catch (Throwable $e) {
            portalAuthMailLog('Student login OTP request failed: ' . $e->getMessage());
        }
    }

    portalAuthRespond(true, $generic, '?page=login&mode=otp');
}

// Handle OTP login - verify OTP
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'verify_login_otp') {
    $otpInput = trim((string)($_POST['otp'] ?? ''));
    $pending = $_SESSION['otp_pending'] ?? null;

    if ($otpInput === '' || !is_array($pending) || empty($pending['user_type']) || empty($pending['user_id'])) {
        portalAuthRespond(false, 'Invalid OTP request. Please request a new OTP.', '?page=login&mode=otp');
    }

    $userType = ($pending['user_type'] === 'admin') ? 'admin' : 'student';
    $userId = (int)$pending['user_id'];
    $remember = !empty($pending['remember']);

    if ($userType === 'admin') {
        $row = findActiveOtp($pdo, $userType, $userId);
        if (!$row || !verifyLoginOtp($pdo, (int)$row['id'], $otpInput)) {
            portalAuthRespond(false, 'Invalid or expired OTP. Please request a new OTP.', '?page=admin_login&mode=otp');
        }
        unset($_SESSION['otp_pending']);
        $stmt = $pdo->prepare("SELECT * FROM admins WHERE id = ? LIMIT 1");
        $stmt->execute([$userId]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($user) {
            session_regenerate_id(true);
            portalApplyAdminSessionFromRow($pdo, $user);
            portalThemeInitializeForAuthenticatedUser($pdo, 'admin', (int)$user['id']);
            portalAuthLogActivity($pdo, 'admin', (int)$user['id'], 'login_otp', []);
            portalAuthCreateNotification($pdo, 'admin', (int)$user['id'], 'Login Successful', 'You signed into the admin portal with OTP.');
            portalAuthRespond(true, 'Login successful.', portalResumeResolveLoginRedirect($pdo, 'admin', (int)$user['id'], '?page=admin_dashboard'));
        }
        portalAuthRespond(false, 'Unable to login. Please try again.', '?page=admin_login&mode=otp');
    }

    $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
    $stmt->execute([$userId]);
    $user = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$user) {
        unset($_SESSION['otp_pending']);
        portalAuthRespond(false, 'Student account not found. Please request a new OTP.', '?page=login&mode=otp');
    }
    if (in_array((string)($user['account_status'] ?? ''), ['Suspended', 'Blocked'], true)) {
        unset($_SESSION['otp_pending']);
        portalAuthRespond(false, 'Your account has been ' . strtolower((string)$user['account_status']) . '. Please contact the administration.', '?page=login&mode=otp');
    }
    if (!portalAuthVerifyStudentOtp($pdo, $user, $otpInput, 'login')) {
        portalAuthRespond(false, 'Invalid or expired OTP. Please try again.', '?page=login&mode=otp');
    }

    unset($_SESSION['otp_pending']);
    portalAuthStudentLogin($pdo, $user, 'otp', $remember);
    portalThemeInitializeForAuthenticatedUser($pdo, 'student', (int)$user['id']);
    portalAuthRespond(true, 'Login successful.', portalResumeResolveLoginRedirect($pdo, 'student', (int)$user['id'], '?page=student_dashboard'));
}


// Handle forgot password - send reset OTP (student) or reset link (admin)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'request_password_reset_otp') {
    $identity = trim((string)($_POST['identity'] ?? ''));

    if ($identity === '') {
        portalAuthRespond(false, 'Please enter your Matric No./Student ID/Email/Phone.', '?page=forgot_password');
    }

    try {
        $student = portalFindStudentByIdentity($pdo, $identity);
        if ($student && !empty($student['email']) && filter_var((string)$student['email'], FILTER_VALIDATE_EMAIL)) {
            $windowStart = !empty($student['reset_send_window_start']) ? strtotime((string)$student['reset_send_window_start']) : 0;
            $sendCount = (int)($student['reset_send_count'] ?? 0);
            if ($windowStart <= 0 || (time() - $windowStart) > 3600) {
                $windowStart = time();
                $sendCount = 0;
                $pdo->prepare("UPDATE students SET reset_send_window_start = ?, reset_send_count = 0 WHERE id = ?")
                    ->execute([date('Y-m-d H:i:s', $windowStart), (int)$student['id']]);
                $student['reset_send_window_start'] = date('Y-m-d H:i:s', $windowStart);
                $student['reset_send_count'] = 0;
            }
            if ($sendCount >= 5) {
                $pdo->prepare("UPDATE students SET reset_otp_blocked_until = ? WHERE id = ?")
                    ->execute([date('Y-m-d H:i:s', time() + 1800), (int)$student['id']]);
                portalAuthRespond(false, 'Too many reset OTP requests. Please try again later.', '?page=forgot_password');
            }

            $issued = portalAuthIssueStudentOtp($pdo, $student, 'reset');
            if (!$issued['ok']) {
                portalAuthRespond(false, (string)$issued['message'], '?page=forgot_password');
            }

            $subject = APP_NAME . " - Password Reset OTP";
            $name = htmlspecialchars(portalAuthStudentName($student), ENT_QUOTES, 'UTF-8');
            $body = "<p>Hello {$name},</p><p>Your password reset OTP is:</p><h2 style='letter-spacing:2px'>" . htmlspecialchars((string)$issued['otp']) . "</h2><p>This code expires in 10 minutes. If you did not request it, please ignore this email.</p>";
            $emailSent = portalAuthSendEmail($pdo, (string)$student['email'], $subject, $body);
            $smsSent = false;
            $resetPhone = portalResolveNotificationPhone($pdo, 'student', (int)$student['id'], $student);
            if ($resetPhone !== '') {
                $resetMessage = APP_NAME . ' password reset OTP: ' . (string)$issued['otp'] . '. It expires in 10 minutes. If you did not request it, ignore this message.';
                try { $smsSent = sendLoggedSms($pdo, 'student', (int)$student['id'], $resetPhone, 'reset_otp:' . (int)$student['id'], $resetMessage, 'reset_otp'); } catch (Throwable $e) {}
            }
            if ($emailSent || $smsSent) {
                $sendCount++;
                $pdo->prepare("UPDATE students SET reset_send_count = ?, reset_send_window_start = ?, reset_otp_last_sent_at = NOW() WHERE id = ?")
                    ->execute([$sendCount, date('Y-m-d H:i:s', $windowStart > 0 ? $windowStart : time()), (int)$student['id']]);
                $_SESSION['student_reset_identity'] = $identity;
                unset($_SESSION['student_reset_verified_id']);
                portalAuthLogActivity($pdo, 'student', (int)$student['id'], 'password_reset_otp_sent', []);
                portalAuthCreateNotification($pdo, 'student', (int)$student['id'], 'Password Reset OTP Sent', $smsSent ? 'A password reset OTP was sent to your email and active SMS channel.' : 'A password reset OTP was sent to your email.');
                portalAuthRespond(true, $smsSent ? 'A reset OTP has been sent to your email and SMS.' : 'A reset OTP has been sent to your email.', '?page=forgot_password');
            }
            try {
                $pdo->prepare("UPDATE students SET reset_otp_last_sent_at = NULL, reset_otp_blocked_until = NULL WHERE id = ?")->execute([(int)$student['id']]);
            } catch (Throwable $e) {}
            portalAuthRespond(false, portalAuthOtpDeliveryFailureMessage('Unable to send reset OTP right now. Please try again later.'), '?page=forgot_password');
        }
    } catch (Throwable $e) {
        portalAuthMailLog('Student password reset OTP request failed: ' . $e->getMessage());
    }

    portalAuthRespond(true, 'If the account exists, a reset OTP has been sent to the email address on file.', '?page=forgot_password');
}

// Handle verify reset OTP
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'verify_password_reset_otp') {
    $identity = trim((string)($_POST['identity'] ?? ($_SESSION['student_reset_identity'] ?? '')));
    $otpInput = trim((string)($_POST['otp'] ?? ''));

    if ($identity === '' || $otpInput === '') {
        portalAuthRespond(false, 'Please enter the OTP sent to your email.', '?page=forgot_password');
    }

    $student = portalFindStudentByIdentity($pdo, $identity);
    if (!$student) {
        portalAuthRespond(false, 'Student account not found.', '?page=forgot_password');
    }
    if (!portalAuthVerifyStudentOtp($pdo, $student, $otpInput, 'reset')) {
        portalAuthRespond(false, 'Invalid or expired reset OTP.', '?page=forgot_password');
    }

    $_SESSION['student_reset_identity'] = $identity;
    $_SESSION['student_reset_verified_id'] = (int)$student['id'];
    portalAuthRespond(true, 'OTP verified. You can now set a new password.', '?page=forgot_password');
}

// Handle perform reset using verified OTP session
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'perform_password_reset_otp') {
    $identity = trim((string)($_POST['identity'] ?? ($_SESSION['student_reset_identity'] ?? '')));
    $password = (string)($_POST['new_password'] ?? '');
    $confirm = (string)($_POST['confirm_password'] ?? '');

    if ($identity === '' || empty($_SESSION['student_reset_verified_id'])) {
        portalAuthRespond(false, 'Please verify your reset OTP first.', '?page=forgot_password');
    }
    if ($password === '' || $confirm === '' || $password !== $confirm) {
        portalAuthRespond(false, 'Passwords do not match.', '?page=forgot_password');
    }
    if (!portalAuthStrongPassword($password)) {
        portalAuthRespond(false, 'Password must be at least 8 characters and include uppercase, lowercase, and a number.', '?page=forgot_password');
    }

    $student = portalFindStudentByIdentity($pdo, $identity);
    if (!$student || (int)$student['id'] !== (int)$_SESSION['student_reset_verified_id']) {
        portalAuthRespond(false, 'Reset session mismatch. Please request a new OTP.', '?page=forgot_password');
    }

    try {
        $hash = password_hash($password, PASSWORD_DEFAULT);
        $pdo->prepare("UPDATE students
                       SET password = ?,
                           reset_otp_code = NULL, reset_otp_expiry = NULL, reset_otp_last_sent_at = NULL,
                           reset_otp_blocked_until = NULL, reset_otp_attempts = 0, reset_otp_consumed = 1,
                           otp_code = NULL, otp_expiry = NULL, otp_last_sent_at = NULL, otp_blocked_until = NULL, otp_attempts = 0, otp_consumed = 0,
                           failed_login_attempts = 0, locked_until = NULL,
                           remember_token = NULL, remember_expires_at = NULL
                       WHERE id = ?")
            ->execute([$hash, (int)$student['id']]);

        unset($_SESSION['student_reset_verified_id'], $_SESSION['student_reset_identity']);
        portalAuthClearRememberCookie();
        portalAuthLogActivity($pdo, 'student', (int)$student['id'], 'password_reset', []);
        portalAuthCreateNotification($pdo, 'student', (int)$student['id'], 'Password Reset Successful', 'Your portal password was reset successfully.');
        portalAuthRespond(true, 'Password reset successful. Please login with your new password.', '?page=login');
    } catch (Throwable $e) {
        portalAuthRespond(false, 'Unable to reset password right now. Please try again.', '?page=forgot_password');
    }
}

// Legacy forgot password (admin and compatibility link-based flow)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'request_password_reset') {
    $userType = ($_POST['user_type'] ?? 'student') === 'admin' ? 'admin' : 'student';
    $identity = trim((string)($_POST['identity'] ?? ''));

    $genericSuccess = "If the account exists, a password reset link has been sent to the email address on file.";

    if ($identity !== '') {
        try {
            if ($userType === 'admin') {
                $stmt = $pdo->prepare("SELECT id, email, full_name FROM admins WHERE username = ? OR email = ? LIMIT 1");
                $stmt->execute([$identity, $identity]);
                $u = $stmt->fetch(PDO::FETCH_ASSOC);
                if ($u && !empty($u['email'])) {
                    $token = createPasswordResetToken($pdo, 'admin', (int)$u['id'], (string)$u['email']);
                    $link = appBaseUrl() . '/index.php?page=reset_password&token=' . urlencode($token);
                    $body = "<p>Hello " . htmlspecialchars((string)$u['full_name']) . ",</p><p>You requested a password reset. Click the link below to set a new password (valid for 1 hour):</p><p><a href=\"" . htmlspecialchars($link) . "\">Reset Password</a></p><p>If you did not request this, please ignore this email.</p>";
                    portalAuthSendEmail($pdo, (string)$u['email'], APP_NAME . " - Password Reset", $body);
                }
            }
        } catch (Throwable $e) {}
    }

    $_SESSION['password_reset_notice'] = $genericSuccess;
    redirect('?page=forgot_password');
}

// Legacy reset password via link
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'reset_password') {
    $token = trim((string)($_POST['token'] ?? ''));
    $p1 = (string)($_POST['new_password'] ?? '');
    $p2 = (string)($_POST['confirm_password'] ?? '');

    if ($token === '') {
        $_SESSION['reset_error'] = 'Invalid or missing reset token.';
        redirect('?page=forgot_password');
    }
    if ($p1 === '' || $p2 === '' || $p1 !== $p2) {
        $_SESSION['reset_error'] = 'Passwords do not match.';
        redirect('?page=reset_password&token=' . urlencode($token));
    }
    if (!portalAuthStrongPassword($p1)) {
        $_SESSION['reset_error'] = 'Password must be at least 8 characters and include uppercase, lowercase, and a number.';
        redirect('?page=reset_password&token=' . urlencode($token));
    }

    $reset = findValidPasswordReset($pdo, $token);
    if (!$reset) {
        $_SESSION['reset_error'] = 'This reset link is invalid or has expired. Please request a new one.';
        redirect('?page=forgot_password');
    }

    try {
        updateUserPassword($pdo, (string)$reset['user_type'], (int)$reset['user_id'], $p1);
        markPasswordResetUsed($pdo, (int)$reset['id']);
        if ((string)$reset['user_type'] === 'student') {
            try {
                $pdo->prepare("UPDATE students
                               SET otp_code = NULL, otp_expiry = NULL, otp_last_sent_at = NULL, otp_blocked_until = NULL, otp_attempts = 0, otp_consumed = 0,
                                   reset_otp_code = NULL, reset_otp_expiry = NULL, reset_otp_last_sent_at = NULL, reset_otp_blocked_until = NULL, reset_otp_attempts = 0, reset_otp_consumed = 1,
                                   remember_token = NULL, remember_expires_at = NULL,
                                   failed_login_attempts = 0, locked_until = NULL
                               WHERE id = ?")->execute([(int)$reset['user_id']]);
            } catch (Throwable $e) {}
            portalAuthLogActivity($pdo, 'student', (int)$reset['user_id'], 'password_reset', []);
            portalAuthCreateNotification($pdo, 'student', (int)$reset['user_id'], 'Password Reset Successful', 'Your portal password was reset successfully.');
        } else {
            portalAuthLogActivity($pdo, 'admin', (int)$reset['user_id'], 'password_reset', []);
            portalAuthCreateNotification($pdo, 'admin', (int)$reset['user_id'], 'Password Reset Successful', 'Your admin password was reset successfully.');
        }
        $_SESSION['reset_success'] = 'Password updated successfully. Please login with your new password.';
        redirect('?page=login');
    } catch (Throwable $e) {
        $_SESSION['reset_error'] = 'Unable to reset password right now. Please try again.';
        redirect('?page=reset_password&token=' . urlencode($token));
    }
}

// Handle change password
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'change_password') {
    $identifier = trim((string)($_POST['identifier'] ?? ''));
    $oldPassword = (string)($_POST['old_password'] ?? '');
    $newPassword = (string)($_POST['new_password'] ?? '');
    $confirmPassword = (string)($_POST['confirm_password'] ?? '');

    if ($identifier === '' || $oldPassword === '' || $newPassword === '' || $confirmPassword === '') {
        portalAuthRespond(false, 'Please fill all password change fields.', '?page=change_password');
    }
    if ($newPassword !== $confirmPassword) {
        portalAuthRespond(false, 'Passwords do not match.', '?page=change_password');
    }
    if (!portalAuthStrongPassword($newPassword)) {
        portalAuthRespond(false, 'Password must be at least 8 characters and include uppercase, lowercase, and a number.', '?page=change_password');
    }

    $student = portalFindStudentByIdentity($pdo, $identifier);
    if (!$student || empty($student['password']) || !password_verify($oldPassword, (string)$student['password'])) {
        portalAuthRespond(false, 'Old password is incorrect.', '?page=change_password');
    }

    try {
        $hash = password_hash($newPassword, PASSWORD_DEFAULT);
        $pdo->prepare("UPDATE students
                       SET password = ?,
                           otp_code = NULL, otp_expiry = NULL, otp_last_sent_at = NULL, otp_blocked_until = NULL, otp_attempts = 0, otp_consumed = 0,
                           reset_otp_code = NULL, reset_otp_expiry = NULL, reset_otp_last_sent_at = NULL, reset_otp_blocked_until = NULL, reset_otp_attempts = 0, reset_otp_consumed = 0,
                           failed_login_attempts = 0, locked_until = NULL,
                           remember_token = NULL, remember_expires_at = NULL
                       WHERE id = ?")
            ->execute([$hash, (int)$student['id']]);
        portalAuthClearRememberCookie();
        portalAuthLogActivity($pdo, 'student', (int)$student['id'], 'password_change', []);
        portalAuthCreateNotification($pdo, 'student', (int)$student['id'], 'Password Changed', 'Your portal password was changed successfully.');
        portalAuthRespond(true, 'Password changed successfully.', '?page=login');
    } catch (Throwable $e) {
        portalAuthRespond(false, 'Unable to change password right now. Please try again.', '?page=change_password');
    }
}

// Handle logout (GET and AJAX/POST)
if ((isset($_GET['action']) && $_GET['action'] === 'logout') || ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'logout')) {
    $redirectPath = '?page=login';
    if (!empty($_SESSION['user_id'])) {
        $userType = (string)($_SESSION['user_type'] ?? 'student');
        $userId = (int)($_SESSION['user_id'] ?? 0);
        $redirectPath = $userType === 'admin' ? '?page=admin_login' : '?page=login';
        portalAuthLogActivity($pdo, $userType, $userId, 'logout', []);
        portalAuthCreateNotification($pdo, $userType, $userId, 'Logout Successful', 'You logged out of the portal successfully.');
    }

    portalAuthClearRememberCookie();
    $_SESSION = [];
    if (session_status() === PHP_SESSION_ACTIVE) {
        session_destroy();
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' || portalAuthWantsJson()) {
        portalAuthJson(['ok' => true, 'message' => 'Logged out successfully.', 'redirect' => $redirectPath]);
    }
    redirect($redirectPath);
}


// Handle admin report export (CSV)
if (isAdmin() && isset($_GET['page']) && $_GET['page'] === 'reports' && isset($_GET['export']) && $_GET['export'] === 'csv') {
    $report = $_GET['report'] ?? 'students';
    $from = $_GET['from'] ?? '';
    $to = $_GET['to'] ?? '';

    $fromDate = $from ? date('Y-m-d 00:00:00', strtotime($from)) : null;
    $toDate = $to ? date('Y-m-d 23:59:59', strtotime($to)) : null;

    $filename = 'report_' . preg_replace('/[^a-z0-9_\-]/i', '_', (string)$report) . '_' . date('Ymd_His') . '.csv';
    header('Content-Type: text/csv; charset=utf-8');
    header('Content-Disposition: attachment; filename=' . $filename);
    $out = fopen('php://output', 'w');

    $addWhere = function(array &$params) use ($fromDate, $toDate): string {
        $w = [];
        if ($fromDate) { $w[] = 'created_at >= ?'; $params[] = $fromDate; }
        if ($toDate) { $w[] = 'created_at <= ?'; $params[] = $toDate; }
        return $w ? (' WHERE ' . implode(' AND ', $w)) : '';
    };

    try {
        if ($report === 'payments') {
            fputcsv($out, ['transaction_id','matric_no','surname','first_name','other_name','transaction_type','amount','status','created_at']);
            $params = [];
            $where = $addWhere($params);
            $sql = "SELECT pt.transaction_id, s.matric_no, s.registration_no, s.surname, s.first_name, s.other_name, pt.transaction_type, pt.amount, pt.status, pt.created_at
                    FROM payment_transactions pt
                    JOIN students s ON pt.student_id = s.id" . $where . " ORDER BY pt.created_at DESC";
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
                fputcsv($out, [$r['transaction_id'], portalReceiptMatricNumber($r), $r['surname'], $r['first_name'], $r['other_name'], $r['transaction_type'], $r['amount'], $r['status'], $r['created_at']]);
            }
        } elseif ($report === 'applications') {
            fputcsv($out, ['matric_no','surname','first_name','other_name','email','application_status','offer_status','created_at']);
            $params = [];
            $where = $addWhere($params);
            $sql = "SELECT matric_no, registration_no, surname, first_name, other_name, email, application_status, offer_status, created_at
                    FROM students" . $where . " ORDER BY created_at DESC";
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
                fputcsv($out, [portalReceiptMatricNumber($r), $r['surname'], $r['first_name'], $r['other_name'], $r['email'], $r['application_status'], $r['offer_status'], $r['created_at']]);
            }
        } else {
            // students
            fputcsv($out, ['matric_no','surname','first_name','other_name','email','mobile_phone','account_status','registration_fee_status','acceptance_fee_status','hostel_fee_status','school_fee_status','created_at']);
            $params = [];
            $where = $addWhere($params);
            $sql = "SELECT matric_no, registration_no, surname, first_name, other_name, email, mobile_phone, account_status, accommodation_status,
                           registration_fee_status, acceptance_fee_status, hostel_fee_status, school_fee_status, created_at
                    FROM students" . $where . " ORDER BY created_at DESC";
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
                fputcsv($out, [portalReceiptMatricNumber($r), $r['surname'], $r['first_name'], $r['other_name'], $r['email'], $r['mobile_phone'], $r['account_status'], $r['registration_fee_status'], $r['acceptance_fee_status'], portalStudentHostelStatusLabel($r), $r['school_fee_status'], $r['created_at']]);
            }
        }
    } catch (Throwable $e) {
        fputcsv($out, ['ERROR', $e->getMessage()]);
    }
    fclose($out);
    exit;
}

// Handle logout
if (isset($_GET['action']) && $_GET['action'] === 'logout') {
    session_destroy();
    redirect('?page=login');
}

// Handle registration

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'register') {
    $registrationPageAccess = portalRegistrationPageAvailability($pdo, [
        'session_name' => portalCurrentAcademicSessionName($pdo),
        'faculty_id' => (int)($_POST['faculty_id'] ?? 0),
        'department_id' => (int)($_POST['department_id'] ?? 0),
        'programme_id' => (int)($_POST['programme_id'] ?? 0),
        'mode_of_study' => trim((string)($_POST['mode_of_study'] ?? '')),
    ]);
    $registrationFormEnabled = !empty($registrationPageAccess['enabled']);
    if (!$registrationFormEnabled) {
        $registrationError = trim((string)($registrationPageAccess['message'] ?? 'Student registration is currently disabled by the administrator. Please try again later.'));
        if ($registrationError === '') {
            $registrationError = 'Student registration is currently disabled by the administrator. Please try again later.';
        }
    }
    // Get form data
    $surname = trim((string)($_POST['surname'] ?? ''));
    $firstName = trim((string)($_POST['firstname'] ?? ''));
    $otherName = trim((string)($_POST['othername'] ?? ''));
    $maidenName = trim((string)($_POST['maidenname'] ?? ''));
    $sex = trim((string)($_POST['sex'] ?? ''));
    $dob = trim((string)($_POST['dob'] ?? ''));
    $nationality = trim((string)($_POST['nationality'] ?? 'Nigerian'));
    $state = trim((string)($_POST['state'] ?? ''));
    $lga = trim((string)($_POST['lga'] ?? ''));
    $phone = trim((string)($_POST['phone'] ?? ''));
    $normalizedPhone = portalAuthNormalizePhone($phone);
    $phoneRes = trim((string)($_POST['phone_res'] ?? ''));
    $email = strtolower(trim((string)($_POST['email'] ?? '')));
    $address = trim((string)($_POST['address'] ?? ''));
    $activities = trim((string)($_POST['activities'] ?? ''));
    $nokName = trim((string)($_POST['nok_name'] ?? ''));
    $nokPhone = trim((string)($_POST['nok_phone'] ?? ''));
    $sponsorName = trim((string)($_POST['sponsor_name'] ?? ''));
    $sponsorPhone = trim((string)($_POST['sponsor_phone'] ?? ''));
    $sponsorOcc = trim((string)($_POST['sponsor_occ'] ?? ''));
    $password = (string)($_POST['password'] ?? '');
    $confirmPassword = (string)($_POST['confirm_password'] ?? '');

    if (!isset($registrationError)) {
        try {
            portalEnsureApplicationAcademicFieldSupport($pdo);
            $registrationAcademic = portalValidateApplicationAcademicSelection(
                $pdo,
                (int)($_POST['faculty_id'] ?? 0),
                (int)($_POST['department_id'] ?? 0),
                (int)($_POST['programme_id'] ?? 0)
            );
        } catch (Throwable $e) {
            $registrationAcademic = null;
            $registrationError = $e->getMessage();
        }
    } else {
        $registrationAcademic = null;
    }

    if (!isset($registrationError) && ($surname === '' || $firstName === '' || $sex === '' || $dob === '' || $state === '' || $lga === '' || $phone === '' || $email === '' || $address === '' || $nokName === '' || $nokPhone === '' || $sponsorName === '' || $sponsorPhone === '' || $sponsorOcc === '')) {
        $registrationError = 'Please complete all required registration fields.';
    } elseif (!isset($registrationError) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $registrationError = 'Please enter a valid email address.';
    } elseif (!isset($registrationError) && $normalizedPhone === '') {
        $registrationError = 'Please enter a valid phone number.';
    } elseif (!isset($registrationError) && $password === '') {
        $registrationError = 'Password is required.';
    } elseif (!isset($registrationError) && $password !== $confirmPassword) {
        $registrationError = 'Passwords do not match';
    } else {
        // Check if email/phone already exists for another student account
        $stmt = $pdo->prepare("SELECT COUNT(*) FROM students WHERE LOWER(COALESCE(email,'')) = ?");
        $stmt->execute([$email]);
        if ((int)$stmt->fetchColumn() > 0) {
            $registrationError = 'Email address already registered';
        } else {
            $existingPhoneStudent = portalFindStudentByPhoneIdentity($pdo, $phone);
            if ($existingPhoneStudent) {
                $registrationError = 'Phone number already registered';
            } else {
                // Generate registration number
                $registrationNo = generateRegistrationNumber();

                // Hash password
                $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

                $registrationSelectedMode = trim((string)($_POST['mode_of_study'] ?? ($registrationAcademic['mode_of_study'] ?? 'Full-Time')));
                $registrationSelectedMode = preg_replace('/\s+/u', ' ', $registrationSelectedMode) ?: $registrationSelectedMode;
                if ($registrationSelectedMode === '') {
                    $registrationSelectedMode = (string)($registrationAcademic['mode_of_study'] ?? 'Full-Time');
                }

                // Insert student data
            $stmt = $pdo->prepare("INSERT INTO students (
                registration_no, surname, first_name, other_name, maiden_name, sex,
                date_of_birth, nationality, state_of_origin, lga, mobile_phone,
                residential_phone, email, mode_of_study, permanent_address, extra_curricular,
                faculty_id, department_id, programme_id,
                faculty, department, programme, program,
                school_name, school, course_in_view, course_of_study, programme_name_cache,
                nok_name, nok_phone, sponsor_name, sponsor_phone, sponsor_occupation,
                subject1, subject2, subject3, password
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");

            $stmt->execute([
                $registrationNo, $surname, $firstName, $otherName, $maidenName, $sex,
                $dob, $nationality, $state, $lga, $phone, $phoneRes, $email, $registrationSelectedMode, $address, $activities,
                (int)($registrationAcademic['faculty_id'] ?? 0), (int)($registrationAcademic['department_id'] ?? 0), (int)($registrationAcademic['programme_id'] ?? 0),
                $registrationAcademic['school'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['programme'] ?? '', $registrationAcademic['programme'] ?? '',
                $registrationAcademic['school'] ?? '', $registrationAcademic['school'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['programme'] ?? '',
                $nokName, $nokPhone, $sponsorName, $sponsorPhone, $sponsorOcc,
                '', '', '', $hashedPassword
            ]);

            $studentId = (int)$pdo->lastInsertId();
            try {
                portalSyncStudentAccountChannels($pdo, $studentId, $email, $phone);
            } catch (Throwable $e) {}

            $savedStudent = null;
            try {
                $savedStudentStmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $savedStudentStmt->execute([$studentId]);
                $savedStudent = $savedStudentStmt->fetch(PDO::FETCH_ASSOC) ?: null;
            } catch (Throwable $e) {
                $savedStudent = null;
            }

            // Add education history
            if (isset($_POST['school_name']) && is_array($_POST['school_name'])) {
                foreach ($_POST['school_name'] as $index => $schoolName) {
                    if (!empty($schoolName)) {
                        $fromDate = $_POST['school_from'][$index] ?? '';
                        $toDate = $_POST['school_to'][$index] ?? '';
                        $qualification = $_POST['school_qual'][$index] ?? '';

                        $stmt = $pdo->prepare("INSERT INTO education_history
                            (student_id, institution_name, from_date, to_date, qualification)
                            VALUES (?, ?, ?, ?, ?)");
                        $stmt->execute([$studentId, $schoolName, $fromDate, $toDate, $qualification]);
                    }
                }
            }

            // Add examination results
            if (isset($_POST['exam_type']) && is_array($_POST['exam_type'])) {
                foreach ($_POST['exam_type'] as $index => $examType) {
                    $examDate = $_POST['exam_date'][$index] ?? '';
                    $examNo = $_POST['exam_no'][$index] ?? '';
                    $examGrades = $_POST['exam_grades'][$index] ?? '';
                    $examAttached = isset($_POST['exam_attached'][$index]) ? 1 : 0;

                    // Save exam results (supports custom exam type label for OTHER)
                $examTypeOther = trim((string)($_POST['exam_type_other'][$index] ?? ''));
                try {
                    $stmt = $pdo->prepare("INSERT INTO examination_results
                        (student_id, exam_type, exam_type_other, exam_date, exam_number, subjects_grades, credentials_attached)
                        VALUES (?, ?, ?, ?, ?, ?, ?)");
                    $stmt->execute([$studentId, $examType, $examTypeOther, $examDate, $examNo, $examGrades, $examAttached]);
                } catch (Throwable $e) {
                    // Backward compatibility for older schemas
                    $stmt = $pdo->prepare("INSERT INTO examination_results
                        (student_id, exam_type, exam_date, exam_number, subjects_grades, credentials_attached)
                        VALUES (?, ?, ?, ?, ?, ?)");
                    $stmt->execute([$studentId, $examType, $examDate, $examNo, $examGrades, $examAttached]);
                }
}
            }

            // Add referee information
            $refName = $_POST['ref_name'] ?? '';
            $refOcc = $_POST['ref_occ'] ?? '';
            $refPost = $_POST['ref_post'] ?? '';
            $refDate = $_POST['ref_date'] ?? '';
            $refAddress = $_POST['ref_address'] ?? '';
            $refSignature = $_POST['ref_signature_data'] ?? '';

            $stmt = $pdo->prepare("INSERT INTO referees
                (student_id, full_name, occupation, post_held, address, date, signature_data)
                VALUES (?, ?, ?, ?, ?, ?, ?)");
            $stmt->execute([$studentId, $refName, $refOcc, $refPost, $refAddress, $refDate, $refSignature]);

            // Store applicant signature
            $applicantSignature = $_POST['applicant_signature_data'] ?? '';
            $stmt = $pdo->prepare("UPDATE students SET signature_data = ? WHERE id = ?");
            $stmt->execute([$applicantSignature, $studentId]);

            // Handle profile image upload
            if (isset($_FILES['profile_image']) && $_FILES['profile_image']['error'] === UPLOAD_ERR_OK) {
                $uploadResult = handleFileUpload($_FILES['profile_image'], 'profile_image');
                if ($uploadResult['success']) {
                    $stmt = $pdo->prepare("UPDATE students SET profile_image = ? WHERE id = ?");
                    $stmt->execute([$uploadResult['filepath'], $studentId]);
                }
            }

            // Handle passport photo upload
            if (isset($_FILES['passport_photo']) && $_FILES['passport_photo']['error'] === UPLOAD_ERR_OK) {
                $uploadResult = handleFileUpload($_FILES['passport_photo'], 'passport_photo');
                if ($uploadResult['success']) {
                    $stmt = $pdo->prepare("UPDATE students SET passport_photo = ? WHERE id = ?");
                    $stmt->execute([$uploadResult['filepath'], $studentId]);
                }
            }

            // Handle document uploads (single-file selector + legacy multi-file)
            // New: single file with selected document type/name
            if (isset($_FILES['document_file']) && ($_FILES['document_file']['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK) {
                $file = $_FILES['document_file'];
                $documentType = trim((string)($_POST['document_type_single'] ?? 'other'));
                $documentName = trim((string)($_POST['document_name_single'] ?? ''));
                if ($documentType === '') $documentType = 'other';
                if ($documentName === '') $documentName = $file['name'];

                $uploadResult = handleFileUpload($file, $documentType);
                if ($uploadResult['success']) {
                    $stmt = $pdo->prepare("INSERT INTO student_documents (student_id, document_name, document_type, file_path, file_size) VALUES (?,?,?,?,?)");
                    $stmt->execute([$studentId, $documentName, $uploadResult['document_type'], $uploadResult['filepath'], (int)$file['size']]);
                }
            }

            // Legacy: multiple files (expects document_type[] optionally)
            if (isset($_FILES['documents']) && is_array($_FILES['documents']['name'])) {
                foreach ($_FILES['documents']['name'] as $key => $name) {
                    if ($_FILES['documents']['error'][$key] === UPLOAD_ERR_OK) {
                        $file = [
                            'name' => $_FILES['documents']['name'][$key],
                            'type' => $_FILES['documents']['type'][$key],
                            'tmp_name' => $_FILES['documents']['tmp_name'][$key],
                            'error' => $_FILES['documents']['error'][$key],
                            'size' => $_FILES['documents']['size'][$key]
                        ];

                        $documentType = $_POST['document_type'][$key] ?? 'other';
                        $documentName = $_POST['document_name'][$key] ?? $name;

                        $uploadResult = handleFileUpload($file, $documentType);

                        if ($uploadResult['success']) {
                            $stmt = $pdo->prepare("INSERT INTO student_documents
                                (student_id, document_name, document_type, file_path, file_size)
                                VALUES (?, ?, ?, ?, ?)");
                            $stmt->execute([
                                $studentId,
                                $documentName,
                                $uploadResult['document_type'],
                                $uploadResult['filepath'],
                                $file['size']
                            ]);
                        }
                    }
                }
            }

            // Send registration confirmation email (best effort, logged, only after the record truly exists)
            try {
                $mailStudent = is_array($savedStudent) ? $savedStudent : [];
                if (!$mailStudent) {
                    $stmtMail = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                    $stmtMail->execute([$studentId]);
                    $mailStudent = $stmtMail->fetch(PDO::FETCH_ASSOC) ?: [];
                }
                if ($mailStudent) {
                    $mailStudent['email'] = $email;
                    $mailStudent['registration_no'] = $registrationNo;
                    $mailStudent['mobile_phone'] = trim((string)$phone);
                    $mailStudent['school_name'] = (string)($registrationAcademic['school'] ?? ($mailStudent['school_name'] ?? ''));
                    $mailStudent['course_of_study'] = (string)($registrationAcademic['course_of_study'] ?? ($mailStudent['course_of_study'] ?? ''));
                    $mailStudent['programme_name_cache'] = (string)($registrationAcademic['programme'] ?? ($mailStudent['programme_name_cache'] ?? ''));
                    portalSendStudentApplicationSubmittedEmail($pdo, $mailStudent);
                }
            } catch (Throwable $e) {}

            if (is_array($savedStudent)) {
                try { portalAuthStudentLogin($pdo, $savedStudent, 'register', false); } catch (Throwable $e) {}
            } else {
                $_SESSION['registration_success'] = true;
                $_SESSION['student_id'] = $studentId;
                $_SESSION['user_id'] = $studentId;
                $_SESSION['user_type'] = 'student';
            }
            flash('success', 'Congratulations! Your account has been created successfully. You can use your email address or phone number to access the portal. Please proceed to make payment.');
            redirect('?page=payment');
            }
        }
    }
}


// Handle student profile image upload
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'upload_profile_image') {
    if (!isStudent()) { redirect('?page=login'); }
    $studentId = (int)($_SESSION['user_id'] ?? 0);
    if ($studentId > 0 && isset($_FILES['profile_image']) && is_uploaded_file($_FILES['profile_image']['tmp_name'])) {
        $file = $_FILES['profile_image'];
        $allowed = ['image/jpeg'=>'jpg','image/png'=>'png','image/jpg'=>'jpg'];
        $mime = '';
        try { $mime = function_exists('mime_content_type') ? (string)mime_content_type($file['tmp_name']) : ''; } catch (Throwable $e) { $mime = ''; }
        if (!isset($allowed[$mime]) || (function_exists('getimagesize') && @getimagesize((string)$file['tmp_name']) === false)) {
            flash('error','Invalid image type. Please upload a real JPG or PNG image.');
            redirect('?page=profile');
        }
        if ((int)($file['size'] ?? 0) <= 0 || (int)($file['size'] ?? 0) > 5*1024*1024) {
            flash('error','Image is empty or too large. Max 5MB.');
            redirect('?page=profile');
        }
        $ext = $allowed[$mime];
        $dir = __DIR__ . '/uploads/profiles';
        if (!is_dir($dir)) { @mkdir($dir, 0775, true); }
        honttechHardenWritableDirectory($dir, false);
        $profileRand = honttechSecureToken(8);
        $rel = 'uploads/profiles/' . $studentId . '_' . time() . '_' . $profileRand . '.' . $ext;
        $dest = __DIR__ . '/' . $rel;
        if (@move_uploaded_file($file['tmp_name'], $dest)) {
            $stmt = $pdo->prepare("UPDATE students SET profile_image = ? WHERE id = ?");
            $stmt->execute([$rel, $studentId]);
            flash('success','Profile picture updated successfully.');
        } else {
            flash('error','Upload failed. Please try again.');
        }
    } else {
        flash('error','Please choose an image to upload.');
    }
    redirect('?page=profile');
}


// Handle accommodation status / Off-Campus claim updates
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_accommodation_status') {
    if (!isStudent()) { redirect('?page=login'); }
    portalEnsureAccommodationVerificationSupport($pdo);
    $studentId = (int)($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0);
    $returnTo = trim((string)($_POST['return_to'] ?? $_SERVER['REQUEST_URI'] ?? '?page=dashboard'));
    if ($studentId > 0) {
        try {
            $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $st->execute([$studentId]);
            $student = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if ($student) {
                $selectedAccommodation = (($_POST['accommodation_status'] ?? 'Campus') === 'Off-Campus') ? 'Off-Campus' : 'Campus';
                if (portalStudentAccommodationLockedForSession($pdo, $student) && strcasecmp((string)($student['accommodation_status'] ?? 'Campus'), $selectedAccommodation) !== 0) {
                    flash('warning', 'Accommodation status is locked for the current session because hostel fee has already been paid.');
                } else {
                    $sessionName = portalNormalizeSessionNameSafe((string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)));
                    $semesterName = portalNormalizeSemesterName((string)($student['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
                    if ($selectedAccommodation === 'Campus') {
                        try { $pdo->prepare("UPDATE accommodation_change_requests SET request_status='Cancelled', updated_at=NOW(), admin_note=COALESCE(admin_note,'Student switched back to Campus') WHERE student_id = ? AND request_status = 'Pending'")->execute([$studentId]); } catch (Throwable $e) {}
                        $pdo->prepare("UPDATE students SET accommodation_status = 'Campus', accommodation_verification_status = 'Not Required', accommodation_verification_note = NULL WHERE id = ?")->execute([$studentId]);
                        portalResynchronizeStudentFeeRequirements($pdo, $studentId);
                        flash('success', 'Accommodation status updated to Campus Accommodation.');
                    } else {
                        $note = trim((string)($_POST['accommodation_note'] ?? ''));
                        if (!portalFeeWorkflowSettingEnabled($pdo, 'off_campus_verification_required', '1')) {
                            $pdo->prepare("UPDATE students SET accommodation_status = 'Off-Campus', accommodation_verification_status = 'Approved', accommodation_verification_note = ?, accommodation_verified_at = NOW(), accommodation_verified_by = NULL, accommodation_last_claimed_at = NOW() WHERE id = ?")
                                ->execute([$note !== '' ? $note : 'Off-Campus selected by student', $studentId]);
                            flash('success', 'Off-Campus accommodation saved successfully. Hostel fee is not required for the approved Off-Campus status.');
                        } else {
                            $existingClaimId = 0;
                            try {
                                $claimSt = $pdo->prepare("SELECT id FROM accommodation_change_requests WHERE student_id = ? AND request_status = 'Pending' ORDER BY id DESC LIMIT 1");
                                $claimSt->execute([$studentId]);
                                $existingClaimId = (int)($claimSt->fetchColumn() ?: 0);
                            } catch (Throwable $e) { $existingClaimId = 0; }
                            if ($existingClaimId > 0) {
                                $pdo->prepare("UPDATE accommodation_change_requests SET requested_status='Off-Campus', session_name=?, semester=?, student_note=?, updated_at=NOW() WHERE id = ?")
                                    ->execute([$sessionName, $semesterName, $note !== '' ? $note : null, $existingClaimId]);
                            } else {
                                $pdo->prepare("INSERT INTO accommodation_change_requests (student_id, requested_status, session_name, semester, request_status, student_note, created_at, updated_at) VALUES (?,?,?,?, 'Pending', ?, NOW(), NOW())")
                                    ->execute([$studentId, 'Off-Campus', $sessionName, $semesterName, $note !== '' ? $note : null]);
                            }
                            $pdo->prepare("UPDATE students SET accommodation_status = 'Off-Campus', accommodation_verification_status = 'Pending', accommodation_verification_note = ?, accommodation_last_claimed_at = NOW() WHERE id = ?")
                                ->execute([$note !== '' ? $note : 'Awaiting admin verification', $studentId]);
                            flash('success', 'Off-Campus claim submitted successfully. It is now pending admin verification.');
                        }
                    }
                }
            }
        } catch (Throwable $e) {
            flash('danger', 'Unable to update accommodation status right now.');
        }
    }
    redirect($returnTo !== '' ? $returnTo : '?page=dashboard');
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'review_off_campus_claim') {
    if (!isAdmin()) { redirect('?page=login'); }
    portalEnsureAccommodationVerificationSupport($pdo);
    $claimId = (int)($_POST['claim_id'] ?? 0);
    $decision = strtolower(trim((string)($_POST['decision'] ?? 'pending')));
    if (!in_array($decision, ['approved', 'rejected', 'pending'], true)) $decision = 'pending';
    $adminNote = trim((string)($_POST['verification_note'] ?? ''));
    $returnTo = trim((string)($_POST['return_to'] ?? '?page=admin_accommodation_verification'));
    if ($claimId > 0) {
        try {
            $st = $pdo->prepare("SELECT * FROM accommodation_change_requests WHERE id = ? LIMIT 1");
            $st->execute([$claimId]);
            $claim = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if ($claim) {
                $studentId = (int)($claim['student_id'] ?? 0);
                $adminId = (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0);
                $pdo->prepare("UPDATE accommodation_change_requests SET request_status = ?, admin_note = ?, reviewed_by = ?, reviewed_at = NOW(), updated_at = NOW() WHERE id = ?")
                    ->execute([ucfirst($decision), $adminNote !== '' ? $adminNote : null, $adminId > 0 ? $adminId : null, $claimId]);
                if ($studentId > 0) {
                    if ($decision === 'approved') {
                        $pdo->prepare("UPDATE students SET accommodation_status = 'Off-Campus', accommodation_verification_status = 'Approved', accommodation_verification_note = ?, accommodation_verified_at = NOW(), accommodation_verified_by = ? WHERE id = ?")
                            ->execute([$adminNote !== '' ? $adminNote : 'Off-Campus claim approved', $adminId > 0 ? $adminId : null, $studentId]);
                    } elseif ($decision === 'rejected') {
                        $pdo->prepare("UPDATE students SET accommodation_status = 'Campus', accommodation_verification_status = 'Rejected', accommodation_verification_note = ?, accommodation_verified_at = NOW(), accommodation_verified_by = ? WHERE id = ?")
                            ->execute([$adminNote !== '' ? $adminNote : 'Off-Campus claim rejected', $adminId > 0 ? $adminId : null, $studentId]);
                    } else {
                        $pdo->prepare("UPDATE students SET accommodation_status = 'Off-Campus', accommodation_verification_status = 'Pending', accommodation_verification_note = ? WHERE id = ?")
                            ->execute([$adminNote !== '' ? $adminNote : 'Pending review', $studentId]);
                    }
                    portalResynchronizeStudentFeeRequirements($pdo, $studentId);
                }
                flash('success', 'Accommodation claim review saved successfully.');
            }
        } catch (Throwable $e) {
            flash('danger', 'Unable to review the Off-Campus claim right now.');
        }
    }
    redirect($returnTo !== '' ? $returnTo : '?page=admin_accommodation_verification');
}

// Handle payment
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'initiate_payment') {
    $studentId = (int)($_SESSION['student_id'] ?? ($_SESSION['user_id'] ?? 0));
    $paymentType = $_POST['payment_type'] ?? 'registration'; // registration, acceptance, hostel, or school
    $installmentNumber = $_POST['installment_number'] ?? 1;
    $totalInstallments = $_POST['total_installments'] ?? 1;

    if ($studentId > 0) {
        // Get student details
        $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
        $stmt->execute([$studentId]);
        $student = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($student) {
            $paymentAcademicSession = trim((string)($_POST['payment_session'] ?? $_GET['session'] ?? ($student['session'] ?? portalCurrentAcademicSessionName($pdo))));
            $paymentSemester = portalNormalizeSemesterName($_POST['payment_semester'] ?? $_GET['semester'] ?? ($student['semester'] ?? 'First Semester'));
            // Get fee based on payment type
            switch ($paymentType) {
                case 'registration':
                    $fee = (float)portalStudentManagedFeeAmount($pdo, 'registration', (array)$student, (string)($student['session'] ?? ''), (string)($student['semester'] ?? ''));
                    $description = 'HontTech Registration Fee';
                    $statusField = 'registration_fee_status';
                    $referenceField = 'registration_fee_reference';
                    $transactionType = 'Registration Fee';
                    break;

                case 'acceptance':
                    $fee = (float)portalStudentManagedFeeAmount($pdo, 'acceptance', (array)$student, (string)($student['session'] ?? ''), (string)($student['semester'] ?? ''));
                    $description = 'HontTech Acceptance Fee';
                    $statusField = 'acceptance_fee_status';
                    $referenceField = 'acceptance_fee_reference';
                    $transactionType = 'Acceptance Fee';
                    break;

                case 'hostel':
                    if (function_exists('portalShouldRequireHostelFeeForStudent') && !portalShouldRequireHostelFeeForStudent($pdo, (array)$student, $paymentAcademicSession, $paymentSemester)) {
                        $paymentError = 'Hostel fee is not currently required for your accommodation status.';
                        break;
                    }
                    $fee = (float)portalStudentManagedFeeAmount($pdo, 'hostel', (array)$student, $paymentAcademicSession, $paymentSemester);
                    $description = 'HontTech Hostel Fee';
                    $statusField = 'hostel_fee_status';
                    $referenceField = 'hostel_fee_reference';
                    $transactionType = 'Hostel Fee';
                    break;

                case 'school':
                    $paymentAcademicSession = trim((string)($_POST['payment_session'] ?? $_GET['session'] ?? ($student['session'] ?? portalCurrentAcademicSessionName($pdo))));
                    $paymentSemester = portalNormalizeSemesterName($_POST['payment_semester'] ?? $_GET['semester'] ?? ($student['semester'] ?? 'First Semester'));
                    $totalFee = (float)portalStudentSchoolFeeAmount($pdo, (array)$student, $paymentAcademicSession, $paymentSemester);
                    $lateFeeSummary = portalSchoolFeePeriodSummary($pdo, (int)$studentId, $paymentAcademicSession, $paymentSemester, (array)$student);
                    $lateFeeBalance = (float)($lateFeeSummary['late_fee_balance'] ?? 0);

                    // Check if installment payment is enabled
                    $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'installment_enabled'");
                    $stmt->execute();
                    // Always offer installment option (2 parts)
                    $installmentEnabled = true;

                    // Enforce: school fee can be either ONE-TIME or 2 INSTALLMENTS
                    portalAssertStudentSchoolPaymentPlanAllowed($pdo, (int)$studentId, (int)$totalInstallments);
                    $requestedTotal = (int)$totalInstallments;
                    $requestedInst = (int)$installmentNumber;

                    // Detect if student already started installment payments
                    $paidInstallments = 0;
                    try {
                        $stx = $pdo->prepare("SELECT COUNT(*) FROM payment_transactions WHERE student_id = ? AND transaction_type = 'School Fee' AND status = 'Approved' AND COALESCE(amount,0) > 0 AND total_installments > 1");
                        $stx->execute([(int)$studentId]);
                        $paidInstallments = (int)$stx->fetchColumn();
                    } catch (Throwable $e) { $paidInstallments = 0; }

                    if ($installmentEnabled && $requestedTotal > 1) {
                        // Force 2 installments
                        $totalInstallments = 2;
                        // Next installment must be sequential
                        $next = min(2, max(1, $paidInstallments + 1));
                        $installmentNumber = $next;

                        if ($paidInstallments >= 2) {
                            $paymentError = 'School fee has already been fully paid.';
                            break;
                        }

                        $principalFee = max(0.0, (float)($lateFeeSummary['principal_outstanding'] ?? ($totalFee / 2)));
                        $fee = $principalFee + $lateFeeBalance;
                        $description = "HontTech School Fee - Installment {$installmentNumber} of {$totalInstallments}";
                        $_POST['_school_fee_base_amount'] = $principalFee;
                        $_POST['_school_fee_late_fee_amount'] = $lateFeeBalance;
                        // Store plan choice
                        try { $pdo->prepare("UPDATE students SET payment_plan = 'Installment' WHERE id = ?")->execute([(int)$studentId]); } catch (Throwable $e) {}
                    } else {
                        // One-time
                        $totalInstallments = 1;
                        $installmentNumber = 1;

                        // If installments already started, block one-time
                        if ($paidInstallments > 0) {
                            $paymentError = 'You have already started installment payment. Please pay the next installment instead of one-time payment.';
                            break;
                        }

                        $principalFee = max(0.0, (float)($lateFeeSummary['principal_outstanding'] ?? $totalFee));
                        $fee = $principalFee + $lateFeeBalance;
                        $description = 'HontTech School Fee';
                        $_POST['_school_fee_base_amount'] = $principalFee;
                        $_POST['_school_fee_late_fee_amount'] = $lateFeeBalance;
                        try { $pdo->prepare("UPDATE students SET payment_plan = 'One-time' WHERE id = ?")->execute([(int)$studentId]); } catch (Throwable $e) {}
                    }

                    $statusField = 'school_fee_status';
                    $referenceField = 'school_fee_reference';
                    $transactionType = 'School Fee';
                    break;


                case 'assigned':
                    $assignedId = (int)($_POST['assigned_id'] ?? 0);
                    $stmt = $pdo->prepare("SELECT * FROM assigned_payments WHERE id = ? AND student_id = ? LIMIT 1");
                    $stmt->execute([$assignedId, $studentId]);
                    $ap = $stmt->fetch(PDO::FETCH_ASSOC);
                    if (!$ap) { $paymentError = "Invalid assigned payment."; break; }
                    if (($ap['status'] ?? '') === 'Paid') { $paymentError = "This payment has already been paid."; break; }
                    $fee = (float)$ap['amount'];
                    $description = 'Assigned Payment: ' . ($ap['title'] ?? 'Custom');
                    $statusField = null;
                    $referenceField = null;
                    $transactionType = 'Custom: ' . ($ap['title'] ?? 'Assigned Payment'); // show custom title in Payment Type
                    $_POST['_assigned_title'] = $ap['title'] ?? '';
                    $_POST['_assigned_id'] = $assignedId;
                    break;


            case 'assigned':
                $statusField = '';
                $emailSubject = "Payment Confirmation - HontTech Registration";
                $emailBody = "<h2>Payment Confirmation</h2>";
                $receiptPage = 'payment_receipt';
                // mark assigned payment paid
                $pendingApId = (int)(($_SESSION['pending_payments'][$reference]['assigned_id'] ?? 0));
                if ($pendingApId > 0) {
                    $stmt = $pdo->prepare("UPDATE assigned_payments SET status = 'Paid', reference = ? WHERE id = ? AND student_id = ?");
                    $stmt->execute([(string)$reference, $pendingApId, (int)$studentId]);
                }
                break;

case 'wallet_funding':
                    $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'wallet_min_fund'");
                    $stmt->execute();
                    $minFund = (float)($stmt->fetchColumn() ?: 0);
                    $fee = (float)($_POST['amount'] ?? 0);
                    if ($minFund > 0 && $fee < $minFund) {
                        $fee = $minFund;
                    }
                    if ($fee <= 0) { $fee = 1000; } // default
                    $description = 'Wallet Funding';
                    $statusField = null;
                    $referenceField = null;
                    $transactionType = 'Wallet Funding';
                    break;
default:
                    $paymentError = "Invalid payment type";
                    break;
            }

            if (!isset($paymentError)) {
            if (!isset($paymentError)) {
            }

                // Generate reference
                $reference = 'Honttech_' . $paymentType . '_' . time() . '_' . $studentId;
                $paymentAcademicSession = trim((string)($_POST['payment_session'] ?? $_GET['session'] ?? ($student['session'] ?? portalCurrentAcademicSessionName($pdo))));
                $paymentSemester = portalNormalizeSemesterName($_POST['payment_semester'] ?? $_GET['semester'] ?? ($student['semester'] ?? 'First Semester'));
                // Keep payment details in session until Paystack confirms success (prevents abandoned pending transactions)
                $transactionId = generateTransactionId();
                if (!isset($_SESSION['pending_payments'])) { $_SESSION['pending_payments'] = []; }
                $_SESSION['pending_payments'][$reference] = [
                    'student_id' => $studentId,
                    'transaction_id' => $transactionId,
                    'transaction_type' => $transactionType,
                    // Base amount for the item being paid (excluding gateway charges)
                    'amount' => (float)$fee,
                    'base_amount' => (float)$fee,
                    'payment_type' => $paymentType,
                    'installment_number' => (int)$installmentNumber,
                    'total_installments' => (int)$totalInstallments,
                    'assigned_id' => (int)($_POST['assigned_id'] ?? ($_POST['_assigned_id'] ?? 0)),
                    'assigned_title' => (string)($_POST['_assigned_title'] ?? ''),
                    'academic_session' => $paymentAcademicSession,
                    'semester' => $paymentSemester,
                    'school_fee_base_amount' => (float)($_POST['_school_fee_base_amount'] ?? $fee),
                    'school_fee_late_fee_amount' => (float)($_POST['_school_fee_late_fee_amount'] ?? 0),
                ];
// Choose payment gateway (allow user selection if enabled)
$gateway = strtolower(trim((string)($_POST['gateway'] ?? explode(',', (string)getSystemSettingValue($pdo, 'default_gateways', getSystemSettingValue($pdo,'default_gateway','paystack')))[0])));
if ($gateway === '') $gateway = 'paystack';

// Save gateway on the pending payment (used during callbacks)
$_SESSION['pending_payments'][$reference]['gateway'] = $gateway;

// Gateway enable flags
$onlineEnabled = getSystemSettingValue($pdo, 'online_payment_enabled', 'enabled');
$gatewayRegistry = portalPaymentGatewayRegistry();
if ($onlineEnabled !== 'enabled') {
    $paymentError = "Online payment is currently disabled. Please use Manual Payment if available.";
} elseif (!isset($gatewayRegistry[$gateway])) {
    $paymentError = "Unknown payment gateway selected.";
} elseif (!portalGatewayEnabled($pdo, $gateway)) {
    $paymentError = portalGatewayLabel($gateway) . " payment is currently disabled.";
} else {
    $gatewayState = portalGatewayConfigurationState($pdo, $gateway);
    if (!$gatewayState['ok']) {
        $paymentError = portalGatewayLabel($gateway) . " is enabled but not fully configured. " . $gatewayState['message'];
    } elseif ($paymentType === 'wallet_funding' && !portalGatewaySupportsWalletFunding($gateway)) {
        $paymentError = portalGatewayLabel($gateway) . " is not enabled for wallet funding on this portal.";
    }
}

// If a gateway error exists, stop here

if (!empty($paymentError)) {
    flash('danger', $paymentError);
    redirect('?page=payment');
}

// ===============================
// Apply payer-bears-charges (gross amount to charge the payer)
// ===============================
$baseAmount = (float)$fee;
$gatewayFee = calcGatewayFee($pdo, $gateway, $baseAmount);
$chargedAmount = round($baseAmount + $gatewayFee, 2);

$_SESSION['pending_payments'][$reference]['gateway_fee'] = $gatewayFee;

$_SESSION['pending_payments'][$reference]['charged_amount'] = $chargedAmount;

                if (!portalGatewayHasNativeCheckout($gateway)) {
                    $hostedCheckoutUrl = portalGatewayCheckoutUrl($pdo, $gateway);
                    $callback_url = trim((string)getSystemSettingValue($pdo, $gateway . '_callback_url', ''));
                    if ($callback_url === '') {
                        $callback_url = appBaseUrl() . '/index.php?action=verify_payment&gateway=' . urlencode($gateway) . '&reference=' . urlencode($reference) . '&type=' . urlencode($paymentType);
                    }
                    if ($hostedCheckoutUrl === '') {
                        $paymentError = portalGatewayLabel($gateway) . ' is enabled but no Hosted Checkout URL / Middleware URL was configured.';
                        flash('danger', $paymentError);
                        redirect('?page=payment');
                    }
                    $query = [
                        'reference' => $reference,
                        'amount' => number_format((float)$chargedAmount, 2, '.', ''),
                        'charged_amount' => number_format((float)$chargedAmount, 2, '.', ''),
                        'base_amount' => number_format((float)$baseAmount, 2, '.', ''),
                        'gateway_fee' => number_format((float)$gatewayFee, 2, '.', ''),
                        'student_id' => (int)$studentId,
                        'email' => (string)($student['email'] ?? ''),
                        'name' => studentFullName($student),
                        'payment_type' => (string)$paymentType,
                        'transaction_id' => (string)$transactionId,
                        'installment_number' => (int)$installmentNumber,
                        'total_installments' => (int)$totalInstallments,
                        'academic_session' => (string)$paymentAcademicSession,
                        'semester' => (string)$paymentSemester,
                        'callback_url' => $callback_url,
                        'return_url' => $callback_url,
                    ];
                    $sep = str_contains($hostedCheckoutUrl, '?') ? '&' : '?';
                    header('Location: ' . $hostedCheckoutUrl . $sep . http_build_query($query));
                    exit;
                }


if ($gateway === 'stripe') {
    $stripeCurrency = strtoupper(trim((string)getSystemSettingValue($pdo, 'stripe_currency', 'USD')));
    $stripeResult = portalStripeCreateCheckoutSession($pdo, ['reference' => $reference, 'amount' => (float)$chargedAmount, 'currency' => $stripeCurrency, 'description' => $transactionType, 'customer_email' => (string)($student['email'] ?? ''), 'payment_type' => $paymentType, 'transaction_id' => $transactionId, 'success_url' => appBaseUrl() . '/index.php?action=verify_payment&gateway=stripe&reference=' . urlencode($reference) . '&type=' . urlencode($paymentType) . '&session_id={CHECKOUT_SESSION_ID}', 'cancel_url' => appBaseUrl() . '/index.php?page=payment']);
    portalLogGatewayCallback($pdo, 'stripe', 'initialize', $reference, $stripeResult, '', !empty($stripeResult['ok']) ? 'initialized' : 'init_failed');
    if (!empty($stripeResult['ok']) && !empty($stripeResult['data']['url'])) { header('Location: ' . $stripeResult['data']['url']); exit; }
    $paymentError = (string)($stripeResult['message'] ?? 'Stripe initialization failed.');
    flash('danger', $paymentError); redirect('?page=payment');
}

if ($gateway === 'paypal') {
    $paypalCurrency = strtoupper(trim((string)getSystemSettingValue($pdo, 'paypal_currency', 'USD')));
    $paypalResult = portalPayPalCreateOrder($pdo, ['reference' => $reference, 'amount' => (float)$chargedAmount, 'currency' => $paypalCurrency, 'description' => $transactionType, 'success_url' => appBaseUrl() . '/index.php?action=verify_payment&gateway=paypal&reference=' . urlencode($reference) . '&type=' . urlencode($paymentType), 'cancel_url' => appBaseUrl() . '/index.php?page=payment']);
    portalLogGatewayCallback($pdo, 'paypal', 'initialize', $reference, $paypalResult, '', !empty($paypalResult['ok']) ? 'initialized' : 'init_failed');
    if (!empty($paypalResult['ok']) && !empty($paypalResult['data']['links']) && is_array($paypalResult['data']['links'])) {
        foreach ($paypalResult['data']['links'] as $link) { if (($link['rel'] ?? '') === 'approve' && !empty($link['href'])) { header('Location: ' . $link['href']); exit; } }
    }
    $paymentError = (string)($paypalResult['message'] ?? 'PayPal initialization failed.');
    flash('danger', $paymentError); redirect('?page=payment');
}

if ($gateway === 'monnify') {
    $monnifyCurrency = strtoupper(trim((string)getSystemSettingValue($pdo, 'monnify_currency', 'NGN')));
    $monnifyResult = portalMonnifyInitializeTransaction($pdo, ['reference' => $reference, 'amount' => (float)$chargedAmount, 'currency' => $monnifyCurrency, 'description' => $transactionType, 'customer_name' => studentFullName($student), 'customer_email' => (string)($student['email'] ?? ''), 'success_url' => appBaseUrl() . '/index.php?action=verify_payment&gateway=monnify&reference=' . urlencode($reference) . '&type=' . urlencode($paymentType)]);
    portalLogGatewayCallback($pdo, 'monnify', 'initialize', $reference, $monnifyResult, '', !empty($monnifyResult['ok']) ? 'initialized' : 'init_failed');
    $checkoutUrl = (string)($monnifyResult['data']['responseBody']['checkoutUrl'] ?? $monnifyResult['data']['checkoutUrl'] ?? '');
    if (!empty($monnifyResult['ok']) && $checkoutUrl !== '') { header('Location: ' . $checkoutUrl); exit; }
    $paymentError = (string)($monnifyResult['message'] ?? 'Monnify initialization failed.');
    flash('danger', $paymentError); redirect('?page=payment');
}

                // Initialize Flutterwave transaction (redirect checkout)
                if ($gateway === 'flutterwave') {
                    $fwSecret = getSystemSettingValue($pdo, 'flutterwave_secret_key', defined('FLUTTERWAVE_SECRET_KEY') ? FLUTTERWAVE_SECRET_KEY : '');
                    $fwCurrency = getSystemSettingValue($pdo, 'flutterwave_currency', 'NGN');
                    if (empty($fwSecret)) {
                        $paymentError = "Flutterwave is selected but not configured. Please set Flutterwave secret key in Admin Settings.";
                        flash('danger', $paymentError);
                        redirect('?page=payment');
                    }

                    $callback_url = appBaseUrl() . '/index.php?action=verify_payment&gateway=flutterwave&reference=' . urlencode($reference) . '&type=' . urlencode($paymentType);

                    $payload = [
                        'tx_ref' => $reference,
                        'amount' => (string)$chargedAmount,
                        'currency' => $fwCurrency,
                        'redirect_url' => $callback_url,
                        'customer' => [
                            'email' => (string)$student['email'],
                            'name' => studentFullName($student),
                        ],
                        'meta' => [
                            'student_id' => $studentId,
                            'registration_no' => $student['registration_no'],
                            'payment_type' => $paymentType,
                            'transaction_id' => $transactionId,
                            'base_amount' => $baseAmount,
                            'gateway_fee' => $gatewayFee,
                            'charged_amount' => $chargedAmount,
                            'installment_number' => $installmentNumber,
                            'total_installments' => $totalInstallments,
                            'academic_session' => $paymentAcademicSession,
                            'semester' => $paymentSemester
                        ],
                        'customizations' => [
                            'title' => APP_NAME,
                            'description' => $transactionType,
                        ],
                    ];

                    $curl = curl_init();
                    curl_setopt_array($curl, [
                        CURLOPT_URL => "https://api.flutterwave.com/v3/payments",
                        CURLOPT_RETURNTRANSFER => true,
                        CURLOPT_CUSTOMREQUEST => "POST",
                        CURLOPT_POSTFIELDS => json_encode($payload),
                        CURLOPT_HTTPHEADER => [
                            "Authorization: Bearer " . $fwSecret,
                            "Content-Type: application/json",
                        ],
                    ]);
                    $response = curl_exec($curl);
                    $err = curl_error($curl);
                    curl_close($curl);

                    if ($err) {
                        $paymentError = "Flutterwave initialization failed. Please try again.";
                        flash('danger', $paymentError);
                        redirect('?page=payment');
                    }
                    $result = json_decode($response, true);
                    if (is_array($result) && ($result['status'] ?? '') === 'success' && !empty($result['data']['link'])) {
                        // Update student with payment reference
                        if (!empty($referenceField)) {
                        $stmt = $pdo->prepare("UPDATE students SET $referenceField = ? WHERE id = ?");
                        $stmt->execute([$reference, $studentId]);
                    }
                        header('Location: ' . $result['data']['link']);
                        exit;
                    } else {
                        $msg = is_array($result) ? ($result['message'] ?? 'Initialization failed') : 'Initialization failed';
                        $paymentError = "Flutterwave initialization failed: " . $msg;
                        flash('danger', $paymentError);
                        redirect('?page=payment');
                    }
                }

// Initialize Interswitch transaction (Web Redirect)
                if ($gateway === 'interswitch') {
                    $mc = trim(getSystemSettingValue($pdo, 'interswitch_merchant_code', ''));
                    $pi = trim(getSystemSettingValue($pdo, 'interswitch_pay_item_id', ''));
                    $currency = trim(getSystemSettingValue($pdo, 'interswitch_currency', '566'));
                    $mode = strtoupper(trim(getSystemSettingValue($pdo, 'interswitch_mode', 'LIVE')));
                    $postUrl = ($mode === 'TEST') ? 'https://newwebpay.qa.interswitchng.com/collections/w/pay' : 'https://newwebpay.interswitchng.com/collections/w/pay';
                    $amountMinor = (int)round(((float)$chargedAmount) * 100);
                    $callback_url = appBaseUrl() . '/index.php?action=verify_payment&gateway=interswitch&reference=' . urlencode($reference) . '&type=' . urlencode($paymentType);
                    echo '<html><head><title>Redirecting to Interswitch...</title></head><body onload="document.forms[0].submit()">';
                    echo '<form method="post" action="' . htmlspecialchars($postUrl) . '">';
                    echo '<input type="hidden" name="merchant_code" value="' . htmlspecialchars($mc) . '">';
                    echo '<input type="hidden" name="pay_item_id" value="' . htmlspecialchars($pi) . '">';
                    echo '<input type="hidden" name="site_redirect_url" value="' . htmlspecialchars($callback_url) . '">';
                    echo '<input type="hidden" name="txn_ref" value="' . htmlspecialchars($reference) . '">';
                    echo '<input type="hidden" name="amount" value="' . htmlspecialchars((string)$amountMinor) . '">';
                    echo '<input type="hidden" name="currency" value="' . htmlspecialchars($currency) . '">';
                    echo '<input type="hidden" name="cust_email" value="' . htmlspecialchars((string)$student['email']) . '">';
                    echo '<input type="hidden" name="cust_name" value="' . htmlspecialchars(studentFullName($student)) . '">';
                    echo '<input type="hidden" name="cust_id" value="' . htmlspecialchars((string)$student['registration_no']) . '">';
                    echo '<noscript><input type="submit" value="Continue to Interswitch"></noscript>';
                    echo '</form><script>
// Fix stale pages on mobile back-forward cache (bfcache)
window.addEventListener("pageshow", function(event){ if (event.persisted){ window.location.reload(); } });
</script>
</body></html>';
                    exit;
                }

// Initialize Paystack transaction
                $curl = curl_init();

                $email = $student['email'];
                $amount = $chargedAmount * 100; // Convert to kobo (includes gateway charges if enabled)

                $callback_url = appBaseUrl() . '/index.php?action=verify_payment&gateway=paystack&reference=' . urlencode($reference) . '&type=' . urlencode($paymentType);

                curl_setopt_array($curl, array(
                    CURLOPT_URL => "https://api.paystack.co/transaction/initialize",
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_CUSTOMREQUEST => "POST",
                    CURLOPT_POSTFIELDS => json_encode([
                        'amount' => $amount,
                        'email' => $email,
                        'reference' => $reference,
                        'callback_url' => $callback_url,
                        'metadata' => [
                            'student_id' => $studentId,
                            'registration_no' => $student['registration_no'],
                            'payment_type' => $paymentType,
                            'transaction_id' => $transactionId,
                            'base_amount' => $baseAmount,
                            'gateway_fee' => $gatewayFee,
                            'charged_amount' => $chargedAmount,
                            'installment_number' => $installmentNumber,
                            'total_installments' => $totalInstallments
                        ]
                    ]),
                    CURLOPT_HTTPHEADER => array(
                        "Authorization: Bearer " . getSystemSettingValue($pdo, 'paystack_secret_key', defined('PAYSTACK_SECRET_KEY') ? PAYSTACK_SECRET_KEY : ''),
                        "Content-Type: application/json",
                    ),
                ));

                $response = curl_exec($curl);
                $err = curl_error($curl);
                curl_close($curl);

                if ($err) {
                    $paymentError = "Payment initialization failed. Please try again.";
                } else {
                    $result = json_decode($response, true);

                    if ($result['status']) {
                        // Update student with payment reference
                        if (!empty($referenceField)) {
                        $stmt = $pdo->prepare("UPDATE students SET $referenceField = ? WHERE id = ?");
                        $stmt->execute([$reference, $studentId]);
                    }
                        // Redirect to payment page
                        header('Location: ' . $result['data']['authorization_url']);
                        exit;
                    } else {
                        $paymentError = "Payment initialization failed: " . $result['message'];
                    }
                }
            }
        }
    }
}


// Manual payment submission (upload teller/receipt)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'submit_manual_payment') {
    if (!isStudent()) { redirect('?page=login'); }
    try { ensurePortalExtensionSupport($pdo); } catch (Throwable $e) {}

    $studentId = (int)($_SESSION['student_id'] ?? ($_SESSION['user_id'] ?? 0));
    $paymentType = (string)($_POST['payment_type'] ?? 'registration');
    $installmentNumber = max(1, (int)($_POST['installment_number'] ?? 1));
    $totalInstallments = max(1, (int)($_POST['total_installments'] ?? 1));
    $enteredAmountPaid = round((float)($_POST['amount_paid'] ?? $_POST['amount'] ?? 0), 2);
    $payerName = trim((string)($_POST['payer_name'] ?? ''));
    $notes = trim((string)($_POST['manual_notes'] ?? ''));
    $assignedId = (int)($_POST['assigned_id'] ?? ($_POST['_assigned_id'] ?? 0));

    if ($paymentType === 'wallet_funding' && (!isset($_POST['amount_paid']) || $enteredAmountPaid <= 0)) {
        flash('danger', 'Enter a valid Amount Paid greater than zero for manual wallet funding.');
        redirect('?page=wallet');
    }

    $student = [];
    $periodStudent = [];
    try {
        $studentStmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
        $studentStmt->execute([$studentId]);
        $student = $studentStmt->fetch(PDO::FETCH_ASSOC) ?: [];
        $periodStudent = $student;
    } catch (Throwable $e) {}
    $paymentAcademicSession = trim((string)($_POST['payment_session'] ?? $_GET['session'] ?? ($periodStudent['session'] ?? portalCurrentAcademicSessionName($pdo))));
    $paymentSemester = portalNormalizeSemesterName($_POST['payment_semester'] ?? $_GET['semester'] ?? ($periodStudent['semester'] ?? 'First Semester'));

    $manualEnabled = getSystemSettingValue($pdo, 'manual_payment_enabled', 'enabled');
    if ($manualEnabled !== 'enabled') {
        flash('danger', 'Manual payment is currently disabled by the administrator.');
        redirect('?page=' . ($paymentType === 'wallet_funding' ? 'wallet' : 'payment'));
    }

    $fee = 0.0; $referenceField=''; $transactionType=''; $redirectPage='payment';
    switch ($paymentType) {
        case 'registration':
            $fee = (float)portalStudentManagedFeeAmount($pdo, 'registration', (array)$student, $paymentAcademicSession, $paymentSemester);
            $referenceField = 'registration_fee_reference';
            $transactionType = 'Registration Fee';
            $redirectPage = 'payment';
            break;
        case 'acceptance':
            $fee = (float)portalStudentManagedFeeAmount($pdo, 'acceptance', (array)$student, $paymentAcademicSession, $paymentSemester);
            $referenceField = 'acceptance_fee_reference';
            $transactionType = 'Acceptance Fee';
            $redirectPage = 'acceptance_payment';
            break;
        case 'hostel':
            $fee = (float)portalStudentManagedFeeAmount($pdo, 'hostel', (array)$student, $paymentAcademicSession, $paymentSemester);
            $referenceField = 'hostel_fee_reference';
            $transactionType = 'Hostel Fee';
            $redirectPage = 'hostel_payment';
            break;
        case 'school':
            $totalFee = (float)portalStudentSchoolFeeAmount($pdo, (array)$student, $paymentAcademicSession, $paymentSemester);
            $lateFeeSummary = portalSchoolFeePeriodSummary($pdo, (int)$studentId, $paymentAcademicSession, $paymentSemester, (array)$student);
            $lateFeeBalance = (float)($lateFeeSummary['late_fee_balance'] ?? 0);
            $referenceField = 'school_fee_reference';
            $transactionType = 'School Fee';
            $redirectPage = 'school_payment';

            portalAssertStudentSchoolPaymentPlanAllowed($pdo, (int)$studentId, (int)$totalInstallments);
            $paidInstallments = 0;
            try {
                $stx = $pdo->prepare("SELECT COUNT(*) FROM payment_transactions WHERE student_id=? AND transaction_type='School Fee' AND status='Approved' AND COALESCE(amount,0) > 0 AND total_installments>1");
                $stx->execute([(int)$studentId]);
                $paidInstallments = (int)$stx->fetchColumn();
            } catch (Throwable $e) { $paidInstallments = 0; }

            if ($totalInstallments > 1) {
                $totalInstallments = 2;
                if ($paidInstallments >= 2) {
                    flash('info','School fee has already been fully paid.');
                    redirect('?page=school_payment');
                }
                $installmentNumber = min(2, max(1, $paidInstallments + 1));
                $principalFee = max(0.0, (float)($lateFeeSummary['principal_outstanding'] ?? ($totalFee / 2)));
                $fee = $principalFee + $lateFeeBalance;
                $_POST['_school_fee_base_amount'] = $principalFee;
                $_POST['_school_fee_late_fee_amount'] = $lateFeeBalance;
                try { $pdo->prepare("UPDATE students SET payment_plan='Installment' WHERE id=?")->execute([(int)$studentId]); } catch (Throwable $e) {}
            } else {
                $totalInstallments = 1;
                $installmentNumber = 1;
                if ($paidInstallments > 0) {
                    flash('warning','You have already started installment payment. Please pay the next installment instead of one-time payment.');
                    redirect('?page=school_payment');
                }
                $principalFee = max(0.0, (float)($lateFeeSummary['principal_outstanding'] ?? $totalFee));
                $fee = $principalFee + $lateFeeBalance;
                $_POST['_school_fee_base_amount'] = $principalFee;
                $_POST['_school_fee_late_fee_amount'] = $lateFeeBalance;
                try { $pdo->prepare("UPDATE students SET payment_plan='One-time' WHERE id=?")->execute([(int)$studentId]); } catch (Throwable $e) {}
            }
            break;
        case 'wallet_funding':
            $fee = (float)($_POST['amount'] ?? 0);
            if ($fee <= 0) { $fee = (float)getSystemSettingValue($pdo, 'wallet_min_fund', '1000'); }
            $referenceField = '';
            $transactionType = 'Wallet Funding';
            $redirectPage = 'wallet';
            break;
        case 'assigned':
            $stmt = $pdo->prepare("SELECT * FROM assigned_payments WHERE id = ? AND student_id = ? LIMIT 1");
            $stmt->execute([$assignedId, $studentId]);
            $ap = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$ap) { $fee = 0; $transactionType=''; $redirectPage='payment&tab=other'; break; }
            if (($ap['status'] ?? '') === 'Paid') { $fee = 0; $transactionType=''; $redirectPage='payment&tab=other'; break; }
            $fee = (float)$ap['amount'];
            $referenceField = '';
            $transactionType = 'Custom: ' . ($ap['title'] ?? 'Assigned Payment');
            $redirectPage = 'payment&tab=other';
            $_POST['_assigned_id'] = $assignedId;
            $_POST['_assigned_title'] = $ap['title'] ?? '';
            break;
    }

    if ($paymentType === 'hostel' && $fee <= 0) {
        flash('danger', 'No matching hostel fee found for the new session scope.');
        redirect('?page=' . $redirectPage);
    }
    if ($paymentType === 'school' && $fee <= 0 && (float)($_POST['_school_fee_late_fee_amount'] ?? 0) <= 0) {
        flash('danger', 'No matching school fee found for the new student scope.');
        redirect('?page=' . $redirectPage);
    }

    if ($transactionType !== '') {
        if ($paymentType === 'wallet_funding') {
            if ($enteredAmountPaid > 0) {
                $fee = $enteredAmountPaid;
            }
        } elseif ($enteredAmountPaid > 0) {
            $fee = $enteredAmountPaid;
        }
    }

    if ($studentId <= 0 || $fee <= 0 || $transactionType === '') {
        flash('danger', 'Invalid payment request.');
        redirect('?page=' . $redirectPage);
    }

    $scopeKey = portalManualPaymentScopeKey($studentId, $paymentType, $paymentAcademicSession, $paymentSemester, $paymentType === 'school' ? $installmentNumber : 1, $paymentType === 'school' ? $totalInstallments : 1, $assignedId);

    $existing = null;
    try {
        $sx = $pdo->prepare("SELECT * FROM payment_transactions WHERE manual_scope_key = ? AND payment_method = 'Manual' AND status NOT IN ('Approved','Successful') ORDER BY id DESC LIMIT 1");
        $sx->execute([$scopeKey]);
        $existing = $sx->fetch(PDO::FETCH_ASSOC) ?: null;
    } catch (Throwable $e) { $existing = null; }

    $upload = null;
    try {
        $upload = portalValidatedManualProofUpload($_FILES['manual_proof'] ?? []);
    } catch (Throwable $e) {
        flash('danger', $e->getMessage());
        redirect('?page=' . $redirectPage);
    }

    if ($existing) {
        try {
            $pdo->beginTransaction();
            $oldProofPath = (string)($existing['manual_proof_path'] ?? '');
            $stmt = $pdo->prepare("UPDATE payment_transactions SET
                    amount = ?,
                    school_fee_base_amount = ?,
                    school_fee_late_fee_amount = ?,
                    amount_paid = ?,
                    approved_amount = NULL,
                    academic_session = ?,
                    semester = ?,
                    status = 'Pending',
                    payment_method = 'Manual',
                    payment_date = NOW(),
                    approval_date = NULL,
                    approved_by = NULL,
                    approval_notes = NULL,
                    receipt_data = NULL,
                    qr_code_data = NULL,
                    manual_proof_path = ?,
                    manual_proof_original_name = ?,
                    manual_proof_uploaded_at = NOW(),
                    proof_locked_at = NULL,
                    wallet_credit_status = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' THEN 'Pending' ELSE wallet_credit_status END,
                    wallet_credited_amount = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' THEN 0 ELSE wallet_credited_amount END,
                    wallet_credited_at = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' THEN NULL ELSE wallet_credited_at END,
                    wallet_credit_reference = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' THEN NULL ELSE wallet_credit_reference END,
                    manual_payer_name = ?,
                    manual_notes = ?,
                    installment_number = ?,
                    total_installments = ?
                WHERE id = ?");
            $stmt->execute([
                $fee,
                (float)($_POST['_school_fee_base_amount'] ?? ($paymentType === 'school' ? $fee : 0)),
                (float)($_POST['_school_fee_late_fee_amount'] ?? 0),
                $fee,
                $paymentAcademicSession,
                $paymentSemester,
                $upload['path'],
                $upload['original_name'],
                $payerName,
                $notes,
                (int)($paymentType === 'school' ? $installmentNumber : 1),
                (int)($paymentType === 'school' ? $totalInstallments : 1),
                (int)$existing['id']
            ]);
            if (!empty($referenceField)) {
                $pdo->prepare("UPDATE students SET {$referenceField} = ? WHERE id = ?")->execute([(string)$existing['reference'], $studentId]);
            }
            if ($paymentType === 'assigned' && $assignedId > 0) {
                $pdo->prepare("UPDATE assigned_payments SET status='Pending', reference=?, updated_at=NOW() WHERE id=? AND student_id=?")
                    ->execute([(string)$existing['reference'], $assignedId, $studentId]);
            }
            $pdo->commit();
            portalSafeRemoveFile($oldProofPath);
            flash('success', 'Existing proof replaced successfully. Status: Pending Approval.');
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            portalSafeRemoveFile((string)($upload['path'] ?? ''));
            error_log('Manual payment replacement failed: ' . $e->getMessage());
            flash('danger', $e->getMessage() !== '' ? $e->getMessage() : 'Unable to replace proof of payment. Please try again.');
        }
        redirect('?page=' . $redirectPage);
    }

    $reference = 'MANUAL_' . $paymentType . '_' . time() . '_' . $studentId;
    $transactionId = '';
    for ($txTry = 0; $txTry < 5; $txTry++) {
        $candidate = (string)generateTransactionId();
        try {
            $txCheck = $pdo->prepare("SELECT id FROM payment_transactions WHERE transaction_id = ? LIMIT 1");
            $txCheck->execute([$candidate]);
            if (!$txCheck->fetchColumn()) {
                $transactionId = $candidate;
                break;
            }
        } catch (Throwable $e) {
            $transactionId = $candidate;
            break;
        }
    }
    if ($transactionId === '') {
        $transactionId = (string)(time() . random_int(1000, 9999));
    }

    try {
        $pdo->beginTransaction();
        $ins = $pdo->prepare("INSERT INTO payment_transactions
            (transaction_id, student_id, transaction_type, amount, school_fee_base_amount, school_fee_late_fee_amount, amount_paid, approved_amount, reference, academic_session, semester, status, payment_method, payment_date, manual_proof_path, manual_proof_original_name, manual_proof_uploaded_at, manual_payer_name, manual_notes, manual_scope_key, installment_number, total_installments)
            VALUES (?,?,?,?,?,?,?,?,?, ?, ?, 'Pending','Manual',NOW(),?,?,?,?,?,?,?, ?)");
        $ins->execute([
            $transactionId,
            $studentId,
            $transactionType,
            $fee,
            (float)($_POST['_school_fee_base_amount'] ?? ($paymentType === 'school' ? $fee : 0)),
            (float)($_POST['_school_fee_late_fee_amount'] ?? 0),
            $fee,
            null,
            $reference,
            $paymentAcademicSession,
            $paymentSemester,
            $upload['path'],
            $upload['original_name'],
            date('Y-m-d H:i:s'),
            $payerName,
            $notes,
            $scopeKey,
            (int)($paymentType === 'school' ? $installmentNumber : 1),
            (int)($paymentType === 'school' ? $totalInstallments : 1)
        ]);

        if ($paymentType === 'assigned' && $assignedId > 0) {
            $pdo->prepare("UPDATE assigned_payments SET status='Pending', reference=?, updated_at=NOW() WHERE id=? AND student_id=?")
                ->execute([$reference, $assignedId, $studentId]);
        }

        if (!empty($referenceField)) {
            $stmt = $pdo->prepare("UPDATE students SET {$referenceField} = ? WHERE id = ?");
            $stmt->execute([$reference, $studentId]);
        }

        $pdo->commit();

        try {
            $admins = $pdo->query("SELECT id FROM admins")->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($admins as $a) {
                createNotification($pdo, 'admin', (int)$a['id'], "Manual payment submitted", "A student submitted a manual payment for {$transactionType}. Please review and approve.", "?page=admin_payments");
            }
        } catch (Throwable $notifyError) {
            error_log('Manual payment admin notification failed: ' . $notifyError->getMessage());
        }

        flash('success', 'Manual payment proof uploaded successfully. Status: Pending Approval.');
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        portalSafeRemoveFile((string)($upload['path'] ?? ''));
        error_log('Manual payment submit failed: ' . $e->getMessage());
        flash('danger', $e->getMessage() !== '' ? $e->getMessage() : 'Unable to submit manual payment. Please try again.');
    }

    redirect('?page=' . $redirectPage);
}

// Handle wallet payments for fees (instant) and wallet funding (via gateways)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'pay_with_wallet') {
    $studentId = (int)($_SESSION['student_id'] ?? ($_SESSION['user_id'] ?? 0));
    $paymentType = (string)($_POST['payment_type'] ?? 'registration'); // registration, acceptance, hostel, school
    // For school fee, wallet can pay one-time or installments
    $installmentNumber = (int)($_POST['installment_number'] ?? 1);
    $totalInstallments = (int)($_POST['total_installments'] ?? 1);
    if ($studentId <= 0) { flash('danger','Session expired. Please login again.'); redirect('?page=login'); }

    if ((string)($_POST['wallet_payment_confirmed'] ?? '') !== '1') {
        flash('warning', 'Wallet payment confirmation was not completed. Please click the wallet button again and confirm the payment popup.');
        $returnUrl = trim((string)($_SERVER['REQUEST_URI'] ?? ''));
        redirect($returnUrl !== '' ? $returnUrl : '?page=payment');
    }

    $st = [];
    try {
        $studentStmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
        $studentStmt->execute([$studentId]);
        $st = $studentStmt->fetch(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {}
    $paymentAcademicSession = trim((string)($_POST['payment_session'] ?? $_GET['session'] ?? (($st['session'] ?? '') !== '' ? $st['session'] : portalCurrentAcademicSessionName($pdo))));
    $paymentSemester = portalNormalizeSemesterName($_POST['payment_semester'] ?? $_GET['semester'] ?? ($st['semester'] ?? 'First Semester'));

    // Determine amount + fields
    $fee = 0.0;
    $statusField = '';
    $referenceField = '';
    $transactionType = '';
    $redirectPage = 'dashboard';

    $receiptPage = 'payment_receipt';
    switch ($paymentType) {
        case 'registration':
            $fee = (float)portalStudentManagedFeeAmount($pdo, 'registration', (array)$st, $paymentAcademicSession, $paymentSemester);
            $statusField = 'registration_fee_status';
            $referenceField = 'registration_fee_reference';
            $transactionType = 'Registration Fee';
            $redirectPage = 'dashboard';
            break;
        case 'acceptance':
            $fee = (float)portalStudentManagedFeeAmount($pdo, 'acceptance', (array)$st, $paymentAcademicSession, $paymentSemester);
            $statusField = 'acceptance_fee_status';
            $referenceField = 'acceptance_fee_reference';
            $transactionType = 'Acceptance Fee';
            $redirectPage = 'dashboard';
            break;
        case 'hostel':
            if (function_exists('portalShouldRequireHostelFeeForStudent') && !portalShouldRequireHostelFeeForStudent($pdo, (array)$st, $paymentAcademicSession, $paymentSemester)) {
                flash('info', 'Hostel fee is not currently required for your accommodation status.');
                redirect('?page=hostel_payment');
            }
            $fee = (float)portalStudentManagedFeeAmount($pdo, 'hostel', (array)$st, $paymentAcademicSession, $paymentSemester);
            $statusField = 'hostel_fee_status';
            $referenceField = 'hostel_fee_reference';
            $transactionType = 'Hostel Fee';
            $redirectPage = 'dashboard';
            break;
        case 'school':
            $totalFee = (float)portalStudentSchoolFeeAmount($pdo, (array)$st, $paymentAcademicSession, $paymentSemester);
            $lateFeeSummary = portalSchoolFeePeriodSummary($pdo, (int)$studentId, $paymentAcademicSession, $paymentSemester, (array)$st);
            $lateFeeBalance = (float)($lateFeeSummary['late_fee_balance'] ?? 0);
            $statusField = 'school_fee_status';
            $referenceField = 'school_fee_reference';
            $transactionType = 'School Fee';
            $redirectPage = 'dashboard';
            $receiptPage = 'school_payment_receipt';

            // Enforce: either one-time OR 2 installments (sequential)
            portalAssertStudentSchoolPaymentPlanAllowed($pdo, (int)$studentId, (int)$totalInstallments);
            $paidInstallments = 0;
            try {
                $stx = $pdo->prepare("SELECT COUNT(*) FROM payment_transactions WHERE student_id = ? AND transaction_type = 'School Fee' AND status = 'Approved' AND COALESCE(amount,0) > 0 AND total_installments > 1");
                $stx->execute([(int)$studentId]);
                $paidInstallments = (int)$stx->fetchColumn();
            } catch (Throwable $e) { $paidInstallments = 0; }

            if ($totalInstallments > 1) {
                $totalInstallments = 2;
                if ($paidInstallments >= 2) {
                    flash('info','School fee has already been fully paid.');
                    redirect('?page=school_payment');
                }
                // next installment is sequential
                $installmentNumber = min(2, max(1, $paidInstallments + 1));
                $principalFee = max(0.0, (float)($lateFeeSummary['principal_outstanding'] ?? ($totalFee / 2)));
                $fee = $principalFee + $lateFeeBalance;
                $_POST['_school_fee_base_amount'] = $principalFee;
                $_POST['_school_fee_late_fee_amount'] = $lateFeeBalance;
                try { $pdo->prepare("UPDATE students SET payment_plan = 'Installment' WHERE id = ?")->execute([(int)$studentId]); } catch (Throwable $e) {}
            } else {
                // One-time
                $totalInstallments = 1;
                $installmentNumber = 1;
                if ($paidInstallments > 0) {
                    flash('warning','You have already started installment payment. Please pay the next installment instead of one-time payment.');
                    redirect('?page=school_payment');
                }
                $principalFee = max(0.0, (float)($lateFeeSummary['principal_outstanding'] ?? $totalFee));
                $fee = $principalFee + $lateFeeBalance;
                $_POST['_school_fee_base_amount'] = $principalFee;
                $_POST['_school_fee_late_fee_amount'] = $lateFeeBalance;
                try { $pdo->prepare("UPDATE students SET payment_plan = 'One-time' WHERE id = ?")->execute([(int)$studentId]); } catch (Throwable $e) {}
            }
            break;

        case 'assigned':
            $assignedId = (int)($_POST['assigned_id'] ?? 0);
            $stmt = $pdo->prepare("SELECT * FROM assigned_payments WHERE id = ? AND student_id = ? LIMIT 1");
            $stmt->execute([$assignedId, $studentId]);
            $ap = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$ap) { flash('danger','Invalid assigned payment.'); redirect('?page=payment&tab=other'); }
            if (($ap['status'] ?? '') === 'Paid') { flash('info','This payment has already been paid.'); redirect('?page=payment&tab=other'); }
            $fee = (float)$ap['amount'];
            $statusField = '';
            $referenceField = '';
            $transactionType = 'Custom: ' . ($ap['title'] ?? 'Assigned Payment');
            $redirectPage = 'payment&tab=other';
            $_POST['_assigned_id'] = $assignedId;
            $_POST['_assigned_title'] = $ap['title'] ?? '';
            break;
default:
            flash('danger','Unknown payment type.');
            redirect('?page=dashboard');
    }

    try {
        // Ensure student exists
        $stmt = $pdo->prepare("SELECT id, email, session, semester FROM students WHERE id = ? LIMIT 1");
        $stmt->execute([$studentId]);
        $st = $stmt->fetch(PDO::FETCH_ASSOC);
        $paymentAcademicSession = trim((string)($_POST['payment_session'] ?? $_GET['session'] ?? ($st['session'] ?? portalCurrentAcademicSessionName($pdo))));
        $paymentSemester = portalNormalizeSemesterName($_POST['payment_semester'] ?? $_GET['semester'] ?? ($st['semester'] ?? 'First Semester'));
        if (!$st) { throw new RuntimeException('Student not found.'); }

        // Global wallet switch
        if (getSystemSettingValue($pdo, 'wallet_enabled', 'enabled') !== 'enabled') {
            throw new RuntimeException('Wallet is currently disabled by admin.');
        }

        // Debit wallet
        $txId = (string)('WAL' . strtoupper(bin2hex(random_bytes(5))));
        debitWallet($pdo, $studentId, $fee, $transactionType . ' paid via Wallet', $txId);

        // Create payment transaction as Approved
        $reference = 'wallet_' . strtolower($paymentType) . '_' . time();
        $stmt = $pdo->prepare("INSERT INTO payment_transactions (transaction_id, student_id, transaction_type, amount, school_fee_base_amount, school_fee_late_fee_amount, reference, academic_session, semester, paystack_reference, status, payment_method, payment_date, approval_date, approved_by, approval_notes, installment_number, total_installments)
                               VALUES (?,?,?,?,?,?,?,?,?, ?, ?, ?, NOW(),NOW(),NULL,?,?,?)");
        $stmt->execute([
            $txId,
            $studentId,
            $transactionType,
            $fee,
            (float)($_POST['_school_fee_base_amount'] ?? ($paymentType === 'school' ? $fee : 0)),
            (float)($_POST['_school_fee_late_fee_amount'] ?? 0),
            $reference,
            $paymentAcademicSession,
            $paymentSemester,
            null,
            'Approved',
            'Wallet',
            'Auto-approved (Wallet)',
            (int)($paymentType === 'school' ? $installmentNumber : 1),
            (int)($paymentType === 'school' ? $totalInstallments : 1)
        ]);

        $walletPaymentTxId = (int)$pdo->lastInsertId();
        $recon = portalReconcileFeePayment(
            $pdo,
            $walletPaymentTxId,
            (float)$fee,
            'wallet_payment',
            [
                'underpayment_to_wallet' => true,
                'notes' => 'Wallet payment reconciliation [' . $reference . ']',
            ]
        );
        if (!$recon['ok']) {
            creditWallet($pdo, $studentId, $fee, 'Wallet payment reversed due to reconciliation failure', 'wallet_reversal:' . $reference);
            throw new RuntimeException((string)($recon['message'] ?? 'Wallet payment reconciliation failed.'));
        }
        $transaction = (array)($recon['transaction'] ?? []);
        if (!$transaction) {
            $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ? LIMIT 1");
            $stmt->execute([$walletPaymentTxId]);
            $transaction = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
        }
        if (empty($recon['should_settle_fee'])) {
            if ($paymentType === 'assigned') {
                $apId = (int)($_POST['_assigned_id'] ?? ($_POST['assigned_id'] ?? 0));
                if ($apId > 0) {
                    $pdo->prepare("UPDATE assigned_payments SET status='Not Paid', updated_at=NOW() WHERE id=? AND student_id=?")
                        ->execute([$apId, $studentId]);
                }
            }
            flash((string)($recon['flash_type'] ?? 'warning'), (string)($recon['message'] ?? 'Wallet payment was not accepted as full settlement.'));
            redirect('?page=' . $redirectPage);
        }

        // If assigned/custom, mark assigned payment as Paid and store reference
        if ($paymentType === 'assigned') {
            $apId = (int)($_POST['_assigned_id'] ?? ($_POST['assigned_id'] ?? 0));
            if ($apId > 0) {
                $pdo->prepare("UPDATE assigned_payments SET status='Paid', reference=?, updated_at=NOW() WHERE id=? AND student_id=?")
                    ->execute([$reference, $apId, $studentId]);
            }
        }


        // Update student fee status
        if ($statusField) {
            if ($paymentType === 'school' && $totalInstallments > 1) {
                // Mark as Paid only when both installments are approved
                $paidNow = 0;
                try {
                    $sx = $pdo->prepare("SELECT COUNT(*) FROM payment_transactions WHERE student_id=? AND transaction_type='School Fee' AND status='Approved' AND COALESCE(amount,0) > 0 AND total_installments>1");
                    $sx->execute([(int)$studentId]);
                    $paidNow = (int)$sx->fetchColumn();
                } catch (Throwable $e) { $paidNow = 0; }
                $newStatus = ($paidNow >= 2) ? 'Paid' : 'Pending';
                $pdo->prepare("UPDATE students SET {$statusField}=?, {$referenceField}=? WHERE id=?")->execute([$newStatus, $reference, $studentId]);
            } else {
                $pdo->prepare("UPDATE students SET {$statusField}='Paid', {$referenceField}=? WHERE id=?")->execute([$reference, $studentId]);
            }
        }

        try { portalRunAcceptanceFeeSuccessHooks($pdo, (int)$studentId, $paymentType); } catch (Throwable $e) {}
        try { portalSynchronizeClearanceForStudent($pdo, (int)$studentId, $paymentAcademicSession, $paymentSemester); } catch (Throwable $e) {}

        if ($paymentType === 'school') {
            try { portalApplyLateFeePaymentToRecords($pdo, (int)$studentId, $paymentAcademicSession, $paymentSemester, (float)($_POST['_school_fee_late_fee_amount'] ?? 0), $reference); } catch (Throwable $e) {}
        }

        // Notifications
        try {
            createNotification($pdo, 'student', $studentId, 'Payment Successful', $transactionType . ' paid successfully via Wallet.', '?page=payment_history');
            $admins = $pdo->query("SELECT id FROM admins")->fetchAll(PDO::FETCH_ASSOC);
            foreach ($admins as $a) {
                createNotification($pdo, 'admin', (int)$a['id'], 'Wallet payment received', "A student paid {$transactionType} using wallet.", "?page=admin_payments");
            }
        } catch (Throwable $e) {}

        // Generate receipt data + QR for wallet payment and redirect to receipt
        try {
            $paymentDate = date('Y-m-d H:i:s');
            // Fetch student details for receipt
            $st2 = $pdo->prepare("SELECT surname, first_name, other_name, registration_no FROM students WHERE id = ? LIMIT 1");
            $st2->execute([$studentId]);
            $stu = $st2->fetch(PDO::FETCH_ASSOC);

            $fullName = trim(($stu['surname'] ?? '') . ' ' . ($stu['first_name'] ?? '') . ' ' . ($stu['other_name'] ?? ''));
            $receiptData = json_encode([
                'transaction_id' => $txId,
                'reference' => $reference,
                'amount_paid' => (float)($transaction['approved_amount'] ?? $fee),
                'base_amount' => (float)($transaction['amount'] ?? $fee),
                'gateway_fee' => 0,
                'charged_amount' => (float)($transaction['approved_amount'] ?? $fee),
                'payment_date' => $paymentDate,
                'student_name' => $fullName,
                'registration_no' => $stu['registration_no'] ?? '',
                'payment_type' => $paymentType,
                'school_fee_base_amount' => (float)($transaction['school_fee_base_amount'] ?? ($_POST['_school_fee_base_amount'] ?? ($paymentType === 'school' ? $fee : 0))),
                'school_fee_late_fee_amount' => (float)($transaction['school_fee_late_fee_amount'] ?? ($_POST['_school_fee_late_fee_amount'] ?? 0)),
                'wallet_credit_amount' => (float)($transaction['wallet_credited_amount'] ?? 0),
                'reconciliation_status' => (string)($transaction['reconciliation_status'] ?? ''),
                'installment_number' => (int)($paymentType === 'school' ? $installmentNumber : 1),
                'total_installments' => (int)($paymentType === 'school' ? $totalInstallments : 1)
            ]);
            $qrCodeData = generateQRCode($receiptData);

            $pdo->prepare("UPDATE payment_transactions SET receipt_data = ?, qr_code_data = ? WHERE reference = ?")
                ->execute([$receiptData, $qrCodeData, $reference]);

            $st3 = $pdo->prepare("SELECT id FROM payment_transactions WHERE reference = ? LIMIT 1");
            $st3->execute([$reference]);
            $txRowId = (int)$st3->fetchColumn();
            if ($txRowId > 0) {
                $_SESSION['transaction_id'] = $txRowId;
                try {
                    $stMailStudent = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                    $stMailStudent->execute([$studentId]);
                    $mailStudent = $stMailStudent->fetch(PDO::FETCH_ASSOC) ?: [];
                    $stMailTx = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ? LIMIT 1");
                    $stMailTx->execute([$txRowId]);
                    $mailTx = $stMailTx->fetch(PDO::FETCH_ASSOC) ?: [];
                    $walletReceiptLink = !empty($mailTx) ? portalPublicReceiptUrl($pdo, $mailTx) : '';
                    if ($mailStudent && $mailTx) {
                        portalSendStudentReceiptNotification($pdo, $mailStudent, $mailTx, $walletReceiptLink);
                        if (($mailTx['transaction_type'] ?? '') === 'Acceptance Fee') {
                            $alSubject = "Admission Letter - " . APP_NAME;
                            $alLink = appBaseUrl() . "/index.php?page=admission_letter";
                            $studentDearWallet = htmlspecialchars(studentFullName($mailStudent), ENT_QUOTES, 'UTF-8');
                            $alBody = "<h2>Admission Letter</h2><p>Dear {$studentDearWallet},</p><p>Congratulations! Your acceptance fee has been confirmed and your admission letter is now available.</p><p><a href='" . htmlspecialchars($alLink, ENT_QUOTES, 'UTF-8') . "'>View Admission Letter</a></p><p>Regards,<br>" . htmlspecialchars(APP_NAME) . "</p>";
                            sendLoggedEmail($pdo, 'student', (int)$mailStudent['id'], (string)$mailStudent['email'], 'admission_letter', $alSubject, $alBody);
                        }
                    }
                } catch (Throwable $e) {}
                flash('success', 'Payment successful using wallet.');
                redirect('?page=' . $receiptPage . '&transaction_id=' . $txRowId);
            }
        } catch (Throwable $e) {
            // If receipt generation fails, still show success
        }

        flash('success', 'Payment successful using wallet.');
    } catch (Throwable $e) {
        flash('danger', $e->getMessage() ?: 'Unable to complete wallet payment.');
    }

    redirect('?page=' . $redirectPage);
}

// Generic hosted/middleware gateway webhook logger
if (isset($_GET['action']) && $_GET['action'] === 'gateway_webhook' && isset($_GET['gateway'])) {
    $provider = strtolower(trim((string)($_GET['gateway'] ?? '')));
    $rawBody = file_get_contents('php://input');
    $jsonBody = json_decode((string)$rawBody, true);
    $payload = is_array($jsonBody) && $jsonBody ? $jsonBody : ($_POST ?: []);
    if (!$payload && isset($_GET['reference'])) {
        $payload = $_GET;
    }
    $reference = trim((string)($payload['reference'] ?? $payload['tx_ref'] ?? $payload['order_id'] ?? ''));
    $eventId = trim((string)($payload['event_id'] ?? $payload['id'] ?? $payload['event'] ?? ''));
    $signature = trim((string)($_SERVER['HTTP_X_SIGNATURE'] ?? $_SERVER['HTTP_X_PAYSTACK_SIGNATURE'] ?? $_SERVER['HTTP_VERIF_HASH'] ?? $_SERVER['HTTP_MONNIFY_SIGNATURE'] ?? $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? $_SERVER['HTTP_PAYPAL_TRANSMISSION_SIG'] ?? ''));
    if ($provider !== '') {
        portalLogGatewayCallback($pdo, $provider, $eventId, $reference, is_array($payload) ? $payload : [], $signature, 'received');
    }
    header('Content-Type: application/json');
    echo json_encode(['ok' => true, 'provider' => $provider, 'reference' => $reference]);
    exit;
}

// Handle payment verification - MODIFIED FOR AUTOMATIC APPROVAL AND REDIRECT TO RECEIPT
if (isset($_GET['action']) && $_GET['action'] === 'verify_payment' && isset($_GET['reference'])) {
    $reference = $_GET['reference'];
    $paymentType = $_GET['type'] ?? 'registration';

    // Verify payment with Paystack
    $gateway = strtolower(trim((string)($_GET['gateway'] ?? 'paystack')));
    if ($gateway === '') $gateway = 'paystack';

    
// Verify payment with selected gateway
if ($gateway === 'interswitch') {
    $result = verifyInterswitchPayment((string)$reference);
} elseif ($gateway === 'flutterwave') {
    $result = verifyFlutterwavePayment((string)$reference);
    if ($result) {
        $pending = $_SESSION['pending_payments'][$reference] ?? [];
        $result = ['data' => ['status' => 'success', 'reference' => $reference, 'amount' => (float)($pending['charged_amount'] ?? $pending['amount'] ?? 0) * 100, 'paid_at' => date('c'), 'metadata' => ['student_id' => (int)($pending['student_id'] ?? ($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0)), 'transaction_id' => (string)($pending['transaction_id'] ?? ''), 'base_amount' => (float)($pending['base_amount'] ?? $pending['amount'] ?? 0), 'gateway_fee' => (float)($pending['gateway_fee'] ?? 0), 'charged_amount' => (float)($pending['charged_amount'] ?? $pending['amount'] ?? 0), 'installment_number' => (int)($pending['installment_number'] ?? 1), 'total_installments' => (int)($pending['total_installments'] ?? 1)]]];
    }
} elseif ($gateway === 'paystack') {
    $result = verifyPaystackPayment($reference);
} elseif ($gateway === 'stripe') {
    $result = verifyStripePayment($pdo, (string)$reference, (string)($_GET['session_id'] ?? ''));
} elseif ($gateway === 'paypal') {
    $result = verifyPayPalPayment($pdo, (string)$reference, (string)($_GET['token'] ?? $_GET['order_id'] ?? ''));
} elseif ($gateway === 'monnify') {
    $result = verifyMonnifyPayment($pdo, (string)$reference);
} else {
    $result = portalBuildManagedGatewayReturnResult($pdo, $gateway, (string)$reference, array_merge($_GET, $_POST));
}
portalLogGatewayCallback($pdo, $gateway !== '' ? $gateway : 'unknown', 'verify-return', (string)$reference, array_merge($_GET, $_POST, ['verification_result' => (bool)$result]), '', $result ? 'verify_success' : 'verify_failed');


    if ($result) {
        // Payment successful
        $studentId = $result['data']['metadata']['student_id'];
        $transactionId = $result['data']['metadata']['transaction_id'];
        $installmentNumber = $result['data']['metadata']['installment_number'] ?? 1;
        $totalInstallments = $result['data']['metadata']['total_installments'] ?? 1;

        // Get payment approval mode - ALWAYS AUTOMATIC FOR PAYSTACK PAYMENTS
        $approvalMode = 'automatic';

        // Update payment status based on payment type
        switch ($paymentType) {
            case 'registration':
                $statusField = 'registration_fee_status';
                $emailSubject = "Registration Fee Payment Confirmation - HontTech Registration";
                $emailBody = "<h2>Registration Fee Payment Confirmation</h2>";
                $receiptPage = 'payment_receipt';
                break;

            case 'acceptance':
                $statusField = 'acceptance_fee_status';
                $emailSubject = "Acceptance Fee Payment Confirmation - HontTech Registration";
                $emailBody = "<h2>Acceptance Fee Payment Confirmation</h2>";
                $receiptPage = 'acceptance_payment_receipt';
                break;

            case 'hostel':
                $statusField = 'hostel_fee_status';
                $emailSubject = "Hostel Fee Payment Confirmation - HontTech Registration";
                $emailBody = "<h2>Hostel Fee Payment Confirmation</h2>";
                $receiptPage = 'hostel_payment_receipt';
                break;

            case 'school':
                $statusField = 'school_fee_status';
                $emailSubject = "School Fee Payment Confirmation - HontTech Registration";
                $emailBody = "<h2>School Fee Payment Confirmation</h2>";
                $receiptPage = 'school_payment_receipt';
                break;

            case 'wallet_funding':
                $statusField = '';
                $emailSubject = "Wallet Funding Confirmation - HontTech Registration";
                $emailBody = "<h2>Wallet Funding Confirmation</h2>";
                $receiptPage = 'wallet';
                break;

            default:
                $statusField = '';
                break;
        }

        // Create transaction only after Paystack confirms success (avoids abandoned pending transactions)
        $pending = $_SESSION['pending_payments'][$reference] ?? null;

        // Prefer Paystack paid_at if available
        $paymentDate = !empty($result['data']['paid_at']) ? date('Y-m-d H:i:s', strtotime($result['data']['paid_at'])) : date('Y-m-d H:i:s');
        $paystackRef = $result['data']['reference'] ?? $reference;
        $paymentAcademicSession = trim((string)($pending['academic_session'] ?? $result['data']['metadata']['academic_session'] ?? ''));
        $paymentSemester = portalNormalizeSemesterName($pending['semester'] ?? $result['data']['metadata']['semester'] ?? 'First Semester');

        // Create the transaction row if it doesn't exist yet
        $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE reference = ? LIMIT 1");
        $stmt->execute([$reference]);
        $transaction = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$transaction) {
            $txnId = $pending['transaction_id'] ?? generateTransactionId();
            $txnType = $pending['transaction_type'] ?? (
                $paymentType === 'registration' ? 'Registration Fee' :
                ($paymentType === 'acceptance' ? 'Acceptance Fee' :
                ($paymentType === 'hostel' ? 'Hostel Fee' : 'School Fee'))
            );
            $baseAmt = (float)($pending['base_amount'] ?? $pending['amount'] ?? 0);
            $gwFee = (float)($pending['gateway_fee'] ?? 0);
            $chargedAmt = (float)($pending['charged_amount'] ?? ($result['data']['amount'] / 100));

            $ins = $pdo->prepare("INSERT INTO payment_transactions
                (transaction_id, student_id, transaction_type, amount, school_fee_base_amount, school_fee_late_fee_amount, amount_paid, approved_amount, gateway_fee, charged_amount, reference, academic_session, semester, paystack_reference, status, payment_method, payment_date, approval_date, wallet_credit_status, installment_number, total_installments)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'Approved', ?, ?, NOW(), ?, ?, ?)");
            $ins->execute([
                $txnId,
                (int)$studentId,
                $txnType,
                $baseAmt,
                (float)($pending['school_fee_base_amount'] ?? ($paymentType === 'school' ? $baseAmt : 0)),
                (float)($pending['school_fee_late_fee_amount'] ?? 0),
                $chargedAmt,
                $baseAmt,
                $gwFee,
                $chargedAmt,
                $reference,
                $paymentAcademicSession,
                $paymentSemester,
                $paystackRef,
                strtoupper($gateway),
                $paymentDate,
                'Pending',
                (int)$installmentNumber,
                (int)$totalInstallments
            ]);

            $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE reference = ? LIMIT 1");
            $stmt->execute([$reference]);
            $transaction = $stmt->fetch(PDO::FETCH_ASSOC);
        } else {
            // Row existed (legacy flow) - approve + set payment date
            $baseAmt = (float)($pending['base_amount'] ?? $pending['amount'] ?? 0);
            $gwFee = (float)($pending['gateway_fee'] ?? 0);
            $chargedAmt = (float)($pending['charged_amount'] ?? ($result['data']['amount'] / 100));
            $upd = $pdo->prepare("UPDATE payment_transactions SET status = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN status ELSE 'Approved' END, paystack_reference = COALESCE(paystack_reference, ?), payment_method = COALESCE(NULLIF(payment_method,''), ?), gateway_fee = COALESCE(NULLIF(gateway_fee,0), ?), charged_amount = COALESCE(charged_amount, ?), amount_paid = COALESCE(NULLIF(amount_paid,0), ?), approved_amount = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN approved_amount ELSE COALESCE(NULLIF(approved_amount,0), ?) END, school_fee_base_amount = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN school_fee_base_amount WHEN COALESCE(school_fee_base_amount,0) <= 0 THEN ? ELSE school_fee_base_amount END, school_fee_late_fee_amount = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN school_fee_late_fee_amount WHEN COALESCE(school_fee_late_fee_amount,0) <= 0 THEN ? ELSE school_fee_late_fee_amount END, amount = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN amount WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' THEN ? ELSE amount END, payment_date = COALESCE(payment_date, ?), approval_date = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN approval_date ELSE COALESCE(approval_date, NOW()) END, wallet_credit_status = CASE WHEN LOWER(COALESCE(transaction_type,'')) = 'wallet funding' AND COALESCE(wallet_credit_status,'') <> 'Credited' THEN 'Pending' ELSE wallet_credit_status END WHERE reference = ?");
            $upd->execute([$paystackRef, strtoupper($gateway), $gwFee, $chargedAmt, $chargedAmt, $baseAmt, (float)($pending['school_fee_base_amount'] ?? ($paymentType === 'school' ? $baseAmt : 0)), (float)($pending['school_fee_late_fee_amount'] ?? 0), $baseAmt, $paymentDate, $reference]);
        }

        // Always refresh the saved transaction row after gateway verification/update
        $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE reference = ? LIMIT 1");
        $stmt->execute([$reference]);
        $transaction = $stmt->fetch(PDO::FETCH_ASSOC) ?: $transaction;

        // Clear pending session for this reference
        if (isset($_SESSION['pending_payments'][$reference])) {
            unset($_SESSION['pending_payments'][$reference]);
        }

        // Wallet funding: credit student's wallet once payment is approved, but never more than once
        if ($paymentType === 'wallet_funding' && !empty($transaction['id'])) {
            $fundBase = (float)($pending['base_amount'] ?? $pending['amount'] ?? 0);
            if ($fundBase <= 0) {
                $fundBase = (float)($transaction['approved_amount'] ?? $transaction['amount'] ?? 0);
            }
            if ($fundBase <= 0) {
                $fundBase = ((float)($result['data']['amount'] ?? 0)) / 100.0;
            }

            $creditSync = portalSynchronizeWalletFundingCredit(
                $pdo,
                (int)$transaction['id'],
                (float)$fundBase,
                'Wallet funded via ' . strtoupper($gateway) . ' [' . $reference . ']'
            );

            if (!$creditSync['ok']) {
                try {
                    $pdo->prepare("UPDATE payment_transactions SET status = 'Pending', approval_date = NULL, wallet_credit_status = 'Failed', approval_notes = CONCAT(COALESCE(approval_notes,''), CASE WHEN COALESCE(approval_notes,'') = '' THEN '' ELSE '\n' END, ?) WHERE id = ?")
                        ->execute(['Gateway payment verified but wallet credit sync failed. ' . (string)($creditSync['message'] ?? ''), (int)$transaction['id']]);
                } catch (Throwable $e) {}
                flash('danger', 'Payment was verified, but wallet credit synchronization did not complete. The transaction has been returned to pending review to prevent duplicate or missing credits.');
                redirect('?page=wallet');
            }

            $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ? LIMIT 1");
            $stmt->execute([(int)$transaction['id']]);
            $transaction = $stmt->fetch(PDO::FETCH_ASSOC) ?: $transaction;
        }

        if ($paymentType !== 'wallet_funding' && !empty($transaction['id'])) {
            $verifiedGrossAmount = round(((float)($result['data']['amount'] ?? 0)) / 100.0, 2);
            $verifiedGatewayFee = round((float)($pending['gateway_fee'] ?? ($transaction['gateway_fee'] ?? 0)), 2);
            $confirmedFeeAmount = round($verifiedGrossAmount - $verifiedGatewayFee, 2);
            if ($confirmedFeeAmount <= 0) {
                $confirmedFeeAmount = round((float)($pending['base_amount'] ?? $pending['amount'] ?? ($transaction['approved_amount'] ?? $transaction['amount'] ?? 0)), 2);
            }

            $recon = portalReconcileFeePayment(
                $pdo,
                (int)$transaction['id'],
                (float)$confirmedFeeAmount,
                'gateway_verification',
                [
                    'underpayment_to_wallet' => true,
                    'notes' => 'Gateway verification via ' . strtoupper($gateway) . ' [' . $reference . ']',
                ]
            );
            if (!$recon['ok']) {
                flash('danger', (string)($recon['message'] ?? 'Payment reconciliation failed after gateway verification.'));
                redirect('?page=' . portalStudentPaymentPageFromTransaction((array)$transaction));
            }
            $transaction = (array)($recon['transaction'] ?? $transaction);
            if (empty($recon['should_settle_fee'])) {
                $assignedIdFromPending = (int)($pending['assigned_id'] ?? 0);
                if ($assignedIdFromPending > 0) {
                    try {
                        $pdo->prepare("UPDATE assigned_payments SET status = 'Not Paid', updated_at = NOW() WHERE id = ? AND student_id = ?")
                            ->execute([$assignedIdFromPending, (int)$studentId]);
                    } catch (Throwable $e) {}
                }
                flash((string)($recon['flash_type'] ?? 'warning'), (string)($recon['message'] ?? 'This payment was not accepted as full settlement.'));
                redirect('?page=' . portalStudentPaymentPageFromTransaction((array)$transaction));
            }
        }

        if ($paymentType === 'school') {
            try { portalApplyLateFeePaymentToRecords($pdo, (int)$studentId, $paymentAcademicSession, $paymentSemester, (float)($transaction['school_fee_late_fee_amount'] ?? ($pending['school_fee_late_fee_amount'] ?? 0)), (string)$reference); } catch (Throwable $e) {}
        }

        // Update student fee status (support one-time OR 2 installments for school fees)
        if ($paymentType === 'school' && (int)$totalInstallments > 1) {
            // Count only school-fee installment transactions
            $paidInstallments = 0;
            $stmt = $pdo->prepare("SELECT COUNT(*) FROM payment_transactions
                WHERE student_id = ? AND transaction_type = 'School Fee' AND status = 'Approved' AND COALESCE(amount,0) > 0 AND total_installments > 1");
            $stmt->execute([$studentId]);
            $paidInstallments = (int)$stmt->fetchColumn();

            $newStatus = ($paidInstallments >= (int)$totalInstallments) ? 'Paid' : 'Pending';
            if (!empty($statusField)) {
                // Also store the latest reference so receipt can be found
                $currentAcademicSession = portalCurrentAcademicSessionName($pdo);
                $stmt = $pdo->prepare("UPDATE students SET {$statusField} = ?, school_fee_reference = ?, school_fee_paid_session = ?, payment_plan = 'Installment' WHERE id = ?");
                $stmt->execute([$newStatus, (string)$reference, $currentAcademicSession !== '' ? $currentAcademicSession : null, $studentId]);
            }
        } else {
            // Non-installment payments: mark paid
            if (!empty($statusField)) {
                // For school one-time, store plan
                if ($paymentType === 'school') {
                    $currentAcademicSession = portalCurrentAcademicSessionName($pdo);
                    $stmt = $pdo->prepare("UPDATE students SET {$statusField} = 'Paid', school_fee_reference = ?, school_fee_paid_session = ?, payment_plan = 'One-time' WHERE id = ?");
                    $stmt->execute([(string)$reference, $currentAcademicSession !== '' ? $currentAcademicSession : null, $studentId]);
                } else {
                    $currentAcademicSession = portalCurrentAcademicSessionName($pdo);
                    if ($paymentType === 'hostel') {
                        $stmt = $pdo->prepare("UPDATE students SET {$statusField} = 'Paid', hostel_fee_paid_session = ? WHERE id = ?");
                        $stmt->execute([$currentAcademicSession !== '' ? $currentAcademicSession : null, $studentId]);
                    } else {
                        $stmt = $pdo->prepare("UPDATE students SET {$statusField} = 'Paid' WHERE id = ?");
                        $stmt->execute([$studentId]);
                    }
                }
            }
        }

        try { portalRunAcceptanceFeeSuccessHooks($pdo, (int)$studentId, $paymentType); } catch (Throwable $e) {}

        if ($paymentType === 'assigned') {
            $assignedIdFromPending = (int)($pending['assigned_id'] ?? 0);
            if ($assignedIdFromPending > 0) {
                try {
                    $pdo->prepare("UPDATE assigned_payments SET status='Paid', reference=?, updated_at=NOW() WHERE id=? AND student_id=?")
                        ->execute([(string)$reference, $assignedIdFromPending, (int)$studentId]);
                } catch (Throwable $e) {}
            }
        }

        // Generate receipt data
        $receiptData = json_encode([
            'transaction_id' => $transactionId,
            'reference' => $reference,
            // Amount paid by payer (gross) + settled amount (net) + gateway charges
            'amount_paid' => (float)($transaction['approved_amount'] ?? ($result['data']['amount'] / 100)),
            'base_amount' => (float)($transaction['amount'] ?? ($pending['base_amount'] ?? $pending['amount'] ?? ($result['data']['amount'] / 100))),
            'gateway_fee' => (float)($pending['gateway_fee'] ?? ($transaction['gateway_fee'] ?? 0)),
            'charged_amount' => (float)($transaction['amount_paid'] ?? ($pending['charged_amount'] ?? ($result['data']['amount'] / 100))),
            'payment_date' => $paymentDate,
            'student_name' => $result['data']['metadata']['student_name'] ?? '',
            'registration_no' => $result['data']['metadata']['registration_no'] ?? '',
            'payment_type' => $paymentType,
            'school_fee_base_amount' => (float)($transaction['school_fee_base_amount'] ?? 0),
            'school_fee_late_fee_amount' => (float)($transaction['school_fee_late_fee_amount'] ?? 0),
            'wallet_credit_amount' => (float)($transaction['wallet_credited_amount'] ?? 0),
            'reconciliation_status' => (string)($transaction['reconciliation_status'] ?? ''),
            'installment_number' => $installmentNumber,
            'total_installments' => $totalInstallments
        ]);

        // Generate QR code data
        $qrCodeData = generateQRCode($receiptData);

        // Update transaction with receipt and QR code data
        $stmt = $pdo->prepare("UPDATE payment_transactions SET
            receipt_data = ?,
            qr_code_data = ?
            WHERE reference = ?");
        $stmt->execute([$receiptData, $qrCodeData, $reference]);

        // Store transaction ID in session for receipt page
        $_SESSION['transaction_id'] = $transaction['id'];

        // Get student details for email
        $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
        $stmt->execute([$studentId]);
        $student = $stmt->fetch(PDO::FETCH_ASSOC);

        $studentDear = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
        // Send payment confirmation email
        $emailBody .= "
        <p>Dear {$studentDear},</p>
        <p>Your payment for {$transaction['transaction_type']} has been received and approved successfully.</p>
        <p>Payment Details:</p>
        <ul>
            <li>Admission Number: " . portalStudentNumberDisplay($student, 'admission') . "</li>
            <li>Amount Paid: NGN " . ($result['data']['amount'] / 100) . "</li>" .
            (((float)($pending['gateway_fee'] ?? 0)) > 0 ?
                "<li>Base Amount: NGN " . number_format((float)($pending['base_amount'] ?? $pending['amount'] ?? 0), 2) . "</li><li>Gateway Charges: NGN " . number_format((float)($pending['gateway_fee'] ?? 0), 2) . "</li>" :
                "") .
            "
            <li>Transaction Reference: {$result['data']['reference']}</li>
            <li>Payment Date: {$paymentDate}</li>";

        if ($paymentType === 'school' && $totalInstallments > 1) {
            $emailBody .= "<li>Installment: {$installmentNumber} of {$totalInstallments}</li>";
        }

        $emailBody .= "</ul>";

        if ($transaction['transaction_type'] === 'Registration Fee') {
            $emailBody .= "<p>Your application is now under review. You will be notified of the decision via email.</p>";
        } elseif ($transaction['transaction_type'] === 'Acceptance Fee') {
            $emailBody .= "<p>Your admission letter is now available. You can access it from your dashboard.</p>";
        } elseif ($transaction['transaction_type'] === 'Hostel Fee') {
            $emailBody .= "<p>Your hostel accommodation has been confirmed. You will receive further details about your room allocation.</p>";
        } elseif ($transaction['transaction_type'] === 'School Fee') {
            if ($transaction['total_installments'] > 1) {
                $emailBody .= "<p>This is installment {$transaction['installment_number']} of {$transaction['total_installments']}. ";
                if ($transaction['installment_number'] < $transaction['total_installments']) {
                    $emailBody .= "Please ensure you pay the remaining installments on time.</p>";
                } else {
                    $emailBody .= "All installments have been paid. Your school fee is now fully paid.</p>";
                }
            } else {
                $emailBody .= "<p>Your school fee has been fully paid.</p>";
            }
        }

        $receiptLink = portalPublicReceiptUrl($pdo, $transaction);
        $emailBody .= "<p>Your payment receipt is available in your dashboard.</p>
        <p><a href='" . htmlspecialchars($receiptLink, ENT_QUOTES, 'UTF-8') . "'>View Receipt</a></p>
        <p>Thank you for your payment.</p>
        <p>Best regards,<br>HontTech Administration</p>";

        $ctx = 'receipt:' . (!empty($transaction['id']) ? (int)$transaction['id'] : md5($student['email'] . $paymentType . date('Y-m-d')));
        portalSendStudentReceiptNotification($pdo, $student, $transaction, $receiptLink);

        // If acceptance fee payment succeeded, email admission letter link too
        if (($transaction['transaction_type'] ?? '') === 'Acceptance Fee') {
            $alSubject = "Admission Letter - " . APP_NAME;
            $alLink = appBaseUrl() . "/index.php?page=admission_letter";
            $studentDear = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
            $alBody = "<h2>Admission Letter</h2>
                      <p>Dear {$studentDear},</p>
                      <p>Congratulations! Your acceptance fee has been confirmed and your admission letter is now available.</p>
                      <p><a href='" . htmlspecialchars($alLink, ENT_QUOTES, 'UTF-8') . "'>View Admission Letter</a></p>
                      <p>Regards,<br>" . htmlspecialchars(APP_NAME) . "</p>";
            sendLoggedEmail($pdo, 'student', (int)$student['id'], (string)$student['email'], 'admission_letter', $alSubject, $alBody);
        }

        // Store success message in session
        $_SESSION[$paymentType . '_payment_success'] = true;

        // Redirect to receipt page immediately after successful payment
        redirect('?page=' . $receiptPage . (!empty($transaction['id']) ? '&transaction_id=' . (int)$transaction['id'] : ''));
    } else {
        // Payment failed
        if (isset($_SESSION['student_id'])) {
            $studentId = $_SESSION['student_id'];

            // Update student payment status
            switch ($paymentType) {
                case 'registration':
                    $statusField = 'registration_fee_status';
                    $failedPage = 'payment_failed';
                    break;

                case 'acceptance':
                    $statusField = 'acceptance_fee_status';
                    $failedPage = 'acceptance_payment_failed';
                    break;

                case 'hostel':
                    $statusField = 'hostel_fee_status';
                    $failedPage = 'hostel_payment_failed';
                    break;

                case 'school':
                    $statusField = 'school_fee_status';
                    $failedPage = 'school_payment_failed';
                    break;

                default:
                    $statusField = '';
                    $failedPage = 'payment_failed';
                    break;
            }

            if (!empty($statusField)) {
                if (!empty($statusField)) {
                $stmt = $pdo->prepare("UPDATE students SET $statusField = 'Failed' WHERE id = ?");
                $stmt->execute([$studentId]);
            }
}

            // Update transaction status
            $stmt = $pdo->prepare("UPDATE payment_transactions SET status = 'Failed' WHERE reference = ?");
            $stmt->execute([$reference]);

            $_SESSION[$paymentType . '_payment_failed'] = true;
            redirect('?page=' . $failedPage);
        }
    }
}

// Handle admin actions
if (isAdmin()) {
    // Add student
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_student') {
        $surname = $_POST['surname'] ?? '';
        $firstName = $_POST['first_name'] ?? '';
        $otherName = $_POST['other_name'] ?? '';
        $sex = $_POST['sex'] ?? '';
        $dob = $_POST['date_of_birth'] ?? '';
        $nationality = $_POST['nationality'] ?? 'Nigerian';
        $state = $_POST['state_of_origin'] ?? '';
        $lga = $_POST['lga'] ?? '';
        $mobilePhone = $_POST['mobile_phone'] ?? '';
        $residentialPhone = $_POST['residential_phone'] ?? '';
        $email = $_POST['email'] ?? '';
        $permanentAddress = $_POST['permanent_address'] ?? '';
        $nokName = $_POST['nok_name'] ?? '';
        $nokPhone = $_POST['nok_phone'] ?? '';
        $sponsorName = $_POST['sponsor_name'] ?? '';
        $sponsorPhone = $_POST['sponsor_phone'] ?? '';
        $sponsorOccupation = $_POST['sponsor_occupation'] ?? '';
        $subject1 = $_POST['subject1'] ?? '';
        $subject2 = $_POST['subject2'] ?? '';
        $subject3 = $_POST['subject3'] ?? '';
        $password = $_POST['password'] ?? '';
        $registrationAcademic = null;
        try {
            if ((int)($_POST['faculty_id'] ?? 0) > 0 || (int)($_POST['department_id'] ?? 0) > 0 || (int)($_POST['programme_id'] ?? 0) > 0) {
                portalEnsureApplicationAcademicFieldSupport($pdo);
                $registrationAcademic = portalValidateApplicationAcademicSelection($pdo, (int)($_POST['faculty_id'] ?? 0), (int)($_POST['department_id'] ?? 0), (int)($_POST['programme_id'] ?? 0));
                $subject1 = ''; $subject2 = ''; $subject3 = '';
            }
        } catch (Throwable $e) { $registrationAcademic = null; }

        // Generate registration number
        $registrationNo = generateRegistrationNumber();

        // Hash password
        $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

        // Insert student data
$stmt = $pdo->prepare("INSERT INTO students (
            registration_no, surname, first_name, other_name, sex,
            date_of_birth, nationality, state_of_origin, lga, mobile_phone,
            residential_phone, email, permanent_address,
            faculty_id, department_id, programme_id, faculty, department, programme, program, school_name, school, course_in_view, course_of_study, programme_name_cache,
            nok_name, nok_phone, sponsor_name, sponsor_phone, sponsor_occupation,
            subject1, subject2, subject3, password
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");

        $stmt->execute([
            $registrationNo, $surname, $firstName, $otherName, $sex,
            $dob, $nationality, $state, $lga, $mobilePhone,
            $residentialPhone, $email, $permanentAddress,
            (int)($registrationAcademic['faculty_id'] ?? 0), (int)($registrationAcademic['department_id'] ?? 0), (int)($registrationAcademic['programme_id'] ?? 0), $registrationAcademic['school'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['programme'] ?? '', $registrationAcademic['programme'] ?? '', $registrationAcademic['school'] ?? '', $registrationAcademic['school'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['programme'] ?? '',
            $nokName, $nokPhone, $sponsorName, $sponsorPhone, $sponsorOccupation,
            $subject1, $subject2, $subject3, $hashedPassword
        ]);

        $_SESSION['student_added'] = true;
        redirect('?page=admin_students');
    }

    // Update student account status (Active/Suspended/Blocked)
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_student_status') {
        $studentId = (int)($_POST['student_id'] ?? 0);
        $requestedStatus = trim((string)($_POST['account_status'] ?? ''));
        $status = portalNormalizeStudentAccountStatus($requestedStatus, '');
        $token = trim((string)($_POST['student_status_token'] ?? $_SERVER['HTTP_X_PORTAL_STUDENT_STATUS_TOKEN'] ?? ''));
        $returnUrl = portalSafeReturnUrl($_POST['return_url'] ?? '', '?page=admin_students');

        $isAjax = (isset($_POST['ajax']) && $_POST['ajax'] === '1')
            || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower((string)$_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');

        $respond = static function(bool $ok, string $message, array $extra = []) use ($isAjax, $returnUrl): void {
            if ($isAjax) {
                header('Content-Type: application/json');
                echo json_encode(array_merge(['ok' => $ok, 'message' => $message], $extra));
                exit;
            }
            flash($ok ? 'success' : 'danger', $message);
            redirect($returnUrl);
        };

        if (!portalAdminStudentStatusUpdateAllowed($pdo)) {
            $respond(false, 'You do not have permission to change student account status.');
        }

        if (!portalStudentStatusUpdateTokenValid($token)) {
            $respond(false, 'Your security token has expired. Please refresh the page and try again.');
        }

        if ($studentId <= 0) {
            $respond(false, 'Invalid student selected.');
        }

        if ($status === '' || !in_array($status, portalStudentAccountStatusAllowedValues(), true)) {
            $respond(false, 'Invalid account status selected.');
        }

        try {
            $stCurrent = $pdo->prepare("SELECT id, account_status FROM students WHERE id = ? LIMIT 1");
            $stCurrent->execute([$studentId]);
            $current = $stCurrent->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$current) {
                $respond(false, 'Student record could not be found.');
            }

            $currentStatus = portalNormalizeStudentAccountStatus($current['account_status'] ?? 'Active');
            if ($currentStatus === $status) {
                $respond(true, 'Student status is already ' . $status . '.', [
                    'student_id' => $studentId,
                    'account_status' => $status,
                    'account_status_class' => strtolower(str_replace(' ', '-', $status)),
                    'message_type' => 'info',
                ]);
            }

            $pdo->beginTransaction();
            $stmt = $pdo->prepare("UPDATE students SET account_status = ? WHERE id = ?");
            $stmt->execute([$status, $studentId]);
            $pdo->commit();

            $_SESSION['student_status_updated'] = true;
            $respond(true, 'Student status updated successfully.', [
                'student_id' => $studentId,
                'account_status' => $status,
                'account_status_class' => strtolower(str_replace(' ', '-', $status)),
                'message_type' => 'success',
            ]);
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            $respond(false, 'Unable to update student status right now.');
        }
    }
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_update_student_application_academics' && isAdmin()) {
        requireStaffPermission($pdo, 'academics_manage');
        $studentId = (int)($_POST['student_id'] ?? 0);
        $returnPage = trim((string)($_POST['return_page'] ?? 'admin_students'));
        $returnAction = trim((string)($_POST['return_action'] ?? 'view'));
        $returnId = (int)($_POST['return_id'] ?? $studentId);
        $returnUrl = '?page=' . urlencode($returnPage) . '&action=' . urlencode($returnAction) . '&id=' . $returnId;

        if ($studentId <= 0) {
            flash('danger', 'Invalid student/application record selected.');
            redirect($returnUrl);
        }

        try {
            portalEnsureApplicationGraceSupport($pdo);
            $studentCurrent = [];
            try {
                $stCurrent = $pdo->prepare("SELECT id, faculty_id, department_id, programme_id, faculty, department, programme, mode_of_study,
                    TRIM(CONCAT_WS(' ', NULLIF(TRIM(COALESCE(surname, '')), ''), NULLIF(TRIM(COALESCE(first_name, '')), ''), NULLIF(TRIM(COALESCE(other_name, '')), ''))) AS full_name,
                    surname, first_name, other_name AS other_names, application_no, registration_no, admission_no
                    FROM students WHERE id = ? LIMIT 1");
                $stCurrent->execute([$studentId]);
                $studentCurrent = $stCurrent->fetch(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) {
                $studentCurrent = [];
            }
            if (!$studentCurrent) {
                throw new RuntimeException('Student record could not be loaded for academic mapping update.');
            }

            $academic = portalValidateApplicationAcademicSelection(
                $pdo,
                (int)($_POST['faculty_id'] ?? 0),
                (int)($_POST['department_id'] ?? 0),
                (int)($_POST['programme_id'] ?? 0)
            );

            $beforeFacultyId = (int)($studentCurrent['faculty_id'] ?? 0);
            $beforeDepartmentId = (int)($studentCurrent['department_id'] ?? 0);
            $beforeProgrammeId = (int)($studentCurrent['programme_id'] ?? 0);
            $afterFacultyId = (int)($academic['faculty_id'] ?? 0);
            $afterDepartmentId = (int)($academic['department_id'] ?? 0);
            $afterProgrammeId = (int)($academic['programme_id'] ?? 0);
            $mappingChanged = $beforeFacultyId !== $afterFacultyId
                || $beforeDepartmentId !== $afterDepartmentId
                || $beforeProgrammeId !== $afterProgrammeId;

            if (!$mappingChanged) {
                flash('info', 'No academic mapping change was detected, so nothing was updated.');
                redirect($returnUrl);
            }

            $verify = portalVerifyLoggedInSuperadminPassword($pdo, (string)($_POST['superadmin_password_confirm'] ?? ''));
            if (empty($verify['ok'])) {
                throw new RuntimeException((string)($verify['message'] ?? 'Superadmin password confirmation is required before changing the academic mapping.'));
            }

            $st = $pdo->prepare("UPDATE students SET
                faculty_id = ?, department_id = ?, programme_id = ?,
                faculty = ?, department = ?, programme = ?, program = ?,
                school_name = ?, school = ?, course_in_view = ?, course_of_study = ?, programme_name_cache = ?, mode_of_study = ?
                WHERE id = ?");
            $st->execute([
                $afterFacultyId,
                $afterDepartmentId,
                $afterProgrammeId,
                (string)($academic['school'] ?? ''),
                (string)($academic['course_of_study'] ?? ''),
                (string)($academic['programme'] ?? ''),
                (string)($academic['programme'] ?? ''),
                (string)($academic['school'] ?? ''),
                (string)($academic['school'] ?? ''),
                (string)($academic['course_of_study'] ?? ''),
                (string)($academic['course_of_study'] ?? ''),
                (string)($academic['programme'] ?? ''),
                (string)($academic['mode_of_study'] ?? 'Full-Time'),
                $studentId
            ]);

            if (function_exists('portalAuthLogActivity')) {
                $adminId = portalCurrentAdminId();
                $verifyAdmin = (array)($verify['admin'] ?? []);
                portalAuthLogActivity($pdo, 'admin', $adminId, 'student_application_academic_mapping_update', [
                    'student_id' => $studentId,
                    'student_name' => trim((string)($studentCurrent['full_name'] ?? trim((string)($studentCurrent['surname'] ?? '') . ' ' . (string)($studentCurrent['other_names'] ?? '')))),
                    'application_no' => (string)($studentCurrent['application_no'] ?? $studentCurrent['registration_no'] ?? $studentCurrent['admission_no'] ?? ''),
                    'confirmed_by_admin_id' => (int)($verifyAdmin['id'] ?? $adminId),
                    'confirmed_by_username' => (string)($verifyAdmin['username'] ?? ''),
                    'before' => [
                        'faculty_id' => $beforeFacultyId,
                        'department_id' => $beforeDepartmentId,
                        'programme_id' => $beforeProgrammeId,
                        'faculty' => (string)($studentCurrent['faculty'] ?? ''),
                        'department' => (string)($studentCurrent['department'] ?? ''),
                        'programme' => (string)($studentCurrent['programme'] ?? ''),
                    ],
                    'after' => [
                        'faculty_id' => $afterFacultyId,
                        'department_id' => $afterDepartmentId,
                        'programme_id' => $afterProgrammeId,
                        'faculty' => (string)($academic['school'] ?? ''),
                        'department' => (string)($academic['course_of_study'] ?? ''),
                        'programme' => (string)($academic['programme'] ?? ''),
                    ],
                ]);
            }

            flash('success', 'School, Course of Study, and Program were updated successfully for this student after Superadmin password confirmation.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Unable to save the academic mapping right now.');
        }

        redirect($returnUrl);
    }


    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_update_submitted_application' && isAdmin()) {
        requireStaffPermission($pdo, 'academics_manage');

        $studentId = (int)($_POST['student_id'] ?? 0);
        $returnPage = trim((string)($_POST['return_page'] ?? 'admin_applications'));
        $returnAction = trim((string)($_POST['return_action'] ?? 'view'));
        $returnId = (int)($_POST['return_id'] ?? $studentId);
        $returnUrl = '?page=' . urlencode($returnPage) . '&action=' . urlencode($returnAction) . '&id=' . $returnId . '&edit=1#adminApplicationEditor';

        if ($studentId <= 0) {
            flash('danger', 'Invalid student/application record selected.');
            redirect($returnUrl);
        }

        try {
            if (!portalAdminSubmittedApplicationEditTokenValid((string)($_POST['admin_application_edit_token'] ?? ''))) {
                throw new RuntimeException('Your admin edit session has expired. Please reopen the submitted form editor and try again.');
            }

            portalEnsureApplicationAcademicFieldSupport($pdo);

            $stCurrent = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $stCurrent->execute([$studentId]);
            $studentCurrent = $stCurrent->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$studentCurrent) {
                throw new RuntimeException('Student record could not be loaded.');
            }

            $academic = portalValidateApplicationAcademicSelection(
                $pdo,
                (int)($_POST['faculty_id'] ?? 0),
                (int)($_POST['department_id'] ?? 0),
                (int)($_POST['programme_id'] ?? 0)
            );

            $surname = trim((string)($_POST['surname'] ?? ''));
            $firstName = trim((string)($_POST['first_name'] ?? ''));
            $otherName = trim((string)($_POST['other_name'] ?? ''));
            $maidenName = trim((string)($_POST['maiden_name'] ?? ''));
            $sex = trim((string)($_POST['sex'] ?? ''));
            $dob = trim((string)($_POST['date_of_birth'] ?? ''));
            $nationality = trim((string)($_POST['nationality'] ?? ''));
            $stateOfOrigin = trim((string)($_POST['state_of_origin'] ?? ''));
            $lga = trim((string)($_POST['lga'] ?? ''));
            $mobilePhone = trim((string)($_POST['mobile_phone'] ?? ''));
            $residentialPhone = trim((string)($_POST['residential_phone'] ?? ''));
            $email = trim((string)($_POST['email'] ?? ''));
            $permanentAddress = trim((string)($_POST['permanent_address'] ?? ''));
            $extraCurricular = trim((string)($_POST['extra_curricular'] ?? ''));
            $nokName = trim((string)($_POST['nok_name'] ?? ''));
            $nokPhone = trim((string)($_POST['nok_phone'] ?? ''));
            $sponsorName = trim((string)($_POST['sponsor_name'] ?? ''));
            $sponsorPhone = trim((string)($_POST['sponsor_phone'] ?? ''));
            $sponsorOccupation = trim((string)($_POST['sponsor_occupation'] ?? ''));
            $applicantSignatureData = (string)($_POST['applicant_signature_data'] ?? (string)($studentCurrent['signature_data'] ?? ''));
            $refSignatureData = (string)($_POST['ref_signature_data'] ?? '');
            $availableLevelOptions = portalAcademicLevelOptions($pdo);
            $normalizedLevelOptions = [];
            foreach ($availableLevelOptions as $levelOption) {
                $levelOption = trim((string)$levelOption);
                if ($levelOption === '') continue;
                $normalizedLevelOptions[strtoupper($levelOption)] = $levelOption;
            }
            $level = trim((string)($_POST['level'] ?? ($studentCurrent['level'] ?? '')));
            if ($level === '') {
                throw new RuntimeException('Please select the student level.');
            }
            $levelKey = strtoupper($level);
            if (!isset($normalizedLevelOptions[$levelKey])) {
                throw new RuntimeException('Please choose a valid level for this student.');
            }
            $level = (string)$normalizedLevelOptions[$levelKey];

            if ($surname === '' || $firstName === '') {
                throw new RuntimeException('Surname and First Name are required.');
            }
            if ($sex === '' || !in_array($sex, ['Male', 'Female'], true)) {
                throw new RuntimeException('Please select a valid Sex value.');
            }
            if ($dob === '' || !preg_match('/^\d{4}\-\d{2}\-\d{2}$/', $dob)) {
                throw new RuntimeException('Please enter a valid Date of Birth.');
            }
            if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
                throw new RuntimeException('Please enter a valid email address.');
            }

            $subjectNames = is_array($_POST['subject_name'] ?? null) ? $_POST['subject_name'] : [];
            $subjectGrades = is_array($_POST['subject_grade'] ?? null) ? $_POST['subject_grade'] : [];
            $subjectTriplets = [];
            foreach ($subjectNames as $idx => $subjectName) {
                $name = trim((string)$subjectName);
                $grade = trim((string)($subjectGrades[$idx] ?? ''));
                if ($name === '' && $grade === '') continue;
                $subjectTriplets[] = ['subject_name' => $name, 'grade' => $grade];
            }
            $subject1 = (string)($subjectTriplets[0]['subject_name'] ?? '');
            $subject2 = (string)($subjectTriplets[1]['subject_name'] ?? '');
            $subject3 = (string)($subjectTriplets[2]['subject_name'] ?? '');

            $beforeSnapshot = [
                'surname' => (string)($studentCurrent['surname'] ?? ''),
                'first_name' => (string)($studentCurrent['first_name'] ?? ''),
                'other_name' => (string)($studentCurrent['other_name'] ?? ''),
                'email' => (string)($studentCurrent['email'] ?? ''),
                'faculty_id' => (int)($studentCurrent['faculty_id'] ?? 0),
                'department_id' => (int)($studentCurrent['department_id'] ?? 0),
                'programme_id' => (int)($studentCurrent['programme_id'] ?? 0),
                'mode_of_study' => (string)($studentCurrent['mode_of_study'] ?? ''),
                'session' => (string)($studentCurrent['session'] ?? ''),
                'semester' => (string)($studentCurrent['semester'] ?? ''),
                'level' => (string)($studentCurrent['level'] ?? ''),
            ];

            $pdo->beginTransaction();

            $up = $pdo->prepare("UPDATE students SET
                surname = ?, first_name = ?, other_name = ?, maiden_name = ?, sex = ?,
                date_of_birth = ?, nationality = ?, state_of_origin = ?, lga = ?, mobile_phone = ?,
                residential_phone = ?, email = ?, permanent_address = ?, extra_curricular = ?,
                faculty_id = ?, department_id = ?, programme_id = ?, mode_of_study = ?, level = ?,
                faculty = ?, department = ?, programme = ?, program = ?,
                school_name = ?, school = ?, course_in_view = ?, course_of_study = ?, programme_name_cache = ?,
                nok_name = ?, nok_phone = ?, sponsor_name = ?, sponsor_phone = ?, sponsor_occupation = ?,
                subject1 = ?, subject2 = ?, subject3 = ?, signature_data = ?
                WHERE id = ?");
            $up->execute([
                $surname, $firstName, $otherName, $maidenName, $sex,
                $dob, $nationality, $stateOfOrigin, $lga, $mobilePhone,
                $residentialPhone, $email, $permanentAddress, $extraCurricular,
                (int)($academic['faculty_id'] ?? 0), (int)($academic['department_id'] ?? 0), (int)($academic['programme_id'] ?? 0), (string)($academic['mode_of_study'] ?? ''), $level,
                (string)($academic['school'] ?? ''), (string)($academic['course_of_study'] ?? ''), (string)($academic['programme'] ?? ''), (string)($academic['programme'] ?? ''),
                (string)($academic['school'] ?? ''), (string)($academic['school'] ?? ''), (string)($academic['course_of_study'] ?? ''), (string)($academic['course_of_study'] ?? ''), (string)($academic['programme'] ?? ''),
                $nokName, $nokPhone, $sponsorName, $sponsorPhone, $sponsorOccupation,
                $subject1, $subject2, $subject3, $applicantSignatureData,
                $studentId
            ]);

            if (portalTableExistsSafe($pdo, 'student_olevel_subjects')) {
                $pdo->prepare("DELETE FROM student_olevel_subjects WHERE student_id = ?")->execute([$studentId]);
                if ($subjectTriplets) {
                    $insSubject = $pdo->prepare("INSERT INTO student_olevel_subjects (student_id, subject_name, grade) VALUES (?, ?, ?)");
                    foreach ($subjectTriplets as $row) {
                        $insSubject->execute([$studentId, (string)$row['subject_name'], (string)$row['grade']]);
                    }
                }
            }

            $pdo->prepare("DELETE FROM education_history WHERE student_id = ?")->execute([$studentId]);
            $schoolNames = is_array($_POST['school_name'] ?? null) ? $_POST['school_name'] : [];
            $schoolFrom = is_array($_POST['school_from'] ?? null) ? $_POST['school_from'] : [];
            $schoolTo = is_array($_POST['school_to'] ?? null) ? $_POST['school_to'] : [];
            $schoolQual = is_array($_POST['school_qual'] ?? null) ? $_POST['school_qual'] : [];
            $insEdu = $pdo->prepare("INSERT INTO education_history (student_id, institution_name, from_date, to_date, qualification) VALUES (?, ?, ?, ?, ?)");
            foreach ($schoolNames as $idx => $schoolNameRaw) {
                $schoolName = trim((string)$schoolNameRaw);
                $fromDate = trim((string)($schoolFrom[$idx] ?? ''));
                $toDate = trim((string)($schoolTo[$idx] ?? ''));
                $qualification = trim((string)($schoolQual[$idx] ?? ''));
                if ($schoolName === '' && $fromDate === '' && $toDate === '' && $qualification === '') continue;
                $insEdu->execute([$studentId, $schoolName, $fromDate !== '' ? $fromDate : null, $toDate !== '' ? $toDate : null, $qualification]);
            }

            $pdo->prepare("DELETE FROM examination_results WHERE student_id = ?")->execute([$studentId]);
            $examTypes = is_array($_POST['exam_type'] ?? null) ? $_POST['exam_type'] : [];
            $examTypesOther = is_array($_POST['exam_type_other'] ?? null) ? $_POST['exam_type_other'] : [];
            $examDates = is_array($_POST['exam_date'] ?? null) ? $_POST['exam_date'] : [];
            $examNumbers = is_array($_POST['exam_no'] ?? null) ? $_POST['exam_no'] : [];
            $examGrades = is_array($_POST['exam_grades'] ?? null) ? $_POST['exam_grades'] : [];
            $examAttachedRaw = $_POST['exam_attached'] ?? [];
            $hasExamTypeOther = portalColumnExistsSafe($pdo, 'examination_results', 'exam_type_other');

            if ($hasExamTypeOther) {
                $insExam = $pdo->prepare("INSERT INTO examination_results (student_id, exam_type, exam_type_other, exam_date, exam_number, subjects_grades, credentials_attached) VALUES (?, ?, ?, ?, ?, ?, ?)");
            } else {
                $insExam = $pdo->prepare("INSERT INTO examination_results (student_id, exam_type, exam_date, exam_number, subjects_grades, credentials_attached) VALUES (?, ?, ?, ?, ?, ?)");
            }

            foreach ($examTypes as $idx => $examTypeRaw) {
                $examType = strtoupper(trim((string)$examTypeRaw));
                $examTypeOther = trim((string)($examTypesOther[$idx] ?? ''));
                $examDate = trim((string)($examDates[$idx] ?? ''));
                $examNumber = trim((string)($examNumbers[$idx] ?? ''));
                $examGradeText = trim((string)($examGrades[$idx] ?? ''));
                $attached = 0;
                if (is_array($examAttachedRaw)) {
                    $attached = (isset($examAttachedRaw[$idx]) || array_key_exists((string)$idx, $examAttachedRaw)) ? 1 : 0;
                }
                if ($examType === '' && $examTypeOther === '' && $examDate === '' && $examNumber === '' && $examGradeText === '') continue;
                if ($examType === '') {
                    $examType = $examTypeOther !== '' ? 'OTHER' : '';
                }
                if ($examType === '') {
                    continue;
                }
                if ($hasExamTypeOther) {
                    $insExam->execute([$studentId, $examType, $examType === 'OTHER' ? $examTypeOther : null, $examDate !== '' ? $examDate : null, $examNumber, $examGradeText, $attached]);
                } else {
                    $insExam->execute([$studentId, $examType, $examDate !== '' ? $examDate : null, $examNumber, $examGradeText, $attached]);
                }
            }

            $refName = trim((string)($_POST['ref_name'] ?? ''));
            $refOcc = trim((string)($_POST['ref_occ'] ?? ''));
            $refPost = trim((string)($_POST['ref_post'] ?? ''));
            $refAddress = trim((string)($_POST['ref_address'] ?? ''));
            $refDate = trim((string)($_POST['ref_date'] ?? ''));
            $hasRefData = ($refName !== '' || $refOcc !== '' || $refPost !== '' || $refAddress !== '' || $refDate !== '' || $refSignatureData !== '');

            $existingRefId = 0;
            try {
                $stRef = $pdo->prepare("SELECT id FROM referees WHERE student_id = ? ORDER BY id DESC LIMIT 1");
                $stRef->execute([$studentId]);
                $existingRefId = (int)($stRef->fetchColumn() ?: 0);
            } catch (Throwable $e) {
                $existingRefId = 0;
            }

            if ($hasRefData) {
                if ($existingRefId > 0) {
                    $upRef = $pdo->prepare("UPDATE referees SET full_name = ?, occupation = ?, post_held = ?, address = ?, date = ?, signature_data = ? WHERE id = ?");
                    $upRef->execute([$refName, $refOcc, $refPost, $refAddress, $refDate !== '' ? $refDate : null, $refSignatureData !== '' ? $refSignatureData : null, $existingRefId]);
                } else {
                    $insRef = $pdo->prepare("INSERT INTO referees (student_id, full_name, occupation, post_held, address, date, signature_data) VALUES (?, ?, ?, ?, ?, ?, ?)");
                    $insRef->execute([$studentId, $refName, $refOcc, $refPost, $refAddress, $refDate !== '' ? $refDate : null, $refSignatureData !== '' ? $refSignatureData : null]);
                }
            } elseif ($existingRefId > 0) {
                $pdo->prepare("DELETE FROM referees WHERE student_id = ?")->execute([$studentId]);
            }

            if (portalTableExistsSafe($pdo, 'course_registrations')) {
                try {
                    $cr = $pdo->prepare("UPDATE course_registrations SET faculty_id = ?, department_id = ?, programme_id = ?, level = ? WHERE student_id = ?");
                    $cr->execute([(int)($academic['faculty_id'] ?? 0), (int)($academic['department_id'] ?? 0), (int)($academic['programme_id'] ?? 0), $level, $studentId]);
                } catch (Throwable $e) { /* keep primary save intact */ }
            }

            $pdo->commit();

            $feeSyncResult = portalResynchronizeStudentFeeRequirements($pdo, $studentId, [
                'faculty_id' => (int)($academic['faculty_id'] ?? 0),
                'department_id' => (int)($academic['department_id'] ?? 0),
                'programme_id' => (int)($academic['programme_id'] ?? 0),
                'mode_of_study' => (string)($academic['mode_of_study'] ?? ''),
                'level' => $level,
            ]);

            if (isset($_FILES['profile_image']) && (int)($_FILES['profile_image']['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK) {
                $uploadResult = handleFileUpload($_FILES['profile_image'], 'profile_image');
                if (!empty($uploadResult['success'])) {
                    $pdo->prepare("UPDATE students SET profile_image = ? WHERE id = ?")->execute([(string)$uploadResult['filepath'], $studentId]);
                }
            }

            if (isset($_FILES['passport_photo']) && (int)($_FILES['passport_photo']['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK) {
                $uploadResult = handleFileUpload($_FILES['passport_photo'], 'passport_photo');
                if (!empty($uploadResult['success'])) {
                    $pdo->prepare("UPDATE students SET passport_photo = ? WHERE id = ?")->execute([(string)$uploadResult['filepath'], $studentId]);
                }
            }

            if (isset($_FILES['documents']) && is_array($_FILES['documents']['name'] ?? null)) {
                foreach (($_FILES['documents']['name'] ?? []) as $idx => $documentName) {
                    $docError = (int)($_FILES['documents']['error'][$idx] ?? UPLOAD_ERR_NO_FILE);
                    if ($docError !== UPLOAD_ERR_OK) continue;
                    $file = [
                        'name' => (string)($_FILES['documents']['name'][$idx] ?? ''),
                        'type' => (string)($_FILES['documents']['type'][$idx] ?? ''),
                        'tmp_name' => (string)($_FILES['documents']['tmp_name'][$idx] ?? ''),
                        'error' => $docError,
                        'size' => (int)($_FILES['documents']['size'][$idx] ?? 0),
                    ];
                    $documentType = trim((string)(($_POST['document_type'][$idx] ?? '') ?: 'other'));
                    $uploadResult = handleFileUpload($file, $documentType);
                    if (empty($uploadResult['success'])) continue;
                    $insDoc = $pdo->prepare("INSERT INTO student_documents (student_id, document_name, document_type, file_path, file_size) VALUES (?, ?, ?, ?, ?)");
                    $insDoc->execute([$studentId, (string)$documentName, (string)$uploadResult['document_type'], (string)$uploadResult['filepath'], (int)$file['size']]);
                }
            }

            if (function_exists('portalAuthLogActivity')) {
                portalAuthLogActivity($pdo, 'admin', portalCurrentAdminId(), 'admin_submitted_application_update', [
                    'student_id' => $studentId,
                    'student_name' => trim((string)studentFullName(array_merge($studentCurrent, ['surname' => $surname, 'first_name' => $firstName, 'other_name' => $otherName]))),
                    'return_page' => $returnPage,
                    'before' => $beforeSnapshot,
                    'after' => [
                        'surname' => $surname,
                        'first_name' => $firstName,
                        'other_name' => $otherName,
                        'email' => $email,
                        'faculty_id' => (int)($academic['faculty_id'] ?? 0),
                        'department_id' => (int)($academic['department_id'] ?? 0),
                        'programme_id' => (int)($academic['programme_id'] ?? 0),
                        'mode_of_study' => (string)($academic['mode_of_study'] ?? ''),
                        'level' => $level,
                        'fee_sync' => $feeSyncResult,
                    ],
                ]);
            }

            flash('success', 'Submitted form updated successfully. The latest values will now appear everywhere this application reads from the same student record.');
            foreach (array_values(array_unique(array_filter(array_map('strval', (array)($feeSyncResult['warnings'] ?? []))))) as $warning) {
                flash('warning', $warning);
            }
        } catch (Throwable $e) {
            if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
                $pdo->rollBack();
            }
            flash('danger', $e->getMessage() ?: 'Unable to update this submitted form right now.');
        }

        redirect($returnUrl);
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_change_student_academic_details' && isAdmin()) {
        requireStaffPermission($pdo, 'academics_manage');
        $studentId = (int)($_POST['student_id'] ?? 0);
        $currentLookup = trim((string)($_POST['current_matric_lookup'] ?? ''));
        $currentVerifierTab = trim((string)($_POST['verifier_tab'] ?? 'overview'));
        if (!in_array($currentVerifierTab, ['overview','history'], true)) { $currentVerifierTab = 'overview'; }
        $returnUrl = '?page=admin_matric_verifier' . ($currentLookup !== '' ? '&matric_number=' . urlencode($currentLookup) : '') . '&verifier_tab=' . urlencode($currentVerifierTab);

        if ($studentId <= 0) {
            flash('danger', 'Invalid student selected for academic details update.');
            redirect($returnUrl);
        }

        try {
            portalEnsureApplicationAcademicFieldSupport($pdo);
            $stCurrent = $pdo->prepare("SELECT id, faculty_id, department_id, programme_id, faculty, department, programme, program, school_name, school, course_in_view, course_of_study, programme_name_cache, mode_of_study, level,
                TRIM(CONCAT_WS(' ', NULLIF(TRIM(COALESCE(surname, '')), ''), NULLIF(TRIM(COALESCE(first_name, '')), ''), NULLIF(TRIM(COALESCE(other_name, '')), ''))) AS full_name,
                surname, first_name, other_name, application_no, registration_no, admission_no, matric_no
                FROM students WHERE id = ? LIMIT 1");
            $stCurrent->execute([$studentId]);
            $studentCurrent = $stCurrent->fetch(PDO::FETCH_ASSOC) ?: [];
            if (!$studentCurrent) {
                throw new RuntimeException('Student record could not be loaded for academic details update.');
            }

            $academic = portalValidateApplicationAcademicSelection(
                $pdo,
                (int)($_POST['faculty_id'] ?? 0),
                (int)($_POST['department_id'] ?? 0),
                (int)($_POST['programme_id'] ?? 0)
            );

            $modeType = trim((string)($_POST['mode_of_study_type'] ?? ''));
            $modeCustom = trim((string)($_POST['mode_of_study_custom'] ?? ''));
            if ($modeType === '' && $modeCustom === '') {
                $currentMode = trim((string)($studentCurrent['mode_of_study'] ?? ''));
                if (in_array($currentMode, ['Full-Time', 'Part-Time'], true)) {
                    $modeType = $currentMode;
                } elseif ($currentMode !== '') {
                    $modeType = 'Others';
                    $modeCustom = $currentMode;
                }
            }
            $modeMeta = portalNormalizeProgrammeModeOfStudyInput($modeType !== '' ? $modeType : 'Full-Time', $modeCustom);
            $afterModeOfStudy = trim((string)($modeMeta['display'] ?? ''));
            if ($afterModeOfStudy === '') {
                $afterModeOfStudy = trim((string)($academic['mode_of_study'] ?? 'Full-Time'));
            }

            $availableLevelOptions = portalAcademicLevelOptions($pdo);
            $normalizedLevelOptions = [];
            foreach ($availableLevelOptions as $levelOption) {
                $levelOption = trim((string)$levelOption);
                if ($levelOption === '') continue;
                $normalizedLevelOptions[strtoupper($levelOption)] = $levelOption;
            }
            $afterLevel = trim((string)($_POST['level'] ?? ($studentCurrent['level'] ?? '')));
            if ($afterLevel === '') {
                throw new RuntimeException('Please select a valid level for this student.');
            }
            $afterLevelKey = strtoupper($afterLevel);
            if (!isset($normalizedLevelOptions[$afterLevelKey])) {
                throw new RuntimeException('Please select a valid level for this student.');
            }
            $afterLevel = (string)$normalizedLevelOptions[$afterLevelKey];

            $beforeFacultyId = (int)($studentCurrent['faculty_id'] ?? 0);
            $beforeDepartmentId = (int)($studentCurrent['department_id'] ?? 0);
            $beforeProgrammeId = (int)($studentCurrent['programme_id'] ?? 0);
            $beforeModeOfStudy = trim((string)($studentCurrent['mode_of_study'] ?? ''));
            $beforeLevel = trim((string)($studentCurrent['level'] ?? ''));
            $afterFacultyId = (int)($academic['faculty_id'] ?? 0);
            $afterDepartmentId = (int)($academic['department_id'] ?? 0);
            $afterProgrammeId = (int)($academic['programme_id'] ?? 0);
            $mappingChanged = $beforeFacultyId !== $afterFacultyId
                || $beforeDepartmentId !== $afterDepartmentId
                || $beforeProgrammeId !== $afterProgrammeId;
            $modeChanged = strcasecmp($beforeModeOfStudy, $afterModeOfStudy) !== 0;
            $levelChanged = strcasecmp($beforeLevel, $afterLevel) !== 0;

            if (!$mappingChanged && !$modeChanged && !$levelChanged) {
                flash('info', 'No academic details change was detected, so nothing was updated.');
                redirect($returnUrl);
            }

            portalEnsureStudentVerifierChangeHistorySchema($pdo);
            $pdo->beginTransaction();

            $up = $pdo->prepare("UPDATE students SET
                faculty_id = ?, department_id = ?, programme_id = ?,
                faculty = ?, department = ?, programme = ?, program = ?,
                school_name = ?, school = ?, course_in_view = ?, course_of_study = ?, programme_name_cache = ?,
                mode_of_study = ?, level = ?
                WHERE id = ?");
            $up->execute([
                $afterFacultyId,
                $afterDepartmentId,
                $afterProgrammeId,
                (string)($academic['school'] ?? ''),
                (string)($academic['course_of_study'] ?? ''),
                (string)($academic['programme'] ?? ''),
                (string)($academic['programme'] ?? ''),
                (string)($academic['school'] ?? ''),
                (string)($academic['school'] ?? ''),
                (string)($academic['course_of_study'] ?? ''),
                (string)($academic['course_of_study'] ?? ''),
                (string)($academic['programme'] ?? ''),
                $afterModeOfStudy,
                $afterLevel,
                $studentId
            ]);

            try {
                if (portalTableExistsSafe($pdo, 'course_registrations')) {
                    $courseRegistrationAssignments = [
                        'faculty_id' => $afterFacultyId,
                        'department_id' => $afterDepartmentId,
                        'programme_id' => $afterProgrammeId,
                        'level' => $afterLevel,
                    ];
                    if (portalColumnExistsSafe($pdo, 'course_registrations', 'mode_of_study')) {
                        $courseRegistrationAssignments['mode_of_study'] = $afterModeOfStudy;
                    }
                    $setClauses = [];
                    $setParams = [];
                    foreach ($courseRegistrationAssignments as $column => $value) {
                        $setClauses[] = "`{$column}` = ?";
                        $setParams[] = $value;
                    }
                    $setParams[] = $studentId;
                    $cr = $pdo->prepare("UPDATE course_registrations SET " . implode(', ', $setClauses) . " WHERE student_id = ?");
                    $cr->execute($setParams);
                }
            } catch (Throwable $e) {}

            try {
                if (portalTableExistsSafe($pdo, 'course_registration_drafts')) {
                    $draftAssignments = [
                        'faculty_id' => $afterFacultyId,
                        'department_id' => $afterDepartmentId,
                        'programme_id' => $afterProgrammeId,
                        'level' => $afterLevel,
                    ];
                    if (portalColumnExistsSafe($pdo, 'course_registration_drafts', 'mode_of_study')) {
                        $draftAssignments['mode_of_study'] = $afterModeOfStudy;
                    }
                    $draftSetClauses = [];
                    $draftParams = [];
                    foreach ($draftAssignments as $column => $value) {
                        if (!portalColumnExistsSafe($pdo, 'course_registration_drafts', $column)) {
                            continue;
                        }
                        $draftSetClauses[] = "`{$column}` = ?";
                        $draftParams[] = $value;
                    }
                    if ($draftSetClauses) {
                        $draftParams[] = $studentId;
                        $draft = $pdo->prepare("UPDATE course_registration_drafts SET " . implode(', ', $draftSetClauses) . " WHERE student_id = ?");
                        $draft->execute($draftParams);
                    }
                }
            } catch (Throwable $e) {}

            $academicSyncTouchedTables = [];
            try {
                $academicSyncTouchedTables = portalSyncStudentAcademicReferences($pdo, $studentId, [
                    'matric_no' => (string)($studentCurrent['matric_no'] ?? ''),
                    'registration_no' => (string)($studentCurrent['registration_no'] ?? ''),
                    'admission_no' => (string)($studentCurrent['admission_no'] ?? ''),
                    'application_no' => (string)($studentCurrent['application_no'] ?? ''),
                ], [
                    'faculty_id' => $afterFacultyId,
                    'department_id' => $afterDepartmentId,
                    'programme_id' => $afterProgrammeId,
                    'school_name' => (string)($academic['school'] ?? ''),
                    'school' => (string)($academic['school'] ?? ''),
                    'faculty' => (string)($academic['school'] ?? ''),
                    'department' => (string)($academic['course_of_study'] ?? ''),
                    'course_of_study' => (string)($academic['course_of_study'] ?? ''),
                    'course_in_view' => (string)($academic['course_of_study'] ?? ''),
                    'programme' => (string)($academic['programme'] ?? ''),
                    'program' => (string)($academic['programme'] ?? ''),
                    'programme_name_cache' => (string)($academic['programme'] ?? ''),
                    'mode_of_study' => $afterModeOfStudy,
                    'level' => $afterLevel,
                    'current_level' => $afterLevel,
                ]);
            } catch (Throwable $e) {
                $academicSyncTouchedTables = [];
            }

            $pdo->commit();
            $feeSyncResult = portalResynchronizeStudentFeeRequirements($pdo, $studentId, [
                'faculty_id' => $afterFacultyId,
                'department_id' => $afterDepartmentId,
                'programme_id' => $afterProgrammeId,
                'mode_of_study' => $afterModeOfStudy,
                'level' => $afterLevel,
            ]);
            try {
                portalLogStudentVerifierChange($pdo, [
                    'student_id' => $studentId,
                    'matric_no' => (string)($studentCurrent['matric_no'] ?? ''),
                    'admission_no' => (string)($studentCurrent['admission_no'] ?? ''),
                    'student_full_name' => trim((string)($studentCurrent['full_name'] ?? trim((string)($studentCurrent['surname'] ?? '') . ' ' . (string)($studentCurrent['first_name'] ?? '') . ' ' . (string)($studentCurrent['other_name'] ?? '')))),
                    'action_type' => portalResolveStudentVerifierActionType([
                        'faculty' => $beforeFacultyId !== $afterFacultyId,
                        'department' => $beforeDepartmentId !== $afterDepartmentId,
                        'programme' => $beforeProgrammeId !== $afterProgrammeId,
                        'mode_of_study' => $modeChanged,
                        'level' => $levelChanged,
                    ]),
                    'old_faculty' => (string)($studentCurrent['school_name'] ?? $studentCurrent['school'] ?? $studentCurrent['faculty'] ?? ''),
                    'new_faculty' => (string)($academic['school'] ?? ''),
                    'old_department' => (string)($studentCurrent['course_of_study'] ?? $studentCurrent['course_in_view'] ?? $studentCurrent['department'] ?? ''),
                    'new_department' => (string)($academic['course_of_study'] ?? ''),
                    'old_programme' => (string)($studentCurrent['programme_name_cache'] ?? $studentCurrent['program'] ?? $studentCurrent['programme'] ?? ''),
                    'new_programme' => (string)($academic['programme'] ?? ''),
                    'old_mode_of_study' => $beforeModeOfStudy,
                    'new_mode_of_study' => $afterModeOfStudy,
                    'old_level' => $beforeLevel,
                    'new_level' => $afterLevel,
                    'changed_by_admin_id' => (int)portalCurrentAdminId(),
                    'source_page' => 'admin_matric_verifier',
                    'remark' => 'Academic details updated from verifier page.',
                    'changed_fields' => [
                        'faculty' => $beforeFacultyId !== $afterFacultyId,
                        'department' => $beforeDepartmentId !== $afterDepartmentId,
                        'programme' => $beforeProgrammeId !== $afterProgrammeId,
                        'mode_of_study' => $modeChanged,
                        'level' => $levelChanged,
                    ],
                ]);
            } catch (Throwable $e) {}

            if (function_exists('portalAuthLogActivity')) {
                $adminId = portalCurrentAdminId();
                portalAuthLogActivity($pdo, 'admin', $adminId, 'student_academic_details_update_from_matric_verifier', [
                    'student_id' => $studentId,
                    'student_name' => trim((string)($studentCurrent['full_name'] ?? trim((string)($studentCurrent['surname'] ?? '') . ' ' . (string)($studentCurrent['first_name'] ?? '') . ' ' . (string)($studentCurrent['other_name'] ?? '')))),
                    'identifier' => (string)($studentCurrent['matric_no'] ?? $studentCurrent['admission_no'] ?? $studentCurrent['application_no'] ?? $studentCurrent['registration_no'] ?? ''),
                    'before' => [
                        'faculty_id' => $beforeFacultyId,
                        'department_id' => $beforeDepartmentId,
                        'programme_id' => $beforeProgrammeId,
                        'mode_of_study' => $beforeModeOfStudy,
                        'level' => $beforeLevel,
                        'faculty' => (string)($studentCurrent['school_name'] ?? $studentCurrent['school'] ?? $studentCurrent['faculty'] ?? ''),
                        'department' => (string)($studentCurrent['course_of_study'] ?? $studentCurrent['course_in_view'] ?? $studentCurrent['department'] ?? ''),
                        'programme' => (string)($studentCurrent['programme_name_cache'] ?? $studentCurrent['program'] ?? $studentCurrent['programme'] ?? ''),
                    ],
                    'after' => [
                        'faculty_id' => $afterFacultyId,
                        'department_id' => $afterDepartmentId,
                        'programme_id' => $afterProgrammeId,
                        'mode_of_study' => $afterModeOfStudy,
                        'level' => $afterLevel,
                        'faculty' => (string)($academic['school'] ?? ''),
                        'department' => (string)($academic['course_of_study'] ?? ''),
                        'programme' => (string)($academic['programme'] ?? ''),
                        'synced_tables' => $academicSyncTouchedTables,
                    ],
                ]);
            }

            flash('success', 'Academic details, Mode of Study, and Level updated successfully for this student and synchronized to the related student records/areas.');
            foreach (array_values(array_unique(array_filter(array_map('strval', (array)($feeSyncResult['warnings'] ?? []))))) as $warning) {
                flash('warning', $warning);
            }
        } catch (Throwable $e) {
            if ($pdo instanceof PDO && $pdo->inTransaction()) {
                $pdo->rollBack();
            }
            flash('danger', $e->getMessage() ?: 'Unable to change the academic details right now.');
        }

        redirect($returnUrl);
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_change_student_matric_number' && isAdmin()) {
        requireStaffPermission($pdo, 'academics_manage');
        $studentId = (int)($_POST['student_id'] ?? 0);
        $currentLookup = trim((string)($_POST['current_matric_lookup'] ?? ''));
        $currentVerifierTab = trim((string)($_POST['verifier_tab'] ?? 'overview'));
        if (!in_array($currentVerifierTab, ['overview','history'], true)) { $currentVerifierTab = 'overview'; }
        $returnUrl = '?page=admin_matric_verifier' . ($currentLookup !== '' ? '&matric_number=' . urlencode($currentLookup) : '') . '&verifier_tab=' . urlencode($currentVerifierTab);

        if ($studentId <= 0) {
            flash('danger', 'Invalid student selected for matric number update.');
            redirect($returnUrl);
        }

        try {
            $result = portalUpdateStudentMatricNumber($pdo, $studentId, (string)($_POST['new_matric_number'] ?? ''), (int)portalCurrentAdminId());
            $oldMatric = (string)($result['old_matric_no'] ?? '');
            $newMatric = (string)($result['new_matric_no'] ?? '');
            flash('success', 'Matric number updated successfully from ' . $oldMatric . ' to ' . $newMatric . '.');
            redirect('?page=admin_matric_verifier&matric_number=' . urlencode($newMatric) . '&verifier_tab=' . urlencode($currentVerifierTab));
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Unable to change the matric number right now.');
            redirect($returnUrl);
        }
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_update_student_grace_resubmission' && isAdmin()) {
        $studentId = (int)($_POST['student_id'] ?? 0);
        $returnPage = trim((string)($_POST['return_page'] ?? 'admin_students'));
        $returnAction = trim((string)($_POST['return_action'] ?? 'view'));
        $returnId = (int)($_POST['return_id'] ?? $studentId);
        $returnUrl = '?page=' . urlencode($returnPage) . '&action=' . urlencode($returnAction) . '&id=' . $returnId;
        $extraGrace = max(0, (int)($_POST['extra_grace_count'] ?? 0));
        $note = trim((string)($_POST['grace_note'] ?? ''));
        $adminId = (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0);

        if ($studentId <= 0) {
            flash('danger', 'Invalid student/application record selected.');
            redirect($returnUrl);
        }

        try {
            portalEnsureApplicationGraceSupport($pdo);
            $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $st->execute([$studentId]);
            $studentRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$studentRow) {
                throw new RuntimeException('Student record not found.');
            }

            $beforeState = portalApplicationResubmissionState($pdo, $studentRow);
            $previousExtra = (int)($beforeState['extra_grace_count'] ?? 0);

            $upd = $pdo->prepare("UPDATE students
                SET application_extra_grace_count = ?,
                    application_granted_by_admin_id = ?,
                    application_granted_at = NOW()
                WHERE id = ?");
            $upd->execute([$extraGrace, $adminId > 0 ? $adminId : null, $studentId]);

            $afterState = portalApplicationResubmissionState($pdo, $studentId);
            $auditAction = $extraGrace > $previousExtra ? 'grant' : ($extraGrace < $previousExtra ? 'adjust' : 'grant');
            if ($extraGrace === 0) {
                $auditAction = 'revoke';
            }
            portalLogApplicationResubmissionAudit(
                $pdo,
                $studentId,
                $adminId,
                $auditAction,
                $previousExtra,
                $extraGrace,
                (int)($afterState['grace_used'] ?? 0),
                $note
            );

            if ($extraGrace > 0) {
                flash('success', 'Admin has granted this student ' . $extraGrace . ' additional resubmission attempt' . ($extraGrace === 1 ? '' : 's') . '.');
            } else {
                flash('success', 'Grace resubmission attempts have been updated for this student.');
            }
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Unable to update grace resubmission attempts right now.');
        }

        redirect($returnUrl);
    }

    // Delete student
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_student') {
        $studentId = $_POST['student_id'] ?? 0;

        if ($studentId > 0) {
            // Delete related records (will cascade due to foreign key constraints)
            $stmt = $pdo->prepare("DELETE FROM students WHERE id = ?");
            $stmt->execute([$studentId]);

            $_SESSION['student_deleted'] = true;
            redirect('?page=admin_students');
        }
    }

    // Delete application
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_application') {
        $studentId = $_POST['student_id'] ?? 0;

        if ($studentId > 0) {
            // Delete related records (will cascade due to foreign key constraints)
            $stmt = $pdo->prepare("DELETE FROM students WHERE id = ?");
            $stmt->execute([$studentId]);

            $_SESSION['application_deleted'] = true;
            redirect('?page=admin_applications');
        }
    }

    // Approve/Reject payment
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_payment_status') {
        $transactionId = $_POST['transaction_id'] ?? 0;
        $status = $_POST['status'] ?? '';
        $notes = $_POST['approval_notes'] ?? '';
        $approvedAmountInput = round((float)($_POST['approved_amount'] ?? 0), 2);

        if ($transactionId > 0 && !empty($status)) {
            // Get transaction details
            $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ?");
            $stmt->execute([$transactionId]);
            $transaction = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($transaction) {
                $enteredAmount = portalPaymentEnteredAmount($transaction);
                $resolvedApprovedAmount = (float)($transaction['approved_amount'] ?? 0);
                if ($status === 'Approved') {
                    if ($approvedAmountInput > 0) {
                        $resolvedApprovedAmount = $approvedAmountInput;
                    } elseif ($resolvedApprovedAmount <= 0) {
                        $resolvedApprovedAmount = $enteredAmount > 0 ? $enteredAmount : (float)($transaction['amount'] ?? 0);
                    }
                } else {
                    $resolvedApprovedAmount = null;
                }

                $walletCreditState = null;
                if (strcasecmp((string)($transaction['transaction_type'] ?? ''), 'Wallet Funding') === 0) {
                    $walletCreditState = ($status === 'Approved') ? 'Pending' : (($status === 'Rejected') ? 'Rejected' : null);
                }

                // Update transaction status
                $stmt = $pdo->prepare("UPDATE payment_transactions SET
                    status = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN status ELSE ? END,
                    amount = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN amount WHEN ? = 'Approved' AND ? > 0 THEN ? ELSE amount END,
                    approved_amount = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN approved_amount ELSE ? END,
                    approval_date = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN approval_date ELSE NOW() END,
                    approved_by = CASE WHEN COALESCE(reconciliation_locked_at,'') <> '' THEN approved_by ELSE ? END,
                    approval_notes = ?,
                    proof_locked_at = CASE WHEN ? = 'Approved' THEN COALESCE(proof_locked_at, NOW()) ELSE proof_locked_at END,
                    wallet_credit_status = COALESCE(?, wallet_credit_status),
                    wallet_credited_amount = CASE WHEN ? = 'Rejected' THEN 0 ELSE wallet_credited_amount END,
                    wallet_credited_at = CASE WHEN ? = 'Rejected' THEN NULL ELSE wallet_credited_at END,
                    wallet_credit_reference = CASE WHEN ? = 'Rejected' THEN NULL ELSE wallet_credit_reference END,
                    reconciliation_status = CASE WHEN ? = 'Rejected' AND COALESCE(reconciliation_locked_at,'') = '' THEN 'Rejected' ELSE reconciliation_status END
                    WHERE id = ?");
                $stmt->execute([$status, $status, $resolvedApprovedAmount, $resolvedApprovedAmount, $resolvedApprovedAmount, $_SESSION['user_id'], $notes, $status, $walletCreditState, $status, $status, $status, $status, $transactionId]);

                $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ? LIMIT 1");
                $stmt->execute([$transactionId]);
                $transaction = $stmt->fetch(PDO::FETCH_ASSOC) ?: $transaction;

                if ($status === 'Approved' && strcasecmp((string)($transaction['transaction_type'] ?? ''), 'Wallet Funding') === 0) {
                    $creditSync = portalSynchronizeWalletFundingCredit(
                        $pdo,
                        (int)$transactionId,
                        (float)($resolvedApprovedAmount > 0 ? $resolvedApprovedAmount : portalPaymentApprovedAmount($transaction)),
                        'Manual wallet funding approved [' . (string)($transaction['reference'] ?? '') . ']'
                    );

                    if (!$creditSync['ok']) {
                        try {
                            $pdo->prepare("UPDATE payment_transactions SET status = 'Pending', approved_amount = NULL, approval_date = NULL, approved_by = NULL, wallet_credit_status = 'Failed', approval_notes = CONCAT(COALESCE(approval_notes,''), CASE WHEN COALESCE(approval_notes,'') = '' THEN '' ELSE '\n' END, ?) WHERE id = ?")
                                ->execute(['Wallet credit synchronization failed during manual approval. ' . (string)($creditSync['message'] ?? ''), (int)$transactionId]);
                        } catch (Throwable $e) {}
                        flash('danger', 'Manual approval was not completed because wallet credit synchronization failed. The transaction has been returned to pending review.');
                        redirect('?page=admin_payments&action=view&id=' . (int)$transactionId);
                    }

                    $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ? LIMIT 1");
                    $stmt->execute([$transactionId]);
                    $transaction = $stmt->fetch(PDO::FETCH_ASSOC) ?: $transaction;
                }

                if ($status === 'Approved' && strcasecmp((string)($transaction['transaction_type'] ?? ''), 'Wallet Funding') !== 0) {
                    $recon = portalReconcileFeePayment(
                        $pdo,
                        (int)$transactionId,
                        (float)($resolvedApprovedAmount > 0 ? $resolvedApprovedAmount : portalPaymentApprovedAmount($transaction)),
                        'manual_approval',
                        [
                            'underpayment_to_wallet' => true,
                            'notes' => (string)$notes,
                            'approved_by' => (int)($_SESSION['user_id'] ?? 0),
                        ]
                    );

                    if (!$recon['ok']) {
                        flash('danger', (string)($recon['message'] ?? 'Manual payment reconciliation failed.'));
                        redirect('?page=admin_payments&action=view&id=' . (int)$transactionId);
                    }

                    $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE id = ? LIMIT 1");
                    $stmt->execute([$transactionId]);
                    $transaction = $stmt->fetch(PDO::FETCH_ASSOC) ?: $transaction;

                    if (empty($recon['should_settle_fee'])) {
                        flash((string)($recon['flash_type'] ?? 'warning'), (string)($recon['message'] ?? 'This payment was not accepted as full settlement.'));
                        redirect('?page=admin_payments&action=view&id=' . (int)$transactionId);
                    }
                }

                // If approved, update student payment status
                if ($status === 'Approved') {
                    switch ($transaction['transaction_type']) {
                        case 'Registration Fee':
                            $paymentType = 'registration_fee_status';
                            break;

                        case 'Acceptance Fee':
                            $paymentType = 'acceptance_fee_status';
                            break;

                        case 'Hostel Fee':
                            $paymentType = 'hostel_fee_status';
                            break;

                        case 'School Fee':
                            $paymentType = 'school_fee_status';

                            // Check if this is an installment payment
                            if ($transaction['total_installments'] > 1) {
                                // Check if all installments are paid
                                $stmt = $pdo->prepare("SELECT COUNT(*) FROM payment_transactions
                                    WHERE student_id = ? AND transaction_type = 'School Fee' AND status = 'Approved' AND COALESCE(amount,0) > 0 AND total_installments > 1");
                                $stmt->execute([$transaction['student_id']]);
                                $paidInstallments = $stmt->fetchColumn();

                                $newStatus = ((int)$paidInstallments >= (int)$transaction['total_installments']) ? 'Paid' : 'Pending';
                                // Always store latest reference for receipt lookup
                                $currentAcademicSession = portalCurrentAcademicSessionName($pdo);
                                $stmt = $pdo->prepare("UPDATE students SET $paymentType = ?, school_fee_reference = ?, school_fee_paid_session = ?, payment_plan='Installment' WHERE id = ?");
                                $stmt->execute([$newStatus, (string)($transaction['reference'] ?? ''), $currentAcademicSession !== '' ? $currentAcademicSession : null, $transaction['student_id']]);
                            } else {
                                // One-time payment
                                $currentAcademicSession = portalCurrentAcademicSessionName($pdo);
                                $stmt = $pdo->prepare("UPDATE students SET $paymentType = 'Paid', school_fee_reference = ?, school_fee_paid_session = ?, payment_plan='One-time' WHERE id = ?");
                                $stmt->execute([(string)($transaction['reference'] ?? ''), $currentAcademicSession !== '' ? $currentAcademicSession : null, $transaction['student_id']]);
                            }
                            try { portalApplyLateFeePaymentToRecords($pdo, (int)$transaction['student_id'], (string)($transaction['academic_session'] ?? ''), (string)($transaction['semester'] ?? ''), (float)($transaction['school_fee_late_fee_amount'] ?? 0), (string)($transaction['reference'] ?? '')); } catch (Throwable $e) {}
                            break;

                        default:
                            $paymentType = '';
                            break;
                    }

                    if ($paymentType && $transaction['transaction_type'] !== 'School Fee') {
                        $currentAcademicSession = portalCurrentAcademicSessionName($pdo);
                        if ($transaction['transaction_type'] === 'Hostel Fee') {
                            $stmt = $pdo->prepare("UPDATE students SET $paymentType = 'Paid', hostel_fee_paid_session = ? WHERE id = ?");
                            $stmt->execute([$currentAcademicSession !== '' ? $currentAcademicSession : null, $transaction['student_id']]);
                        } else {
                            $stmt = $pdo->prepare("UPDATE students SET $paymentType = 'Paid' WHERE id = ?");
                            $stmt->execute([$transaction['student_id']]);
                        }
                    }

                    try { portalRunAcceptanceFeeSuccessHooks($pdo, (int)$transaction['student_id'], (string)($transaction['transaction_type'] ?? '')); } catch (Throwable $e) {}
                    try { portalSynchronizeClearanceForStudent($pdo, (int)$transaction['student_id'], (string)($transaction['academic_session'] ?? ''), (string)($transaction['semester'] ?? '')); } catch (Throwable $e) {}

// If this is a Custom/Assigned payment, mark the assigned payment as Paid as well
if (stripos((string)$transaction['transaction_type'], 'Custom:') === 0) {
    $customTitle = trim(substr((string)$transaction['transaction_type'], strlen('Custom:')));
    // First try by reference (most reliable)
    $upd = $pdo->prepare("UPDATE assigned_payments
        SET status = 'Paid', updated_at = NOW()
        WHERE student_id = ? AND reference = ? AND status <> 'Paid'");
    $upd->execute([$transaction['student_id'], $transaction['reference']]);
    if ($upd->rowCount() === 0 && $customTitle !== '') {
        // Fallback by title
        $upd2 = $pdo->prepare("UPDATE assigned_payments
            SET status = 'Paid', updated_at = NOW()
            WHERE student_id = ? AND title = ? AND status <> 'Paid'");
        $upd2->execute([$transaction['student_id'], $customTitle]);
    }
}

 // Legacy: some older Assigned payments used transaction_type '' or 'assigned'
 $ttLower = strtolower(trim((string)($transaction['transaction_type'] ?? '')));
 $refLower = strtolower(trim((string)($transaction['reference'] ?? '')));
 if (in_array($ttLower, ['', 'assigned', 'assigned payment'], true) || strpos($refLower, 'assigned') !== false) {
     $updL = $pdo->prepare("UPDATE assigned_payments
         SET status = 'Paid', updated_at = NOW()
         WHERE student_id = ? AND reference = ? AND status <> 'Paid'");
     $updL->execute([(int)$transaction['student_id'], (string)$transaction['reference']]);
 }

                    // Generate receipt data
                    $receiptData = json_encode([
                        'transaction_id' => $transaction['transaction_id'],
                        'reference' => $transaction['reference'],
                        'amount' => $transaction['amount'],
                        'amount_paid' => (float)($transaction['approved_amount'] ?? portalPaymentApprovedAmount($transaction)),
                        'wallet_credit_amount' => (float)($transaction['wallet_credited_amount'] ?? 0),
                        'reconciliation_status' => (string)($transaction['reconciliation_status'] ?? ''),
                        'payment_date' => $transaction['payment_date'],
                        'approval_date' => date('Y-m-d H:i:s'),
                        'student_id' => $transaction['student_id'],
                        'payment_type' => $transaction['transaction_type'],
                        'school_fee_base_amount' => (float)($transaction['school_fee_base_amount'] ?? 0),
                        'school_fee_late_fee_amount' => (float)($transaction['school_fee_late_fee_amount'] ?? 0),
                        'installment_number' => $transaction['installment_number'],
                        'total_installments' => $transaction['total_installments']
                    ]);

                    // Generate QR code data
                    $qrCodeData = generateQRCode($receiptData);

                    // Update transaction with receipt and QR code data
                    $stmt = $pdo->prepare("UPDATE payment_transactions SET
                        receipt_data = ?,
                        qr_code_data = ?
                        WHERE id = ?");
                    $stmt->execute([$receiptData, $qrCodeData, $transactionId]);

                    // Get student details for email
                    $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                    $stmt->execute([$transaction['student_id']]);
                    $student = $stmt->fetch(PDO::FETCH_ASSOC);

                    $studentDear = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');
                    // Send approval email
                    $emailSubject = "Payment Approved - HontTech Registration";
                    $emailBody = "
                    <h2>Payment Approved</h2>
                    <p>Dear {$studentDear},</p>
                    <p>Your payment for {$transaction['transaction_type']} has been approved.</p>
                    <p>Payment Details:</p>
                    <ul>
                        <li>Admission Number: " . portalStudentNumberDisplay($student, 'admission') . "</li>
                        <li>Amount: NGN {$transaction['amount']}</li>
                        <li>Transaction Reference: {$transaction['reference']}</li>
                        <li>Approval Date: " . date('d M Y, g:i A') . "</li>";

                    if ($transaction['total_installments'] > 1) {
                        $emailBody .= "<li>Installment: {$transaction['installment_number']} of {$transaction['total_installments']}</li>";
                    }

                    $emailBody .= "</ul>";

                    if ($transaction['transaction_type'] === 'Registration Fee') {
                        $emailBody .= "<p>Your application is now under review. You will be notified of the decision via email.</p>";
                    } elseif ($transaction['transaction_type'] === 'Acceptance Fee') {
                        $emailBody .= "<p>Your admission letter is now available. You can access it from your dashboard.</p>";
                    } elseif ($transaction['transaction_type'] === 'Hostel Fee') {
                        $emailBody .= "<p>Your hostel accommodation has been confirmed. You will receive further details about your room allocation.</p>";
                    } elseif ($transaction['transaction_type'] === 'School Fee') {
                        if ($transaction['total_installments'] > 1) {
                            $emailBody .= "<p>This is installment {$transaction['installment_number']} of {$transaction['total_installments']}. ";
                            if ($transaction['installment_number'] < $transaction['total_installments']) {
                                $emailBody .= "Please ensure you pay the remaining installments on time.</p>";
                            } else {
                                $emailBody .= "All installments have been paid. Your school fee is now fully paid.</p>";
                            }
                        } else {
                            $emailBody .= "<p>Your school fee has been fully paid.</p>";
                        }
                    }

                    $emailBody .= "<p>Thank you for your payment.</p>
                    <p>Best regards,<br>HontTech Administration</p>";

                    $receiptLink = portalPublicReceiptUrl($pdo, $transaction);
                    $emailBody .= "<p><strong>Receipt:</strong> <a href='" . htmlspecialchars($receiptLink, ENT_QUOTES, 'UTF-8') . "'>View Payment Receipt</a></p>";
                    $ctx = 'receipt:' . (int)$transactionId;
                    portalSendStudentReceiptNotification($pdo, $student, $transaction, $receiptLink);

                    // If acceptance fee was approved, send admission letter link as well (once per day)
                    if ($transaction['transaction_type'] === 'Acceptance Fee') {
                        $alSubject = "Admission Letter - " . APP_NAME;
                        $alLink = appBaseUrl() . "/index.php?page=admission_letter";
                        $alBody = "<h2>Admission Letter</h2>
                                  <p>Dear {$studentDear},</p>
                                  <p>Congratulations! Your acceptance fee has been confirmed and your admission letter is now available.</p>
                                  <p><a href='" . htmlspecialchars($alLink, ENT_QUOTES, 'UTF-8') . "'>View Admission Letter</a></p>
                                  <p>Regards,<br>" . htmlspecialchars(APP_NAME) . "</p>";
                        sendLoggedEmail($pdo, 'student', (int)$student['id'], (string)$student['email'], 'admission_letter', $alSubject, $alBody);
                    }
                }

                $_SESSION['payment_status_updated'] = true;
                redirect('?page=admin_payments');
            }
        }
    }


// Admin: assign/record payments (bulk or individual)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_assign_payment') {
    if (!isAdmin()) { redirect('?page=admin_login'); }

    $scope = $_POST['assign_scope'] ?? 'single'; // single|all
    $studentId = (int)($_POST['student_id'] ?? 0);

    $rawType = $_POST['transaction_type'] ?? '';
    $customName = trim((string)($_POST['custom_fee_name'] ?? ''));
    // If admin selected Custom Fee, use the provided fee name as the transaction type for storage/display.
    $type = ($rawType === '__custom__') ? $customName : $rawType;

    $amount = (float)($_POST['amount'] ?? 0);
    $status = $_POST['status'] ?? 'Approved';
    $method = 'Admin Assigned';

    // Validate
    $allowedStatus = ['Pending','Approved','Rejected','Successful','Failed'];
    $allowedTypes = ['Registration Fee','Acceptance Fee','Hostel Fee','School Fee','__custom__'];

    $isCustom = ($rawType === '__custom__');
    if ($isCustom) {
        if ($customName === '' || mb_strlen($customName) < 3) {
            $_SESSION['flash_error'] = 'Please enter a valid custom fee name.';
            redirect('?page=admin_payments');
        }
    } else {
        if (!in_array($rawType, $allowedTypes, true)) {
            $_SESSION['flash_error'] = 'Invalid payment type selected.';
            redirect('?page=admin_payments');
        }
    }
    if ($amount <= 0 || !in_array($status, $allowedStatus, true)) {
        $_SESSION['flash_error'] = 'Invalid payment assignment details.';
        redirect('?page=admin_payments');
    }

// Determine students to apply to
    $studentIds = [];
    if ($scope === 'all') {
        $st = $pdo->query("SELECT id FROM students");
        $studentIds = $st->fetchAll(PDO::FETCH_COLUMN, 0);
    } else {
        if ($studentId <= 0) {
            $_SESSION['flash_error'] = 'Please select a student.';
            redirect('?page=admin_payments');
        }
        $studentIds = [$studentId];
    }

    $adminId = (int)($_SESSION['user_id'] ?? 0);
    $now = date('Y-m-d H:i:s');


    // If admin is assigning a CUSTOM payment, create an assigned_payments record (status Not Paid) so student can pay under Other Payments
    if ($isCustom) {
        try {
            $apIns = $pdo->prepare("INSERT INTO assigned_payments (student_id, title, amount, status) VALUES (?,?,?,'Not Paid')");
            foreach ($studentIds as $sid) {
                $sid = (int)$sid;
                if ($sid <= 0) continue;
                $apIns->execute([$sid, $customName, $amount]);

                // Notify student
                try {
                    createNotification($pdo, 'student', $sid, 'New Payment Assigned', "Admin assigned a payment: {$customName} (₦" . number_format($amount,2) . "). Status: Not Paid.", "?page=payment&tab=other");
                    // Email (best effort)
                    $stx = $pdo->prepare("SELECT email, first_name, surname FROM students WHERE id=? LIMIT 1");
                    $stx->execute([$sid]);
                    $sx = $stx->fetch(PDO::FETCH_ASSOC);
                    if (!empty($sx['email'])) {
                        $subj = "New Payment Assigned - HontTech";
                        $body = "<p>Dear {$sx['first_name']} {$sx['surname']},</p><p>A new payment has been assigned to you: <strong>{$customName}</strong> (₦" . number_format($amount,2) . ").</p><p>Status: <strong>Not Paid</strong>. Please login and pay under <strong>Payment → Other Payments</strong>.</p><p>Regards,<br>HontTech</p>";
                        sendLoggedEmail($pdo, 'student', (int)$sid, (string)$sx['email'], 'assigned_payment:' . (int)$sid . ':' . md5($customName . '|' . number_format($amount, 2, '.', '')), $subj, $body);
                    }
                } catch (Throwable $e) {}
            }

            $_SESSION['flash_success'] = 'Custom payment assigned successfully (status: Not Paid).';
        } catch (Throwable $e) {
            error_log('Assign custom payment failed: ' . $e->getMessage());
            $_SESSION['flash_error'] = 'Unable to assign custom payment. Please try again.';
        }
        redirect('?page=admin_payments');
    }

$ins = $pdo->prepare("INSERT INTO payment_transactions
        (transaction_id, student_id, transaction_type, amount, reference, academic_session, semester, status, payment_method, payment_date, approval_date, approved_by, approval_notes, installment_number, total_installments, created_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 1, ?)");
    $updatedAny = false;

    foreach ($studentIds as $sid) {
        $sid = (int)$sid;
        if ($sid <= 0) continue;

        // unique transaction id
        $txid = (string)random_int(100000, 999999);
        // ensure uniqueness
        $tries = 0;
        while ($tries < 5) {
            $chk = $pdo->prepare("SELECT id FROM payment_transactions WHERE transaction_id = ? LIMIT 1");
            $chk->execute([$txid]);
            if (!$chk->fetchColumn()) break;
            $txid = (string)random_int(100000, 999999);
            $tries++;
        }

        $ref = "ADMIN_" . strtoupper(str_replace(' ', '', $type)) . "_" . time() . "_" . $sid;
        $notes = trim($_POST['notes'] ?? '');
        $sp = $pdo->prepare("SELECT session, semester FROM students WHERE id = ? LIMIT 1");
        $sp->execute([$sid]);
        $sPeriod = $sp->fetch(PDO::FETCH_ASSOC) ?: [];
        $paymentAcademicSession = trim((string)($sPeriod['session'] ?? portalCurrentAcademicSessionName($pdo)));
        $paymentSemester = portalNormalizeSemesterName($sPeriod['semester'] ?? 'First Semester');

        $payDate = ($status === 'Approved' || $status === 'Successful') ? $now : null;
        $apprDate = ($status === 'Approved') ? $now : null;
        $apprBy = ($status === 'Approved') ? $adminId : null;

        $ins->execute([$txid, $sid, $type, $amount, $ref, $status, $method, $payDate, $apprDate, $apprBy, $notes, $now]);
        $updatedAny = true;

        // Update student fee status when approved/successful and run acceptance-fee matric trigger once.
        if (in_array($status, ['Approved', 'Successful'], true)) {
            $col = null;
            if ($type === 'Registration Fee') $col = 'registration_fee_status';
            if ($type === 'Acceptance Fee') $col = 'acceptance_fee_status';
            if ($type === 'Hostel Fee') $col = 'hostel_fee_status';
            if ($type === 'School Fee') $col = 'school_fee_status';
            if ($col) {
                $upd = $pdo->prepare("UPDATE students SET {$col} = 'Paid' WHERE id = ?");
                $upd->execute([$sid]);
            }
            try { portalRunAcceptanceFeeSuccessHooks($pdo, (int)$sid, $type); } catch (Throwable $e) {}
        }
    }

    $_SESSION['flash_success'] = $updatedAny ? 'Payment assigned successfully.' : 'No students were updated.';
    redirect('?page=admin_payments');
}

// Update application status - MODIFIED TO GENERATE ID CARD WHEN APPROVED
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_status') {
        $studentId = $_POST['student_id'] ?? 0;
        $status = trim((string)($_POST['status'] ?? ''));
        $remarks = trim((string)($_POST['remarks'] ?? ''));

        if ($studentId > 0 && $status !== '') {
            $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $stmt->execute([$studentId]);
            $student = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($student) {
                $oldStatus = trim((string)($student['application_status'] ?? 'Pending'));
                $stmt = $pdo->prepare("UPDATE students SET application_status = ? WHERE id = ?");
                $stmt->execute([$status, $studentId]);

                // If status is approved, set acceptance fee status to pending and generate ID card
                if ($status === 'Approved') {
                    $stmt = $pdo->prepare("UPDATE students SET acceptance_fee_status = 'Pending' WHERE id = ?");
                    $stmt->execute([$studentId]);

                    // Generate ID card
                    $stmt = $pdo->prepare("UPDATE students SET id_card_generated = TRUE, id_card_generated_date = NOW() WHERE id = ?");
                    $stmt->execute([$studentId]);
                }

                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC);

                $emailOk = true;
                if ($student && !empty($student['email']) && $oldStatus !== $status) {
                    try {
                        $emailOk = portalSendStudentApplicationStatusNotification($pdo, $student, $oldStatus, $status, $remarks);
                        if (in_array(strtolower(trim($status)), ['approved', 'admitted'], true)) {
                            portalSendStudentAdmissionGrantedEmail($pdo, $student, $status, $remarks);
                        }
                    } catch (Throwable $e) {
                        $emailOk = false;
                    }
                }

                // In-app notification to student (and prompt action where needed)
                createNotification(
                    $pdo,
                    'student',
                    (int)$studentId,
                    'Application Status Updated',
                    'Your application status changed from ' . ($oldStatus !== '' ? $oldStatus : 'Pending') . ' to ' . $status . '.',
                    '?page=student_dashboard'
                );

                if ($status === 'Approved') {
                    // Ensure offer is pending until student accepts/rejects
                    try {
                        $pdo->prepare("UPDATE students SET offer_status = 'Pending' WHERE id = ?")->execute([$studentId]);
                    } catch (Throwable $e) {}

                    createNotification(
                        $pdo,
                        'student',
                        (int)$studentId,
                        'Admission Offered',
                        'Your admission has been offered. Kindly go to your school portal to ACCEPT or REJECT the admission offer.',
                        '?page=student_dashboard'
                    );
                }

                $_SESSION['status_updated'] = $emailOk ? 'Application status updated and notification sent.' : 'Email notification failed to send, but the action was completed.';
                redirect('?page=admin_dashboard');
            }
        }
    }

    // Update admin decision
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_decision') {
        $studentId = $_POST['student_id'] ?? 0;
        $qualified = isset($_POST['qualified']) ? 1 : 0;
        $officerName = $_POST['officer_name'] ?? '';
        $officerSignature = $_POST['officer_signature'] ?? '';

        if ($studentId > 0 && !empty($officerName)) {
            // Check if decision already exists
            $stmt = $pdo->prepare("SELECT COUNT(*) FROM admin_decisions WHERE student_id = ?");
            $stmt->execute([$studentId]);
            $decisionExists = $stmt->fetchColumn() > 0;

            if ($decisionExists) {
                // Update existing decision
                $stmt = $pdo->prepare("UPDATE admin_decisions SET qualified = ?, officer_name = ?, officer_signature = ? WHERE student_id = ?");
                $stmt->execute([$qualified, $officerName, $officerSignature, $studentId]);
            } else {
                // Insert new decision
                $stmt = $pdo->prepare("INSERT INTO admin_decisions (student_id, qualified, officer_name, officer_signature) VALUES (?, ?, ?, ?)");
                $stmt->execute([$studentId, $qualified, $officerName, $officerSignature]);
            }

            $_SESSION['decision_updated'] = true;
            redirect('?page=admin_dashboard');
        }
    }

    // Update system settings
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_user_progress_control') {
        $allowedProgressControls = [
            portalLazyLoaderSettingKey() => 'Enable or disable tracking of safe user progress locations.',
            portalResumeProgressSettingKey() => 'Enable or disable restoring the last safe saved user location after login.',
            portalThemeFeatureSettingKey() => 'Enable or disable the Light / Dark mode switcher for logged-in portal users.',
            portalSearchBarSettingKey() => 'Enable or disable the live portal search bar for admins and students.'
        ];

        $controlKey = trim((string)($_POST['control_key'] ?? ''));
        $controlValue = strtolower(trim((string)($_POST['control_value'] ?? 'disabled'))) === 'enabled' ? 'enabled' : 'disabled';
        $returnUrl = portalSafeReturnUrl((string)($_POST['return_url'] ?? '?page=settings#user-progress-controls'), '?page=settings#user-progress-controls');

        if (!isAdmin()) {
            flash('danger', 'Only admins can update User Progress controls.');
            redirect($returnUrl);
        }

        if (!isset($allowedProgressControls[$controlKey])) {
            flash('danger', 'Invalid User Progress control selected.');
            redirect($returnUrl);
        }

        setSystemSettingValue($pdo, $controlKey, $controlValue, $allowedProgressControls[$controlKey]);

        $controlLabels = [
            portalLazyLoaderSettingKey() => 'Lazy Loader',
            portalResumeProgressSettingKey() => 'Resume Progress',
            portalThemeFeatureSettingKey() => 'Light / Dark Mode',
            portalSearchBarSettingKey() => 'Portal Search Bar'
        ];
        $controlLabel = $controlLabels[$controlKey] ?? 'Setting';
        flash('success', $controlLabel . ' has been ' . ($controlValue === 'enabled' ? 'enabled' : 'disabled') . '.');
        redirect($returnUrl);
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_branding_settings') {
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=theme_branding'));
        if (!isAdmin()) {
            flash('danger', 'Only authorized admins can update Branding settings.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding');
        }

        $defaults = portalBrandingDefaults($pdo);
        $settingsToSave = [];
        foreach (portalBrandingColorFieldKeys() as $key) {
            $picker = trim((string)($_POST[$key . '_picker'] ?? ''));
            $manual = trim((string)($_POST[$key] ?? ''));
            $source = $manual !== '' ? $manual : $picker;
            $settingsToSave[$key] = portalNormalizeHexColor($source, (string)($defaults[$key] ?? '#003366'));
        }
        $settingsToSave['branding_apply_scope'] = in_array((string)($_POST['branding_apply_scope'] ?? 'global'), ['global', 'selected'], true) ? (string)$_POST['branding_apply_scope'] : 'global';
        foreach (['branding_apply_admin_pages','branding_apply_student_pages','branding_apply_login_page','branding_apply_sidebar','branding_apply_header','branding_apply_dashboard_cards','branding_apply_forms','branding_apply_tables','branding_apply_buttons','branding_apply_links','branding_apply_status_badges'] as $toggleKey) {
            $settingsToSave[$toggleKey] = isset($_POST[$toggleKey]) ? '1' : '0';
        }
        foreach ($settingsToSave as $key => $value) {
            setSystemSettingValue($pdo, $key, (string)$value, 'Portal branding setting');
        }
        if (($settingsToSave['branding_apply_scope'] ?? 'global') === 'global') {
            setSystemSettingValue($pdo, 'theme_color_1', (string)$settingsToSave['branding_primary_color'], 'Legacy primary theme color synchronized with global Branding');
            setSystemSettingValue($pdo, 'theme_color_2', (string)$settingsToSave['branding_accent_color'], 'Legacy accent theme color synchronized with global Branding');
            setSystemSettingValue($pdo, 'theme_color_3', (string)$settingsToSave['branding_background_color'], 'Legacy background theme color synchronized with global Branding');
            setSystemSettingValue($pdo, 'theme_color_4', (string)$settingsToSave['branding_surface_color'], 'Legacy surface theme color synchronized with global Branding');
            setSystemSettingValue($pdo, 'theme_color_5', (string)$settingsToSave['branding_text_color'], 'Legacy text theme color synchronized with global Branding');
        }
        flash('success', 'Branding settings updated successfully.');
        redirect($returnTo !== '' ? $returnTo : '?page=theme_branding');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'reset_branding_settings') {
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=theme_branding'));
        if (!isAdmin()) {
            flash('danger', 'Only authorized admins can reset Branding settings.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding');
        }
        $defaults = portalBrandingDefaults($pdo);
        foreach ($defaults as $key => $value) {
            setSystemSettingValue($pdo, $key, (string)$value, 'Portal branding default setting');
        }
        setSystemSettingValue($pdo, 'theme_color_1', (string)$defaults['branding_primary_color'], 'Legacy primary theme color synchronized with Branding');
        setSystemSettingValue($pdo, 'theme_color_2', (string)$defaults['branding_accent_color'], 'Legacy accent theme color synchronized with Branding');
        setSystemSettingValue($pdo, 'theme_color_3', (string)$defaults['branding_background_color'], 'Legacy background theme color synchronized with Branding');
        setSystemSettingValue($pdo, 'theme_color_4', (string)$defaults['branding_surface_color'], 'Legacy surface theme color synchronized with Branding');
        setSystemSettingValue($pdo, 'theme_color_5', (string)$defaults['branding_text_color'], 'Legacy text theme color synchronized with Branding');
        flash('success', 'Branding settings have been reset to the portal defaults.');
        redirect($returnTo !== '' ? $returnTo : '?page=theme_branding');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_admin_theme_settings') {
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=theme_branding&branding_tab=admin_theme#settings-admin-theme'));
        if (!isAdmin()) {
            flash('danger', 'Only authorized admins can update Admin Theme settings.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding&branding_tab=admin_theme#settings-admin-theme');
        }
        $defaults = portalAdminThemeDefaults($pdo);
        $settingsToSave = [];
        foreach (portalAdminThemeColorFieldKeys() as $key) {
            $picker = trim((string)($_POST[$key . '_picker'] ?? ''));
            $manual = trim((string)($_POST[$key] ?? ''));
            $source = $manual !== '' ? $manual : $picker;
            $settingsToSave[$key] = portalNormalizeHexColor($source, (string)($defaults[$key] ?? '#003366'));
        }
        foreach ($settingsToSave as $key => $value) {
            setSystemSettingValue($pdo, $key, (string)$value, 'Admin theme setting');
        }
        flash('success', 'Admin theme settings updated successfully.');
        redirect($returnTo !== '' ? $returnTo : '?page=theme_branding&branding_tab=admin_theme#settings-admin-theme');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'reset_admin_theme_settings') {
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=theme_branding&branding_tab=admin_theme#settings-admin-theme'));
        if (!isAdmin()) {
            flash('danger', 'Only authorized admins can reset Admin Theme settings.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding&branding_tab=admin_theme#settings-admin-theme');
        }
        foreach (portalAdminThemeDefaults($pdo) as $key => $value) {
            setSystemSettingValue($pdo, $key, (string)$value, 'Admin theme default setting');
        }
        flash('success', 'Admin theme settings have been reset to the defaults.');
        redirect($returnTo !== '' ? $returnTo : '?page=theme_branding&branding_tab=admin_theme#settings-admin-theme');
    }


    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'reset_student_dashboard_theme') {
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=add#dashboard-design-rules'));
        if (!isAdmin()) {
            flash('danger', 'Only authorized admins can reset Student Dashboard Design Rules.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding&branding_tab=dashboard_rules#dashboard-design-rules');
        }
        try {
            portalEnsureDashboardDesignRulesSchema($pdo);
            $pdo->exec("DELETE FROM dashboard_design_rules");
            flash('success', 'Student dashboard theme rules have been reset to the safe defaults.');
        } catch (Throwable $e) {
            flash('danger', 'Unable to reset the student dashboard theme rules right now.');
        }
        redirect($returnTo !== '' ? $returnTo : '?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=add#dashboard-design-rules');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_dashboard_design_rule') {
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=saved#dashboard-design-rules'));
        if (!isAdmin()) {
            flash('danger', 'Only authorized admins can manage student dashboard design rules.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding#dashboard-design-rules');
        }

        $ruleId = (int)($_POST['rule_id'] ?? 0);
        $existingRule = $ruleId > 0 ? portalDashboardDesignRuleById($pdo, $ruleId) : null;
        $payload = portalBuildDashboardDesignRulePayload($pdo, $_POST, is_array($existingRule) ? $existingRule : null);
        $adminId = (int)($_SESSION['user_id'] ?? 0);

        try {
            $savedRuleId = portalSaveDashboardDesignRule($pdo, $payload, $adminId, $ruleId);
            flash('success', $ruleId > 0 ? 'Dashboard design rule updated successfully.' : 'Dashboard design rule created successfully.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=saved#dashboard-design-rules');
        } catch (Throwable $e) {
            flash('danger', 'Unable to save dashboard design rule right now.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding#dashboard-design-rules');
        }
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'toggle_dashboard_design_rule') {
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=saved#dashboard-design-rules'));
        if (!isAdmin()) {
            flash('danger', 'Only authorized admins can update student dashboard design rules.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding#dashboard-design-rules');
        }

        $ruleId = (int)($_POST['rule_id'] ?? 0);
        $setActive = isset($_POST['set_active']) ? (int)$_POST['set_active'] : -1;
        if ($ruleId <= 0 || !in_array($setActive, [0, 1], true)) {
            flash('danger', 'Invalid dashboard design rule selected.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding#dashboard-design-rules');
        }

        try {
            portalEnsureDashboardDesignRulesSchema($pdo);
            $stmt = $pdo->prepare("UPDATE dashboard_design_rules SET is_active = ?, updated_by = ? WHERE id = ?");
            $stmt->execute([$setActive, (int)($_SESSION['user_id'] ?? 0), $ruleId]);
            flash('success', $setActive === 1 ? 'Dashboard design rule enabled.' : 'Dashboard design rule disabled.');
        } catch (Throwable $e) {
            flash('danger', 'Unable to update the dashboard design rule status.');
        }
        redirect($returnTo !== '' ? $returnTo : '?page=theme_branding#dashboard-design-rules');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_dashboard_design_rule') {
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=saved#dashboard-design-rules'));
        if (!isAdmin()) {
            flash('danger', 'Only authorized admins can delete student dashboard design rules.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding#dashboard-design-rules');
        }

        $ruleId = (int)($_POST['rule_id'] ?? 0);
        if ($ruleId <= 0) {
            flash('danger', 'Invalid dashboard design rule selected.');
            redirect($returnTo !== '' ? $returnTo : '?page=theme_branding#dashboard-design-rules');
        }

        try {
            portalEnsureDashboardDesignRulesSchema($pdo);
            $stmt = $pdo->prepare("DELETE FROM dashboard_design_rules WHERE id = ?");
            $stmt->execute([$ruleId]);
            flash('success', 'Dashboard design rule deleted successfully.');
        } catch (Throwable $e) {
            flash('danger', 'Unable to delete the selected dashboard design rule.');
        }
        redirect($returnTo !== '' ? $returnTo : '?page=theme_branding#dashboard-design-rules');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_settings') {
        $settings = [
            'registration_fee' => $_POST['registration_fee'] ?? 10000,
            'acceptance_fee' => $_POST['acceptance_fee'] ?? 15000,
            'hostel_fee' => $_POST['hostel_fee'] ?? 25000,
            'school_fee' => $_POST['school_fee'] ?? 75000,
            'payment_description' => $_POST['payment_description'] ?? 'HontTech Registration Fee',
            'academic_session' => $_POST['academic_session'] ?? date('Y') . '/' . (date('Y') + 1),
            'application_deadline' => $_POST['application_deadline'] ?? date('Y-m-d', strtotime('+6 months')),
            'email_notifications' => isset($_POST['email_notifications']) ? 'enabled' : 'disabled',
            'payment_approval' => $_POST['payment_approval'] ?? 'automatic',
            'installment_enabled' => isset($_POST['installment_enabled']) ? 'enabled' : 'disabled',
            'late_fee_enabled' => isset($_POST['late_fee_enabled']) ? 'enabled' : 'disabled',
            'late_fee_type' => in_array((string)($_POST['late_fee_type'] ?? 'fixed'), ['fixed','percentage'], true) ? (string)$_POST['late_fee_type'] : 'fixed',
            'late_fee_value' => (string)max(0, (float)($_POST['late_fee_value'] ?? 0)),
            'late_fee_grace_days' => (string)max(0, (int)($_POST['late_fee_grace_days'] ?? 0)),
            'late_fee_apply_mode' => in_array((string)($_POST['late_fee_apply_mode'] ?? 'session_closure'), ['session_closure','semester_end','both'], true) ? (string)$_POST['late_fee_apply_mode'] : 'session_closure',
            'late_fee_apply_to' => in_array((string)($_POST['late_fee_apply_to'] ?? 'all_unpaid'), ['all_unpaid','approved_only','active_only'], true) ? (string)$_POST['late_fee_apply_to'] : 'all_unpaid',
            'late_fee_auto_generate' => isset($_POST['late_fee_auto_generate']) ? 'enabled' : 'disabled',
            'late_fee_reason' => trim((string)($_POST['late_fee_reason'] ?? 'Late school fee charged after deadline.')),
            'late_fee_first_semester_end_date' => trim((string)($_POST['late_fee_first_semester_end_date'] ?? '')),
            'late_fee_second_semester_end_date' => trim((string)($_POST['late_fee_second_semester_end_date'] ?? '')),
            'portal_title' => trim($_POST['portal_title'] ?? 'HontTech System'),
            'paystack_enabled' => isset($_POST['paystack_enabled']) ? 'enabled' : 'disabled',
            'paystack_public_key' => trim($_POST['paystack_public_key'] ?? ''),
            'paystack_secret_key' => trim($_POST['paystack_secret_key'] ?? ''),
            'online_payment_enabled' => isset($_POST['online_payment_enabled']) ? 'enabled' : 'disabled',
            'default_payment_method' => in_array(strtolower(trim((string)($_POST['default_payment_method'] ?? 'auto'))), ['auto','manual'], true) ? strtolower(trim((string)($_POST['default_payment_method'] ?? 'auto'))) : 'auto',
            'default_gateway' => strtolower(trim((string)($_POST['default_gateway'] ?? trim((string)($_POST['default_gateways_text'] ?? 'paystack'))))),
            'default_gateways' => strtolower(trim((string)($_POST['default_gateway'] ?? trim((string)($_POST['default_gateways_text'] ?? 'paystack'))))),
            'flutterwave_enabled' => isset($_POST['flutterwave_enabled']) ? 'enabled' : 'disabled',
            'flutterwave_public_key' => trim($_POST['flutterwave_public_key'] ?? ''),
            'flutterwave_secret_key' => trim($_POST['flutterwave_secret_key'] ?? ''),
            'flutterwave_currency' => trim($_POST['flutterwave_currency'] ?? 'NGN'),
            'monnify_enabled' => isset($_POST['monnify_enabled']) ? 'enabled' : 'disabled',
            'monnify_api_key' => trim($_POST['monnify_api_key'] ?? ''),
            'monnify_secret_key' => trim($_POST['monnify_secret_key'] ?? ''),
            'monnify_contract_code' => trim($_POST['monnify_contract_code'] ?? ''),
            'remita_enabled' => isset($_POST['remita_enabled']) ? 'enabled' : 'disabled',
            'remita_merchant_id' => trim($_POST['remita_merchant_id'] ?? ''),
            'remita_service_type_id' => trim($_POST['remita_service_type_id'] ?? ''),
            'remita_api_key' => trim($_POST['remita_api_key'] ?? ''),
            'paypal_enabled' => isset($_POST['paypal_enabled']) ? 'enabled' : 'disabled',
            'paypal_client_id' => trim($_POST['paypal_client_id'] ?? ''),
            'paypal_client_secret' => trim($_POST['paypal_client_secret'] ?? ''),
            'interswitch_enabled' => isset($_POST['interswitch_enabled']) ? 'enabled' : 'disabled',
            'interswitch_merchant_code' => trim($_POST['interswitch_merchant_code'] ?? ''),
            'interswitch_pay_item_id' => trim($_POST['interswitch_pay_item_id'] ?? ''),
            'interswitch_currency' => trim($_POST['interswitch_currency'] ?? '566'),
            'interswitch_mode' => trim($_POST['interswitch_mode'] ?? 'LIVE'),
            'manual_payment_enabled' => isset($_POST['manual_payment_enabled']) ? 'enabled' : 'disabled',
            'manual_payment_instructions' => trim($_POST['manual_payment_instructions'] ?? ''),
            'smtp_host' => trim($_POST['smtp_host'] ?? ''),
            'smtp_port' => trim($_POST['smtp_port'] ?? ''),
            'smtp_username' => trim($_POST['smtp_username'] ?? ''),
            'smtp_password' => (string)($_POST['smtp_password'] ?? ''),
            'smtp_from_email' => trim($_POST['smtp_from_email'] ?? ''),
            'smtp_from_name' => trim($_POST['smtp_from_name'] ?? ''),
            'smtp_reply_to' => trim($_POST['smtp_reply_to'] ?? ''),
            'idcard_signatory_name' => trim($_POST['idcard_signatory_name'] ?? ''),
            'admission_signatory_name' => trim($_POST['admission_signatory_name'] ?? ''),
            'application_signatory_name' => trim($_POST['application_signatory_name'] ?? ''),
            'account_officer_signatory_name' => trim($_POST['account_officer_signatory_name'] ?? ''),
            'lazy_loader_enabled' => strtolower(trim((string)($_POST['lazy_loader_enabled'] ?? getSystemSettingValue($pdo, 'lazy_loader_enabled', getSystemSettingValue($pdo, 'resume_user_progress_enabled', 'disabled'))))) === 'enabled' ? 'enabled' : 'disabled',
            'resume_user_progress_enabled' => strtolower(trim((string)($_POST['resume_user_progress_enabled'] ?? getSystemSettingValue($pdo, 'resume_user_progress_enabled', getSystemSettingValue($pdo, 'lazy_loader_enabled', 'disabled'))))) === 'enabled' ? 'enabled' : 'disabled',
            'theme_mode_enabled' => strtolower(trim((string)($_POST['theme_mode_enabled'] ?? getSystemSettingValue($pdo, 'theme_mode_enabled', 'enabled')))) === 'enabled' ? 'enabled' : 'disabled',
            'portal_search_bar_enabled' => strtolower(trim((string)($_POST['portal_search_bar_enabled'] ?? getSystemSettingValue($pdo, 'portal_search_bar_enabled', 'enabled')))) === 'enabled' ? 'enabled' : 'disabled',
            'school_fee_enforcement_on_promotion' => isset($_POST['school_fee_enforcement_on_promotion']) ? 'enabled' : 'disabled',
            'school_fee_enforcement_on_session_change' => isset($_POST['school_fee_enforcement_on_session_change']) ? 'enabled' : 'disabled',
            'fee_difference_auto_calculation' => isset($_POST['fee_difference_auto_calculation']) ? 'enabled' : 'disabled',
            'previous_debt_blocking' => isset($_POST['previous_debt_blocking']) ? 'enabled' : 'disabled',
            'hostel_fee_depend_on_accommodation_status' => isset($_POST['hostel_fee_depend_on_accommodation_status']) ? 'enabled' : 'disabled',
            'off_campus_verification_required' => isset($_POST['off_campus_verification_required']) ? 'enabled' : 'disabled',
            'pending_claim_behavior' => in_array((string)($_POST['pending_claim_behavior'] ?? getSystemSettingValue($pdo, 'pending_claim_behavior', 'exempt_until_review')), ['exempt_until_review','require_hostel_until_review'], true) ? (string)($_POST['pending_claim_behavior'] ?? getSystemSettingValue($pdo, 'pending_claim_behavior', 'exempt_until_review')) : 'exempt_until_review',
            'dashboard_payment_popup_enabled' => isset($_POST['dashboard_payment_popup_enabled']) ? 'enabled' : 'disabled',
        ];

        foreach (portalPaymentGatewayRegistry() as $providerKey => $providerDef) {
            $settings[$providerKey . '_enabled'] = isset($_POST[$providerKey . '_enabled']) ? 'enabled' : (($settings[$providerKey . '_enabled'] ?? getSystemSettingValue($pdo, $providerKey . '_enabled', (!empty($providerDef['native_checkout']) && $providerKey === 'paystack') ? 'enabled' : 'disabled')));
            foreach ((array)($providerDef['fields'] ?? []) as $fieldKey => $fieldMeta) {
                $settingKey = $providerKey . '_' . $fieldKey;
                if (array_key_exists($settingKey, $settings)) {
                    if (($fieldMeta['type'] ?? 'text') === 'select') {
                        $settings[$settingKey] = strtolower(trim((string)($settings[$settingKey] ?? ($fieldMeta['default'] ?? 'live'))));
                    }
                    continue;
                }
                $incoming = $_POST[$settingKey] ?? null;
                $value = trim((string)($incoming ?? getSystemSettingValue($pdo, $settingKey, (string)($fieldMeta['default'] ?? ''))));
                if (($fieldMeta['type'] ?? 'text') === 'select') {
                    $value = strtolower($value);
                    $options = array_map('strtolower', array_keys((array)($fieldMeta['options'] ?? [])));
                    if ($options && !in_array($value, $options, true)) {
                        $value = strtolower((string)($fieldMeta['default'] ?? $options[0] ?? 'live'));
                    }
                }
                $settings[$settingKey] = $value;
            }
            foreach (['charge_percent','charge_flat','charge_cap','charge_flat_waive_below'] as $chargeKey) {
                $settingKey = $providerKey . '_' . $chargeKey;
                if (!array_key_exists($settingKey, $settings)) {
                    $settings[$settingKey] = trim((string)($_POST[$settingKey] ?? getSystemSettingValue($pdo, $settingKey, '0')));
                }
            }
        }

        $gatewayRegistry = portalPaymentGatewayRegistry();
        $defaultGateway = strtolower(trim((string)($settings['default_gateway'] ?? 'paystack')));
        if ($defaultGateway === '' || !isset($gatewayRegistry[$defaultGateway])) {
            $defaultGateway = strtolower(trim((string)array_key_first($gatewayRegistry)));
        }
        if ($defaultGateway === '') { $defaultGateway = 'paystack'; }
        $settings['default_gateway'] = $defaultGateway;
        $settings['default_gateways'] = $defaultGateway;
        if (!in_array((string)($settings['default_payment_method'] ?? 'auto'), ['auto','manual'], true)) {
            $settings['default_payment_method'] = 'auto';
        }

        foreach ($settings as $key => $value) {
            // Self-healing: insert setting if it does not exist yet (or update if it exists)
            setSystemSettingValue($pdo, (string)$key, (string)$value);
        }
        try {
            $postedAcademicSession = portalNormalizeSessionNameSafe((string)($settings['academic_session'] ?? ''));
            if ($postedAcademicSession !== '' && portalTableExistsSafe($pdo, 'academic_sessions')) {
                $stmt = $pdo->prepare("SELECT id FROM academic_sessions WHERE name = ? ORDER BY id DESC LIMIT 1");
                $stmt->execute([$postedAcademicSession]);
                $postedSessionId = (int)($stmt->fetchColumn() ?: 0);
                if ($postedSessionId <= 0) {
                    try {
                        $ins = $pdo->prepare("INSERT INTO academic_sessions (name, is_current, is_active) VALUES (?, 1, 1)");
                        $ins->execute([$postedAcademicSession]);
                        $postedSessionId = (int)($pdo->lastInsertId() ?: 0);
                    } catch (Throwable $e) {
                        $postedSessionId = 0;
                    }
                }
                if ($postedSessionId > 0) {
                    try { $pdo->exec("UPDATE academic_sessions SET is_current = 0 WHERE COALESCE(is_current,0) <> 0"); } catch (Throwable $e) {}
                    try {
                        $up = $pdo->prepare("UPDATE academic_sessions SET is_current = 1, is_active = 1 WHERE id = ?");
                        $up->execute([$postedSessionId]);
                    } catch (Throwable $e) {}
                }
            }
        } catch (Throwable $e) {}
        try { if (function_exists('portalSynchronizeCurrentSessionAcrossPortal')) portalSynchronizeCurrentSessionAcrossPortal($pdo); } catch (Throwable $e) {}
        try { if (function_exists('portalSeedManagedFeesIntoManager')) portalSeedManagedFeesIntoManager($pdo); } catch (Throwable $e) {}

        $_SESSION['settings_updated'] = true;
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=admin_settings'));
        redirect($returnTo !== '' ? $returnTo : '?page=admin_settings');
    }




    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'toggle_payment_gateway_status') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        $provider = strtolower(trim((string)($_POST['provider'] ?? '')));
        $registry = portalPaymentGatewayRegistry();
        if (!isset($registry[$provider])) {
            flash('danger', 'Unknown payment gateway selected.');
            redirect('?page=admin_fees_manager&fees_tab=payment_gateways');
        }
        $requestedState = strtolower(trim((string)($_POST['next_state'] ?? '')));
        $enable = $requestedState === 'enable' ? true : ($requestedState === 'disable' ? false : !portalGatewayEnabled($pdo, $provider));
        setSystemSettingValue($pdo, $provider . '_enabled', $enable ? 'enabled' : 'disabled', portalGatewayLabel($provider) . ' gateway status');
        if ($enable) {
            $currentDefault = portalDefaultGatewayProvider($pdo);
            if ($currentDefault === '') {
                setSystemSettingValue($pdo, 'default_gateway', $provider, 'Default online payment gateway');
                setSystemSettingValue($pdo, 'default_gateways', $provider, 'Default gateways shown first');
            }
        } else {
            $currentDefault = portalDefaultGatewayProvider($pdo);
            if ($currentDefault === $provider) {
                $fallback = portalGatewayFallbackProvider($pdo, $provider);
                setSystemSettingValue($pdo, 'default_gateway', $fallback, 'Default online payment gateway');
                setSystemSettingValue($pdo, 'default_gateways', $fallback, 'Default gateways shown first');
            }
        }
        flash('success', portalGatewayLabel($provider) . ($enable ? ' enabled successfully.' : ' disabled successfully.'));
        redirect('?page=admin_fees_manager&fees_tab=payment_gateways#gateway-card-' . urlencode($provider));
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_payment_gateway_configuration') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        $provider = strtolower(trim((string)($_POST['provider'] ?? '')));
        $registry = portalPaymentGatewayRegistry();
        if (!isset($registry[$provider])) {
            flash('danger', 'Unknown payment gateway selected.');
            redirect('?page=admin_fees_manager&fees_tab=payment_gateways');
        }
        try {
            $keys = portalGatewaySettingKeysForProvider($provider);
            if ($keys) {
                $ph = implode(',', array_fill(0, count($keys), '?'));
                $stmt = $pdo->prepare("DELETE FROM system_settings WHERE setting_key IN ($ph)");
                $stmt->execute($keys);
            }
            setSystemSettingValue($pdo, $provider . '_enabled', 'disabled', portalGatewayLabel($provider) . ' gateway status');
            $currentDefault = portalDefaultGatewayProvider($pdo);
            if ($currentDefault === $provider) {
                $fallback = portalGatewayFallbackProvider($pdo, $provider);
                setSystemSettingValue($pdo, 'default_gateway', $fallback, 'Default online payment gateway');
                setSystemSettingValue($pdo, 'default_gateways', $fallback, 'Default gateways shown first');
            }
            flash('success', portalGatewayLabel($provider) . ' configuration deleted and gateway disabled.');
        } catch (Throwable $e) {
            flash('danger', 'Unable to delete that gateway configuration right now.');
        }
        redirect('?page=admin_fees_manager&fees_tab=payment_gateways');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'test_payment_gateway_connection') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        $provider = strtolower(trim((string)($_POST['provider'] ?? '')));
        $registry = portalPaymentGatewayRegistry();
        if (!isset($registry[$provider])) {
            flash('danger', 'Unknown payment gateway selected for test.');
            redirect('?page=admin_fees_manager&fees_tab=payment_gateways');
        }
        $state = portalGatewayConfigurationState($pdo, $provider);
        if (!$state['ok']) {
            flash('warning', portalGatewayLabel($provider) . ' is not fully configured. ' . $state['message']);
            redirect('?page=admin_fees_manager&fees_tab=payment_gateways#gateway-card-' . urlencode($provider));
        }
        $mode = portalGatewayModeValue($pdo, $provider);
        $baseUrl = trim((string)getSystemSettingValue($pdo, $provider . '_' . ($mode === 'sandbox' ? 'sandbox_url' : 'base_url'), ''));
        if ($baseUrl === '') {
            $baseUrl = trim((string)getSystemSettingValue($pdo, $provider . '_base_url', ''));
        }
        $ok = false;
        $message = '';
        if ($baseUrl !== '') {
            try {
                $ch = curl_init();
                curl_setopt_array($ch, [
                    CURLOPT_URL => $baseUrl,
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_NOBODY => true,
                    CURLOPT_TIMEOUT => 10,
                    CURLOPT_FOLLOWLOCATION => true,
                    CURLOPT_SSL_VERIFYPEER => true,
                    CURLOPT_SSL_VERIFYHOST => 2,
                ]);
                curl_exec($ch);
                $errno = curl_errno($ch);
                $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $err = curl_error($ch);
                curl_close($ch);
                if ($errno === 0 && $status >= 200 && $status < 500) {
                    $ok = true;
                    $message = portalGatewayLabel($provider) . ' connection test reached endpoint (' . $status . ').';
                } else {
                    $message = portalGatewayLabel($provider) . ' endpoint test could not complete' . ($err !== '' ? ': ' . $err : '.');
                }
            } catch (Throwable $e) {
                $message = portalGatewayLabel($provider) . ' endpoint test failed.';
            }
        } else {
            $ok = true;
            $message = portalGatewayLabel($provider) . ' credentials are saved. No base URL was set, so endpoint reachability was skipped.';
        }
        flash($ok ? 'success' : 'warning', $message);
        redirect('?page=admin_fees_manager&fees_tab=payment_gateways#gateway-card-' . urlencode($provider));
    }



    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_late_fee_rule') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        portalEnsureLateFeeRuleSchema($pdo);
        $editId = max(0, (int)($_POST['edit_late_fee_rule_id'] ?? 0));

        try {
            $row = portalValidateLateFeeRuleScope($pdo, [
                'id' => $editId,
                'session_name' => $_POST['late_fee_rule_session_name'] ?? '',
                'semester' => $_POST['late_fee_rule_semester'] ?? '',
                'faculty_id' => $_POST['late_fee_rule_faculty_id'] ?? 0,
                'department_id' => $_POST['late_fee_rule_department_id'] ?? 0,
                'programme_id' => $_POST['late_fee_rule_programme_id'] ?? 0,
                'mode_of_study' => $_POST['late_fee_rule_mode_of_study'] ?? '',
                'mode_of_study_custom' => $_POST['late_fee_rule_mode_of_study_custom'] ?? '',
                'course_of_study_id' => $_POST['late_fee_rule_course_of_study_id'] ?? 0,
                'course_of_study' => $_POST['late_fee_rule_course_of_study_label'] ?? ($_POST['late_fee_rule_course_of_study'] ?? ''),
                'level' => $_POST['late_fee_rule_level'] ?? '',
                'late_fee_type' => $_POST['late_fee_rule_type'] ?? 'fixed',
                'late_fee_value' => $_POST['late_fee_rule_value'] ?? 0,
                'trigger_condition' => $_POST['late_fee_rule_trigger_condition'] ?? 'session_closure',
                'due_date' => $_POST['late_fee_rule_due_date'] ?? '',
                'is_enabled' => isset($_POST['late_fee_rule_is_enabled']) ? 1 : 0,
                'reason' => $_POST['late_fee_rule_reason'] ?? '',
            ]);

            if ($row['trigger_condition'] === 'due_date' && $row['due_date'] === '') {
                throw new RuntimeException('Please choose a due date when using the configured due date trigger.');
            }

            $dupParams = [
                $row['session_name'] !== '' ? $row['session_name'] : null,
                $row['semester'] !== '' ? $row['semester'] : null,
                $row['faculty_id'] > 0 ? $row['faculty_id'] : null,
                $row['department_id'] > 0 ? $row['department_id'] : null,
                $row['programme_id'] > 0 ? $row['programme_id'] : null,
                $row['mode_of_study'] !== '' ? $row['mode_of_study'] : null,
                $row['mode_of_study_custom'] !== '' ? $row['mode_of_study_custom'] : null,
                $row['course_of_study_id'] > 0 ? $row['course_of_study_id'] : null,
                $row['course_of_study'] !== '' ? $row['course_of_study'] : null,
                $row['level'] !== '' ? $row['level'] : null,
                $row['trigger_condition'],
                $editId,
            ];

            $dupSql = "SELECT id
                FROM late_fee_rules
                WHERE COALESCE(session_name, '') <=> COALESCE(?, '')
                  AND COALESCE(semester, '') <=> COALESCE(?, '')
                  AND COALESCE(faculty_id, 0) <=> COALESCE(?, 0)
                  AND COALESCE(department_id, 0) <=> COALESCE(?, 0)
                  AND COALESCE(programme_id, 0) <=> COALESCE(?, 0)
                  AND COALESCE(mode_of_study, '') <=> COALESCE(?, '')
                  AND COALESCE(mode_of_study_custom, '') <=> COALESCE(?, '')
                  AND COALESCE(course_of_study_id, 0) <=> COALESCE(?, 0)
                  AND (
                        COALESCE(course_of_study_id, 0) <> 0
                        OR COALESCE(course_of_study, '') <=> COALESCE(?, '')
                  )
                  AND COALESCE(level, '') <=> COALESCE(?, '')
                  AND COALESCE(trigger_condition, 'session_closure') = ?
                  AND id <> ?
                LIMIT 1";

            $dup = $pdo->prepare($dupSql);
            $dup->execute($dupParams);
            if ((int)($dup->fetchColumn() ?: 0) > 0) {
                throw new RuntimeException('A late fee rule already exists for that exact scope and trigger.');
            }

            $adminId = (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0);

            if ($editId > 0) {
                $st = $pdo->prepare("UPDATE late_fee_rules
                    SET session_name = ?,
                        semester = ?,
                        faculty_id = ?,
                        department_id = ?,
                        programme_id = ?,
                        mode_of_study = ?,
                        mode_of_study_custom = ?,
                        course_of_study_id = ?,
                        course_of_study = ?,
                        level = ?,
                        late_fee_type = ?,
                        late_fee_value = ?,
                        trigger_condition = ?,
                        due_date = ?,
                        is_enabled = ?,
                        reason = ?,
                        updated_by = ?,
                        updated_at = NOW()
                    WHERE id = ?");
                $st->execute([
                    $row['session_name'] !== '' ? $row['session_name'] : null,
                    $row['semester'] !== '' ? $row['semester'] : null,
                    $row['faculty_id'] > 0 ? $row['faculty_id'] : null,
                    $row['department_id'] > 0 ? $row['department_id'] : null,
                    $row['programme_id'] > 0 ? $row['programme_id'] : null,
                    $row['mode_of_study'] !== '' ? $row['mode_of_study'] : null,
                    $row['mode_of_study_custom'] !== '' ? $row['mode_of_study_custom'] : null,
                    $row['course_of_study_id'] > 0 ? $row['course_of_study_id'] : null,
                    $row['course_of_study'] !== '' ? $row['course_of_study'] : null,
                    $row['level'] !== '' ? $row['level'] : null,
                    $row['late_fee_type'],
                    $row['late_fee_value'],
                    $row['trigger_condition'],
                    $row['due_date'] !== '' ? $row['due_date'] : null,
                    $row['is_enabled'],
                    $row['reason'] !== '' ? $row['reason'] : null,
                    $adminId > 0 ? $adminId : null,
                    $editId,
                ]);
                flash('success', 'Late fee rule updated successfully.');
            } else {
                $st = $pdo->prepare("INSERT INTO late_fee_rules (
                        session_name,
                        semester,
                        faculty_id,
                        department_id,
                        programme_id,
                        mode_of_study,
                        mode_of_study_custom,
                        course_of_study_id,
                        course_of_study,
                        level,
                        late_fee_type,
                        late_fee_value,
                        trigger_condition,
                        due_date,
                        is_enabled,
                        reason,
                        created_by,
                        updated_by
                    ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
                $st->execute([
                    $row['session_name'] !== '' ? $row['session_name'] : null,
                    $row['semester'] !== '' ? $row['semester'] : null,
                    $row['faculty_id'] > 0 ? $row['faculty_id'] : null,
                    $row['department_id'] > 0 ? $row['department_id'] : null,
                    $row['programme_id'] > 0 ? $row['programme_id'] : null,
                    $row['mode_of_study'] !== '' ? $row['mode_of_study'] : null,
                    $row['mode_of_study_custom'] !== '' ? $row['mode_of_study_custom'] : null,
                    $row['course_of_study_id'] > 0 ? $row['course_of_study_id'] : null,
                    $row['course_of_study'] !== '' ? $row['course_of_study'] : null,
                    $row['level'] !== '' ? $row['level'] : null,
                    $row['late_fee_type'],
                    $row['late_fee_value'],
                    $row['trigger_condition'],
                    $row['due_date'] !== '' ? $row['due_date'] : null,
                    $row['is_enabled'],
                    $row['reason'] !== '' ? $row['reason'] : null,
                    $adminId > 0 ? $adminId : null,
                    $adminId > 0 ? $adminId : null,
                ]);
                flash('success', 'Late fee rule created successfully.');
            }
        } catch (RuntimeException $e) {
            flash('danger', $e->getMessage() !== '' ? $e->getMessage() : 'Unable to save the late fee rule right now.');
            redirect('?page=admin_fees_manager&fees_tab=late_fee' . ($editId > 0 ? '&edit_late_fee_rule_id=' . $editId : ''));
        } catch (Throwable $e) {
            flash('danger', 'Unable to save the late fee rule right now.');
            redirect('?page=admin_fees_manager&fees_tab=late_fee' . ($editId > 0 ? '&edit_late_fee_rule_id=' . $editId : ''));
        }

        redirect('?page=admin_fees_manager&fees_tab=late_fee');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'toggle_late_fee_rule') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        portalEnsureLateFeeRuleSchema($pdo);
        $ruleId = max(0, (int)($_POST['late_fee_rule_id'] ?? 0));
        $nextState = strtolower(trim((string)($_POST['next_state'] ?? 'enable'))) === 'disable' ? 0 : 1;
        try {
            $pdo->prepare("UPDATE late_fee_rules SET is_enabled = ?, updated_by = ?, updated_at = NOW() WHERE id = ?")
                ->execute([$nextState, (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0) ?: null, $ruleId]);
            flash('success', $nextState ? 'Late fee rule enabled successfully.' : 'Late fee rule disabled successfully.');
        } catch (Throwable $e) {
            flash('danger', 'Unable to update that late fee rule right now.');
        }
        redirect('?page=admin_fees_manager&fees_tab=late_fee');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_late_fee_rule') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        portalEnsureLateFeeRuleSchema($pdo);
        $ruleId = max(0, (int)($_POST['late_fee_rule_id'] ?? 0));
        try {
            $pdo->prepare("DELETE FROM late_fee_rules WHERE id = ?")->execute([$ruleId]);
            flash('success', 'Late fee rule deleted successfully.');
        } catch (Throwable $e) {
            flash('danger', 'Unable to delete that late fee rule right now.');
        }
        redirect('?page=admin_fees_manager&fees_tab=late_fee');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'generate_late_fees_now') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        portalEnsureLateFeeSupport($pdo);
        $sessionName = portalNormalizeSessionNameSafe((string)($_POST['session_name'] ?? portalCurrentAcademicSessionName($pdo)));
        $semester = portalNormalizeSemesterName((string)($_POST['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
        $count = 0;
        try {
            $rows = $pdo->query("SELECT * FROM students ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
            foreach ($rows as $studentRow) {
                $res = portalGenerateLateFeesForStudent($pdo, (int)($studentRow['id'] ?? 0), $sessionName, $semester);
                if (!empty($res['assessment']['generated'])) $count++;
            }
            flash('success', $count > 0 ? ($count . ' late fee record(s) generated successfully.') : 'No new eligible late fee records were generated.');
        } catch (Throwable $e) {
            flash('danger', 'Unable to generate late fees right now.');
        }
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=admin_settings#settings-payments'));
        redirect($returnTo !== '' ? $returnTo : '?page=admin_settings#settings-payments');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'recalculate_student_late_fee') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        portalEnsureLateFeeSupport($pdo);
        $studentId = (int)($_POST['student_id'] ?? 0);
        $sessionName = portalNormalizeSessionNameSafe((string)($_POST['session_name'] ?? portalCurrentAcademicSessionName($pdo)));
        $semester = portalNormalizeSemesterName((string)($_POST['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
        $remarks = trim((string)($_POST['remarks'] ?? 'Late fee recalculated by admin'));
        try {
            $waive = $pdo->prepare("UPDATE school_fee_late_fee_records SET status = 'Waived', waived_at = NOW(), waived_by = ?, remarks = ?, updated_by = ? WHERE student_id = ? AND academic_session = ? AND semester = ? AND status IN ('Active','Partially Paid')");
            $adminId = (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0);
            $waive->execute([$adminId > 0 ? $adminId : null, $remarks, $adminId > 0 ? $adminId : null, $studentId, $sessionName, $semester]);
            $res = portalGenerateLateFeesForStudent($pdo, $studentId, $sessionName, $semester);
            flash('success', $res['message'] ?? 'Late fee recalculated.');
        } catch (Throwable $e) {
            flash('danger', 'Unable to recalculate late fee for this student.');
        }
        redirect('?page=admin_students&action=view&id=' . $studentId);
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'waive_student_late_fee') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        portalEnsureLateFeeSupport($pdo);
        $studentId = (int)($_POST['student_id'] ?? 0);
        $sessionName = portalNormalizeSessionNameSafe((string)($_POST['session_name'] ?? portalCurrentAcademicSessionName($pdo)));
        $semester = portalNormalizeSemesterName((string)($_POST['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
        $remarks = trim((string)($_POST['remarks'] ?? 'Late fee waived by admin'));
        portalWaiveLateFeeForStudent($pdo, $studentId, $sessionName, $semester, $remarks);
        flash('success', 'Late fee waived successfully.');
        redirect('?page=admin_students&action=view&id=' . $studentId);
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_school_fee_scope') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        portalSchoolFeeSettingsSupport($pdo);

        $editId = (int)($_POST['edit_id'] ?? 0);
        $feeType = portalNormalizeManagedFeeType((string)($_POST['fee_type'] ?? 'school'));
        $customTitle = portalNormalizeInlineWhitespace((string)($_POST['custom_title'] ?? ''));
        $sessionName = portalNormalizeSessionNameSafe((string)($_POST['session_name'] ?? ''));
        $semesterRaw = trim((string)($_POST['semester'] ?? ''));
        $semester = $semesterRaw !== '' ? portalNormalizeSemesterName($semesterRaw) : '';
        $facultyId = max(0, (int)($_POST['faculty_id'] ?? 0));
        $departmentId = max(0, (int)($_POST['department_id'] ?? 0));
        $programmeId = max(0, (int)($_POST['programme_id'] ?? 0));
        $level = strtoupper(trim((string)($_POST['level'] ?? '')));
        $amount = (float)str_replace(',', '', (string)($_POST['amount'] ?? '0'));
        $isActive = isset($_POST['is_active']) ? 1 : 0;
        $notes = portalNormalizeInlineWhitespace((string)($_POST['notes'] ?? ''));
        $adminId = (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0);
        $returnTo = trim((string)($_POST['return_to'] ?? ''));
        $redirectUrl = portalManagedFeeCleanReturnUrl($returnTo, '?page=admin_fees_manager&fee_type=' . urlencode($feeType));

        try {
            if ($amount <= 0) {
                throw new RuntimeException('Fee amount must be greater than zero.');
            }
            if ($feeType === 'custom' && $customTitle === '') {
                throw new RuntimeException('Please enter a custom fee title/name.');
            }
            if ($sessionName !== '') {
                $validSessions = array_map('strval', portalAvailableSessionOptions($pdo));
                if ($validSessions && !in_array($sessionName, $validSessions, true)) {
                    throw new RuntimeException('Please select a valid session.');
                }
            }
            if ($level !== '') {
                $validLevels = array_map(static function($v){ return strtoupper(trim((string)$v)); }, portalAcademicLevelOptions($pdo));
                $validLevels = array_values(array_filter(array_unique($validLevels), static function($v){ return $v !== ''; }));
                if ($validLevels && !in_array($level, $validLevels, true)) {
                    throw new RuntimeException('Please select a valid level.');
                }
            }
            if ($facultyId > 0) {
                $st = $pdo->prepare("SELECT id FROM faculties WHERE id = ? LIMIT 1");
                $st->execute([$facultyId]);
                if (!$st->fetchColumn()) throw new RuntimeException('Invalid faculty selected.');
            }
            if ($departmentId > 0) {
                $st = $pdo->prepare("SELECT id, faculty_id FROM departments WHERE id = ? LIMIT 1");
                $st->execute([$departmentId]);
                $row = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                if (!$row) throw new RuntimeException('Invalid department selected.');
                if ($facultyId > 0 && (int)($row['faculty_id'] ?? 0) !== $facultyId) throw new RuntimeException('The selected department does not belong to the chosen faculty.');
                if ($facultyId <= 0) $facultyId = (int)($row['faculty_id'] ?? 0);
            }
            if ($programmeId > 0) {
                $st = $pdo->prepare("SELECT id, department_id, faculty_id FROM programmes WHERE id = ? LIMIT 1");
                $st->execute([$programmeId]);
                $row = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                if (!$row) throw new RuntimeException('Invalid programme selected.');
                if ($departmentId > 0 && (int)($row['department_id'] ?? 0) !== $departmentId) throw new RuntimeException('The selected programme does not belong to the chosen department.');
                if ($facultyId > 0 && (int)($row['faculty_id'] ?? 0) !== $facultyId) throw new RuntimeException('The selected programme does not belong to the chosen faculty.');
                if ($departmentId <= 0) $departmentId = (int)($row['department_id'] ?? 0);
                if ($facultyId <= 0) $facultyId = (int)($row['faculty_id'] ?? 0);
            }

            $scopeHash = portalManagedFeeScopeHash([
                'session_name' => $sessionName,
                'semester' => $semester,
                'faculty_id' => $facultyId,
                'department_id' => $departmentId,
                'programme_id' => $programmeId,
                'level' => $level,
                'custom_title' => $customTitle,
            ], $feeType);

            $dup = $pdo->prepare("SELECT id FROM school_fee_settings WHERE COALESCE(fee_type,'school') = ? AND scope_hash = ? AND id <> ? LIMIT 1");
            $dup->execute([$feeType, $scopeHash, $editId]);
            if ($dup->fetchColumn()) {
                throw new RuntimeException('A fee record already exists for this exact scope.');
            }

            $ownTxn = !$pdo->inTransaction();
            if ($ownTxn) { $pdo->beginTransaction(); }

            $savedId = $editId;
            if ($editId > 0) {
                $exists = $pdo->prepare("SELECT id FROM school_fee_settings WHERE id = ? LIMIT 1");
                $exists->execute([$editId]);
                if (!(int)($exists->fetchColumn() ?: 0)) {
                    throw new RuntimeException('The fee record you are trying to edit no longer exists.');
                }
                $stmt = $pdo->prepare("UPDATE school_fee_settings SET fee_type = ?, custom_title = ?, session_name = ?, semester = ?, faculty_id = ?, department_id = ?, programme_id = ?, level = ?, amount = ?, is_active = ?, scope_hash = ?, notes = ?, updated_by = ?, updated_at = NOW() WHERE id = ?");
                $ok = $stmt->execute([
                    $feeType,
                    ($feeType === 'custom' && $customTitle !== '') ? $customTitle : null,
                    $sessionName !== '' ? $sessionName : null,
                    $semester !== '' ? $semester : null,
                    $facultyId > 0 ? $facultyId : null,
                    $departmentId > 0 ? $departmentId : null,
                    $programmeId > 0 ? $programmeId : null,
                    $level !== '' ? $level : null,
                    $amount,
                    $isActive,
                    $scopeHash,
                    $notes !== '' ? $notes : null,
                    $adminId > 0 ? $adminId : null,
                    $editId,
                ]);
                if (!$ok) {
                    throw new RuntimeException('The fee record could not be updated.');
                }
            } else {
                $stmt = $pdo->prepare("INSERT INTO school_fee_settings (fee_type, custom_title, session_name, semester, faculty_id, department_id, programme_id, level, amount, is_active, scope_hash, notes, created_by, updated_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
                $ok = $stmt->execute([
                    $feeType,
                    ($feeType === 'custom' && $customTitle !== '') ? $customTitle : null,
                    $sessionName !== '' ? $sessionName : null,
                    $semester !== '' ? $semester : null,
                    $facultyId > 0 ? $facultyId : null,
                    $departmentId > 0 ? $departmentId : null,
                    $programmeId > 0 ? $programmeId : null,
                    $level !== '' ? $level : null,
                    $amount,
                    $isActive,
                    $scopeHash,
                    $notes !== '' ? $notes : null,
                    $adminId > 0 ? $adminId : null,
                    $adminId > 0 ? $adminId : null,
                ]);
                if (!$ok) {
                    throw new RuntimeException('The fee record could not be saved.');
                }
                $savedId = (int)$pdo->lastInsertId();
                if ($savedId <= 0) {
                    $stSaved = $pdo->prepare("SELECT id FROM school_fee_settings WHERE COALESCE(fee_type,'school') = ? AND scope_hash = ? ORDER BY id DESC LIMIT 1");
                    $stSaved->execute([$feeType, $scopeHash]);
                    $savedId = (int)($stSaved->fetchColumn() ?: 0);
                }
            }

            $verify = $pdo->prepare("SELECT id, fee_type, amount, is_active, scope_hash FROM school_fee_settings WHERE id = ? LIMIT 1");
            $verify->execute([$savedId]);
            $savedRow = $verify->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$savedRow) {
                throw new RuntimeException('The fee record did not persist to storage.');
            }
            if (portalNormalizeManagedFeeType((string)($savedRow['fee_type'] ?? 'school')) !== $feeType) {
                throw new RuntimeException('The saved fee type did not match the submitted value.');
            }
            if ((string)($savedRow['scope_hash'] ?? '') !== $scopeHash) {
                throw new RuntimeException('The saved fee scope could not be verified.');
            }
            if ((float)($savedRow['amount'] ?? -1) != $amount) {
                throw new RuntimeException('The saved fee amount could not be verified.');
            }
            if ((int)($savedRow['is_active'] ?? -1) !== $isActive) {
                throw new RuntimeException('The saved fee status could not be verified.');
            }

            if ($ownTxn && $pdo->inTransaction()) { $pdo->commit(); }

            try { portalManagedFeeSyncAfterMutation($pdo, $feeType); } catch (Throwable $e) {}

            flash('success', $editId > 0 ? 'Fee record updated successfully.' : 'Fee record saved successfully.');
        } catch (Throwable $e) {
            try { if ($pdo->inTransaction()) { $pdo->rollBack(); } } catch (Throwable $e2) {}
            flash('danger', $e->getMessage() ?: 'Unable to save fee record.');
        }
        redirect($redirectUrl);
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'toggle_school_fee_scope') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        portalSchoolFeeSettingsSupport($pdo);
        $id = (int)($_POST['id'] ?? 0);
        $isActive = isset($_POST['is_active']) ? 1 : 0;
        $returnTo = portalManagedFeeCleanReturnUrl((string)($_POST['return_to'] ?? ''), '?page=admin_fees_manager');
        if ($id > 0) {
            try {
                $stType = $pdo->prepare("SELECT fee_type FROM school_fee_settings WHERE id = ? LIMIT 1");
                $stType->execute([$id]);
                $toggleFeeType = portalNormalizeManagedFeeType((string)($stType->fetchColumn() ?: 'school'));
                $stmt = $pdo->prepare("UPDATE school_fee_settings SET is_active = ?, updated_by = ?, updated_at = NOW() WHERE id = ?");
                $stmt->execute([$isActive, (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0), $id]);
                if ($stmt->rowCount() > 0) {
                    try { portalManagedFeeSyncAfterMutation($pdo, $toggleFeeType); } catch (Throwable $e) {}
                    flash('success', $isActive ? 'Fee record activated successfully.' : 'Fee record deactivated successfully.');
                } else {
                    flash('warning', 'No fee record status changed.');
                }
            } catch (Throwable $e) {
                flash('danger', 'Unable to update fee record status.');
            }
        }
        redirect($returnTo);
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_school_fee_scope') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        portalSchoolFeeSettingsSupport($pdo);
        $id = (int)($_POST['id'] ?? 0);
        $returnTo = portalManagedFeeCleanReturnUrl((string)($_POST['return_to'] ?? ''), '?page=admin_fees_manager');
        if ($id > 0) {
            try {
                $stType = $pdo->prepare("SELECT fee_type FROM school_fee_settings WHERE id = ? LIMIT 1");
                $stType->execute([$id]);
                $deletedFeeType = portalNormalizeManagedFeeType((string)($stType->fetchColumn() ?: 'school'));
                $stmt = $pdo->prepare("DELETE FROM school_fee_settings WHERE id = ? LIMIT 1");
                $stmt->execute([$id]);
                if ($stmt->rowCount() > 0) {
                    try { portalManagedFeeSyncAfterMutation($pdo, $deletedFeeType); } catch (Throwable $e2) {}
                    flash('success', 'Fee record deleted successfully.');
                } else {
                    flash('warning', 'The fee record was not found or was already removed.');
                }
            } catch (Throwable $e) {
                flash('danger', 'Unable to delete fee record.');
            }
        }
        redirect($returnTo);
    }

    // Save auto-numbering format settings
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_auto_number_format') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        requireStaffPermission($pdo, 'academics_manage');
        portalEnsureNumberingSchema($pdo);

        $editId = (int)($_POST['edit_id'] ?? 0);
        $numberType = trim((string)($_POST['number_type'] ?? 'matric'));
        if (!in_array($numberType, ['application','registration','matric','admission'], true)) $numberType = 'matric';

        $scopeType = trim((string)($_POST['scope_type'] ?? 'global'));
        if (!in_array($scopeType, ['global','session','faculty','department','programme'], true)) $scopeType = 'global';

        $facultyId = max(0, (int)($_POST['faculty_id'] ?? 0));
        $departmentId = max(0, (int)($_POST['department_id'] ?? 0));
        $programmeId = max(0, (int)($_POST['programme_id'] ?? 0));
        $scopeSession = trim((string)($_POST['scope_session'] ?? ''));
        $scopeId = 0;
        if ($scopeType === 'faculty') $scopeId = $facultyId;
        if ($scopeType === 'department') $scopeId = $departmentId;
        if ($scopeType === 'programme') $scopeId = $programmeId;
        if ($scopeType === 'session' && $scopeSession === '') {
            flash('danger', 'Please select a valid scope before saving.');
            redirect('?page=admin_numbering_settings&numbering_tab=setup' . ($editId > 0 ? '&edit=' . $editId : ''));
        }
        if (in_array($scopeType, ['faculty','department','programme'], true) && $scopeId <= 0) {
            flash('danger', 'Please select a valid scope before saving.');
            redirect('?page=admin_numbering_settings&numbering_tab=setup' . ($editId > 0 ? '&edit=' . $editId : ''));
        }

        $prefix = trim((string)($_POST['prefix'] ?? ''));
        $affix = trim((string)($_POST['affix'] ?? ''));
        $serialDigits = (int)($_POST['serial_digits'] ?? 4);
        $startFrom = (int)($_POST['start_from'] ?? 1);
        $isActive = isset($_POST['is_active']) ? 1 : 0;

        if ($prefix === '') {
            flash('danger', 'Prefix is required for this numbering format.');
            redirect('?page=admin_numbering_settings&numbering_tab=setup' . ($editId > 0 ? '&edit=' . $editId : ''));
        }
        if ($serialDigits <= 0) {
            flash('danger', 'Serial digits must be greater than zero.');
            redirect('?page=admin_numbering_settings&numbering_tab=setup' . ($editId > 0 ? '&edit=' . $editId : ''));
        }
        if ($startFrom <= 0) {
            flash('danger', 'Starting serial must be greater than zero.');
            redirect('?page=admin_numbering_settings&numbering_tab=setup' . ($editId > 0 ? '&edit=' . $editId : ''));
        }
        $prefixError = portalValidateNumberingTemplate($prefix);
        $affixError = portalValidateNumberingTemplate($affix);
        if ($prefixError !== '' || $affixError !== '') {
            flash('danger', $prefixError !== '' ? $prefixError : $affixError);
            redirect('?page=admin_numbering_settings&numbering_tab=setup' . ($editId > 0 ? '&edit=' . $editId : ''));
        }

        try {
            if ($departmentId > 0) {
                $st = $pdo->prepare("SELECT faculty_id FROM departments WHERE id = ? LIMIT 1");
                $st->execute([$departmentId]);
                $departmentFacultyId = (int)($st->fetchColumn() ?: 0);
                if ($departmentFacultyId <= 0 || ($facultyId > 0 && $departmentFacultyId !== $facultyId)) {
                    throw new RuntimeException('Invalid scope selection. The selected department does not belong to the chosen faculty.');
                }
                $facultyId = $departmentFacultyId;
            }
            if ($programmeId > 0) {
                $st = $pdo->prepare("SELECT faculty_id, department_id FROM programmes WHERE id = ? LIMIT 1");
                $st->execute([$programmeId]);
                $programRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                if (!$programRow) {
                    throw new RuntimeException('Invalid scope selection. The selected programme does not exist.');
                }
                if ($departmentId > 0 && (int)($programRow['department_id'] ?? 0) !== $departmentId) {
                    throw new RuntimeException('Invalid scope selection. The selected programme does not belong to the chosen department.');
                }
                if ($facultyId > 0 && (int)($programRow['faculty_id'] ?? 0) !== $facultyId) {
                    throw new RuntimeException('Invalid scope selection. The selected programme does not belong to the chosen faculty.');
                }
                $departmentId = (int)($programRow['department_id'] ?? 0);
                $facultyId = (int)($programRow['faculty_id'] ?? 0);
            }
            if ($scopeType === 'faculty') $scopeId = $facultyId;
            if ($scopeType === 'department') $scopeId = $departmentId;
            if ($scopeType === 'programme') $scopeId = $programmeId;
        } catch (Throwable $e) {
            flash('danger', $e->getMessage());
            redirect('?page=admin_numbering_settings&numbering_tab=setup' . ($editId > 0 ? '&edit=' . $editId : ''));
        }

        try {
            $check = $pdo->prepare("SELECT id FROM auto_number_formats WHERE number_type = ? AND scope_type = ? AND scope_id = ? AND scope_session = ? AND id <> ? LIMIT 1");
            $check->execute([$numberType, $scopeType, $scopeId, $scopeSession, $editId]);
            if ($check->fetch(PDO::FETCH_ASSOC)) {
                $typeLabel = ucwords(str_replace('_', ' ', $numberType));
                throw new RuntimeException('A ' . $typeLabel . ' format already exists for this scope.');
            }

            if ($editId > 0) {
                $stmt = $pdo->prepare("UPDATE auto_number_formats SET number_type = ?, scope_type = ?, scope_id = ?, scope_session = ?, prefix = ?, affix = ?, serial_digits = ?, start_from = ?, is_active = ? WHERE id = ?");
                $stmt->execute([$numberType, $scopeType, $scopeId, $scopeSession, $prefix, $affix, $serialDigits, $startFrom, $isActive, $editId]);
            } else {
                $stmt = $pdo->prepare("INSERT INTO auto_number_formats (number_type, scope_type, scope_id, scope_session, prefix, affix, serial_digits, start_from, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
                $stmt->execute([$numberType, $scopeType, $scopeId, $scopeSession, $prefix, $affix, $serialDigits, $startFrom, $isActive]);
            }
            portalSynchronizeNumberAliases($pdo, $numberType);
            flash('success', ucfirst($numberType) . ' numbering format saved successfully. Existing assigned numbers were preserved.');
        } catch (Throwable $e) {
            flash('danger', 'Unable to save numbering format: ' . $e->getMessage());
        }
        redirect('?page=admin_numbering_settings&numbering_tab=setup');
    }

    // Toggle auto-numbering format status
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'toggle_auto_number_format_status') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        requireStaffPermission($pdo, 'academics_manage');
        portalEnsureNumberingSchema($pdo);
        $id = (int)($_POST['id'] ?? 0);
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=admin_numbering_settings&numbering_tab=formats'));
        if (!preg_match('/^\?page=admin_numbering_settings(?:&|$)/', $returnTo)) {
            $returnTo = '?page=admin_numbering_settings&numbering_tab=formats';
        }
        if ($id > 0) {
            try {
                $pdo->prepare("UPDATE auto_number_formats SET is_active = IF(is_active = 1, 0, 1) WHERE id = ?")->execute([$id]);
                flash('success', 'Numbering format status updated.');
            } catch (Throwable $e) {
                flash('danger', 'Unable to update numbering format status.');
            }
        }
        redirect($returnTo);
    }

    // Delete auto-numbering format settings
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_auto_number_format') {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        requireStaffPermission($pdo, 'academics_manage');
        portalEnsureNumberingSchema($pdo);
        $id = (int)($_POST['id'] ?? 0);
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=admin_numbering_settings&numbering_tab=formats'));
        if (!preg_match('/^\?page=admin_numbering_settings(?:&|$)/', $returnTo)) {
            $returnTo = '?page=admin_numbering_settings&numbering_tab=formats';
        }
        if ($id > 0) {
            try {
                $pdo->prepare("DELETE FROM auto_number_formats WHERE id = ?")->execute([$id]);
                flash('success', 'Numbering format deleted safely. Existing assigned numbers were preserved.');
            } catch (Throwable $e) {
                flash('danger', 'Unable to delete numbering format.');
            }
        }
        redirect($returnTo);
    }

    // Update wallet settings only (separate form to avoid file-upload validation issues)
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_wallet_settings') {
        $walletSettings = [
            'wallet_enabled' => isset($_POST['wallet_enabled']) ? 'enabled' : 'disabled',
            'wallet_funding_enabled' => isset($_POST['wallet_funding_enabled']) ? 'enabled' : 'disabled',
            'wallet_min_fund' => trim($_POST['wallet_min_fund'] ?? '1000')
        ];

        foreach ($walletSettings as $key => $value) {
            setSystemSettingValue($pdo, (string)$key, (string)$value);
        }

        $_SESSION['settings_updated'] = true;
        $returnTo = trim((string)($_POST['return_to'] ?? '?page=admin_settings'));
        redirect($returnTo !== '' ? $returnTo : '?page=admin_settings');
    }


    // Handle logo and favicon upload - Modified to handle separate logos
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'upload_asset') {
        $assetType = $_POST['asset_type'] ?? '';

        if (isset($_FILES['asset_file']) && $_FILES['asset_file']['error'] === UPLOAD_ERR_OK) {
            $file = $_FILES['asset_file'];

            // Check file type
            $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/x-icon'];
            if (!in_array($file['type'], $allowedTypes)) {
                $_SESSION['upload_error'] = "Invalid file type. Please upload a valid image file.";
                redirect('?page=admin_settings');
                exit;
            }

            // Check file size
            if ($file['size'] > 2097152) { // 2MB
                $_SESSION['upload_error'] = "File is too large. Maximum size is 2MB.";
                redirect('?page=admin_settings');
                exit;
            }

            // Generate unique filename
            $fileInfo = pathinfo($file['name']);
            $extension = strtolower($fileInfo['extension']);
            $filename = uniqid() . '.' . $extension;
            $filepath = UPLOAD_DIR . $filename;

            // Move file to upload directory
            if (move_uploaded_file($file['tmp_name'], $filepath)) {
                // Delete existing asset of the same type
                $stmt = $pdo->prepare("DELETE FROM system_assets WHERE asset_type = ?");
                $stmt->execute([$assetType]);

                // Insert new asset
                $stmt = $pdo->prepare("INSERT INTO system_assets (asset_type, file_path) VALUES (?, ?)");
                $stmt->execute([$assetType, $filepath]);

                $_SESSION['upload_success'] = "Asset uploaded successfully.";
            } else {
                $_SESSION['upload_error'] = "Failed to upload file.";
            }
        } else {
            $_SESSION['upload_error'] = "Please select a file to upload.";
        }

        redirect('?page=admin_settings');
    }


// -------------------------
// ADMIN WALLET MANAGEMENT
// -------------------------
if (isAdmin() && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_wallet_update') {
    $studentId = (int)($_POST['student_id'] ?? 0);
    $op = (string)($_POST['op'] ?? '');
    $amount = (float)($_POST['amount'] ?? 0);
    $narration = trim((string)($_POST['narration'] ?? ''));
    $status = (string)($_POST['wallet_status'] ?? '');
    $enabled = isset($_POST['wallet_is_enabled']) ? 1 : 0;

    if ($studentId <= 0) { flash('danger', 'Invalid student selected.'); redirect('?page=admin_wallets'); }
    ensureWallet($pdo, $studentId);

    try {
        if ($op === 'credit') {
            if ($amount <= 0) throw new RuntimeException('Enter a valid credit amount.');
            if ($narration === '') $narration = 'Admin credit';
            $walletTxId = 0;
            $pdo->beginTransaction();
            $beforeBalance = getWalletBalance($pdo, $studentId);
            $afterBalance = $beforeBalance + $amount;
            $relatedRef = 'ADMIN_' . time();
            $pdo->prepare("UPDATE wallets SET balance = balance + ? WHERE student_id = ?")->execute([$amount, $studentId]);
            $walletTxId = portalInsertWalletTransaction($pdo, $studentId, 'credit', $amount, $narration, $relatedRef, $relatedRef, $beforeBalance, $afterBalance);
            $pdo->commit();
            try { if ($walletTxId > 0) { portalSendWalletTransactionNotificationById($pdo, $walletTxId); } } catch (Throwable $e) {}
            flash('success', 'Wallet credited successfully.');

        } elseif ($op === 'debit') {
            if ($amount <= 0) throw new RuntimeException('Enter a valid debit amount.');
            if ($narration === '') $narration = 'Admin debit';
            $walletTxId = 0;
            $pdo->beginTransaction();
            $stb = $pdo->prepare("SELECT balance FROM wallets WHERE student_id = ? LIMIT 1");
            $stb->execute([$studentId]);
            $bal = (float)($stb->fetchColumn() ?? 0);
            if ($bal + 0.00001 < $amount) throw new RuntimeException('Insufficient wallet balance to debit.');
            $afterBalance = $bal - $amount;
            $relatedRef = 'ADMIN_' . time();
            $pdo->prepare("UPDATE wallets SET balance = balance - ? WHERE student_id = ?")->execute([$amount, $studentId]);
            $walletTxId = portalInsertWalletTransaction($pdo, $studentId, 'debit', $amount, $narration, $relatedRef, $relatedRef, $bal, $afterBalance);
            $pdo->commit();
            try { if ($walletTxId > 0) { portalSendWalletTransactionNotificationById($pdo, $walletTxId); } } catch (Throwable $e) {}
            flash('success', 'Wallet debited successfully.');

        } elseif ($op === 'update_flags') {
            // Best-effort (older DB may not have these columns)
            if ($status !== '' || isset($_POST['wallet_is_enabled'])) {
                try {
                    if ($status !== '') {
                        $pdo->prepare("UPDATE wallets SET status = ? WHERE student_id = ?")->execute([$status, $studentId]);
                    }
                } catch (Throwable $e) {}
                try {
                    $pdo->prepare("UPDATE wallets SET is_enabled = ? WHERE student_id = ?")->execute([$enabled, $studentId]);
                } catch (Throwable $e) {}
                flash('success', 'Wallet settings updated.');
                // Notify student (wallet status/enable changes)
                try {
                    $stuStmt = $pdo->prepare("SELECT email, surname, first_name, other_name FROM students WHERE id = ? LIMIT 1");
                    $stuStmt->execute([$studentId]);
                    $stuRow = $stuStmt->fetch(PDO::FETCH_ASSOC) ?: [];
                    $sEmail = (string)($stuRow['email'] ?? '');
                    $sName  = trim((string)($stuRow['surname'] ?? '') . ', ' . (string)($stuRow['first_name'] ?? '') . ' ' . (string)($stuRow['other_name'] ?? ''));
                    $parts = [];
                    if ($status !== '') { $parts[] = "Status: {$status}"; }
                    $parts[] = "Enabled: " . ($enabled === 1 ? 'Yes' : 'No');
                    $msg = "Admin updated your wallet settings. " . implode(' | ', $parts) . ".";
                    createNotification($pdo, 'student', (int)$studentId, 'Wallet Updated', $msg, '?page=wallet');
                    if (!empty($sEmail)) {
                        $walletSubject = APP_NAME . ': Wallet Updated';
                        $walletBody = portalWrapNotificationEmail($pdo, 'Wallet Updated', '<p>Dear ' . htmlspecialchars($sName, ENT_QUOTES, 'UTF-8') . ',</p><p>' . nl2br(htmlspecialchars($msg, ENT_QUOTES, 'UTF-8')) . '</p>');
                        sendLoggedEmail($pdo, 'student', (int)$studentId, $sEmail, 'wallet_update:' . (int)$studentId, $walletSubject, $walletBody);
                    }
                } catch (Throwable $e) {}

            } else {
                flash('info', 'No changes to update.');
            }
        } else {
            flash('danger', 'Unknown wallet operation.');
        }
    } catch (Throwable $e) {
        try { if ($pdo->inTransaction()) $pdo->rollBack(); } catch (Throwable $e2) {}
        flash('danger', $e->getMessage() ?: 'Wallet operation failed.');
    }

    redirect('?page=admin_wallets');
}


// =====================================================================
// ACADEMICS MANAGERS (Faculty/Dept/Programme, Sessions/Promotion, Courses)
// Self-healing + safe CRUD (Admin only)
// =====================================================================
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && (isAdmin() || isStudent())) {
    $act = (string)$_POST['action'];

    
// -------- Faculty / Department / Programme --------
    if (in_array($act, ['add_faculty','edit_faculty','delete_faculty','add_department','edit_department','delete_department','add_programme','edit_programme','delete_programme'], true)) {
        $allowedStructureSubtabs = ['manage_faculties','add_faculty','manage_department','add_department','manage_programmes','add_programme'];
        $requestedStructureSubtab = trim((string)($_POST['return_subtab'] ?? ''));
        if (!in_array($requestedStructureSubtab, $allowedStructureSubtabs, true)) {
            $requestedStructureSubtab = '';
        }
        $structureRedirectTab = 'faculties';
        if (str_contains($act, 'department')) {
            $structureRedirectTab = 'departments';
        } elseif (str_contains($act, 'programme')) {
            $structureRedirectTab = 'programmes';
        }
        $structureRedirect = '?page=admin_structure_manager&tab=' . urlencode($structureRedirectTab);
        if ($requestedStructureSubtab !== '') {
            $structureRedirect .= '&subtab=' . urlencode($requestedStructureSubtab);
        }
        $returnQueryString = trim((string)($_POST['return_query'] ?? ''));
        if ($returnQueryString !== '') {
            $parsedReturnQuery = [];
            parse_str($returnQueryString, $parsedReturnQuery);
            if (is_array($parsedReturnQuery) && (($parsedReturnQuery['page'] ?? '') === 'admin_structure_manager')) {
                $safeQuery = [
                    'page' => 'admin_structure_manager',
                    'tab' => $structureRedirectTab,
                ];
                if ($requestedStructureSubtab !== '') {
                    $safeQuery['subtab'] = $requestedStructureSubtab;
                }
                if ($structureRedirectTab === 'faculties') {
                    $safeQuery['faculty_search_name'] = trim((string)($parsedReturnQuery['faculty_search_name'] ?? ''));
                    $safeQuery['faculty_search_code'] = trim((string)($parsedReturnQuery['faculty_search_code'] ?? ''));
                    $status = trim((string)($parsedReturnQuery['faculty_search_status'] ?? ''));
                    if (in_array($status, ['active', 'inactive'], true)) {
                        $safeQuery['faculty_search_status'] = $status;
                    }
                } elseif ($structureRedirectTab === 'departments') {
                    $safeQuery['department_search_name'] = trim((string)($parsedReturnQuery['department_search_name'] ?? ''));
                    $safeQuery['department_search_code'] = trim((string)($parsedReturnQuery['department_search_code'] ?? ''));
                    $facultyId = max(0, (int)($parsedReturnQuery['department_filter_faculty_id'] ?? 0));
                    $status = trim((string)($parsedReturnQuery['department_filter_status'] ?? ''));
                    if ($facultyId > 0) {
                        $safeQuery['department_filter_faculty_id'] = $facultyId;
                    }
                    if (in_array($status, ['active', 'inactive'], true)) {
                        $safeQuery['department_filter_status'] = $status;
                    }
                } elseif ($structureRedirectTab === 'programmes') {
                    $safeQuery['programme_search_name'] = trim((string)($parsedReturnQuery['programme_search_name'] ?? ''));
                    $safeQuery['programme_search_code'] = trim((string)($parsedReturnQuery['programme_search_code'] ?? ''));
                    $facultyId = max(0, (int)($parsedReturnQuery['programme_filter_faculty_id'] ?? 0));
                    $departmentId = max(0, (int)($parsedReturnQuery['programme_filter_department_id'] ?? 0));
                    $mode = trim((string)($parsedReturnQuery['programme_filter_mode'] ?? ''));
                    $status = trim((string)($parsedReturnQuery['programme_filter_status'] ?? ''));
                    if ($facultyId > 0) {
                        $safeQuery['programme_filter_faculty_id'] = $facultyId;
                    }
                    if ($departmentId > 0) {
                        $safeQuery['programme_filter_department_id'] = $departmentId;
                    }
                    if ($mode !== '') {
                        $safeQuery['programme_filter_mode'] = $mode;
                    }
                    if (in_array($status, ['active', 'inactive'], true)) {
                        $safeQuery['programme_filter_status'] = $status;
                    }
                }
                $structureRedirect = '?' . http_build_query(array_filter($safeQuery, static function($value) {
                    return $value !== null && $value !== '';
                }));
            }
        }
        try {
            if ($act === 'add_faculty') {
                $name = trim((string)($_POST['name'] ?? ''));
                $code = trim((string)($_POST['code'] ?? ''));
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                if ($name === '') throw new RuntimeException('Faculty name is required.');
                $stmt = $pdo->prepare("INSERT INTO faculties (name, code, is_active) VALUES (?,?,?)");
                $stmt->execute([$name, $code !== '' ? $code : null, $isActive]);
                flash('success', 'Faculty added successfully.');
                redirect($structureRedirect);
            }

            if ($act === 'edit_faculty') {
                $id = (int)($_POST['id'] ?? 0);
                $name = trim((string)($_POST['name'] ?? ''));
                $code = trim((string)($_POST['code'] ?? ''));
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                if ($id <= 0) throw new RuntimeException('Invalid faculty selected.');
                if ($name === '') throw new RuntimeException('Faculty name is required.');
                $stmt = $pdo->prepare("UPDATE faculties SET name=?, code=?, is_active=? WHERE id=?");
                $stmt->execute([$name, $code !== '' ? $code : null, $isActive, $id]);
                flash('success', 'Faculty updated.');
                redirect($structureRedirect);
            }

            if ($act === 'delete_faculty') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id <= 0) throw new RuntimeException('Invalid faculty selected.');
                $pdo->prepare("DELETE FROM faculties WHERE id=?")->execute([$id]);
                flash('success', 'Faculty deleted.');
                redirect($structureRedirect);
            }

            if ($act === 'add_department') {
                $facultyId = (int)($_POST['faculty_id'] ?? 0);
                $name = trim((string)($_POST['name'] ?? ''));
                $code = trim((string)($_POST['code'] ?? ''));
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                if ($facultyId <= 0) throw new RuntimeException('Select a faculty.');
                if ($name === '') throw new RuntimeException('Department name is required.');
                $stmt = $pdo->prepare("INSERT INTO departments (faculty_id, name, code, is_active) VALUES (?,?,?,?)");
                $stmt->execute([$facultyId, $name, $code !== '' ? $code : null, $isActive]);
                flash('success', 'Department added.');
                redirect($structureRedirect);
            }

            if ($act === 'edit_department') {
                $id = (int)($_POST['id'] ?? 0);
                $facultyId = (int)($_POST['faculty_id'] ?? 0);
                $name = trim((string)($_POST['name'] ?? ''));
                $code = trim((string)($_POST['code'] ?? ''));
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                if ($id <= 0) throw new RuntimeException('Invalid department selected.');
                if ($facultyId <= 0) throw new RuntimeException('Select a faculty.');
                if ($name === '') throw new RuntimeException('Department name is required.');
                $stmt = $pdo->prepare("UPDATE departments SET faculty_id=?, name=?, code=?, is_active=? WHERE id=?");
                $stmt->execute([$facultyId, $name, $code !== '' ? $code : null, $isActive, $id]);
                flash('success', 'Department updated.');
                redirect($structureRedirect);
            }

            if ($act === 'delete_department') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id <= 0) throw new RuntimeException('Invalid department selected.');
                $pdo->prepare("DELETE FROM departments WHERE id=?")->execute([$id]);
                flash('success', 'Department deleted.');
                redirect($structureRedirect);
            }

            if ($act === 'add_programme') {
                portalEnsureProgrammeModeOfStudySupport($pdo);
                $facultyId = (int)($_POST['faculty_id'] ?? 0);
                $departmentId = (int)($_POST['department_id'] ?? 0);
                $name = trim((string)($_POST['name'] ?? ''));
                $code = trim((string)($_POST['code'] ?? ''));
                $modeMeta = portalNormalizeProgrammeModeOfStudyInput($_POST['mode_of_study_type'] ?? 'Full-Time', $_POST['mode_of_study_custom'] ?? '');
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                if ($facultyId <= 0) throw new RuntimeException('Select a faculty.');
                if ($departmentId <= 0) throw new RuntimeException('Select a department.');
                if ($name === '') throw new RuntimeException('Programme name is required.');
                $stmt = $pdo->prepare("INSERT INTO programmes (faculty_id, department_id, name, mode_of_study_type, mode_of_study_custom, code, is_active) VALUES (?,?,?,?,?,?,?)");
                $stmt->execute([$facultyId, $departmentId, $name, $modeMeta['type'], $modeMeta['custom'], $code !== '' ? $code : null, $isActive]);
                portalSynchronizeProgrammeModeOfStudyAcrossStudents($pdo, (int)$pdo->lastInsertId());
                flash('success', 'Programme added.');
                redirect($structureRedirect);
            }

            if ($act === 'edit_programme') {
                portalEnsureProgrammeModeOfStudySupport($pdo);
                $id = (int)($_POST['id'] ?? 0);
                $facultyId = (int)($_POST['faculty_id'] ?? 0);
                $departmentId = (int)($_POST['department_id'] ?? 0);
                $name = trim((string)($_POST['name'] ?? ''));
                $code = trim((string)($_POST['code'] ?? ''));
                $modeMeta = portalNormalizeProgrammeModeOfStudyInput($_POST['mode_of_study_type'] ?? 'Full-Time', $_POST['mode_of_study_custom'] ?? '');
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                if ($id <= 0) throw new RuntimeException('Invalid programme selected.');
                if ($facultyId <= 0) throw new RuntimeException('Select a faculty.');
                if ($departmentId <= 0) throw new RuntimeException('Select a department.');
                if ($name === '') throw new RuntimeException('Programme name is required.');
                $stmt = $pdo->prepare("UPDATE programmes SET faculty_id=?, department_id=?, name=?, mode_of_study_type=?, mode_of_study_custom=?, code=?, is_active=? WHERE id=?");
                $stmt->execute([$facultyId, $departmentId, $name, $modeMeta['type'], $modeMeta['custom'], $code !== '' ? $code : null, $isActive, $id]);
                portalSynchronizeProgrammeModeOfStudyAcrossStudents($pdo, $id);
                flash('success', 'Programme updated.');
                redirect($structureRedirect);
            }

            if ($act === 'delete_programme') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id <= 0) throw new RuntimeException('Invalid programme selected.');
                $pdo->prepare("DELETE FROM programmes WHERE id=?")->execute([$id]);
                flash('success', 'Programme deleted.');
                redirect($structureRedirect);
            }
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Operation failed.');
            redirect($structureRedirect);
        }
    }


    // -------- Academic Levels --------
    if (in_array($act, ['add_level','edit_level','delete_level'], true)) {
        if (!isAdmin()) { redirect('?page=admin_login'); }
        requireStaffPermission($pdo, 'academics_manage');
        portalEnsureAcademicLevelsSchema($pdo);
        try {
            if ($act === 'add_level') {
                $levelName = trim((string)($_POST['level_name'] ?? ''));
                $sortOrder = max(0, (int)($_POST['sort_order'] ?? 0));
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                if ($levelName === '') throw new RuntimeException('Level name is required.');
                $st = $pdo->prepare("INSERT INTO academic_levels (level_name, sort_order, is_active) VALUES (?,?,?) ON DUPLICATE KEY UPDATE sort_order = VALUES(sort_order), is_active = VALUES(is_active)");
                $st->execute([$levelName, $sortOrder, $isActive]);
                flash('success', 'Academic level saved successfully.');
                redirect('?page=admin_levels');
            }
            if ($act === 'edit_level') {
                $id = (int)($_POST['id'] ?? 0);
                $levelName = trim((string)($_POST['level_name'] ?? ''));
                $sortOrder = max(0, (int)($_POST['sort_order'] ?? 0));
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                if ($id <= 0) throw new RuntimeException('Invalid level selected.');
                if ($levelName === '') throw new RuntimeException('Level name is required.');
                $st = $pdo->prepare("UPDATE academic_levels SET level_name = ?, sort_order = ?, is_active = ? WHERE id = ?");
                $st->execute([$levelName, $sortOrder, $isActive, $id]);
                flash('success', 'Academic level updated.');
                redirect('?page=admin_levels');
            }
            if ($act === 'delete_level') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id <= 0) throw new RuntimeException('Invalid level selected.');
                $existingLevelName = '';
                try {
                    $check = $pdo->prepare("SELECT level_name FROM academic_levels WHERE id = ? LIMIT 1");
                    $check->execute([$id]);
                    $existingLevelName = trim((string)($check->fetchColumn() ?: ''));
                } catch (Throwable $e) {}
                $st = $pdo->prepare("DELETE FROM academic_levels WHERE id = ?");
                $st->execute([$id]);
                if ($st->rowCount() < 1) {
                    throw new RuntimeException('Academic level not found or already removed.');
                }
                flash('success', ($existingLevelName !== '' ? $existingLevelName . ' ' : 'Academic level ') . 'deleted successfully.');
                redirect('?page=admin_levels');
            }
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Unable to save academic level.');
            redirect('?page=admin_levels');
        }
    }

    // -------- Academic Sessions + Promotion --------
    if (in_array($act, ['add_session','edit_session','delete_session','set_current_session','preview_promote_students','promote_students'], true)) {
        $allowedSessionSubtabs = ['manage_session','add_session','promote_students','promotion_history'];
        $requestedSessionSubtab = trim((string)($_POST['return_subtab'] ?? ''));
        if (!in_array($requestedSessionSubtab, $allowedSessionSubtabs, true)) {
            $requestedSessionSubtab = '';
        }
        $sessionRedirect = '?page=admin_sessions_manager';
        if ($requestedSessionSubtab !== '') {
            $sessionRedirect .= '&subtab=' . urlencode($requestedSessionSubtab);
        }
        $sessionReturnQueryString = trim((string)($_POST['return_query'] ?? ''));
        if ($sessionReturnQueryString !== '') {
            $parsedSessionReturnQuery = [];
            parse_str($sessionReturnQueryString, $parsedSessionReturnQuery);
            if (is_array($parsedSessionReturnQuery) && (($parsedSessionReturnQuery['page'] ?? '') === 'admin_sessions_manager')) {
                $safeSessionQuery = [
                    'page' => 'admin_sessions_manager',
                ];
                if ($requestedSessionSubtab !== '') {
                    $safeSessionQuery['subtab'] = $requestedSessionSubtab;
                }
                if (($safeSessionQuery['subtab'] ?? '') === 'manage_session') {
                    $safeSessionQuery['session_search_name'] = trim((string)($parsedSessionReturnQuery['session_search_name'] ?? ''));
                    $safeSessionQuery['session_filter_current'] = trim((string)($parsedSessionReturnQuery['session_filter_current'] ?? ''));
                    $safeSessionQuery['session_filter_status'] = trim((string)($parsedSessionReturnQuery['session_filter_status'] ?? ''));
                    $safeSessionQuery['session_filter_access'] = trim((string)($parsedSessionReturnQuery['session_filter_access'] ?? ''));
                } elseif (($safeSessionQuery['subtab'] ?? '') === 'promotion_history') {
                    $safeSessionQuery['promotion_history_q'] = trim((string)($parsedSessionReturnQuery['promotion_history_q'] ?? ''));
                    $safeSessionQuery['promotion_history_session'] = trim((string)($parsedSessionReturnQuery['promotion_history_session'] ?? ''));
                    $safeSessionQuery['promotion_history_level'] = trim((string)($parsedSessionReturnQuery['promotion_history_level'] ?? ''));
                    $safeSessionQuery['promotion_history_status'] = trim((string)($parsedSessionReturnQuery['promotion_history_status'] ?? ''));
                    $safeSessionQuery['promotion_history_from'] = trim((string)($parsedSessionReturnQuery['promotion_history_from'] ?? ''));
                    $safeSessionQuery['promotion_history_to'] = trim((string)($parsedSessionReturnQuery['promotion_history_to'] ?? ''));
                }
                $sessionRedirect = '?' . http_build_query(array_filter($safeSessionQuery, static function($value) {
                    return $value !== null && $value !== '';
                }));
            }
        }
        try {
            try { portalSelfHealAcademicSessions($pdo); } catch (Throwable $e) {}
            if ($act === 'add_session') {
                portalEnsureAcademicSessionRegistrationControl($pdo);
                $name = portalPostedSessionNameSafe($_POST);
                $start = trim((string)($_POST['start_date'] ?? ''));
                $end = trim((string)($_POST['end_date'] ?? ''));
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                $isCurrent = isset($_POST['is_current']) ? 1 : 0;
                $registrationSemesterAccess = portalNormalizeRegistrationSemesterAccess($_POST['registration_semester_access'] ?? 'both');
                if ($name === '') throw new RuntimeException('Session name is required (e.g., 2026/2027).');

                $dup = $pdo->prepare("SELECT COUNT(*) FROM academic_sessions WHERE name = ?");
                $dup->execute([$name]);
                if ((int)$dup->fetchColumn() > 0) throw new RuntimeException('That academic session already exists.');

                $pdo->beginTransaction();
                if ($isCurrent === 1) {
                    $pdo->exec("UPDATE academic_sessions SET is_current = 0");
                }
                $stmt = $pdo->prepare("INSERT INTO academic_sessions (name, start_date, end_date, is_current, is_active, registration_semester_access) VALUES (?,?,?,?,?,?)");
                $stmt->execute([$name, ($start!==''?$start:null), ($end!==''?$end:null), $isCurrent, $isActive, $registrationSemesterAccess]);
                $pdo->commit();

                // sync system setting
                if ($isCurrent === 1) {
                    $up = $pdo->prepare("INSERT INTO system_settings (setting_key, setting_value, description)
                                         VALUES ('academic_session', ?, 'Current academic session')
                                         ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
                    $up->execute([$name]);
                }

                flash('success', 'Academic session added.');
                redirect($sessionRedirect);
            }

            if ($act === 'edit_session') {
                portalEnsureAcademicSessionRegistrationControl($pdo);
                $id = (int)($_POST['id'] ?? 0);
                $name = portalPostedSessionNameSafe($_POST);
                $start = trim((string)($_POST['start_date'] ?? ''));
                $end = trim((string)($_POST['end_date'] ?? ''));
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                $isCurrent = isset($_POST['is_current']) ? 1 : 0;
                $registrationSemesterAccess = portalNormalizeRegistrationSemesterAccess($_POST['registration_semester_access'] ?? 'both');
                if ($id <= 0) throw new RuntimeException('Invalid session selected.');
                if ($name === '') throw new RuntimeException('Session name is required.');

                $dup = $pdo->prepare("SELECT COUNT(*) FROM academic_sessions WHERE name = ? AND id <> ?");
                $dup->execute([$name, $id]);
                if ((int)$dup->fetchColumn() > 0) throw new RuntimeException('Another academic session already uses that name.');

                $pdo->beginTransaction();
                if ($isCurrent === 1) {
                    $pdo->exec("UPDATE academic_sessions SET is_current = 0");
                }
                $stmt = $pdo->prepare("UPDATE academic_sessions SET name=?, start_date=?, end_date=?, is_current=?, is_active=?, registration_semester_access=? WHERE id=?");
                $stmt->execute([$name, ($start!==''?$start:null), ($end!==''?$end:null), $isCurrent, $isActive, $registrationSemesterAccess, $id]);
                $pdo->commit();

                if ($isCurrent === 1) {
                    $up = $pdo->prepare("INSERT INTO system_settings (setting_key, setting_value, description)
                                         VALUES ('academic_session', ?, 'Current academic session')
                                         ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
                    $up->execute([$name]);
                }

                flash('success', 'Academic session updated.');
                redirect($sessionRedirect);
            }

            if ($act === 'delete_session') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id <= 0) throw new RuntimeException('Invalid session selected.');
                $pdo->prepare("DELETE FROM academic_sessions WHERE id=?")->execute([$id]);
                flash('success', 'Academic session deleted.');
                redirect($sessionRedirect);
            }

            if ($act === 'set_current_session') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id <= 0) throw new RuntimeException('Invalid session selected.');
                $pdo->beginTransaction();
                $pdo->exec("UPDATE academic_sessions SET is_current = 0");
                $pdo->prepare("UPDATE academic_sessions SET is_current = 1 WHERE id=?")->execute([$id]);
                $pdo->commit();
                $name = (string)($pdo->query("SELECT name FROM academic_sessions WHERE id=".(int)$id)->fetchColumn() ?? '');
                if ($name !== '') {
                    $up = $pdo->prepare("INSERT INTO system_settings (setting_key, setting_value, description)
                                         VALUES ('academic_session', ?, 'Current academic session')
                                         ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
                    $up->execute([$name]);
                }
                flash('success', 'Current academic session updated.');
                redirect($sessionRedirect);
            }

            if (in_array($act, ['preview_promote_students', 'promote_students'], true)) {
                portalEnsureApplicationAcademicFieldSupport($pdo);
                try { portalEnsureBulkPromotionHistorySchema($pdo); } catch (Throwable $e) {}

                $_SESSION['bulk_promotion_form_values'] = [
                    'from_level' => (string)($_POST['from_level'] ?? 'ALL'),
                    'to_level' => (string)($_POST['to_level'] ?? 'ALL'),
                    'to_session' => (string)($_POST['to_session'] ?? ''),
                    'faculty_id' => (int)($_POST['faculty_id'] ?? 0),
                    'department_id' => (int)($_POST['department_id'] ?? 0),
                    'programme_id' => (int)($_POST['programme_id'] ?? 0),
                    'mode_of_study' => (string)($_POST['mode_of_study'] ?? 'ALL'),
                    'mode_of_study_custom' => (string)($_POST['mode_of_study_custom'] ?? ''),
                    'superadmin_identity' => (string)($_POST['superadmin_identity'] ?? ''),
                ];

                $fromLevel = trim((string)($_POST['from_level'] ?? ''));
                $toLevel = trim((string)($_POST['to_level'] ?? ''));
                $toSession = portalNormalizeSessionNameSafe((string)($_POST['to_session'] ?? ''));
                $facultyId = (int)($_POST['faculty_id'] ?? 0);
                $departmentId = (int)($_POST['department_id'] ?? 0);
                $programmeId = (int)($_POST['programme_id'] ?? 0);
                $modeOfStudy = trim((string)($_POST['mode_of_study'] ?? 'ALL'));
                $modeOfStudyCustom = trim((string)($_POST['mode_of_study_custom'] ?? ''));
                $modeOfStudy = preg_replace('/\s+/u', ' ', $modeOfStudy) ?: $modeOfStudy;
                $modeOfStudyCustom = preg_replace('/\s+/u', ' ', $modeOfStudyCustom) ?: $modeOfStudyCustom;
                $superadminIdentity = trim((string)($_POST['superadmin_identity'] ?? ''));
                $superadminPassword = (string)($_POST['superadmin_password'] ?? '');
                $availablePromotionLevels = portalAcademicLevelOptions($pdo);
                $normalizedPromotionLevels = [];
                foreach ($availablePromotionLevels as $levelOpt) {
                    $levelOpt = trim((string)$levelOpt);
                    if ($levelOpt === '') continue;
                    $normalizedPromotionLevels[strtoupper($levelOpt)] = $levelOpt;
                }
                $fromLevelKey = strtoupper($fromLevel);
                $toLevelKey = strtoupper($toLevel);
                $fromLevelAll = ($fromLevelKey === 'ALL');
                $toLevelAll = ($toLevelKey === 'ALL');
                if (!$fromLevelAll) {
                    if ($fromLevel === '' || !isset($normalizedPromotionLevels[$fromLevelKey])) {
                        throw new RuntimeException('Please choose a valid From Level.');
                    }
                    $fromLevel = (string)$normalizedPromotionLevels[$fromLevelKey];
                }
                if (!$toLevelAll) {
                    if ($toLevel === '' || !isset($normalizedPromotionLevels[$toLevelKey])) {
                        throw new RuntimeException('Please choose a valid To Level.');
                    }
                    $toLevel = (string)$normalizedPromotionLevels[$toLevelKey];
                }
                if ($toSession === '') {
                    throw new RuntimeException('To session is required.');
                }
                $availablePromotionSessions = portalAvailableSessionOptions($pdo);
                $normalizedPromotionSessions = [];
                foreach ($availablePromotionSessions as $sessionOpt) {
                    $sessionOpt = portalNormalizeSessionNameSafe((string)$sessionOpt);
                    if ($sessionOpt === '') continue;
                    $normalizedPromotionSessions[strtoupper($sessionOpt)] = $sessionOpt;
                }
                $toSessionKey = strtoupper($toSession);
                if ($toSessionKey === 'ALL') {
                    throw new RuntimeException('Please choose a specific To Session. "All" cannot be used as the promotion target session.');
                }
                if (!isset($normalizedPromotionSessions[$toSessionKey])) {
                    throw new RuntimeException('Please choose a valid To Session from the academic sessions list.');
                }
                $toSession = (string)$normalizedPromotionSessions[$toSessionKey];
                if ($programmeId > 0) {
                    $progCheck = $pdo->prepare("SELECT p.id, COALESCE(d.faculty_id, p.faculty_id, 0) AS faculty_id, COALESCE(p.department_id,0) AS department_id
                                                FROM programmes p
                                                LEFT JOIN departments d ON d.id = p.department_id
                                                WHERE p.id = ? LIMIT 1");
                    $progCheck->execute([$programmeId]);
                    $progRow = $progCheck->fetch(PDO::FETCH_ASSOC) ?: null;
                    if (!$progRow) {
                        throw new RuntimeException('Selected programme is invalid. Please choose a valid programme.');
                    }
                    $progFacultyId = (int)($progRow['faculty_id'] ?? 0);
                    $progDepartmentId = (int)($progRow['department_id'] ?? 0);
                    if ($departmentId > 0 && $progDepartmentId !== $departmentId) {
                        throw new RuntimeException('Selected programme does not belong to the chosen department.');
                    }
                    if ($departmentId <= 0 && $facultyId > 0 && $progFacultyId > 0 && $progFacultyId !== $facultyId) {
                        throw new RuntimeException('Selected programme does not belong to the chosen faculty.');
                    }
                }

                $availablePromotionModes = portalModeOfStudyOptionValues($pdo);
                $normalizedPromotionModes = [];
                foreach ($availablePromotionModes as $modeOpt) {
                    $modeOpt = trim((string)$modeOpt);
                    if ($modeOpt === '') continue;
                    $normalizedPromotionModes[strtoupper($modeOpt)] = $modeOpt;
                }
                $modeOfStudyFilterType = 'all';
                $modeOfStudyFilterValue = '';
                $modeOfStudyLabel = 'All Modes of Study';
                $modeOfStudyKey = strtoupper($modeOfStudy);
                if ($modeOfStudyKey !== '' && $modeOfStudyKey !== 'ALL') {
                    if ($modeOfStudyKey === 'OTHERS') {
                        $modeOfStudyFilterType = 'others';
                        $modeOfStudyLabel = 'Others';
                        if ($modeOfStudyCustom !== '') {
                            $modeOfStudyFilterType = 'exact';
                            $modeOfStudyFilterValue = $modeOfStudyCustom;
                            $modeOfStudyLabel = $modeOfStudyCustom;
                        }
                    } else {
                        if (!isset($normalizedPromotionModes[$modeOfStudyKey])) {
                            throw new RuntimeException('Please choose a valid Mode of Study.');
                        }
                        $modeOfStudyFilterType = 'exact';
                        $modeOfStudyFilterValue = (string)$normalizedPromotionModes[$modeOfStudyKey];
                        $modeOfStudyLabel = $modeOfStudyFilterValue;
                    }
                }

                $facultyName = 'All Faculty';
                if ($facultyId > 0) {
                    try {
                        $nameStmt = $pdo->prepare("SELECT name FROM faculties WHERE id = ? LIMIT 1");
                        $nameStmt->execute([$facultyId]);
                        $facultyName = trim((string)($nameStmt->fetchColumn() ?: ''));
                    } catch (Throwable $e) {}
                    if ($facultyName === '') $facultyName = 'Selected Faculty';
                }
                $departmentName = 'All Department';
                if ($departmentId > 0) {
                    try {
                        $nameStmt = $pdo->prepare("SELECT name FROM departments WHERE id = ? LIMIT 1");
                        $nameStmt->execute([$departmentId]);
                        $departmentName = trim((string)($nameStmt->fetchColumn() ?: ''));
                    } catch (Throwable $e) {}
                    if ($departmentName === '') $departmentName = 'Selected Department';
                }
                $programmeName = 'All Programmes';
                if ($programmeId > 0) {
                    try {
                        $nameStmt = $pdo->prepare("SELECT name FROM programmes WHERE id = ? LIMIT 1");
                        $nameStmt->execute([$programmeId]);
                        $programmeName = trim((string)($nameStmt->fetchColumn() ?: ''));
                    } catch (Throwable $e) {}
                    if ($programmeName === '') $programmeName = 'Selected Programme';
                }

                // Build filters safely
                $where = "1=1";
                $params = [];
                if (!$fromLevelAll) {
                    $where .= " AND (level IS NULL OR level = ? OR level = '')";
                    $params[] = $fromLevel;
                }
                if ($facultyId > 0) { $where .= " AND faculty_id = ?"; $params[] = $facultyId; }
                if ($departmentId > 0) { $where .= " AND department_id = ?"; $params[] = $departmentId; }
                if ($programmeId > 0) { $where .= " AND programme_id = ?"; $params[] = $programmeId; }
                if ($modeOfStudyFilterType === 'exact') {
                    $where .= " AND LOWER(TRIM(COALESCE(mode_of_study, ''))) = LOWER(TRIM(?))";
                    $params[] = $modeOfStudyFilterValue;
                } elseif ($modeOfStudyFilterType === 'others') {
                    $where .= " AND TRIM(COALESCE(mode_of_study, '')) <> '' AND UPPER(TRIM(COALESCE(mode_of_study, ''))) NOT IN ('FULL-TIME','PART-TIME')";
                }

                $matchedCount = 0;
                $targetRows = [];
                $sourceSessionBucket = [];
                try {
                    $targetStmt = $pdo->prepare("SELECT id, COALESCE(NULLIF(TRIM(session),''), NULLIF(TRIM(session_name),'')) AS current_session FROM students WHERE {$where}");
                    $targetStmt->execute($params);
                    $targetRows = $targetStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    $matchedCount = count($targetRows);
                    foreach ($targetRows as $targetRow) {
                        $srcSession = portalNormalizeSessionNameSafe((string)($targetRow['current_session'] ?? ''));
                        if ($srcSession !== '') $sourceSessionBucket[$srcSession] = true;
                    }
                } catch (Throwable $e) {
                    $targetRows = [];
                    $matchedCount = 0;
                }
                $fromSessionLabel = '';
                if (count($sourceSessionBucket) === 1) {
                    $sourceSessionKeys = array_keys($sourceSessionBucket);
                    $fromSessionLabel = (string)($sourceSessionKeys[0] ?? '');
                } elseif (count($sourceSessionBucket) > 1) {
                    $fromSessionLabel = 'Multiple';
                }

                $previewPayload = [
                    'from_level' => !$fromLevelAll ? $fromLevel : 'All',
                    'to_level' => !$toLevelAll ? $toLevel : 'All',
                    'to_session' => $toSession,
                    'faculty_name' => $facultyName,
                    'department_name' => $departmentName,
                    'programme_name' => $programmeName,
                    'mode_of_study' => $modeOfStudyLabel,
                    'matched_count' => $matchedCount,
                    'from_session' => $fromSessionLabel,
                    'generated_at' => date('Y-m-d H:i:s'),
                ];

                if ($act === 'preview_promote_students') {
                    $_SESSION['bulk_promotion_preview'] = $previewPayload;
                    flash('info', "Promotion preview ready. Matched {$matchedCount} student(s) for the selected scope.");
                    redirect($sessionRedirect);
                }

                $verify = portalVerifySuperadminCredential($pdo, $superadminIdentity, $superadminPassword);
                if (empty($verify['ok'])) {
                    throw new RuntimeException((string)($verify['message'] ?? 'Superadmin password verification failed.'));
                }

                $verifiedAdmin = (array)($verify['admin'] ?? []);
                $verifiedIdentity = trim((string)($verifiedAdmin['email'] ?? ''));
                if ($verifiedIdentity === '') $verifiedIdentity = trim((string)($verifiedAdmin['username'] ?? ''));
                if ($verifiedIdentity === '') $verifiedIdentity = trim((string)($superadminIdentity ?? ''));

                $actingAdminId = (int)portalCurrentAdminId();
                $actingAdminName = '';
                if ($actingAdminId > 0) {
                    try {
                        $actingStmt = $pdo->prepare("SELECT full_name, username, email FROM admins WHERE id = ? LIMIT 1");
                        $actingStmt->execute([$actingAdminId]);
                        $actingAdminRow = $actingStmt->fetch(PDO::FETCH_ASSOC) ?: [];
                        $actingAdminName = trim((string)($actingAdminRow['full_name'] ?? ''));
                        if ($actingAdminName === '') $actingAdminName = trim((string)($actingAdminRow['username'] ?? ''));
                        if ($actingAdminName === '') $actingAdminName = trim((string)($actingAdminRow['email'] ?? ''));
                    } catch (Throwable $e) {}
                }

                $pdo->beginTransaction();
                if ($toLevelAll) {
                    $stmt = $pdo->prepare("UPDATE students SET session = ? WHERE {$where}");
                    $stmt->execute(array_merge([$toSession], $params));
                } else {
                    $stmt = $pdo->prepare("UPDATE students SET level = ?, session = ? WHERE {$where}");
                    $stmt->execute(array_merge([$toLevel, $toSession], $params));
                }
                $affected = (int)$stmt->rowCount();
                $pdo->commit();

                $feeWarnings = [];
                $schoolFeeResyncRan = false;
                $hostelFeeResyncRan = false;
                foreach ($targetRows as $targetRow) {
                    $promotedStudentId = (int)($targetRow['id'] ?? 0);
                    if ($promotedStudentId <= 0) continue;
                    $sync = portalResynchronizeStudentFeeRequirements($pdo, $promotedStudentId, [
                        'session_name' => $toSession,
                        'level' => !$toLevelAll ? $toLevel : null,
                    ]);
                    $schoolFeeResyncRan = true;
                    $targetSourceSession = portalNormalizeSessionNameSafe((string)($targetRow['current_session'] ?? ''));
                    if ($targetSourceSession === '' || strcasecmp($targetSourceSession, $toSession) !== 0) {
                        $hostelFeeResyncRan = true;
                    }
                    foreach ((array)($sync['warnings'] ?? []) as $warning) {
                        $warning = trim((string)$warning);
                        if ($warning !== '') $feeWarnings[$warning] = true;
                    }
                }

                $effectiveMatchedCount = max(0, max((int)$matchedCount, (int)$affected));
                $hasActualPromotionUpdate = ($affected > 0);

                $historyStatus = 'Successful';
                if ($hasActualPromotionUpdate) {
                    $historyStatus = $feeWarnings ? 'Partial' : 'Successful';
                } elseif ($effectiveMatchedCount === 0) {
                    $historyStatus = 'No Match';
                } else {
                    $historyStatus = 'Failed';
                }

                if ($hasActualPromotionUpdate) {
                    $summaryMessage = 'Promotion completed. Updated ' . $affected . ' student(s) to ' . (!$toLevelAll ? $toLevel : 'the selected scope') . ' in ' . $toSession . '. Matched ' . $effectiveMatchedCount . ' student(s). School fee re-synchronization: ' . ($schoolFeeResyncRan ? 'Ran' : 'Not needed') . '. Hostel fee re-synchronization: ' . ($hostelFeeResyncRan ? 'Ran' : 'Not needed') . '.';
                } elseif ($effectiveMatchedCount === 0) {
                    $summaryMessage = 'No students matched the selected promotion scope. No promotion was applied.';
                } else {
                    $summaryMessage = 'Promotion was not applied because no student record required an update for the selected scope.';
                }

                try {
                    portalInsertBulkPromotionHistory($pdo, [
                        'action_type' => 'promote',
                        'performed_by_admin_id' => (int)($verifiedAdmin['id'] ?? 0),
                        'performed_by_identity' => $verifiedIdentity,
                        'acting_admin_name' => $actingAdminName,
                        'from_level' => !$fromLevelAll ? $fromLevel : 'All',
                        'to_level' => !$toLevelAll ? $toLevel : 'All',
                        'from_session' => $fromSessionLabel,
                        'to_session' => $toSession,
                        'faculty_id' => $facultyId,
                        'faculty_name' => $facultyName,
                        'department_id' => $departmentId,
                        'department_name' => $departmentName,
                        'programme_id' => $programmeId,
                        'programme_name' => $programmeName,
                        'mode_of_study' => $modeOfStudyLabel,
                        'mode_of_study_filter_type' => $modeOfStudyFilterType,
                        'matched_count' => $effectiveMatchedCount,
                        'updated_count' => $affected,
                        'status' => $historyStatus,
                        'school_fee_resync_triggered' => $schoolFeeResyncRan,
                        'hostel_fee_resync_triggered' => $hostelFeeResyncRan,
                        'warning_count' => count($feeWarnings),
                        'message' => $summaryMessage,
                        'warnings_json' => $feeWarnings ? json_encode(array_values(array_keys($feeWarnings)), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null,
                        'details_json' => json_encode([
                            'filters' => [
                                'from_level' => !$fromLevelAll ? $fromLevel : 'All',
                                'to_level' => !$toLevelAll ? $toLevel : 'All',
                                'to_session' => $toSession,
                                'faculty_id' => $facultyId,
                                'department_id' => $departmentId,
                                'programme_id' => $programmeId,
                                'mode_of_study' => $modeOfStudy,
                                'mode_of_study_custom' => $modeOfStudyCustom,
                            ],
                            'matched_count' => $effectiveMatchedCount,
                            'updated_count' => $affected,
                            'from_session' => $fromSessionLabel,
                        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                    ]);
                } catch (Throwable $e) {}

                if ($hasActualPromotionUpdate) {
                    flash('success', $summaryMessage);
                } elseif ($effectiveMatchedCount === 0) {
                    flash('warning', $summaryMessage);
                } else {
                    flash('info', $summaryMessage);
                }
                foreach (array_keys($feeWarnings) as $warning) {
                    flash('warning', $warning);
                }
                redirect($sessionRedirect);
            }
        } catch (Throwable $e) {
            try { if ($pdo->inTransaction()) $pdo->rollBack(); } catch (Throwable $e2) {}
            if (in_array($act, ['preview_promote_students', 'promote_students'], true)) {
                $_SESSION['bulk_promotion_form_values'] = [
                    'from_level' => (string)($_POST['from_level'] ?? 'ALL'),
                    'to_level' => (string)($_POST['to_level'] ?? 'ALL'),
                    'to_session' => (string)($_POST['to_session'] ?? ''),
                    'faculty_id' => (int)($_POST['faculty_id'] ?? 0),
                    'department_id' => (int)($_POST['department_id'] ?? 0),
                    'programme_id' => (int)($_POST['programme_id'] ?? 0),
                    'mode_of_study' => (string)($_POST['mode_of_study'] ?? 'ALL'),
                    'mode_of_study_custom' => (string)($_POST['mode_of_study_custom'] ?? ''),
                    'superadmin_identity' => (string)($_POST['superadmin_identity'] ?? ''),
                ];
            }

            $safeMessage = portalBulkPromotionSafeMessage((string)$e->getMessage(), 'Operation failed.');
            if ($act === 'promote_students') {
                try {
                    portalEnsureBulkPromotionHistorySchema($pdo);
                    portalInsertBulkPromotionHistory($pdo, [
                        'action_type' => 'promote',
                        'performed_by_admin_id' => (int)portalCurrentAdminId(),
                        'performed_by_identity' => trim((string)($_POST['superadmin_identity'] ?? '')) ?: null,
                        'acting_admin_name' => trim((string)($_SESSION['full_name'] ?? $_SESSION['username'] ?? $_SESSION['email'] ?? '')) ?: null,
                        'from_level' => trim((string)($_POST['from_level'] ?? '')) ?: 'All',
                        'to_level' => trim((string)($_POST['to_level'] ?? '')) ?: 'All',
                        'to_session' => portalNormalizeSessionNameSafe((string)($_POST['to_session'] ?? '')),
                        'faculty_id' => (int)($_POST['faculty_id'] ?? 0),
                        'department_id' => (int)($_POST['department_id'] ?? 0),
                        'programme_id' => (int)($_POST['programme_id'] ?? 0),
                        'mode_of_study' => trim((string)($_POST['mode_of_study_custom'] ?? ($_POST['mode_of_study'] ?? ''))) ?: 'All Modes of Study',
                        'mode_of_study_filter_type' => trim((string)($_POST['mode_of_study'] ?? 'ALL')),
                        'matched_count' => 0,
                        'updated_count' => 0,
                        'status' => 'Failed',
                        'school_fee_resync_triggered' => 0,
                        'hostel_fee_resync_triggered' => 0,
                        'warning_count' => 0,
                        'message' => $safeMessage,
                        'details_json' => json_encode([
                            'filters' => [
                                'from_level' => (string)($_POST['from_level'] ?? ''),
                                'to_level' => (string)($_POST['to_level'] ?? ''),
                                'to_session' => (string)($_POST['to_session'] ?? ''),
                                'faculty_id' => (int)($_POST['faculty_id'] ?? 0),
                                'department_id' => (int)($_POST['department_id'] ?? 0),
                                'programme_id' => (int)($_POST['programme_id'] ?? 0),
                                'mode_of_study' => (string)($_POST['mode_of_study'] ?? 'ALL'),
                                'mode_of_study_custom' => (string)($_POST['mode_of_study_custom'] ?? ''),
                            ],
                        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                    ]);
                } catch (Throwable $historyError) {}
            }

            flash('danger', $safeMessage);
            redirect($sessionRedirect);
        }
    }


    // -------- Registrable Courses CRUD / Import --------
    if (in_array($act, ['add_course','edit_course','delete_course','import_courses'], true)) {
        try {
            portalEnsureRegistrableCourseScopeSchema($pdo);

            if ($act === 'import_courses') {
                if (!isAdmin()) throw new RuntimeException('Unauthorized.');
                if (empty($_FILES['courses_csv']['tmp_name']) || !is_uploaded_file($_FILES['courses_csv']['tmp_name'])) {
                    throw new RuntimeException('Please choose a CSV file to import.');
                }
                $fh = fopen($_FILES['courses_csv']['tmp_name'], 'r');
                if (!$fh) throw new RuntimeException('Unable to open the uploaded CSV file.');
                $headers = fgetcsv($fh);
                if (!$headers || !is_array($headers)) {
                    fclose($fh);
                    throw new RuntimeException('The CSV file is empty.');
                }
                $headerMap = [];
                foreach ($headers as $idx => $name) {
                    $headerMap[$idx] = moduleNormalizeCsvHeader((string)$name);
                }
                $requiredHeaders = [
                    'Course Code' => ['course_code'],
                    'Course Title' => ['course_title'],
                    'Credit Unit' => ['credit_unit'],
                    'Course Type' => ['course_type'],
                    'Registrable' => ['registrable'],
                    'Faculty' => ['faculty'],
                    'Department' => ['department'],
                    'Programme' => ['programme'],
                    'Level' => ['level'],
                    'Semester' => ['semester'],
                    'Year' => ['year'],
                    'Session' => ['session'],
                    'General Course' => ['general_course', 'is_general_course'],
                ];
                $availableHeaders = array_values(array_filter($headerMap, static fn($value) => $value !== ''));
                foreach ($requiredHeaders as $label => $acceptedHeaders) {
                    $matched = false;
                    foreach ($acceptedHeaders as $acceptedHeader) {
                        if (in_array(moduleNormalizeCsvHeader($acceptedHeader), $availableHeaders, true)) {
                            $matched = true;
                            break;
                        }
                    }
                    if (!$matched) {
                        fclose($fh);
                        throw new RuntimeException('CSV format is invalid. Missing header: ' . $label);
                    }
                }

                $feedback = [];
                $inserted = 0;
                $failed = 0;
                $insertStmt = $pdo->prepare("INSERT INTO registrable_courses
                    (course_code, course_title, credit_unit, faculty_id, department_id, programme_id, mode_of_study, level, semester, session_name, year, course_type, registrable, lecturer_staff_id, is_general_course)
                    VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
                $rowNum = 1;
                while (($row = fgetcsv($fh)) !== false) {
                    $rowNum++;
                    $assoc = [];
                    foreach ($headerMap as $idx => $key) {
                        if ($key === '') { continue; }
                        $value = isset($row[$idx]) ? (string)$row[$idx] : '';
                        $value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
                        $value = str_replace(["\xC2\xA0", "\xE2\x80\x8B"], ' ', $value);
                        $value = preg_replace('/\s+/u', ' ', trim($value, " \t\n\r\0\x0B\"'")) ?: trim($value, " \t\n\r\0\x0B\"'");
                        $assoc[$key] = $value;
                    }
                    if (!array_key_exists('is_general_course', $assoc) && array_key_exists('general_course', $assoc)) {
                        $assoc['is_general_course'] = $assoc['general_course'];
                    } elseif (!array_key_exists('general_course', $assoc) && array_key_exists('is_general_course', $assoc)) {
                        $assoc['general_course'] = $assoc['is_general_course'];
                    }
                    $blank = true;
                    foreach ($assoc as $v) { if (trim((string)$v) !== '') { $blank = false; break; } }
                    if ($blank) continue;

                    try {
                        $courseCode = strtoupper(trim((string)($assoc['course_code'] ?? '')));
                        $courseTitle = trim((string)($assoc['course_title'] ?? ''));
                        $creditValue = trim((string)($assoc['credit_unit'] ?? ''));
                        $facultyValue = trim((string)($assoc['faculty'] ?? ''));
                        $departmentValue = trim((string)($assoc['department'] ?? ''));
                        $programmeValue = trim((string)($assoc['programme'] ?? ''));
                        $levelValue = trim((string)($assoc['level'] ?? ''));
                        $semesterValue = trim((string)($assoc['semester'] ?? ''));
                        $yearValue = trim((string)($assoc['year'] ?? ''));
                        $sessionValue = trim((string)($assoc['session'] ?? ''));
                        $courseTypeValue = strtoupper(trim((string)($assoc['course_type'] ?? 'C')));
                        $registrableValue = ucfirst(strtolower(trim((string)($assoc['registrable'] ?? 'Yes'))));
                        $modeOfStudyValue = trim((string)($assoc['mode_of_study'] ?? ''));
                        $generalCourseValue = strtolower(trim((string)($assoc['is_general_course'] ?? '0')));

                        if ($courseCode === '') {
                            throw new RuntimeException('Import failed on row ' . $rowNum . ': course_code value missing.');
                        }
                        if ($courseTitle === '') {
                            throw new RuntimeException('Import failed on row ' . $rowNum . ': course_title value missing.');
                        }
                        if ($creditValue === '') {
                            throw new RuntimeException('Import failed on row ' . $rowNum . ': credit_unit value missing.');
                        }
                        if (!is_numeric($creditValue) || (float)$creditValue < 0) {
                            throw new RuntimeException("Import failed on row {$rowNum}: credit_unit '{$creditValue}' is invalid. Expected a non-negative number.");
                        }
                        if ($levelValue === '') {
                            throw new RuntimeException('Import failed on row ' . $rowNum . ': level value missing.');
                        }
                        if ($semesterValue === '') {
                            throw new RuntimeException('Import failed on row ' . $rowNum . ': semester value missing.');
                        }
                        $semesterMap = ['first' => 'First', '1st' => 'First', '1' => 'First', 'second' => 'Second', '2nd' => 'Second', '2' => 'Second'];
                        $semesterKey = strtolower($semesterValue);
                        if (!isset($semesterMap[$semesterKey])) {
                            throw new RuntimeException("Import failed on row {$rowNum}: semester '{$semesterValue}' is invalid. Expected First or Second.");
                        }
                        $semesterValue = $semesterMap[$semesterKey];
                        if ($yearValue === '') {
                            throw new RuntimeException('Import failed on row ' . $rowNum . ': year value missing.');
                        }
                        if ($sessionValue === '') {
                            throw new RuntimeException('Import failed on row ' . $rowNum . ': session value missing.');
                        }
                        $courseTypeAliases = ['C' => 'C', 'COMPULSORY' => 'C', 'CORE' => 'C', 'E' => 'E', 'ELECTIVE' => 'E'];
                        $courseTypeKey = strtoupper(preg_replace('/\s+/u', '', $courseTypeValue) ?: $courseTypeValue);
                        if (!isset($courseTypeAliases[$courseTypeKey])) {
                            throw new RuntimeException("Import failed on row {$rowNum}: course_type '{$courseTypeValue}' is invalid. Expected C or E.");
                        }
                        $courseTypeValue = $courseTypeAliases[$courseTypeKey];
                        $registrableAliases = ['YES' => 'Yes', 'NO' => 'No', '1' => 'Yes', '0' => 'No', 'TRUE' => 'Yes', 'FALSE' => 'No'];
                        $registrableKey = strtoupper(preg_replace('/\s+/u', '', $registrableValue) ?: $registrableValue);
                        if (!isset($registrableAliases[$registrableKey])) {
                            throw new RuntimeException("Import failed on row {$rowNum}: registrable '{$registrableValue}' is invalid. Expected Yes or No.");
                        }
                        $registrableValue = $registrableAliases[$registrableKey];

                        $facultyMatch = portalResolveFacultyRecordByText($pdo, $facultyValue);
                        $facultyId = (int)($facultyMatch['id'] ?? 0);
                        $departmentMatch = portalResolveDepartmentRecordByText($pdo, $departmentValue, $facultyId);
                        $departmentId = (int)($departmentMatch['id'] ?? 0);
                        $departmentFallback = ($departmentValue !== '' && $departmentId <= 0) ? portalResolveDepartmentRecordByText($pdo, $departmentValue, 0) : ['id' => 0];
                        $programmeMatch = portalResolveProgrammeRecordByText($pdo, $programmeValue, $departmentId);
                        $programmeId = (int)($programmeMatch['id'] ?? 0);
                        $programmeFallback = ($programmeValue !== '' && $programmeId <= 0) ? portalResolveProgrammeRecordByText($pdo, $programmeValue, 0) : ['id' => 0];

                        if ($facultyValue !== '' && $facultyId <= 0) {
                            $message = "Import failed on row {$rowNum}: faculty '{$facultyValue}' not found.";
                            if (($facultyMatch['suggestion'] ?? '') !== '') {
                                $message .= " Did you mean '{$facultyMatch['suggestion']}'?";
                            }
                            if (($departmentFallback['id'] ?? 0) > 0 && !empty($departmentFallback['faculty_id'])) {
                                $message .= " Department '{$departmentValue}' exists under faculty '" . portalFacultyNameById($pdo, (int)$departmentFallback['faculty_id']) . "'.";
                            }
                            throw new RuntimeException($message);
                        }

                        if ($departmentValue !== '' && $departmentId <= 0) {
                            $message = "Import failed on row {$rowNum}: department '{$departmentValue}' not found";
                            if (($facultyMatch['name'] ?? '') !== '') {
                                $message .= " under faculty '{$facultyMatch['name']}'";
                            }
                            $message .= '.';
                            if (($departmentFallback['id'] ?? 0) > 0) {
                                $elsewhereFacultyName = portalFacultyNameById($pdo, (int)($departmentFallback['faculty_id'] ?? 0));
                                if ($elsewhereFacultyName !== '') {
                                    $message .= " It exists under faculty '{$elsewhereFacultyName}'.";
                                }
                            } elseif (($departmentMatch['suggestion'] ?? '') !== '') {
                                $message .= " Did you mean '{$departmentMatch['suggestion']}'?";
                            }
                            throw new RuntimeException($message);
                        }

                        if ($programmeValue !== '' && $programmeId <= 0) {
                            $message = "Import failed on row {$rowNum}: programme '{$programmeValue}' not found";
                            if (($departmentMatch['name'] ?? '') !== '') {
                                $message .= " under department '{$departmentMatch['name']}'";
                            }
                            $message .= '.';
                            if (($programmeFallback['id'] ?? 0) > 0) {
                                $elsewhereDepartmentName = portalDepartmentNameById($pdo, (int)($programmeFallback['department_id'] ?? 0));
                                if ($elsewhereDepartmentName !== '') {
                                    $message .= " It exists under department '{$elsewhereDepartmentName}'.";
                                }
                            } elseif (($programmeMatch['suggestion'] ?? '') !== '') {
                                $message .= " Did you mean '{$programmeMatch['suggestion']}'?";
                            }
                            throw new RuntimeException($message);
                        }

                        if ($programmeId > 0 && $departmentId <= 0) {
                            $departmentId = (int)($programmeMatch['department_id'] ?? 0);
                        }
                        if ($departmentId > 0 && $facultyId <= 0) {
                            $facultyId = (int)($departmentMatch['faculty_id'] ?? $departmentFallback['faculty_id'] ?? 0);
                        }
                        if ($facultyId <= 0 && $departmentId <= 0 && $programmeId <= 0) {
                            throw new RuntimeException('Import failed on row ' . $rowNum . ': course scope is incomplete. Provide at least a valid faculty, department, or programme.');
                        }

                        $raw = [
                            'course_code' => $courseCode,
                            'course_title' => $courseTitle,
                            'credit_unit' => (int)$creditValue,
                            'course_type' => $courseTypeValue,
                            'registrable' => $registrableValue,
                            'faculty_id' => $facultyId,
                            'department_id' => $departmentId,
                            'programme_id' => $programmeId,
                            'mode_of_study' => $modeOfStudyValue,
                            'level' => $levelValue,
                            'semester' => $semesterValue,
                            'year' => $yearValue,
                            'session_name' => $sessionValue,
                            'is_general_course' => in_array($generalCourseValue, ['1','yes','true','general'], true) ? 1 : 0,
                            'lecturer_staff_id' => 0,
                        ];
                        $norm = portalValidateRegistrableCoursePayload($pdo, $raw, 0);
                        $insertStmt->execute([
                            $norm['course_code'],
                            $norm['course_title'],
                            $norm['credit_unit'],
                            $norm['faculty_id'] > 0 ? $norm['faculty_id'] : null,
                            $norm['department_id'] > 0 ? $norm['department_id'] : null,
                            $norm['programme_id'] > 0 ? $norm['programme_id'] : null,
                            $norm['mode_of_study'],
                            $norm['level'],
                            $norm['semester'],
                            $norm['session_name'],
                            $norm['year'],
                            $norm['course_type'],
                            $norm['registrable'],
                            null,
                            $norm['is_general_course'],
                        ]);
                        $inserted++;
                        $feedback[] = ['row' => $rowNum, 'status' => 'success', 'message' => "Imported course '{$norm['course_code']}' successfully."];
                    } catch (Throwable $e) {
                        $failed++;
                        $feedback[] = ['row' => $rowNum, 'status' => 'danger', 'message' => $e->getMessage() ?: ('Import failed on row ' . $rowNum . '.')];
                    }
                }
                fclose($fh);
                $_SESSION['__course_import_feedback'] = $feedback;
                unset($_SESSION['__registrable_course_form_old']);
                flash($failed > 0 ? 'warning' : 'success', "Import complete: {$inserted} row(s) imported, {$failed} row(s) skipped.");
                redirect(portalRegistrableCourseManagerRedirectUrl([
                    'faculty_id' => $_POST['_filter_faculty_id'] ?? 0,
                    'department_id' => $_POST['_filter_department_id'] ?? 0,
                    'programme_id' => $_POST['_filter_programme_id'] ?? 0,
                    'mode_of_study' => $_POST['_filter_mode_of_study'] ?? '',
                    'level' => $_POST['_filter_level'] ?? '',
                    'semester' => $_POST['_filter_semester'] ?? '',
                    'session_name' => $_POST['_filter_session_name'] ?? '',
                    'year' => $_POST['_filter_year'] ?? '',
                ]));
            }

            if ($act === 'add_course' || $act === 'edit_course') {
                $id = (int)($_POST['id'] ?? 0);
                $norm = portalValidateRegistrableCoursePayload($pdo, [
                    'id' => $id,
                    'course_code' => $_POST['course_code'] ?? '',
                    'course_title' => $_POST['course_title'] ?? '',
                    'credit_unit' => $_POST['credit_unit'] ?? 0,
                    'faculty_id' => $_POST['faculty_id'] ?? 0,
                    'department_id' => $_POST['department_id'] ?? 0,
                    'programme_id' => $_POST['programme_id'] ?? 0,
                    'mode_of_study' => $_POST['mode_of_study'] ?? '',
                    'level' => $_POST['level'] ?? '',
                    'semester' => $_POST['semester'] ?? 'First',
                    'session_name' => trim((string)($_POST['session_name'] ?? setting('academic_session',''))),
                    'year' => $_POST['year'] ?? '',
                    'course_type' => $_POST['course_type'] ?? 'C',
                    'registrable' => $_POST['registrable'] ?? 'Yes',
                    'lecturer_staff_id' => $_POST['lecturer_staff_id'] ?? 0,
                    'is_general_course' => $_POST['is_general_course'] ?? 0,
                ], $act === 'edit_course' ? $id : 0);

                if ($act === 'add_course') {
                    $stmt = $pdo->prepare("INSERT INTO registrable_courses
                        (course_code, course_title, credit_unit, faculty_id, department_id, programme_id, mode_of_study, level, semester, session_name, year, course_type, registrable, lecturer_staff_id, is_general_course)
                        VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
                    $stmt->execute([
                        $norm['course_code'], $norm['course_title'], $norm['credit_unit'],
                        $norm['faculty_id'] > 0 ? $norm['faculty_id'] : null,
                        $norm['department_id'] > 0 ? $norm['department_id'] : null,
                        $norm['programme_id'] > 0 ? $norm['programme_id'] : null,
                        $norm['mode_of_study'],
                        $norm['level'],
                        $norm['semester'],
                        $norm['session_name'],
                        $norm['year'],
                        $norm['course_type'],
                        $norm['registrable'],
                        ($norm['lecturer_staff_id'] > 0 ? $norm['lecturer_staff_id'] : null),
                        $norm['is_general_course'],
                    ]);
                    unset($_SESSION['__registrable_course_form_old']);
                    flash('success', 'Course added successfully and synchronized with Course Registration.');
                    redirect(portalRegistrableCourseManagerRedirectUrl([
                        'faculty_id' => $_POST['_filter_faculty_id'] ?? 0,
                        'department_id' => $_POST['_filter_department_id'] ?? 0,
                        'programme_id' => $_POST['_filter_programme_id'] ?? 0,
                        'mode_of_study' => $_POST['_filter_mode_of_study'] ?? '',
                        'level' => $_POST['_filter_level'] ?? '',
                        'semester' => $_POST['_filter_semester'] ?? '',
                        'session_name' => $_POST['_filter_session_name'] ?? '',
                        'year' => $_POST['_filter_year'] ?? '',
                    ]));
                } else {
                    if ($id <= 0) throw new RuntimeException('Invalid course selected.');
                    $stmt = $pdo->prepare("UPDATE registrable_courses SET
                        course_code=?, course_title=?, credit_unit=?, faculty_id=?, department_id=?, programme_id=?, mode_of_study=?, level=?, semester=?, session_name=?, year=?, course_type=?, registrable=?, lecturer_staff_id=?, is_general_course=?
                        WHERE id=?");
                    $stmt->execute([
                        $norm['course_code'], $norm['course_title'], $norm['credit_unit'],
                        $norm['faculty_id'] > 0 ? $norm['faculty_id'] : null,
                        $norm['department_id'] > 0 ? $norm['department_id'] : null,
                        $norm['programme_id'] > 0 ? $norm['programme_id'] : null,
                        $norm['mode_of_study'],
                        $norm['level'],
                        $norm['semester'],
                        $norm['session_name'],
                        $norm['year'],
                        $norm['course_type'],
                        $norm['registrable'],
                        ($norm['lecturer_staff_id'] > 0 ? $norm['lecturer_staff_id'] : null),
                        $norm['is_general_course'],
                        $id
                    ]);
                    try {
                        $syncStmt = $pdo->prepare("UPDATE course_registration_items SET course_code = ?, course_title = ?, credit_unit = ?, course_type = ? WHERE course_id = ?");
                        $syncStmt->execute([$norm['course_code'], $norm['course_title'], $norm['credit_unit'], $norm['course_type'], $id]);
                    } catch (Throwable $e) {}
                    unset($_SESSION['__registrable_course_form_old']);
                    flash('success', 'Course updated and synchronized with Course Registration.');
                    redirect(portalRegistrableCourseManagerRedirectUrl([
                        'faculty_id' => $_POST['_filter_faculty_id'] ?? 0,
                        'department_id' => $_POST['_filter_department_id'] ?? 0,
                        'programme_id' => $_POST['_filter_programme_id'] ?? 0,
                        'mode_of_study' => $_POST['_filter_mode_of_study'] ?? '',
                        'level' => $_POST['_filter_level'] ?? '',
                        'semester' => $_POST['_filter_semester'] ?? '',
                        'session_name' => $_POST['_filter_session_name'] ?? '',
                        'year' => $_POST['_filter_year'] ?? '',
                    ]));
                }
            }

            if ($act === 'delete_course') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id <= 0) throw new RuntimeException('Invalid course selected.');
                $pdo->prepare("DELETE FROM registrable_courses WHERE id=?")->execute([$id]);
                unset($_SESSION['__registrable_course_form_old']);
                flash('success', 'Course deleted. It will no longer appear in Course Registration.');
                redirect(portalRegistrableCourseManagerRedirectUrl([
                    'faculty_id' => $_POST['_filter_faculty_id'] ?? 0,
                    'department_id' => $_POST['_filter_department_id'] ?? 0,
                    'programme_id' => $_POST['_filter_programme_id'] ?? 0,
                    'mode_of_study' => $_POST['_filter_mode_of_study'] ?? '',
                    'level' => $_POST['_filter_level'] ?? '',
                    'semester' => $_POST['_filter_semester'] ?? '',
                    'session_name' => $_POST['_filter_session_name'] ?? '',
                    'year' => $_POST['_filter_year'] ?? '',
                ]));
            }
        } catch (Throwable $e) {
            if (in_array($act, ['add_course', 'edit_course'], true)) {
                $_SESSION['__registrable_course_form_old'] = [
                    'action' => $act,
                    'id' => (int)($_POST['id'] ?? 0),
                    'course_code' => trim((string)($_POST['course_code'] ?? '')),
                    'course_title' => trim((string)($_POST['course_title'] ?? '')),
                    'credit_unit' => trim((string)($_POST['credit_unit'] ?? '0')),
                    'faculty_id' => max(0, (int)($_POST['faculty_id'] ?? 0)),
                    'department_id' => max(0, (int)($_POST['department_id'] ?? 0)),
                    'programme_id' => max(0, (int)($_POST['programme_id'] ?? 0)),
                    'mode_of_study' => trim((string)($_POST['mode_of_study'] ?? '')),
                    'level' => trim((string)($_POST['level'] ?? '')),
                    'semester' => trim((string)($_POST['semester'] ?? '')),
                    'session_name' => trim((string)($_POST['session_name'] ?? '')),
                    'year' => trim((string)($_POST['year'] ?? '')),
                    'course_type' => trim((string)($_POST['course_type'] ?? 'C')),
                    'registrable' => trim((string)($_POST['registrable'] ?? 'Yes')),
                    'is_general_course' => !empty($_POST['is_general_course']) ? 1 : 0,
                ];
            }
            flash('danger', $e->getMessage() ?: 'Operation failed.');
            redirect(portalRegistrableCourseManagerRedirectUrl([
                'faculty_id' => $_POST['_filter_faculty_id'] ?? $_GET['faculty_id'] ?? 0,
                'department_id' => $_POST['_filter_department_id'] ?? $_GET['department_id'] ?? 0,
                'programme_id' => $_POST['_filter_programme_id'] ?? $_GET['programme_id'] ?? 0,
                'mode_of_study' => $_POST['_filter_mode_of_study'] ?? $_GET['mode_of_study'] ?? '',
                'level' => $_POST['_filter_level'] ?? $_GET['level'] ?? '',
                'semester' => $_POST['_filter_semester'] ?? $_GET['semester'] ?? '',
                'session_name' => $_POST['_filter_session_name'] ?? $_GET['session_name'] ?? '',
                'year' => $_POST['_filter_year'] ?? $_GET['year'] ?? '',
            ]));
        }
    }

    // -------- Student Course Registration (Create/Update) --------
    if (in_array($act, ['save_course_registration','delete_course_registration'], true)) {
        try {
            if (!isStudent()) throw new RuntimeException('Unauthorized.');

            $studentId = (int)($_SESSION['user_id'] ?? 0);
            if ($studentId <= 0) throw new RuntimeException('Invalid student session.');

            if ($act === 'delete_course_registration') {
                $regId = (int)($_POST['registration_id'] ?? 0);
                if ($regId <= 0) throw new RuntimeException('Invalid registration.');
                // Ensure ownership
                $st = $pdo->prepare("SELECT student_id FROM course_registrations WHERE id=?");
                $st->execute([$regId]);
                $owner = (int)($st->fetchColumn() ?? 0);
                if ($owner !== $studentId) throw new RuntimeException('You can only delete your own registration.');
                $pdo->prepare("DELETE FROM course_registrations WHERE id=?")->execute([$regId]);
                flash('success', 'Course registration deleted.');
                redirect('?page=student_course_registration');
            }

            // Save/Update
            portalEnsureCourseRegistrationProfileSupport($pdo);
            $semester = (string)($_POST['semester'] ?? 'First');
            if (!in_array($semester, ['First','Second'], true)) $semester = 'First';
            $sessionName = portalNormalizeSessionNameSafe((string)($_POST['session_name'] ?? ''));
            if ($sessionName === '') $sessionName = portalNormalizeSessionNameSafe((string)setting('academic_session',''));
            if ($sessionName === '') $sessionName = portalCurrentAcademicSessionName($pdo);
            $allowedSemesters = portalAcademicSessionAllowedRegistrationSemesters($pdo, $sessionName);
            if (!$allowedSemesters) {
                throw new RuntimeException('Semester registration is currently disabled for both semesters for this academic session. Please contact the portal administrator.');
            }
            if (!in_array($semester, $allowedSemesters, true)) {
                throw new RuntimeException('You are not allowed to register for ' . $semester . ' Semester right now. Allowed semester access for ' . $sessionName . ' is ' . portalAcademicSessionRegistrationAccessLabel(portalAcademicSessionRegistrationAccess($pdo, $sessionName)) . '.');
            }
            $student = [];
            try { $st = $pdo->prepare("SELECT * FROM students WHERE id=? LIMIT 1"); $st->execute([$studentId]); $student = $st->fetch(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
            if (!$student) throw new RuntimeException('Student record not found.');
            $schoolFeeGuard = portalCourseRegistrationSchoolFeeGuard($pdo, (array)$student, $sessionName, $semester);
            if (empty($schoolFeeGuard['allowed'])) throw new RuntimeException((string)($schoolFeeGuard['message'] ?? 'School fee clearance is required before course registration.'));
            $studentScopeContext = portalStudentContext($pdo, (array)$student);
            $facultyId = (int)($studentScopeContext['faculty_id'] ?? ($student['faculty_id'] ?? 0));
            $departmentId = (int)($studentScopeContext['department_id'] ?? ($student['department_id'] ?? 0));
            $programmeId = (int)($studentScopeContext['programme_id'] ?? ($student['programme_id'] ?? 0));
            if ($facultyId <= 0 && $departmentId <= 0 && $programmeId <= 0) throw new RuntimeException('Your faculty/department/programme course mapping is incomplete. Please contact the portal administrator before continuing course registration.');
            $level = portalStudentResolvedCurrentLevel($pdo, (array)$student, $sessionName);
            $title = trim((string)($_POST['title'] ?? ($student['title'] ?? '')));
            $surname = trim((string)($_POST['surname'] ?? ($student['surname'] ?? '')));
            [$firstName, $otherName] = portalSplitCourseRegistrationOtherNames((string)($_POST['other_names'] ?? portalCourseRegistrationOtherNames($student)));
            if ($firstName === '') $firstName = trim((string)($student['first_name'] ?? ''));
            $maidenName = trim((string)($_POST['maiden_name'] ?? ($student['maiden_name'] ?? '')));
            $dateOfBirth = trim((string)($_POST['date_of_birth'] ?? ($student['date_of_birth'] ?? '')));
            $maritalStatus = trim((string)($_POST['marital_status'] ?? ($student['marital_status'] ?? '')));
            $sex = trim((string)($_POST['sex'] ?? ($student['sex'] ?? 'Male'))); if (!in_array($sex, ['Male','Female'], true)) $sex = 'Male';
            $nationality = trim((string)($_POST['nationality'] ?? ($student['nationality'] ?? '')));
            $stateOfOrigin = trim((string)($_POST['state_of_origin'] ?? ($student['state_of_origin'] ?? '')));
            $lga = trim((string)($_POST['lga'] ?? ($student['lga'] ?? '')));
            $permanentAddress = trim((string)($_POST['permanent_address'] ?? ($student['permanent_address'] ?? '')));
            $email = trim((string)($_POST['email'] ?? ($student['email'] ?? '')));
            $mobilePhone = trim((string)($_POST['mobile_phone'] ?? ($student['mobile_phone'] ?? '')));
            $modeOfStudy = trim((string)($_POST['mode_of_study'] ?? portalInferModeOfStudy($student))); if ($modeOfStudy === '') $modeOfStudy = 'Full-Time';
            $carryOverStatus = (string)($_POST['carry_over_status'] ?? 'No'); if (!in_array($carryOverStatus, ['Yes','No'], true)) $carryOverStatus = 'No';
            $carryOverNote = trim((string)($_POST['carry_over_note'] ?? ''));
            if ($surname === '' || $firstName === '' || $dateOfBirth === '' || $nationality === '' || $stateOfOrigin === '' || $lga === '' || $permanentAddress === '' || $email === '' || $mobilePhone === '') throw new RuntimeException('Please complete all required Personal Details fields before submitting registration.');
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) throw new RuntimeException('Please provide a valid email address.');
            $courseIds = $_POST['course_ids'] ?? []; if (!is_array($courseIds)) $courseIds = []; $courseIds = array_values(array_unique(array_filter(array_map('intval', $courseIds), fn($v)=>$v>0)));
            $carryOverCourseIds = $_POST['carry_over_course_ids'] ?? []; if (!is_array($carryOverCourseIds)) $carryOverCourseIds = []; $carryOverCourseIds = array_values(array_unique(array_filter(array_map('intval', $carryOverCourseIds), fn($v)=>$v>0)));
            $eligibleCarryOvers = portalFetchEligibleCarryOverCourses($pdo, (array)$student, $sessionName, $semester);
            $eligibleCarryOverMap = [];
            foreach ($eligibleCarryOvers as $carryRow) {
                $carryCourseId = (int)($carryRow['course_id'] ?? 0);
                if ($carryCourseId > 0) $eligibleCarryOverMap[$carryCourseId] = $carryRow;
            }
            if ($carryOverStatus === 'Yes') {
                if (!$eligibleCarryOverMap) {
                    throw new RuntimeException('No carry-over courses available for this student.');
                }
                if (!$carryOverCourseIds) {
                    throw new RuntimeException('Please select at least one carry-over course or change the carry-over field to No.');
                }
                foreach ($carryOverCourseIds as $carryCourseId) {
                    if (!isset($eligibleCarryOverMap[$carryCourseId])) {
                        throw new RuntimeException('One or more selected carry-over courses are no longer available. Please reload the page and try again.');
                    }
                }
            } else {
                $carryOverCourseIds = [];
                $carryOverNote = '';
            }
            if (!$courseIds && !$carryOverCourseIds) {
                throw new RuntimeException('Please select at least one course to register.');
            }
            $studyYear = portalStudentRegistrableCourseYear($pdo, (array)$student);
            $scope = portalRegistrableCourseScopeWhere([
                'faculty_id' => $facultyId,
                'department_id' => $departmentId,
                'programme_id' => $programmeId,
                'semester' => $semester,
                'session_name' => $sessionName,
                'level' => $level,
                'year' => $studyYear,
                'mode_of_study' => $modeOfStudy,
                'require_registrable' => true,
            ]);
            $in = implode(',', array_fill(0, count($courseIds), '?'));
            $courseSql = "SELECT * FROM registrable_courses WHERE id IN ($in) AND " . $scope['sql'] . " ORDER BY course_type ASC, course_code ASC";
            $stmt = $pdo->prepare($courseSql);
            $stmt->execute(array_merge($courseIds, $scope['params']));
            $courses = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
            if (count($courses) !== count($courseIds)) throw new RuntimeException('One or more selected courses are no longer synchronized with the Registrable Courses Manager for your faculty/department/programme/session/semester/level/year scope. Please reload the page and try again.');
            $selectedCourseCodeMap = [];
            foreach ($courses as $courseRow) {
                $courseCodeKey = strtoupper(trim((string)($courseRow['course_code'] ?? '')));
                if ($courseCodeKey !== '') $selectedCourseCodeMap[$courseCodeKey] = true;
            }
            $selectedCarryRows = [];
            foreach ($carryOverCourseIds as $carryCourseId) {
                $carryRow = $eligibleCarryOverMap[$carryCourseId] ?? null;
                if (!$carryRow) continue;
                $carryCodeKey = strtoupper(trim((string)($carryRow['course_code'] ?? '')));
                if ($carryCodeKey !== '' && isset($selectedCourseCodeMap[$carryCodeKey])) {
                    throw new RuntimeException('A carry-over course cannot be registered twice in the same form. Remove the duplicate selection and try again.');
                }
                if ($carryCodeKey !== '') $selectedCourseCodeMap[$carryCodeKey] = true;
                $selectedCarryRows[] = $carryRow;
            }
            $totalUnits = 0;
            foreach ($courses as $c) { $totalUnits += (int)($c['credit_unit'] ?? 0); }
            foreach ($selectedCarryRows as $carryRow) { $totalUnits += (int)($carryRow['credit_unit'] ?? 0); }
            if ($carryOverStatus === 'Yes' && !$selectedCarryRows) {
                throw new RuntimeException('Please select at least one carry-over course to continue.');
            }
            $modeOfStudy = trim((string)$modeOfStudy); if ($modeOfStudy === '') $modeOfStudy = 'Full-Time';
    $regSet = portalRegistrationRuleForContext($pdo, [
                'session_name' => $sessionName,
                'semester' => $semester,
                'faculty_id' => $facultyId,
                'department_id' => $departmentId,
                'programme_id' => $programmeId,
                'level' => $level,
            ]);
            if (!portalRegistrationIsOpen($regSet['is_open'] ?? 'Yes')) {
                $scopeText = portalRegistrationScopeSummary($pdo, [
                    'session_name' => $sessionName,
                    'semester' => $semester,
                    'faculty_id' => $facultyId,
                    'department_id' => $departmentId,
                    'programme_id' => $programmeId,
                    'level' => $level,
                ]);
                throw new RuntimeException('Course registration is closed for your session/semester/department/programme/level.' . ($scopeText !== '' ? ' Scope: ' . $scopeText . '.' : ''));
            }
            $minU = (int)($regSet['min_units'] ?? 0); $maxU = (int)($regSet['max_units'] ?? 30); if ($totalUnits < $minU) throw new RuntimeException('Selected credit load (' . $totalUnits . ') is below the minimum allowed (' . $minU . ') for this session/semester.'); if ($maxU > 0 && $totalUnits > $maxU) throw new RuntimeException('Selected credit load (' . $totalUnits . ') exceeds the maximum allowed (' . $maxU . ') for this session/semester.');
            $pdo->beginTransaction();
            $studentProfile = portalResolveCourseFormProfile($pdo, $student);
            $resolvedSchoolName = trim((string)($studentProfile['school'] ?? ''));
            $resolvedCourseInView = trim((string)($studentProfile['course_in_view'] ?? ''));
            $resolvedCourseOfStudy = trim((string)($studentProfile['course_of_study'] ?? ''));
            $resolvedProgrammeName = trim((string)($studentProfile['programme'] ?? ''));
            $pdo->prepare("UPDATE students SET title=?, surname=?, first_name=?, other_name=?, maiden_name=?, date_of_birth=?, marital_status=?, sex=?, nationality=?, state_of_origin=?, lga=?, permanent_address=?, email=?, mobile_phone=?, mode_of_study=?, level=?, session=?, semester=?, school_name=?, course_in_view=?, course_of_study=?, programme_name_cache=? WHERE id=?")->execute([($title!==''?$title:null),$surname,$firstName,($otherName!==''?$otherName:null),($maidenName!==''?$maidenName:null),$dateOfBirth,($maritalStatus!==''?$maritalStatus:null),$sex,$nationality,$stateOfOrigin,$lga,$permanentAddress,$email,$mobilePhone,$modeOfStudy,$level,($sessionName!==''?$sessionName:null),$semester,($resolvedSchoolName!=='' && $resolvedSchoolName!=='Not Set' ? $resolvedSchoolName : null),($resolvedCourseInView!=='' && $resolvedCourseInView!=='Not Set' ? $resolvedCourseInView : null),($resolvedCourseOfStudy!=='' && $resolvedCourseOfStudy!=='Not Set' ? $resolvedCourseOfStudy : null),($resolvedProgrammeName!=='' && $resolvedProgrammeName!=='Not Set' ? $resolvedProgrammeName : null),$studentId]);
            $existingId = 0; try { $st = $pdo->prepare("SELECT id FROM course_registrations WHERE student_id=? AND COALESCE(session_name,'')=? AND semester=? AND COALESCE(level,'')=? ORDER BY id DESC LIMIT 1"); $st->execute([$studentId,(string)$sessionName,$semester,(string)$level]); $existingId = (int)($st->fetchColumn() ?? 0); } catch (Throwable $e) {}
            if ($existingId > 0) { $pdo->prepare("UPDATE course_registrations SET faculty_id=?, department_id=?, programme_id=?, total_units=?, carry_over_status=?, carry_over_note=? WHERE id=?")->execute([($facultyId>0?$facultyId:null),($departmentId>0?$departmentId:null),($programmeId>0?$programmeId:null),$totalUnits,$carryOverStatus,($carryOverNote!==''?$carryOverNote:null),$existingId]); $pdo->prepare("DELETE FROM course_registration_items WHERE registration_id=?")->execute([$existingId]); $regId = $existingId; } else { $pdo->prepare("INSERT INTO course_registrations (student_id, session_name, semester, faculty_id, department_id, programme_id, level, total_units, carry_over_status, carry_over_note) VALUES (?,?,?,?,?,?,?,?,?,?)")->execute([$studentId,($sessionName!==''?$sessionName:null),$semester,($facultyId>0?$facultyId:null),($departmentId>0?$departmentId:null),($programmeId>0?$programmeId:null),($level!==''?$level:null),$totalUnits,$carryOverStatus,($carryOverNote!==''?$carryOverNote:null)]); $regId = (int)$pdo->lastInsertId(); }
            $isFirstCourseSubmission = ($existingId <= 0);
            portalEnsureCourseRegistrationCarryOverItemSupport($pdo);
            $insItem = $pdo->prepare("INSERT INTO course_registration_items (registration_id, course_id, course_code, course_title, credit_unit, course_type, is_carry_over, carry_over_source_session, carry_over_source_semester) VALUES (?,?,?,?,?,?,?,?,?)");
            foreach ($courses as $c) {
                $insItem->execute([$regId,(int)$c['id'],(string)$c['course_code'],(string)$c['course_title'],(int)$c['credit_unit'],in_array((string)($c['course_type'] ?? 'C'), ['C','E'], true) ? (string)$c['course_type'] : 'C',0,null,null]);
            }
            foreach ($selectedCarryRows as $carryRow) {
                $insItem->execute([$regId,(int)($carryRow['course_id'] ?? 0),(string)($carryRow['course_code'] ?? ''),(string)($carryRow['course_title'] ?? ''),(int)($carryRow['credit_unit'] ?? 0),in_array((string)($carryRow['course_type'] ?? 'C'), ['C','E'], true) ? (string)$carryRow['course_type'] : 'C',1,((string)($carryRow['source_session'] ?? '') !== '' ? (string)$carryRow['source_session'] : null),((string)($carryRow['source_semester'] ?? '') !== '' ? (string)$carryRow['source_semester'] : null)]);
            }
            portalSynchronizeCourseRegistrationForScoresheet($pdo, $regId, $studentId);
            portalEnsureCourseRegistrationPublicToken($pdo, $regId);
            $pdo->commit();
            $feeSyncResult = portalResynchronizeStudentFeeRequirements($pdo, $studentId, [
                'session_name' => $sessionName,
                'semester' => $semester,
                'faculty_id' => $facultyId,
                'department_id' => $departmentId,
                'programme_id' => $programmeId,
                'mode_of_study' => $modeOfStudy,
                'level' => $level,
            ]);
            try { if ($isFirstCourseSubmission) { portalSendStudentCourseFormSubmittedEmail($pdo, $student, ['id' => $regId, 'session_name' => $sessionName, 'semester' => $semester, 'level' => $level, 'total_units' => $totalUnits], $courses); } } catch (Throwable $e) {}

            flash('success', 'Course registration saved successfully.');
            foreach (array_values(array_unique(array_filter(array_map('strval', (array)($feeSyncResult['warnings'] ?? []))))) as $warning) {
                flash('warning', $warning);
            }
            if ((int)($_POST['preview_after_save'] ?? 0) === 1) {
                redirect('?page=course_form&reg_id=' . $regId);
            }
            redirect('?page=student_course_registration&tab=register&view=confirmation&semester=' . urlencode((string)$semester) . '&level=' . urlencode((string)$level));
        } catch (Throwable $e) {
            try { if ($pdo->inTransaction()) $pdo->rollBack(); } catch (Throwable $e2) {}
            flash('danger', $e->getMessage() ?: 'Operation failed.');
            redirect('?page=student_course_registration');
        }
    }

}

    // -------- Admin Registration Settings (Open/Close + Credit Load) --------
    if (($act ?? '') === 'save_registration_settings') {
        try {
            if (!isAdmin()) throw new RuntimeException('Unauthorized.');

            $sessionName = trim((string)($_POST['session_name'] ?? ''));
            if ($sessionName === '') $sessionName = (string)setting('academic_session','');

            $semester = (string)($_POST['semester'] ?? 'First');
            if (!in_array($semester, ['First','Second'], true)) $semester = 'First';

            $isOpen = portalNormalizeRegistrationOpenValue($_POST['is_open'] ?? 'Yes');

            portalRegistrationSettingsSupport($pdo);

            // Departmental selectors (0 means ALL)
            $facultyId = (int)($_POST['faculty_id'] ?? 0);
            $departmentId = (int)($_POST['department_id'] ?? 0);
            $programmeId = (int)($_POST['programme_id'] ?? 0);
            $level = trim((string)($_POST['level'] ?? ''));
            if ($level === '') $level = null;

            $minUnits = (int)($_POST['min_units'] ?? 0);
            $maxUnits = (int)($_POST['max_units'] ?? 30);
            if ($minUnits < 0) $minUnits = 0;
            if ($maxUnits < 0) $maxUnits = 0;
            if ($maxUnits > 0 && $minUnits > $maxUnits) {
                throw new RuntimeException('Minimum credit load cannot be greater than maximum credit load.');
            }

            // Detect schema variants: some installs have session_id with FK to academic_sessions
            $useSessionId = false;
            try {
                $st = $pdo->prepare("SHOW COLUMNS FROM registration_settings LIKE 'session_id'");
                $st->execute();
                $useSessionId = ($st->rowCount() > 0);
            } catch (Throwable $e) {}

            $sessionId = null;
            if ($useSessionId) {
                // Ensure session exists, then get its id (to satisfy FK)
                if ($sessionName === '') throw new RuntimeException('Session is required.');
                $sessionId = portalEnsureAcademicSessionRecord($pdo, $sessionName);
                if ($sessionId <= 0) {
                    throw new RuntimeException('Invalid session selected. Please create/select a valid academic session.');
                }
            }

            // Normalize ALL selections to NULL in DB
            $dbFaculty = ($facultyId > 0 ? $facultyId : null);
            $dbDept    = ($departmentId > 0 ? $departmentId : null);
            $dbProgramme = ($programmeId > 0 ? $programmeId : null);
            $dbLevel   = ($level !== null ? $level : null);

            // Upsert without relying on a specific UNIQUE key (handles old schemas safely)
            $existingId = 0;
            if ($useSessionId) {
                $q = $pdo->prepare("SELECT id FROM registration_settings
                                    WHERE session_id=? AND semester=?
                                      AND faculty_id <=> ? AND department_id <=> ? AND COALESCE(programme_id,0) <=> COALESCE(?,0) AND level <=> ?
                                    LIMIT 1");
                $q->execute([$sessionId, $semester, $dbFaculty, $dbDept, $dbProgramme, $dbLevel]);
                $existingId = (int)($q->fetchColumn() ?? 0);

                if ($existingId > 0) {
                    $u = $pdo->prepare("UPDATE registration_settings
                                        SET is_open=?, faculty_id=?, department_id=?, programme_id=?, level=?, min_units=?, max_units=?, updated_at=CURRENT_TIMESTAMP
                                        WHERE id=?");
                    $u->execute([$isOpen, $dbFaculty, $dbDept, $dbProgramme, $dbLevel, $minUnits, $maxUnits, $existingId]);
                } else {
                    $i = $pdo->prepare("INSERT INTO registration_settings
                        (session_id, session_name, semester, is_open, faculty_id, department_id, programme_id, level, min_units, max_units)
                        VALUES (?,?,?,?,?,?,?,?,?,?)");
                    $i->execute([$sessionId, ($sessionName!==''?$sessionName:null), $semester, $isOpen, $dbFaculty, $dbDept, $dbProgramme, $dbLevel, $minUnits, $maxUnits]);
                }
            } else {
                $q = $pdo->prepare("SELECT id FROM registration_settings
                                    WHERE COALESCE(session_name,'')=? AND semester=?
                                      AND faculty_id <=> ? AND department_id <=> ? AND COALESCE(programme_id,0) <=> COALESCE(?,0) AND level <=> ?
                                    LIMIT 1");
                $q->execute([(string)$sessionName, $semester, $dbFaculty, $dbDept, $dbProgramme, $dbLevel]);
                $existingId = (int)($q->fetchColumn() ?? 0);

                if ($existingId > 0) {
                    $u = $pdo->prepare("UPDATE registration_settings
                                        SET is_open=?, faculty_id=?, department_id=?, programme_id=?, level=?, min_units=?, max_units=?, updated_at=CURRENT_TIMESTAMP
                                        WHERE id=?");
                    $u->execute([$isOpen, $dbFaculty, $dbDept, $dbProgramme, $dbLevel, $minUnits, $maxUnits, $existingId]);
                } else {
                    $i = $pdo->prepare("INSERT INTO registration_settings
                        (session_name, semester, is_open, faculty_id, department_id, programme_id, level, min_units, max_units)
                        VALUES (?,?,?,?,?,?,?,?,?)");
                    $i->execute([($sessionName!==''?$sessionName:null), $semester, $isOpen, $dbFaculty, $dbDept, $dbProgramme, $dbLevel, $minUnits, $maxUnits]);
                }
            }

            try {
                if ($useSessionId && (int)$sessionId > 0) {
                    $dup = $pdo->prepare("SELECT id FROM registration_settings WHERE session_id=? AND semester=? AND faculty_id <=> ? AND department_id <=> ? AND COALESCE(programme_id,0) <=> COALESCE(?,0) AND level <=> ? ORDER BY id DESC");
                    $dup->execute([(int)$sessionId, $semester, $dbFaculty, $dbDept, $dbProgramme, $dbLevel]);
                } else {
                    $dup = $pdo->prepare("SELECT id FROM registration_settings WHERE COALESCE(session_name,'')=? AND semester=? AND faculty_id <=> ? AND department_id <=> ? AND COALESCE(programme_id,0) <=> COALESCE(?,0) AND level <=> ? ORDER BY id DESC");
                    $dup->execute([(string)$sessionName, $semester, $dbFaculty, $dbDept, $dbProgramme, $dbLevel]);
                }
                $ids = array_map('intval', $dup->fetchAll(PDO::FETCH_COLUMN) ?: []);
                if (count($ids) > 1) {
                    $keepId = array_shift($ids);
                    $placeholders = implode(',', array_fill(0, count($ids), '?'));
                    $del = $pdo->prepare("DELETE FROM registration_settings WHERE id IN ($placeholders)");
                    $del->execute($ids);
                }
            } catch (Throwable $e) {}

            if (portalRegistrationIsOpen($isOpen)) {
                flash('success', 'Registration has been reopened for the selected scope. Students in this scope can register courses.');
            } else {
                flash('success', 'Registration has been closed for the selected scope.');
            }

            redirect('?page=admin_registration_settings&session_name=' . urlencode($sessionName) . '&semester=' . urlencode($semester)
                . '&faculty_id=' . urlencode((string)$facultyId)
                . '&department_id=' . urlencode((string)$departmentId)
                . '&programme_id=' . urlencode((string)$programmeId)
                . '&level=' . urlencode((string)($level ?? ''))
            );
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Operation failed.');
            redirect('?page=admin_registration_settings');
        }
    }


    // Delete a registration setting rule
    if (($act ?? '') === 'delete_registration_setting' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            if (!isAdmin()) throw new RuntimeException('Unauthorized.');
            $id = (int)($_POST['id'] ?? 0);
            if ($id <= 0) throw new RuntimeException('Invalid rule.');
            $pdo->prepare("DELETE FROM registration_settings WHERE id=?")->execute([$id]);
            flash('success','Registration scope rule deleted successfully.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Operation failed.');
        }
        redirect('?page=admin_registration_settings');
    }


// Admin: save Grading & Result Settings
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (($_POST['action'] ?? '') === 'save_grading_result_settings')) {
    if (!isLoggedIn() || !isAdmin()) { redirect('?page=login'); }
    portalRequireAcademicResultPagesAccess($pdo);
    $activeTab = trim((string)($_POST['grading_settings_active_tab'] ?? 'grading_ca'));
    if (!preg_match('/^grading_(ca|exam|grade|cgp|cgpa)$/', $activeTab)) {
        $activeTab = 'grading_ca';
    }
    try {
        $validated = portalValidateGradingResultSettingsInput($_POST);
        foreach (($validated['clean'] ?? []) as $key => $value) {
            setSystemSettingValue($pdo, $key, (string)$value, 'Grading & result settings');
        }
        $adminName = trim((string)($_SESSION['full_name'] ?? $_SESSION['name'] ?? $_SESSION['username'] ?? $_SESSION['email'] ?? 'System Administrator'));
        setSystemSettingValue($pdo, 'result_settings_last_updated_by', $adminName, 'Grading & result settings');
        setSystemSettingValue($pdo, 'result_settings_last_updated_on', date('Y-m-d H:i:s'), 'Grading & result settings');
        flash('success', 'Grading & Result Settings saved successfully.');
    } catch (Throwable $e) {
        flash('danger', $e->getMessage() ?: 'Unable to save grading settings right now.');
    }
    redirect('?page=grading_result_settings#' . rawurlencode($activeTab));
}

// Admin: save Transcript Settings
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (($_POST['action'] ?? '') === 'save_transcript_settings')) {
    if (!isLoggedIn() || !isAdmin()) { redirect('?page=login'); }
    portalRequireAcademicResultPagesAccess($pdo);
    $transcriptPageEnabled = !empty($_POST['transcript_page_enabled_toggle']) ? 'Yes' : 'No';
    setSystemSettingValue($pdo, 'transcript_page_enabled', $transcriptPageEnabled, 'Transcript settings');
    foreach (portalTranscriptSettingsDefaults() as $key => $defaultValue) {
        $value = isset($_POST[$key]) ? trim((string)$_POST[$key]) : (string)$defaultValue;
        setSystemSettingValue($pdo, $key, $value, 'Transcript settings');
    }
    flash('success', 'Transcript settings saved successfully.');
    redirect('?page=admin_transcript&tab=' . urlencode(trim((string)($_POST['current_tab'] ?? 'transcript'))));
}

// Admin: save homepage/about page contents + managed document texts
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (($_POST['action'] ?? '') === 'admin_save_pages')) {
    if (!isLoggedIn() || !isAdmin()) { redirect('?page=login'); }
    $defaults = portalPageDefaults();
    $toggleSettingKeys = array_values(array_filter(array_map(static function(array $meta): string {
        return (string)($meta['setting_key'] ?? '');
    }, portalManagedContentToggleDefaults())));
    foreach ($defaults as $key => $defaultValue) {
        $value = isset($_POST[$key]) ? (string)$_POST[$key] : $defaultValue;
        if (in_array($key, $toggleSettingKeys, true)) {
            $value = !empty($_POST[$key . '_toggle']) ? 'Yes' : 'No';
        }
        setSystemSettingValue($pdo, $key, trim($value), 'Portal page content');
    }
    foreach (portalDocumentContentDefaults() as $slug => $fields) {
        foreach ($fields as $field => $defaultValue) {
            $key = 'doc_' . $slug . '_' . $field;
            $value = isset($_POST[$key]) ? (string)$_POST[$key] : (string)$defaultValue;
            setSystemSettingValue($pdo, $key, trim($value), 'Managed document content');
        }
    }
    portalSaveRegistrationPageScopeRowsFromPost($pdo, $_POST);
    flash('success', 'Pages content saved successfully.');
    redirect('?page=admin_pages');
}

// Admin: restore graduated student back to active status
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (($_POST['action'] ?? '') === 'restore_graduated_student')) {
    if (!isLoggedIn() || !isAdmin()) { redirect('?page=login'); }
    $studentId = (int)($_POST['student_id'] ?? 0);
    $reason = trim((string)($_POST['restore_reason'] ?? ''));
    if ($studentId <= 0) {
        flash('danger', 'Invalid student record selected.');
        redirect('?page=admin_graduated');
    }
    try { ensurePortalExtensionSupport($pdo); } catch (Throwable $e) {}
    try {
        $pdo->beginTransaction();
        $pdo->prepare("UPDATE students SET graduation_status='Active', graduated_at=NULL, graduation_year=NULL, graduation_restored_at=NOW() WHERE id=?")
            ->execute([$studentId]);
        $pdo->prepare("INSERT INTO graduation_restore_logs (student_id, restored_by, reason, restored_at) VALUES (?,?,?,NOW())")
            ->execute([$studentId, (int)($_SESSION['user_id'] ?? 0), $reason !== '' ? $reason : null]);
        $pdo->commit();
        flash('success', 'Student restored from Graduated status successfully.');
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        flash('danger', 'Unable to restore graduated student.');
    }
    redirect('?page=admin_graduated');
}

// Get current page
 $page = $_GET['page'] ?? 'home';
if ($page === 'course_registration') { $page = 'student_course_registration'; }
if ($page === 'course_form_verify') { $page = 'public_course_form'; }


// =============================
// Staff Role Menu/Page Permission Enforcement (hide + block direct access)
// =============================
if (!function_exists('pageMenuKeyMap')) {
    function pageMenuKeyMap(): array {
        return [
            // Admin core
            'admin_dashboard' => 'menu_admin_dashboard',
            'admin_students' => 'menu_admin_students',
            'admin_applications' => 'menu_admin_applications',
            'admin_add_application' => 'menu_admin_add_application',
            'admin_payments' => 'menu_admin_payments',
            'admin_wallets' => 'menu_admin_wallets',
            'admin_settings' => 'menu_admin_settings',
            'theme_branding' => 'menu_admin_settings',
            'reports' => 'menu_reports',
            'messages' => 'menu_messages',
            'notifications' => 'menu_notifications',

            // Faculty/Dept/Programme + Sessions + Courses
            'admin_structure_manager' => 'menu_structure_manager',
            'admin_sessions_manager' => 'menu_sessions_manager',
            'admin_courses_manager' => 'menu_registrable_courses',
            'admin_numbering_settings' => 'menu_numbering_settings',
            'admin_matric_verifier' => 'menu_numbering_settings',
            'admin_pages' => 'menu_pages',
            'admin_graduated' => 'menu_graduated',

            // Registrations
            'admin_registrations_manager' => 'menu_registrations_manager',
            'admin_yet_to_register' => 'menu_yet_to_register',
            'admin_registration_settings' => 'menu_registration_settings',
            'admin_previous_admission_record' => 'menu_previous_admission_record',
            'admin_clearance' => 'menu_admin_clearance',

            // Results
            'grading_result_settings' => 'menu_grading_result_settings',
            'results_manager' => 'menu_results_manager',
            'results_approvals' => 'menu_results_approvals',
            'download_scoresheet' => 'menu_download_scoresheet',
            'admin_transcript' => 'menu_transcript_admin',

            // Staff/Roles
            'staff_users' => 'menu_staff_users',
            'staff_roles' => 'menu_staff_roles',

            // Modules
            'lms_admin' => 'menu_lms_admin',
            'library_admin' => 'menu_library_admin',
            'cbt_admin' => 'menu_cbt_admin',

            // HRM
            'hrm_dashboard' => 'menu_hrm',
            'hrm_staff_management' => 'menu_hrm',
            'hrm_departments' => 'menu_hrm',
            'hrm_units' => 'menu_hrm',
            'hrm_designations' => 'menu_hrm',
            'hrm_attendance' => 'menu_hrm',
            'hrm_leave_management' => 'menu_hrm',
            'hrm_payroll' => 'menu_hrm',
            'hrm_recruitment' => 'menu_hrm',
            'hrm_performance_review' => 'menu_hrm',
            'hrm_staff_documents' => 'menu_hrm',
            'hrm_reports' => 'menu_hrm',
            'hrm_settings' => 'menu_hrm',
            'hrm_notifications' => 'menu_hrm',

            // Infrastructure
            'notification_settings' => 'menu_notification_settings',
            'feature_toggles' => 'menu_feature_toggles',
        ];
    }
}

if (!function_exists('canAccessPage')) {
    function canAccessPage(PDO $pdo, string $page): bool {
        if (!isAdmin()) return true; // only enforced for staff/admin
        $map = pageMenuKeyMap();
        if (!isset($map[$page])) return true; // pages not mapped remain accessible
        return menuEnabled($pdo, $map[$page]);
    }
}

// Enforce direct URL access (if a page is restricted, it won't open even if typed manually)
if (isAdmin() && isset($_GET['page'])) {
    $reqPage = (string)($_GET['page'] ?? '');
    if ($reqPage !== '' && !canAccessPage($pdo, $reqPage)) {
        flash('danger', 'Access denied: You do not have permission to view that page.');
        $page = 'admin_dashboard';
    }
}


try {
    if (isset($pdo) && ($pdo instanceof PDO)) {
        portalResumeTrackCurrentPage($pdo, (string)$page, (array)$_GET);
    }
} catch (Throwable $e) {}

// Get system assets for use in the HTML - Modified to handle separate logos
 $leftLogoPath = getSystemAsset('left_logo');
 $rightLogoPath = getSystemAsset('right_logo');
 $faviconPath = getSystemAsset('favicon');


 $pwaIconPath = $faviconPath ?: (getSystemAsset('left_logo') ?: (getSystemAsset('right_logo') ?: null));
$stampPath = getSystemAsset('official_stamp');
/**
 * =============================
 * Notifications helpers (safe)
 * =============================
 */
if (!function_exists('getUnreadNotificationCount')) {
    function getUnreadNotificationCount(PDO $pdo, string $role, int $userId): int {
        if ($userId <= 0 || ($role !== 'admin' && $role !== 'student')) return 0;
        try {
            $stmt = $pdo->prepare("
                SELECT COUNT(*)
                FROM notifications
                WHERE user_role = ?
                  AND user_id = ?
                  AND is_read = 0
                  AND archived = 0
            ");
            $stmt->execute([$role, $userId]);
            return (int)$stmt->fetchColumn();
        } catch (Throwable $e) {
            return 0;
        }
    }
}

if (!function_exists('createNotification')) {
    function createNotification(PDO $pdo, string $role, int $userId, string $title, string $body = '', ?string $link = null): void {
        if ($userId <= 0 || ($role !== 'admin' && $role !== 'student')) return;
        try {
            $stmt = $pdo->prepare("
                INSERT INTO notifications (user_role, user_id, title, body, link, is_read, read_at, archived, archived_at, created_at)
                VALUES (?,?,?,?,?,0,NULL,0,NULL,NOW())
            ");
            $stmt->execute([$role, $userId, $title, $body, $link]);
        } catch (Throwable $e) {
            // Silent fail
        }
    }
}

if (!function_exists('archiveNotificationForUser')) {
    function archiveNotificationForUser(PDO $pdo, int $notificationId, string $role, int $userId): void {
        if ($notificationId <= 0 || $userId <= 0) return;
        try {
            $stmt = $pdo->prepare("
                UPDATE notifications
                SET is_read = 1,
                    read_at = COALESCE(read_at, NOW()),
                    archived = 1,
                    archived_at = COALESCE(archived_at, NOW())
                WHERE id = ? AND user_role = ? AND user_id = ?
            ");
            $stmt->execute([$notificationId, $role, $userId]);
        } catch (Throwable $e) {
            // Silent fail
        }
    }
}

if (!function_exists('getNotificationsForUser')) {
    function getNotificationsForUser(PDO $pdo, string $role, int $userId, int $limit = 30): array {
        if ($userId <= 0 || ($role !== 'admin' && $role !== 'student')) return [];
        $limit = max(1, min(200, $limit));
        try {
            $stmt = $pdo->prepare("
                SELECT id, title, body, link, is_read, archived, created_at
                FROM notifications
                WHERE user_role = ? AND user_id = ?
                ORDER BY created_at DESC
                LIMIT {$limit}
            ");
            $stmt->execute([$role, $userId]);
            return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    }
}


if (!function_exists('portalRenderSafeRichText')) {
    function portalRenderSafeRichText($html, string $default = ''): string {
        $value = trim((string)$html);
        if ($value === '') {
            $value = trim($default);
        }
        if ($value === '') return '';

        $value = preg_replace('~<\s*(script|style|iframe|object|embed|form|input|button|textarea|select|option|link|meta)[^>]*>.*?<\s*/\s*\\1\s*>~is', '', $value) ?? $value;
        $value = preg_replace('/<!--.*?-->/s', '', $value) ?? $value;
        $value = preg_replace('/\son[a-z]+\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/iu', '', $value) ?? $value;
        $value = preg_replace('/\s(href|src)\s*=\s*(["\'])\s*javascript:[^"\']*\2/iu', '', $value) ?? $value;
        $value = preg_replace('/\sstyle\s*=\s*("[^"]*expression\([^\"]*"|\'[^\']*expression\([^\']*\')/iu', '', $value) ?? $value;
        $allowed = '<p><br><strong><b><em><i><u><ul><ol><li><blockquote><h1><h2><h3><h4><h5><h6><span><div><a><table><thead><tbody><tr><th><td><hr><sup><sub><code><pre>';
        $value = strip_tags($value, $allowed);
        $value = preg_replace_callback('/<a\b([^>]*)>/iu', static function ($matches) {
            $attrs = (string)($matches[1] ?? '');
            if (stripos($attrs, 'target=') === false) $attrs .= ' target="_blank"';
            if (stripos($attrs, 'rel=') === false) $attrs .= ' rel="noopener noreferrer"';
            return '<a' . $attrs . '>';
        }, $value) ?? $value;
        return $value;
    }
}

?>
<?php
$portalThemeFeatureEnabled = false;
$portalThemeCurrentMode = 'light';
$portalThemeMetaColor = (string)getSystemSettingValue($pdo,'theme_color_1','#003366');
try {
    if (isset($pdo) && ($pdo instanceof PDO)) {
        $portalThemeFeatureEnabled = portalThemeFeatureEnabled($pdo) && isLoggedIn();
        $portalThemeCurrentMode = $portalThemeFeatureEnabled ? portalThemeCurrentMode($pdo, 'light') : 'light';
        if ($portalThemeCurrentMode === 'dark') {
            $portalThemeMetaColor = '#0f172a';
        }
    }
} catch (Throwable $e) {
    $portalThemeFeatureEnabled = false;
    $portalThemeCurrentMode = 'light';
    $portalThemeMetaColor = (string)getSystemSettingValue($pdo,'theme_color_1','#003366');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HontTech Registration System - Honest Technologies University</title>
    <?php if ($faviconPath): ?>
    <link rel="icon" type="image/x-icon" href="<?php echo $faviconPath; ?>">
    <?php endif; ?>
<!-- PWA -->
<link rel="manifest" href="?pwa=manifest">
<meta name="theme-color" content="<?php echo htmlspecialchars($portalThemeMetaColor); ?>">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="HontTech">
<meta name="application-name" content="HontTech">
<meta name="msapplication-TileColor" content="<?php echo htmlspecialchars($portalThemeMetaColor); ?>">
<?php if (!empty($pwaIconPath)): ?>
<link rel="apple-touch-icon" href="<?php echo htmlspecialchars($pwaIconPath); ?>">
<meta name="msapplication-TileImage" content="<?php echo htmlspecialchars($pwaIconPath); ?>">
<?php endif; ?>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
    <script src="https://cdn.tiny.cloud/1/39k1s4iz9ytbav9niar2ate2qz9hjzm9a8tspandt1j9bfro/tinymce/8/tinymce.min.js" referrerpolicy="origin"></script>
<?php
try { if (isset($pdo) && ($pdo instanceof PDO)) { portalBrandingEnsureSettings($pdo); } } catch (Throwable $e) {}
$portalBrandingSettings = (isset($pdo) && ($pdo instanceof PDO) && function_exists('portalBrandingSettings')) ? portalBrandingSettings($pdo) : [];
$portalAdminThemeSettings = (isset($pdo) && ($pdo instanceof PDO) && function_exists('portalAdminThemeSettings')) ? portalAdminThemeSettings($pdo) : [];
$portalBrandingApplies = (isset($pdo) && ($pdo instanceof PDO) && function_exists('portalBrandingAppliesToCurrentPage')) ? portalBrandingAppliesToCurrentPage($pdo, (string)($page ?? '')) : false;
$portalLegacyPrimary = (string)getSystemSettingValue($pdo,'theme_color_1','#003366');
$portalLegacyAccent = (string)getSystemSettingValue($pdo,'theme_color_2','#f0a500');
$portalLegacyBackground = (string)getSystemSettingValue($pdo,'theme_color_3','#f4f7f6');
$portalLegacySurface = (string)getSystemSettingValue($pdo,'theme_color_4','#ffffff');
$portalLegacyText = (string)getSystemSettingValue($pdo,'theme_color_5','#333333');
$portalBrandPrimary = (string)($portalBrandingSettings['branding_primary_color'] ?? $portalLegacyPrimary);
$portalBrandSecondary = (string)($portalBrandingSettings['branding_secondary_color'] ?? '#0B5ED7');
$portalBrandAccent = (string)($portalBrandingSettings['branding_accent_color'] ?? $portalLegacyAccent);
$portalBrandSidebar = (string)($portalBrandingSettings['branding_sidebar_bg_color'] ?? $portalBrandPrimary);
$portalBrandHeader = (string)($portalBrandingSettings['branding_header_bg_color'] ?? $portalBrandPrimary);
$portalBrandButton = (string)($portalBrandingSettings['branding_button_color'] ?? $portalBrandPrimary);
$portalBrandLink = (string)($portalBrandingSettings['branding_link_color'] ?? $portalBrandAccent);
$portalBrandCardHeader = (string)($portalBrandingSettings['branding_card_header_color'] ?? $portalBrandPrimary);
$portalBrandCardBg = (string)($portalBrandingSettings['branding_card_background_color'] ?? $portalBrandPrimary);
$portalBrandCardText = (string)($portalBrandingSettings['branding_card_text_color'] ?? (function_exists('portalContrastColor') ? portalContrastColor($portalBrandCardBg) : '#FFFFFF'));
$portalBrandTableHeader = (string)($portalBrandingSettings['branding_table_header_color'] ?? $portalBrandPrimary);
$portalBrandBadgeSuccess = (string)($portalBrandingSettings['branding_badge_success_color'] ?? '#198754');
$portalBrandBadgeWarning = (string)($portalBrandingSettings['branding_badge_warning_color'] ?? '#F59E0B');
$portalBrandBadgeDanger = (string)($portalBrandingSettings['branding_badge_danger_color'] ?? '#DC3545');
$portalBrandSurface = (string)($portalBrandingSettings['branding_surface_color'] ?? $portalLegacySurface);
$portalBrandText = (string)($portalBrandingSettings['branding_text_color'] ?? $portalLegacyText);
$portalBrandBackground = (string)($portalBrandingSettings['branding_background_color'] ?? $portalLegacyBackground);
$portalThemePrimaryForPage = $portalBrandingApplies ? $portalBrandPrimary : $portalLegacyPrimary;
$portalThemeAccentForPage = $portalBrandingApplies ? $portalBrandAccent : $portalLegacyAccent;
$portalThemeBackgroundForPage = $portalBrandingApplies ? $portalBrandBackground : $portalLegacyBackground;
$portalThemeSurfaceForPage = $portalBrandingApplies ? $portalBrandSurface : $portalLegacySurface;
$portalThemeTextForPage = $portalBrandingApplies ? $portalBrandText : $portalLegacyText;
$portalBrandPrimaryText = function_exists('portalContrastColor') ? portalContrastColor($portalBrandPrimary) : '#FFFFFF';
$portalBrandSidebarText = function_exists('portalContrastColor') ? portalContrastColor($portalBrandSidebar) : '#FFFFFF';
$portalBrandHeaderText = function_exists('portalContrastColor') ? portalContrastColor($portalBrandHeader) : '#FFFFFF';
$portalBrandButtonText = function_exists('portalContrastColor') ? portalContrastColor($portalBrandButton) : '#FFFFFF';
$portalBrandCardHeaderText = function_exists('portalContrastColor') ? portalContrastColor($portalBrandCardHeader) : '#FFFFFF';
$portalBrandTableHeaderText = function_exists('portalContrastColor') ? portalContrastColor($portalBrandTableHeader) : '#FFFFFF';
$portalBrandSidebarBorder = function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandSidebarText, .12) : 'rgba(255,255,255,.12)';
$portalBrandSidebarHover = function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandSidebarText, .10) : 'rgba(255,255,255,.10)';
$portalBrandButtonShadow = function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandButton, .24) : 'rgba(0,0,0,.24)';
$portalBrandFormFocus = function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandAccent, .22) : 'rgba(240,165,0,.22)';
$portalBrandSectionActive = static function(string $key) use ($portalBrandingSettings, $portalBrandingApplies): bool {
    if (!$portalBrandingApplies) return false;
    return function_exists('portalBrandingSectionActive') ? portalBrandingSectionActive($portalBrandingSettings, $key) : true;
};
$portalPageBrandContext = function_exists('portalBrandingPageContext') ? portalBrandingPageContext((string)($page ?? '')) : '';
$portalAdminThemeApplies = ($portalPageBrandContext === 'admin' && (string)($_SESSION['user_type'] ?? '') === 'admin');
$portalAdminThemeBackground = (string)($portalAdminThemeSettings['admin_theme_background_color'] ?? '#F4F7F6');
$portalAdminThemeCardBg = (string)($portalAdminThemeSettings['admin_theme_card_background_color'] ?? '#FFFFFF');
$portalAdminThemeBorder = (string)($portalAdminThemeSettings['admin_theme_border_color'] ?? '#D9E2EC');
$portalAdminThemeHeader = (string)($portalAdminThemeSettings['admin_theme_section_header_bg_color'] ?? $portalBrandPrimary);
$portalAdminThemeHeaderText = function_exists('portalContrastColor') ? portalContrastColor($portalAdminThemeHeader) : '#FFFFFF';
$portalAdminThemeSidebar = (string)($portalAdminThemeSettings['admin_theme_sidebar_bg_color'] ?? $portalBrandSidebar);
$portalAdminThemeSidebarActive = (string)($portalAdminThemeSettings['admin_theme_sidebar_active_color'] ?? $portalBrandSecondary);
$portalAdminThemeSidebarText = (string)($portalAdminThemeSettings['admin_theme_sidebar_text_color'] ?? $portalBrandSidebarText);
$portalAdminThemeStatBg = (string)($portalAdminThemeSettings['admin_theme_stat_card_background_color'] ?? $portalBrandCardBg);
$portalAdminThemeStatNumber = (string)($portalAdminThemeSettings['admin_theme_stat_card_number_color'] ?? (function_exists('portalContrastColor') ? portalContrastColor($portalAdminThemeStatBg) : '#FFFFFF'));
$portalAdminThemeStatLabel = (string)($portalAdminThemeSettings['admin_theme_stat_card_label_color'] ?? $portalAdminThemeStatNumber);
$portalAdminThemeCardTitle = (string)($portalAdminThemeSettings['admin_theme_card_title_color'] ?? $portalBrandPrimary);
$portalAdminThemeHeading = (string)($portalAdminThemeSettings['admin_theme_heading_color'] ?? $portalBrandPrimary);
$portalAdminThemeText = (string)($portalAdminThemeSettings['admin_theme_body_text_color'] ?? $portalBrandText);
$portalAdminThemeMuted = (string)($portalAdminThemeSettings['admin_theme_muted_text_color'] ?? '#64748B');
$portalAdminThemeButton = (string)($portalAdminThemeSettings['admin_theme_button_color'] ?? $portalBrandButton);
$portalAdminThemeButtonText = (string)($portalAdminThemeSettings['admin_theme_button_text_color'] ?? (function_exists('portalContrastColor') ? portalContrastColor($portalAdminThemeButton) : '#FFFFFF'));
$portalAdminThemeTabActive = (string)($portalAdminThemeSettings['admin_theme_tab_active_color'] ?? $portalBrandPrimary);
$portalAdminThemeTabActiveText = function_exists('portalContrastColor') ? portalContrastColor($portalAdminThemeTabActive) : '#FFFFFF';
$portalAdminThemeTabInactive = (string)($portalAdminThemeSettings['admin_theme_tab_inactive_color'] ?? '#E8EEF7');
$portalAdminThemeTabText = (string)($portalAdminThemeSettings['admin_theme_tab_text_color'] ?? $portalBrandPrimary);
$portalAdminThemeTableHeaderBg = (string)($portalAdminThemeSettings['admin_theme_table_header_bg_color'] ?? $portalBrandTableHeader);
$portalAdminThemeTableHeaderText = (string)($portalAdminThemeSettings['admin_theme_table_header_text_color'] ?? (function_exists('portalContrastColor') ? portalContrastColor($portalAdminThemeTableHeaderBg) : '#FFFFFF'));
$portalAdminThemeBadgeAccent = (string)($portalAdminThemeSettings['admin_theme_badge_accent_color'] ?? $portalBrandBadgeSuccess);
$portalAdminThemeBadgeAccentText = function_exists('portalContrastColor') ? portalContrastColor($portalAdminThemeBadgeAccent) : '#FFFFFF';

$portalStudentDashboardRule = [];
$portalStudentDashboardThemeActive = false;
$portalStudentDashboardBodyClasses = '';
if (isset($pdo) && ($pdo instanceof PDO) && ((string)($page ?? '') === 'student_dashboard') && ((string)($_SESSION['user_type'] ?? '') === 'student') && function_exists('portalResolveStudentDashboardDesignRule')) {
    try {
        $portalStudentThemeStudentId = (int)($_SESSION['user_id'] ?? 0);
        if ($portalStudentThemeStudentId > 0) {
            $portalStudentThemeStmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
            $portalStudentThemeStmt->execute([$portalStudentThemeStudentId]);
            $portalStudentThemeStudent = $portalStudentThemeStmt->fetch(PDO::FETCH_ASSOC) ?: [];
            if ($portalStudentThemeStudent) {
                $portalStudentDashboardRule = portalResolveStudentDashboardDesignRule($pdo, $portalStudentThemeStudent);
                $portalStudentDashboardThemeActive = !empty($portalStudentDashboardRule);
                $portalStudentDashboardBodyClasses = trim('student-dashboard-theme-active ' . (string)($portalStudentDashboardRule['body_class'] ?? 'dashboard-layout-standard'));
            }
        }
    } catch (Throwable $e) {
        $portalStudentDashboardRule = [];
        $portalStudentDashboardThemeActive = false;
        $portalStudentDashboardBodyClasses = '';
    }
}

?>
    <style>
        :root {
            --primary-color: <?php echo htmlspecialchars($portalLegacyPrimary); ?>;
            --accent-color: <?php echo htmlspecialchars($portalLegacyAccent); ?>;
            --bg-color: <?php echo htmlspecialchars($portalLegacyBackground); ?>;
            --white: <?php echo htmlspecialchars($portalLegacySurface); ?>;
            --text-color: <?php echo htmlspecialchars($portalLegacyText); ?>;
            --secondary-color: <?php echo htmlspecialchars($portalBrandSecondary); ?>;
            --portal-sidebar-bg: <?php echo htmlspecialchars($portalBrandSidebar); ?>;
            --portal-sidebar-text: <?php echo htmlspecialchars($portalBrandSidebarText); ?>;
            --portal-header-bg: <?php echo htmlspecialchars($portalBrandHeader); ?>;
            --portal-header-text: <?php echo htmlspecialchars($portalBrandHeaderText); ?>;
            --portal-button-bg: <?php echo htmlspecialchars($portalBrandButton); ?>;
            --portal-button-text: <?php echo htmlspecialchars($portalBrandButtonText); ?>;
            --portal-link-color: <?php echo htmlspecialchars($portalBrandLink); ?>;
            --portal-card-header-bg: <?php echo htmlspecialchars($portalBrandCardHeader); ?>;
            --portal-card-header-text: <?php echo htmlspecialchars($portalBrandCardHeaderText); ?>;
            --portal-card-bg: <?php echo htmlspecialchars($portalBrandCardBg); ?>;
            --portal-card-text: <?php echo htmlspecialchars($portalBrandCardText); ?>;
            --portal-table-header-bg: <?php echo htmlspecialchars($portalBrandTableHeader); ?>;
            --portal-table-header-text: <?php echo htmlspecialchars($portalBrandTableHeaderText); ?>;
            --portal-badge-success: <?php echo htmlspecialchars($portalBrandBadgeSuccess); ?>;
            --portal-badge-warning: <?php echo htmlspecialchars($portalBrandBadgeWarning); ?>;
            --portal-badge-danger: <?php echo htmlspecialchars($portalBrandBadgeDanger); ?>;
            --border-color: #ddd;
            --error-color: #d9534f;
            --success-color: #5cb85c;
            --sidebar-width: 260px;
        }

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: var(--bg-color);
            display: flex;
            min-height: 100vh;
            overflow-x: hidden;
        }

        /* Auth (Login) background */
        .auth-bg {
            min-height: 100vh;
            width: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 30px 15px;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            position: relative;
        }
        .auth-bg::before{
            content:"";
            position:absolute;
            inset:0;
            background: radial-gradient(circle at top left, rgba(0,51,102,.65), rgba(0,0,0,.72));
        }
        .auth-bg > .auth-inner { position: relative; z-index: 1; width: 100%; }
        .auth-card{
            backdrop-filter: blur(6px);
            background: rgba(255,255,255,.92);
            border: 1px solid rgba(255,255,255,.35);
            box-shadow: 0 20px 60px rgba(0,0,0,.35);
            border-radius: 18px;
            overflow: hidden;
        }

        /* Wallet balance hero */
        .balance-hero{
            border-radius: 18px;
            background: linear-gradient(135deg, var(--primary-color), #0b2f57);
            color: #fff;
            box-shadow: 0 18px 40px rgba(0,0,0,.18);
            position: relative;
            overflow: hidden;
        }
        .balance-hero::after{
            content:"";
            position:absolute;
            width: 240px; height: 240px;
            right:-120px; top:-120px;
            background: rgba(240,165,0,.28);
            border-radius: 50%;
        }
        .balance-hero-inner{ padding: 18px; position: relative; z-index: 1; }
        .balance-hero-title{ font-weight: 700; letter-spacing:.02em; opacity:.9; }
        .balance-hero-amount{ font-size: 1.75rem; font-weight: 800; margin-top: 6px; }
        .balance-hero-sub{ opacity:.9; margin-top: 4px; font-size:.95rem; }
        .balance-hero-actions{ display:flex; gap:10px; margin-top: 12px; flex-wrap: wrap; }

        /* Exam subject/grade UI */
        .exam-grade-rows .sg-row{
            display:flex;
            gap:10px;
            align-items:center;
            margin-bottom: 8px;
        }
        .exam-grade-rows .sg-row .sg-subject{
            flex: 1;
            font-weight: 600;
            font-size: .92rem;
        }
        .exam-grade-rows .sg-row select{
            width: 110px;
        }

        /* QR code should be opaque and printable */
        .qr-code img, .qr-code canvas{
            opacity: 1 !important;
            background: #fff !important;
            padding: 6px;
            border-radius: 8px;
        }


        .richtext-output p:last-child,
        .richtext-output ul:last-child,
        .richtext-output ol:last-child,
        .richtext-output pre:last-child,
        .richtext-output blockquote:last-child {
            margin-bottom: 0;
        }
        .richtext-output table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: .75rem;
        }
        .richtext-output th,
        .richtext-output td {
            border: 1px solid #dbe5f1;
            padding: .45rem .6rem;
            vertical-align: top;
        }
        .richtext-output blockquote {
            border-left: 4px solid rgba(13, 110, 253, .25);
            padding-left: .85rem;
            color: #475569;
            margin-bottom: .75rem;
        }
        .richtext-output pre {
            background: #0f172a;
            color: #e2e8f0;
            padding: .85rem 1rem;
            border-radius: 12px;
            overflow: auto;
        }
        .tox-tinymce {
            border-radius: 14px !important;
            border-color: #d9e6ff !important;
        }
        .tox .tox-toolbar, .tox .tox-toolbar__primary, .tox .tox-menubar {
            background: #f8fbff !important;
        }

        /* Sidebar Styles */
        .sidebar {
            width: var(--sidebar-width);
            background-color: var(--primary-color);
            color: var(--white);
            position: fixed;
            top: 0;
            left: 0;
            height: 100vh;
            overflow-y: auto;
            transition: transform 0.3s ease;
            z-index: 1000;
        }

        .sidebar-header {
            padding: 20px;
            text-align: center;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }

        .sidebar-brand {
            font-weight: bold;
            font-size: 1.2rem;
            margin-top: 10px;
            display: block;
            color: var(--white);
            text-decoration: none;
        }

        .sidebar-menu {
            padding: 20px 0;
            list-style: none;
        }

        .sidebar-menu li {
            margin-bottom: 5px;
        }

        .sidebar-menu a {
            display: flex;
            align-items: center;
            padding: 12px 20px;
            color: rgba(255, 255, 255, 0.8);
            text-decoration: none;
            transition: all 0.2s;
        }

        .sidebar-menu a:hover, .sidebar-menu a.active {
            background-color: rgba(255, 255, 255, 0.1);
            color: var(--white);
            border-left: 4px solid var(--accent-color);
        }

        .sidebar-menu i {
            margin-right: 10px;
            font-size: 1.1rem;
        }

        .sidebar-footer {
            padding: 20px;
            border-top: 1px solid rgba(255, 255, 255, 0.1);
            text-align: center;
            font-size: 0.85rem;
            color: rgba(255, 255, 255, 0.6);
        }
        .portal-search-wrap{padding:14px 14px 4px;border-bottom:1px solid rgba(255,255,255,.08);position:relative;}
        .portal-search-input-group{position:relative;}
        .portal-search-icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:rgba(255,255,255,.72);pointer-events:none;}
        .portal-search-input{width:100%;border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.09);color:#fff;border-radius:12px;padding:10px 12px 10px 36px;outline:none;box-shadow:none;}
        .portal-search-input::placeholder{color:rgba(255,255,255,.62);}
        .portal-search-input:focus{border-color:rgba(255,255,255,.45);background:rgba(255,255,255,.14);}
        .portal-search-results{margin-top:8px;background:#fff;color:#0f172a;border-radius:12px;box-shadow:0 18px 36px rgba(2,6,23,.24);max-height:320px;overflow:auto;}
        .portal-search-kind{display:inline-flex;align-items:center;justify-content:center;margin-left:8px;padding:2px 7px;border-radius:999px;background:#e2e8f0;color:#0f172a;font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.02em;}
        .portal-search-result small{display:block;color:#475569;margin-top:2px;}
        .portal-search-result{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;text-decoration:none;color:#0f172a;border-bottom:1px solid #eef2f7;}
        .portal-search-result:last-child{border-bottom:none;}
        .portal-search-result:hover{background:#f8fbff;color:#0b5ed7;}
        .portal-search-result small{display:block;color:#64748b;line-height:1.25;}
        .portal-search-empty{padding:10px 12px;color:#64748b;font-size:.92rem;}

        /* Main Content Styles */
        .main-content {
            margin-left: var(--sidebar-width);
            flex: 1;
            padding: 20px;
            width: calc(100% - var(--sidebar-width));
            min-height: 100vh;
        }

        /* Admin Settings page: avoid forcing tall panels that create large blank areas */
        body.page-admin_settings .main-content , body.page-theme_branding .main-content { min-height: auto; }
        body.page-admin_settings .card , body.page-theme_branding .card { height: auto; }
        body.page-admin_settings .card-body , body.page-theme_branding .card-body { min-height: auto !important; }

        body.page-admin_settings .portal-brand-color-group .form-control-color , body.page-theme_branding .portal-brand-color-group .form-control-color { width: 58px; min-width: 58px; padding: .25rem; }
        body.page-admin_settings .branding-toggle-wrap , body.page-theme_branding .branding-toggle-wrap { min-width: 170px; }
        body.page-admin_settings .branding-preview-wrap , body.page-theme_branding .branding-preview-wrap { --preview-primary: #003366; --preview-secondary: #0B5ED7; --preview-sidebar: #003366; --preview-header: #003366; --preview-button: #003366; --preview-link: #f0a500; --preview-card-header: #003366; --preview-card-bg: #003366; --preview-card-text: #FFFFFF; --preview-table-header: #003366; --preview-success: #198754; --preview-warning: #F59E0B; --preview-danger: #DC3545; --preview-surface: #FFFFFF; }
        body.page-admin_settings .portal-brand-preview-sidebar , body.page-theme_branding .portal-brand-preview-sidebar { background: linear-gradient(135deg, var(--preview-sidebar), var(--preview-primary)); }
        body.page-admin_settings .portal-brand-preview-header , body.page-theme_branding .portal-brand-preview-header { background: linear-gradient(135deg, var(--preview-header), var(--preview-secondary)); }
        body.page-admin_settings .portal-brand-preview-btn , body.page-theme_branding .portal-brand-preview-btn { background: var(--preview-button); border-color: var(--preview-button); }
        body.page-admin_settings .portal-brand-preview-link , body.page-theme_branding .portal-brand-preview-link { color: var(--preview-link); text-decoration: none; }
        body.page-admin_settings .portal-brand-preview-card-header , body.page-theme_branding .portal-brand-preview-card-header { background: var(--preview-card-header); color: #fff; }
        body.page-admin_settings .portal-brand-preview-stat-card , body.page-theme_branding .portal-brand-preview-stat-card { background: var(--preview-card-bg); color: var(--preview-card-text); border-radius: 16px; padding: 16px; box-shadow: 0 12px 24px rgba(15, 23, 42, .12); }
        body.page-admin_settings .portal-brand-preview-stat-card .value , body.page-theme_branding .portal-brand-preview-stat-card .value { font-size: 1.55rem; font-weight: 800; line-height: 1.1; margin-bottom: 4px; color: inherit; }
        body.page-admin_settings .portal-brand-preview-stat-card .label , body.page-theme_branding .portal-brand-preview-stat-card .label { font-size: .92rem; opacity: .9; color: inherit; }
        body.page-admin_settings .portal-brand-preview-table-header , body.page-theme_branding .portal-brand-preview-table-header { background: var(--preview-table-header) !important; color: #fff; }
        body.page-admin_settings .portal-brand-preview-badge-success , body.page-theme_branding .portal-brand-preview-badge-success { background: var(--preview-success); color: #fff; }
        body.page-admin_settings .portal-brand-preview-badge-warning , body.page-theme_branding .portal-brand-preview-badge-warning { background: var(--preview-warning); }
        body.page-admin_settings .portal-brand-preview-badge-danger , body.page-theme_branding .portal-brand-preview-badge-danger { background: var(--preview-danger); color: #fff; }
        body.page-admin_settings .branding-preview-surface , body.page-theme_branding .branding-preview-surface { background: var(--preview-surface); }
        body.page-admin_settings #settings-branding .card-header , body.page-theme_branding #settings-branding .card-header { border-bottom: 0; }

        body.page-admin_settings .admin-branding-preview-wrap , body.page-theme_branding .admin-branding-preview-wrap { --admin-preview-bg:#F4F7F6; --admin-preview-card-bg:#FFFFFF; --admin-preview-border:#D9E2EC; --admin-preview-header:#003366; --admin-preview-sidebar:#003366; --admin-preview-sidebar-active:#0B5ED7; --admin-preview-sidebar-text:#FFFFFF; --admin-preview-stat-bg:#003366; --admin-preview-stat-number:#FFFFFF; --admin-preview-stat-label:#FFFFFF; --admin-preview-card-title:#003366; --admin-preview-heading:#003366; --admin-preview-text:#333333; --admin-preview-muted:#64748B; --admin-preview-btn:#003366; --admin-preview-btn-text:#FFFFFF; --admin-preview-tab-active:#003366; --admin-preview-tab-inactive:#E8EEF7; --admin-preview-tab-text:#003366; --admin-preview-table-bg:#003366; --admin-preview-table-text:#FFFFFF; --admin-preview-badge:#198754; }
        body.page-admin_settings .portal-admin-preview-sidebar , body.page-theme_branding .portal-admin-preview-sidebar { background: linear-gradient(135deg, var(--admin-preview-sidebar), var(--admin-preview-sidebar-active)); color: var(--admin-preview-sidebar-text); }
        body.page-admin_settings .portal-admin-preview-header , body.page-theme_branding .portal-admin-preview-header { background: var(--admin-preview-header); color: <?php echo htmlspecialchars(function_exists('portalContrastColor') ? portalContrastColor('#003366') : '#FFFFFF'); ?>; }
        body.page-admin_settings .admin-branding-preview-surface , body.page-theme_branding .admin-branding-preview-surface { background: var(--admin-preview-bg); color: var(--admin-preview-text); border-top: 1px solid var(--admin-preview-border); }
        body.page-admin_settings .portal-admin-preview-btn , body.page-theme_branding .portal-admin-preview-btn { background: var(--admin-preview-btn); border-color: var(--admin-preview-btn); color: var(--admin-preview-btn-text); }
        body.page-admin_settings .portal-admin-preview-stat-card , body.page-theme_branding .portal-admin-preview-stat-card { background: var(--admin-preview-stat-bg); color: var(--admin-preview-stat-label); border-radius: 16px; padding: 16px; box-shadow: 0 12px 24px rgba(15,23,42,.12); }
        body.page-admin_settings .portal-admin-preview-stat-card .value , body.page-theme_branding .portal-admin-preview-stat-card .value { color: var(--admin-preview-stat-number); font-size: 1.55rem; font-weight: 800; }
        body.page-admin_settings .portal-admin-preview-stat-card .label , body.page-theme_branding .portal-admin-preview-stat-card .label { color: var(--admin-preview-stat-label); opacity: .95; }
        body.page-admin_settings .portal-admin-preview-card , body.page-theme_branding .portal-admin-preview-card { background: var(--admin-preview-card-bg); border: 1px solid var(--admin-preview-border); }
        body.page-admin_settings .portal-admin-preview-card-header , body.page-theme_branding .portal-admin-preview-card-header { background: var(--admin-preview-header); color: var(--admin-preview-btn-text); }
        body.page-admin_settings .portal-admin-preview-card-title , body.page-theme_branding .portal-admin-preview-card-title { color: var(--admin-preview-card-title); }
        body.page-admin_settings .portal-admin-preview-body , body.page-theme_branding .portal-admin-preview-body { color: var(--admin-preview-text); }
        body.page-admin_settings .portal-admin-preview-muted , body.page-theme_branding .portal-admin-preview-muted { color: var(--admin-preview-muted); }
        body.page-admin_settings .admin-branding-preview-tabs .nav-link , body.page-theme_branding .admin-branding-preview-tabs .nav-link { cursor: default; border: 1px solid var(--admin-preview-border); background: var(--admin-preview-tab-inactive); color: var(--admin-preview-tab-text); }
        body.page-admin_settings .admin-branding-preview-tabs .nav-link.active , body.page-theme_branding .admin-branding-preview-tabs .nav-link.active { background: var(--admin-preview-tab-active); color: <?php echo htmlspecialchars(function_exists('portalContrastColor') ? portalContrastColor('#003366') : '#FFFFFF'); ?>; border-color: var(--admin-preview-tab-active); }
        body.page-admin_settings .portal-admin-preview-table-header , body.page-theme_branding .portal-admin-preview-table-header { background: var(--admin-preview-table-bg) !important; color: var(--admin-preview-table-text) !important; }
        body.page-admin_settings .portal-admin-preview-link , body.page-theme_branding .portal-admin-preview-link { color: var(--admin-preview-card-title); text-decoration: none; }
        body.page-admin_settings .portal-admin-preview-badge , body.page-theme_branding .portal-admin-preview-badge { background: var(--admin-preview-badge); color: <?php echo htmlspecialchars(function_exists('portalContrastColor') ? portalContrastColor('#198754') : '#FFFFFF'); ?>; }
        body.page-admin_settings .portal-admin-preview-alert , body.page-theme_branding .portal-admin-preview-alert { background: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba('#198754', .12) : 'rgba(25,135,84,.12)'); ?>; border: 1px solid var(--admin-preview-badge); color: var(--admin-preview-text); }


        @media screen {
<?php if ($portalAdminThemeApplies): ?>
        body {
            background-color: <?php echo htmlspecialchars($portalAdminThemeBackground); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeText); ?> !important;
        }
        .main-content {
            background-color: <?php echo htmlspecialchars($portalAdminThemeBackground); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeText); ?> !important;
        }
        .sidebar {
            background-color: <?php echo htmlspecialchars($portalAdminThemeSidebar); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeSidebarText); ?> !important;
        }
        .sidebar-brand,
        .sidebar-menu a,
        .sidebar-footer,
        .portal-search-input,
        .portal-search-input::placeholder,
        .portal-search-icon {
            color: <?php echo htmlspecialchars($portalAdminThemeSidebarText); ?> !important;
        }
        .sidebar-menu a:hover,
        .sidebar-menu a.active {
            background-color: <?php echo htmlspecialchars($portalAdminThemeSidebarActive); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeSidebarText); ?> !important;
            border-left-color: <?php echo htmlspecialchars($portalAdminThemeSidebarText); ?> !important;
        }
        .sidebar-header, .sidebar-footer, .portal-search-wrap {
            border-color: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalAdminThemeSidebarText, .12) : 'rgba(255,255,255,.12)'); ?> !important;
        }
        .portal-search-input {
            border-color: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalAdminThemeSidebarText, .18) : 'rgba(255,255,255,.18)'); ?> !important;
            background: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalAdminThemeSidebarText, .08) : 'rgba(255,255,255,.08)'); ?> !important;
        }
        .page-header,
        .card-header,
        .section-header,
        .main-content .card > .card-header {
            background-color: <?php echo htmlspecialchars($portalAdminThemeHeader); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeHeaderText); ?> !important;
            border-color: <?php echo htmlspecialchars($portalAdminThemeHeader); ?> !important;
        }
        .main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6,
        .main-content .page-title, .main-content .card-title {
            color: <?php echo htmlspecialchars($portalAdminThemeHeading); ?> !important;
        }
        .main-content .text-muted, .main-content small, .main-content .small, .main-content .stats-label, .main-content .stats-note {
            color: <?php echo htmlspecialchars($portalAdminThemeMuted); ?> !important;
        }
        .main-content .card, .main-content .dashboard-card, .main-content .summary-card {
            background-color: <?php echo htmlspecialchars($portalAdminThemeCardBg); ?> !important;
            border-color: <?php echo htmlspecialchars($portalAdminThemeBorder); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeText); ?> !important;
        }
        .main-content .card-body, .main-content .dashboard-card .card-body, .main-content .summary-card .card-body {
            color: <?php echo htmlspecialchars($portalAdminThemeText); ?> !important;
        }
        .main-content .card-title, .main-content .dashboard-card .card-title, .main-content .summary-card .card-title {
            color: <?php echo htmlspecialchars($portalAdminThemeCardTitle); ?> !important;
        }
        .main-content .stats-card, .main-content .stat-card {
            background: <?php echo htmlspecialchars($portalAdminThemeStatBg); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeStatLabel); ?> !important;
            border: 1px solid <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalAdminThemeStatBg, .72) : $portalAdminThemeStatBg); ?> !important;
            box-shadow: 0 12px 24px <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalAdminThemeStatBg, .18) : 'rgba(15,23,42,.18)'); ?> !important;
        }
        .main-content .stats-card h1, .main-content .stats-card h2, .main-content .stats-card h3, .main-content .stats-card h4, .main-content .stats-card h5, .main-content .stats-card h6,
        .main-content .stats-card .dashboard-stat-value, .main-content .stats-card .dashboard-stat-value-sm, .main-content .stats-card .dashboard-stat-value-xs,
        .main-content .stat-card h1, .main-content .stat-card h2, .main-content .stat-card h3, .main-content .stat-card h4, .main-content .stat-card h5, .main-content .stat-card h6 {
            color: <?php echo htmlspecialchars($portalAdminThemeStatNumber); ?> !important;
        }
        .main-content .stats-card p, .main-content .stats-card .stats-label, .main-content .stat-card p, .main-content .stat-card .stats-label {
            color: <?php echo htmlspecialchars($portalAdminThemeStatLabel); ?> !important;
        }
        .main-content .btn-primary, .main-content .btn-primary:hover, .main-content .btn-primary:focus, .main-content .btn-primary:active,
        .main-content .btn-outline-primary:hover, .main-content .btn-outline-primary:focus, .main-content .btn-outline-primary:active, .main-content .btn-outline-primary.active {
            background-color: <?php echo htmlspecialchars($portalAdminThemeButton); ?> !important;
            border-color: <?php echo htmlspecialchars($portalAdminThemeButton); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeButtonText); ?> !important;
            box-shadow: 0 .5rem 1rem <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalAdminThemeButton, .24) : 'rgba(0,0,0,.24)'); ?> !important;
        }
        .main-content .btn-outline-primary {
            color: <?php echo htmlspecialchars($portalAdminThemeButton); ?> !important;
            border-color: <?php echo htmlspecialchars($portalAdminThemeButton); ?> !important;
        }
        .main-content .nav-tabs .nav-link {
            background-color: <?php echo htmlspecialchars($portalAdminThemeTabInactive); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeTabText); ?> !important;
            border-color: <?php echo htmlspecialchars($portalAdminThemeBorder); ?> !important;
        }
        .main-content .nav-tabs .nav-link.active,
        .main-content .nav-tabs .nav-item.show .nav-link {
            background-color: <?php echo htmlspecialchars($portalAdminThemeTabActive); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeTabActiveText); ?> !important;
            border-color: <?php echo htmlspecialchars($portalAdminThemeTabActive); ?> !important;
        }
        .main-content .table thead th,
        .main-content .table > :not(caption) > * > th {
            background-color: <?php echo htmlspecialchars($portalAdminThemeTableHeaderBg); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeTableHeaderText); ?> !important;
            border-color: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalAdminThemeTableHeaderBg, .85) : $portalAdminThemeTableHeaderBg); ?> !important;
        }
        .main-content .badge, .main-content .alert {
            border-color: <?php echo htmlspecialchars($portalAdminThemeBadgeAccent); ?> !important;
        }
        .main-content .badge:not(.bg-success):not(.text-bg-success):not(.bg-warning):not(.text-bg-warning):not(.bg-danger):not(.text-bg-danger),
        .main-content .text-bg-primary, .main-content .bg-primary {
            background-color: <?php echo htmlspecialchars($portalAdminThemeBadgeAccent); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeBadgeAccentText); ?> !important;
        }
        .main-content .alert-primary, .main-content .alert-info, .main-content .alert-light {
            background-color: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalAdminThemeBadgeAccent, .12) : 'rgba(25,135,84,.12)'); ?> !important;
            color: <?php echo htmlspecialchars($portalAdminThemeText); ?> !important;
        }
        .main-content a:not(.btn):not(.dropdown-item):not(.sidebar-brand), .main-content .link-primary, .main-content .text-primary {
            color: <?php echo htmlspecialchars($portalAdminThemeCardTitle); ?> !important;
        }
        .main-content .form-control:focus, .main-content .form-select:focus, .main-content textarea:focus {
            border-color: <?php echo htmlspecialchars($portalAdminThemeButton); ?> !important;
            box-shadow: 0 0 0 .2rem <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalAdminThemeButton, .18) : 'rgba(0,0,0,.18)'); ?> !important;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_sidebar')): ?>
        .sidebar {
            background-color: var(--portal-sidebar-bg) !important;
            color: var(--portal-sidebar-text) !important;
        }
        .sidebar-header,
        .sidebar-footer,
        .portal-search-wrap {
            border-color: <?php echo htmlspecialchars($portalBrandSidebarBorder); ?> !important;
        }
        .sidebar-brand,
        .sidebar-menu a,
        .sidebar-footer,
        .portal-search-input,
        .portal-search-icon,
        .portal-search-input::placeholder {
            color: var(--portal-sidebar-text) !important;
        }
        .portal-search-input {
            border-color: <?php echo htmlspecialchars($portalBrandSidebarBorder); ?> !important;
            background: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandSidebarText, .08) : 'rgba(255,255,255,.08)'); ?> !important;
        }
        .portal-search-input:focus {
            background: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandSidebarText, .14) : 'rgba(255,255,255,.14)'); ?> !important;
        }
        .sidebar-menu a:hover,
        .sidebar-menu a.active {
            background-color: <?php echo htmlspecialchars($portalBrandSidebarHover); ?> !important;
            color: var(--portal-sidebar-text) !important;
            border-left-color: var(--accent-color) !important;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_header')): ?>
        .navbar,
        .topbar,
        .main-header {
            background-color: var(--portal-header-bg) !important;
            color: var(--portal-header-text) !important;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_buttons')): ?>
        .btn-primary,
        .btn-primary:focus,
        .btn-primary:active,
        .btn-primary:hover {
            background-color: var(--portal-button-bg) !important;
            border-color: var(--portal-button-bg) !important;
            color: var(--portal-button-text) !important;
            box-shadow: 0 .5rem 1rem <?php echo htmlspecialchars($portalBrandButtonShadow); ?> !important;
        }
        .btn-outline-primary {
            color: var(--portal-button-bg) !important;
            border-color: var(--portal-button-bg) !important;
        }
        .btn-outline-primary:hover,
        .btn-outline-primary:focus,
        .btn-outline-primary:active,
        .btn-outline-primary.active {
            background-color: var(--portal-button-bg) !important;
            border-color: var(--portal-button-bg) !important;
            color: var(--portal-button-text) !important;
        }
        .page-link.active,
        .active > .page-link {
            background-color: var(--portal-button-bg) !important;
            border-color: var(--portal-button-bg) !important;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_links')): ?>
        .main-content a:not(.btn):not(.dropdown-item):not(.sidebar-brand),
        .main-content .nav-link,
        .main-content .link-primary,
        .main-content .text-primary {
            color: var(--portal-link-color);
        }
        .main-content a:not(.btn):not(.dropdown-item):hover,
        .main-content .nav-link:hover {
            color: var(--portal-link-color);
            opacity: .9;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_dashboard_cards')): ?>
        .balance-hero {
            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
        }
        .dashboard-card,
        .stats-card,
        .summary-card,
        .stat-card {
            border-top: 3px solid var(--portal-card-header-bg);
        }
        .dashboard-stats-grid .dashboard-card,
        .dashboard-stats-grid .stats-card,
        .dashboard-stats-grid .summary-card,
        .dashboard-stats-grid .stat-card {
            background: var(--portal-card-bg) !important;
            color: var(--portal-card-text) !important;
            box-shadow: 0 12px 24px <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandCardBg, .18) : 'rgba(15,23,42,.18)'); ?> !important;
        }
        .dashboard-stats-grid .dashboard-card h1,
        .dashboard-stats-grid .dashboard-card h2,
        .dashboard-stats-grid .dashboard-card h3,
        .dashboard-stats-grid .dashboard-card h4,
        .dashboard-stats-grid .dashboard-card h5,
        .dashboard-stats-grid .dashboard-card h6,
        .dashboard-stats-grid .dashboard-card p,
        .dashboard-stats-grid .dashboard-card span,
        .dashboard-stats-grid .dashboard-card small,
        .dashboard-stats-grid .stats-card h1,
        .dashboard-stats-grid .stats-card h2,
        .dashboard-stats-grid .stats-card h3,
        .dashboard-stats-grid .stats-card h4,
        .dashboard-stats-grid .stats-card h5,
        .dashboard-stats-grid .stats-card h6,
        .dashboard-stats-grid .stats-card p,
        .dashboard-stats-grid .stats-card span,
        .dashboard-stats-grid .stats-card small,
        .dashboard-stats-grid .summary-card h1,
        .dashboard-stats-grid .summary-card h2,
        .dashboard-stats-grid .summary-card h3,
        .dashboard-stats-grid .summary-card h4,
        .dashboard-stats-grid .summary-card h5,
        .dashboard-stats-grid .summary-card h6,
        .dashboard-stats-grid .summary-card p,
        .dashboard-stats-grid .summary-card span,
        .dashboard-stats-grid .summary-card small,
        .dashboard-stats-grid .stat-card h1,
        .dashboard-stats-grid .stat-card h2,
        .dashboard-stats-grid .stat-card h3,
        .dashboard-stats-grid .stat-card h4,
        .dashboard-stats-grid .stat-card h5,
        .dashboard-stats-grid .stat-card h6,
        .dashboard-stats-grid .stat-card p,
        .dashboard-stats-grid .stat-card span,
        .dashboard-stats-grid .stat-card small {
            color: inherit !important;
        }
        .dashboard-stats-grid .stats-card .stats-label,
        .dashboard-stats-grid .stats-card p,
        .dashboard-stats-grid .dashboard-card p {
            opacity: .92;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_forms')): ?>
        .form-control:focus,
        .form-select:focus,
        textarea:focus,
        .signature-canvas:focus {
            border-color: var(--accent-color) !important;
            box-shadow: 0 0 0 .2rem <?php echo htmlspecialchars($portalBrandFormFocus); ?> !important;
        }
        .form-check-input:checked {
            background-color: var(--portal-button-bg) !important;
            border-color: var(--portal-button-bg) !important;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_tables')): ?>
        .table thead th,
        .table > thead > tr > th,
        .table > :not(caption) > * > th {
            background-color: var(--portal-table-header-bg) !important;
            color: var(--portal-table-header-text) !important;
            border-color: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandTableHeader, .85) : $portalBrandTableHeader); ?> !important;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_dashboard_cards')): ?>
        .card-header {
            background-color: var(--portal-card-header-bg);
            color: var(--portal-card-header-text);
        }
        .card-header .nav-link,
        .card-header a,
        .card-header .text-muted,
        .card-header small {
            color: inherit !important;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_status_badges')): ?>
        .badge.bg-success,
        .badge.text-bg-success,
        .bg-success,
        .text-bg-success {
            background-color: var(--portal-badge-success) !important;
            color: <?php echo htmlspecialchars(function_exists('portalContrastColor') ? portalContrastColor($portalBrandBadgeSuccess) : '#FFFFFF'); ?> !important;
        }
        .badge.bg-warning,
        .badge.text-bg-warning,
        .bg-warning,
        .text-bg-warning {
            background-color: var(--portal-badge-warning) !important;
            color: <?php echo htmlspecialchars(function_exists('portalContrastColor') ? portalContrastColor($portalBrandBadgeWarning) : '#0F172A'); ?> !important;
        }
        .badge.bg-danger,
        .badge.text-bg-danger,
        .bg-danger,
        .text-bg-danger {
            background-color: var(--portal-badge-danger) !important;
            color: <?php echo htmlspecialchars(function_exists('portalContrastColor') ? portalContrastColor($portalBrandBadgeDanger) : '#FFFFFF'); ?> !important;
        }
<?php endif; ?>
<?php if ($portalBrandSectionActive('branding_apply_header') || $portalBrandSectionActive('branding_apply_buttons')): ?>
        .notification-bell,
        .theme-switcher {
            border: 1px solid <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandButton, .16) : 'rgba(0,0,0,.16)'); ?>;
        }
        .notification-bell,
        .theme-toggle-btn,
        .navbar-toggle {
            color: var(--portal-button-bg);
        }
<?php endif; ?>
<?php if ($portalBrandingApplies && portalBrandingPageContext((string)($page ?? '')) === 'login'): ?>
        .auth-bg::before {
            background: radial-gradient(circle at top left, <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandPrimary, .72) : 'rgba(0,51,102,.72)'); ?>, <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba($portalBrandSecondary, .88) : 'rgba(11,94,215,.88)'); ?>) !important;
        }
<?php endif; ?>
        }

        .navbar-toggle {
            display: none;
            background: none;
            border: none;
            color: var(--primary-color);
            font-size: 1.5rem;
            cursor: pointer;
        }


        /* Notification bell */
        .notification-bell {
            position: fixed;
            top: 12px;
            right: 14px;
            z-index: 1200;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 44px;
            height: 44px;
            border-radius: 999px;
            background: #ffffff;
            color: var(--primary-color);
            box-shadow: 0 6px 18px rgba(0,0,0,0.12);
            text-decoration: none;
        }
        .notification-bell:hover {
            transform: translateY(-1px);
            text-decoration: none;
            color: var(--primary-color);
        }
        .notification-count {
            position: absolute;
            top: 6px;
            right: 6px;
            min-width: 18px;
            height: 18px;
            padding: 0 5px;
            border-radius: 999px;
            background: #dc3545;
            color: #fff;
            font-size: 11px;
            font-weight: 700;
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }
        .theme-switcher {
            position: fixed;
            top: 12px;
            right: 66px;
            z-index: 1200;
            display: inline-flex;
            align-items: center;
            gap: 6px;
            padding: 6px;
            border-radius: 999px;
            background: rgba(255,255,255,0.96);
            box-shadow: 0 6px 18px rgba(0,0,0,0.12);
            backdrop-filter: blur(8px);
        }
        .theme-toggle-btn {
            width: 36px;
            height: 36px;
            border: 1px solid rgba(15, 23, 42, 0.08);
            border-radius: 999px;
            background: transparent;
            color: var(--primary-color);
            display: inline-flex;
            align-items: center;
            justify-content: center;
            font-size: 1rem;
            transition: all .2s ease;
        }
        .theme-toggle-btn:hover {
            transform: translateY(-1px);
        }
        .theme-toggle-btn.active {
            background: var(--primary-color);
            color: #fff;
            box-shadow: 0 8px 20px rgba(0, 51, 102, .22);
        }
        body.theme-dark {
            background-color: #0f172a;
            color: #e5e7eb;
        }
        body.theme-dark .main-content,
        body.theme-dark .card,
        body.theme-dark .form-section,
        body.theme-dark .auth-card,
        body.theme-dark .modal-content,
        body.theme-dark .offcanvas,
        body.theme-dark .dropdown-menu,
        body.theme-dark .list-group-item,
        body.theme-dark .notification-bell,
        body.theme-dark .theme-switcher {
            background-color: #111827;
            color: #e5e7eb;
        }
        body.theme-dark .theme-switcher {
            box-shadow: 0 12px 28px rgba(0,0,0,0.3);
        }
        body.theme-dark .sidebar {
            background: linear-gradient(180deg, #071a33 0%, #0b2447 100%);
        }
        body.theme-dark .sidebar-footer,
        body.theme-dark .sidebar-menu a {
            color: rgba(255,255,255,0.84);
        }
        body.theme-dark .sidebar-menu a:hover,
        body.theme-dark .sidebar-menu a.active {
            background-color: rgba(255,255,255,0.12);
        }
        body.theme-dark .card {
            box-shadow: 0 10px 28px rgba(0,0,0,.28);
        }
        body.theme-dark .card-header {
            background: linear-gradient(135deg, var(--primary-color), #0b2f57);
            color: #fff;
        }
        body.theme-dark .table,
        body.theme-dark .table th,
        body.theme-dark .table td,
        body.theme-dark .richtext-output,
        body.theme-dark .richtext-output th,
        body.theme-dark .richtext-output td {
            color: #e5e7eb;
            border-color: #334155;
        }
        body.theme-dark .table-striped > tbody > tr:nth-of-type(odd) > * {
            --bs-table-accent-bg: rgba(255,255,255,.03);
            color: #e5e7eb;
        }
        body.theme-dark .form-control,
        body.theme-dark .form-select,
        body.theme-dark textarea,
        body.theme-dark .signature-canvas {
            background: #0b1220;
            color: #e5e7eb;
            border-color: #334155;
        }
        body.theme-dark .form-control:focus,
        body.theme-dark .form-select:focus,
        body.theme-dark textarea:focus {
            background: #0b1220;
            color: #fff;
            border-color: #60a5fa;
            box-shadow: 0 0 0 .2rem rgba(96,165,250,.18);
        }
        body.theme-dark .form-control::placeholder,
        body.theme-dark textarea::placeholder,
        body.theme-dark .text-muted,
        body.theme-dark .form-text,
        body.theme-dark small,
        body.theme-dark .small {
            color: #94a3b8 !important;
        }
        body.theme-dark .alert-light,
        body.theme-dark .bg-light,
        body.theme-dark .bg-light-subtle,
        body.theme-dark .border,
        body.theme-dark hr,
        body.theme-dark .list-group-item,
        body.theme-dark .dropdown-menu,
        body.theme-dark .modal-content,
        body.theme-dark .offcanvas,
        body.theme-dark .form-section {
            border-color: #334155 !important;
        }
        body.theme-dark .alert-light,
        body.theme-dark .bg-light,
        body.theme-dark .bg-light-subtle {
            background-color: #0f172a !important;
            color: #e5e7eb !important;
        }
        body.theme-dark .dropdown-item {
            color: #e5e7eb;
        }
        body.theme-dark .dropdown-item:hover,
        body.theme-dark .dropdown-item:focus {
            background-color: #1e293b;
            color: #fff;
        }
        body.theme-dark .btn-outline-primary,
        body.theme-dark .btn-outline-secondary,
        body.theme-dark .btn-outline-danger,
        body.theme-dark .btn-outline-success {
            color: #e5e7eb;
            border-color: #475569;
        }
        body.theme-dark .btn-outline-primary:hover,
        body.theme-dark .btn-outline-secondary:hover,
        body.theme-dark .btn-outline-danger:hover,
        body.theme-dark .btn-outline-success:hover {
            color: #fff;
        }
        body.theme-dark .navbar-toggle {
            background: #111827;
            color: #e5e7eb;
        }
        body.theme-dark .notification-bell {
            color: #e5e7eb;
        }
        body.theme-dark .logo-placeholder {
            background-color: #e5e7eb;
            color: #0f172a;
            border-color: #475569;
        }
        /* Mobile Responsive */
        @media (max-width: 768px) {
            .sidebar {
                transform: translateX(-100%);
            }

            .sidebar.show {
                transform: translateX(0);
            }

            .main-content {
                margin-left: 0;
                width: 100%;
            }

            .navbar-toggle {
                display: block;
                position: fixed;
                top: 15px;
                left: 15px;
                z-index: 1100;
                background: white;
                border-radius: 50%;
                width: 40px;
                height: 40px;
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            }
            .theme-switcher {
                top: 62px;
                right: 12px;
            }
        }

        .card {
            margin-bottom: 20px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
            border: none;
        }

        .card-header {
            background-color: var(--primary-color);
            color: var(--white);
            font-weight: bold;
            text-transform: uppercase;
        }

        .btn-primary {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
        }

        .btn-primary:hover {
            background-color: #002244;
            border-color: #002244;
        }

        .btn-success {
            background-color: var(--success-color);
            border-color: var(--success-color);
        }

        .btn-danger {
            background-color: var(--error-color);
            border-color: var(--error-color);
        }

        .dashboard-summary-panel-compact{padding:10px 14px;max-width:560px;margin-left:auto}.dashboard-summary-grid-compact{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px 16px}.dashboard-summary-panel-compact .dashboard-summary-label{font-size:.72rem;margin-bottom:2px}.dashboard-summary-panel-compact .dashboard-summary-value{font-size:.95rem}@media (max-width: 991.98px){.dashboard-summary-panel-compact{max-width:100%;margin-left:0}.dashboard-summary-grid-compact{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width: 575.98px){.dashboard-summary-grid-compact{grid-template-columns:1fr}}
        .signature-canvas {
            border: 1px dashed #999;
            background: #fff;
            cursor: crosshair;
            width: 100%;
            height: 150px;
            display: block;
        }

        .table-responsive {
            overflow-x: auto;
        }

        .status-badge {
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 0.8rem;
            font-weight: bold;
        }

        .status-pending { background-color: #f0ad4e; color: #fff; }
        .status-approved { background-color: #5cb85c; color: #fff; }
        .status-successful { background-color: #0275d8; color: #fff; }
        .status-credited { background-color: #198754; color: #fff; }
        .status-rejected { background-color: #d9534f; color: #fff; }
        .status-failed { background-color: #6c757d; color: #fff; }
        .status-under-review { background-color: #5bc0de; color: #fff; }
        .status-suspended { background-color: #f0ad4e; color: #fff; }
        .status-blocked { background-color: #d9534f; color: #fff; }
        .status-active { background-color: #5cb85c; color: #fff; }

        .login-container {
            max-width: 500px;
            margin: 50px auto;
        }

        .logo-area {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 20px;
            width: 100%;
            margin-bottom: 15px;
        }

        .logo-placeholder {
            width: 80px;
            height: 80px;
            background-color: #eee;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 10px;
            color: #777;
            border: 1px solid #ccc;
        }

        .form-section {
            margin-bottom: 30px;
            padding: 20px;
            border: 1px solid var(--border-color);
            border-radius: 5px;
        }

        .form-section h3 {
            color: var(--primary-color);
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid var(--border-color);
        }

        .alert {
            margin-bottom: 20px;
        }

        .dashboard-card {
            transition: transform 0.3s;
        }

        .dashboard-card:hover {
            transform: translateY(-5px);
        }

        .dashboard-stats-grid {
            display: grid;
            grid-template-columns: repeat(5, minmax(0, 1fr));
            gap: 18px;
        }

        .stats-card {
            background: linear-gradient(135deg, var(--primary-color), #004080);
            color: white;
            border-radius: 10px;
            padding: 18px;
            margin-bottom: 0;
            min-height: 104px;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;
            gap: 6px;
            box-shadow: 0 10px 22px rgba(0, 51, 102, 0.12);
        }

        .stats-card h3,
        .stats-card .dashboard-stat-value {
            font-size: 1.95rem;
            line-height: 1.08;
            margin: 0;
            word-break: break-word;
        }

        .stats-card .dashboard-stat-value-sm {
            font-size: 1.4rem;
        }

        .stats-card .dashboard-stat-value-xs {
            font-size: 1.05rem;
        }

        .stats-card p,
        .stats-card .stats-label {
            margin: 0;
            opacity: 0.88;
        }

        .stats-card .stats-label {
            font-size: 0.96rem;
            margin-top: 0;
        }

        .stats-card .stats-note {
            display: none;
        }

        
        .course-reg-prev-layout {
            display: grid;
            grid-template-columns: minmax(300px, 360px) minmax(0, 1fr);
            gap: 18px;
            align-items: start;
        }

        .course-reg-prev-filter-card,
        .course-reg-prev-preview-card {
            background: #fff;
            border: 1px solid rgba(2, 6, 23, 0.08);
            border-radius: 18px;
            box-shadow: 0 12px 28px rgba(15, 23, 42, 0.06);
        }

        .course-reg-prev-filter-card {
            padding: 18px;
            position: sticky;
            top: 84px;
        }

        .course-reg-prev-preview-card {
            overflow: hidden;
        }

        .course-reg-prev-filter-head,
        .course-reg-prev-preview-head {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            gap: 12px;
            padding: 18px 18px 0;
        }

        .course-reg-prev-filter-card .course-reg-prev-filter-head {
            padding: 0 0 14px;
        }

        .course-reg-prev-filter-kicker {
            font-size: 0.74rem;
            font-weight: 800;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: #667085;
            margin-bottom: 4px;
        }

        .course-reg-prev-help {
            margin-top: 12px;
            padding: 12px 14px;
            border-radius: 14px;
            background: linear-gradient(135deg, rgba(13,110,253,0.06), rgba(255,255,255,0.95));
            border: 1px solid rgba(13,110,253,0.12);
            color: #475467;
            font-size: 0.9rem;
            line-height: 1.5;
        }

        .course-reg-prev-preview-body {
            min-height: 520px;
            padding: 18px;
            background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
        }

        .course-reg-prev-empty-state,
        .course-reg-prev-loading {
            min-height: 460px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            text-align: center;
            color: #667085;
            border: 2px dashed rgba(13,110,253,0.14);
            border-radius: 20px;
            background: rgba(255,255,255,0.82);
            padding: 24px;
        }

        .course-reg-prev-empty-state .icon {
            width: 72px;
            height: 72px;
            border-radius: 50%;
            background: rgba(13,110,253,0.08);
            color: var(--primary-color);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.9rem;
            margin-bottom: 14px;
        }

        .course-reg-prev-empty-state .title {
            font-size: 1.05rem;
            font-weight: 800;
            color: #102a43;
            margin-bottom: 6px;
        }

        .course-reg-prev-empty-state .text {
            max-width: 540px;
            line-height: 1.6;
        }

        @media (max-width: 991.98px) {
            .course-reg-prev-layout {
                grid-template-columns: 1fr;
            }
            .course-reg-prev-filter-card {
                position: static;
            }
            .course-reg-prev-preview-body,
            .course-reg-prev-empty-state,
            .course-reg-prev-loading {
                min-height: 280px;
            }
        }

.dashboard-summary-panel {
            border: 1px solid rgba(13,110,253,0.18);
            border-radius: 12px;
            background: rgba(13,110,253,0.04);
            padding: 14px 16px;
        }

        .dashboard-summary-grid {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 12px 18px;
        }

        .dashboard-summary-item {
            min-width: 0;
        }

        .dashboard-summary-label {
            display: block;
            font-size: 0.78rem;
            line-height: 1.2;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.04em;
            color: #5b6570;
            margin-bottom: 4px;
        }

        .dashboard-summary-value {
            display: block;
            font-size: 1rem;
            line-height: 1.35;
            color: #1e2430;
            word-break: break-word;
        }

        .student-dashboard-hero {
            display: grid;
            grid-template-columns: minmax(0, 1fr) minmax(320px, 640px);
            gap: 18px;
            align-items: stretch;
            margin-bottom: 18px;
            padding: 18px 20px;
            border-radius: 22px;
            border: 1px solid rgba(13,110,253,0.14);
            background: linear-gradient(135deg, rgba(13,110,253,0.05), rgba(255,255,255,0.98));
            box-shadow: 0 12px 24px rgba(15,23,42,0.06);
        }

        .student-dashboard-welcome {
            min-width: 0;
            padding-right: 6px;
            display: flex;
            flex-direction: column;
            justify-content: center;
        }

        .student-dashboard-minihero {
            display: flex;
            align-items: stretch;
            gap: 14px;
            padding: 0;
            border-radius: 0;
            border: 0;
            background: transparent;
            box-shadow: none;
            min-width: 0;
        }

        .student-dashboard-minihero-info {
            flex: 1 1 auto;
            min-width: 0;
        }

        .student-dashboard-minihero-grid {
            display: grid;
            grid-template-columns: minmax(0, 1.18fr) minmax(0, 1fr) minmax(112px, 0.72fr);
            gap: 12px;
            align-items: stretch;
        }

        .student-dashboard-minihero-card {
            min-width: 0;
            background: rgba(255,255,255,0.9);
            border: 1px solid rgba(13,110,253,0.12);
            border-radius: 14px;
            padding: 12px 14px;
        }

        .student-dashboard-minihero-card--matric {
            min-width: 0;
        }

        .student-dashboard-minihero-card--level {
            max-width: 190px;
            justify-self: start;
        }

        .student-dashboard-minihero-label {
            display: block;
            font-size: 0.72rem;
            line-height: 1.15;
            font-weight: 800;
            text-transform: uppercase;
            letter-spacing: 0.06em;
            color: #667085;
            margin-bottom: 5px;
        }

        .student-dashboard-minihero-value {
            display: block;
            font-size: 1.02rem;
            line-height: 1.3;
            font-weight: 800;
            color: #102a43;
            word-break: break-word;
        }

        .student-dashboard-minihero-card--matric .student-dashboard-minihero-value {
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            word-break: normal;
            font-size: clamp(0.98rem, 1.35vw, 1.15rem);
            letter-spacing: 0.01em;
        }

        .student-dashboard-minihero-card--level .student-dashboard-minihero-value {
            font-size: 0.98rem;
        }

        .student-dashboard-minihero-photo {
            width: 96px;
            flex: 0 0 96px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .student-dashboard-minihero-photo img,
        .student-dashboard-minihero-avatar {
            width: 88px;
            height: 88px;
            border-radius: 50%;
            object-fit: cover;
            border: 4px solid #fff;
            box-shadow: 0 10px 20px rgba(15,23,42,0.14);
            background: #f8fafc;
        }

        .student-dashboard-minihero-avatar {
            display: flex;
            align-items: center;
            justify-content: center;
            color: #94a3b8;
            font-size: 1.9rem;
        }

        .student-dashboard-minihero-note {
            margin-top: 10px;
            font-size: 0.84rem;
            color: #667085;
        }

        @media (max-width: 991.98px) {
            .student-dashboard-hero {
                grid-template-columns: 1fr;
                padding: 16px;
            }
            .student-dashboard-minihero {
                padding: 0;
            }
        }

        @media (max-width: 767.98px) {
            .student-dashboard-minihero {
                flex-direction: column-reverse;
                align-items: center;
                text-align: left;
            }
            .student-dashboard-minihero-grid {
                grid-template-columns: 1fr;
            }
            .student-dashboard-minihero-card--level {
                max-width: none;
            }
            .student-dashboard-minihero-card--matric .student-dashboard-minihero-value {
                white-space: normal;
                overflow: visible;
                text-overflow: clip;
            }
            .student-dashboard-minihero-photo {
                width: auto;
                flex-basis: auto;
            }
        }

        a.stats-card-link {
            display: block;
            text-decoration: none;
            color: #fff;
            cursor: pointer;
            height: 100%;
        }
        a.stats-card-link:hover, a.stats-card-link:focus {
            text-decoration: none;
            color: #fff;
            filter: brightness(1.05);
        }

        @media (max-width: 1599.98px) {
            .dashboard-stats-grid {
                grid-template-columns: repeat(4, minmax(0, 1fr));
            }
        }

        @media (max-width: 1199.98px) {
            .dashboard-stats-grid {
                grid-template-columns: repeat(3, minmax(0, 1fr));
            }
        }

        @media (max-width: 991.98px) {
            .dashboard-stats-grid {
                grid-template-columns: repeat(2, minmax(0, 1fr));
            }

            .dashboard-summary-grid {
                grid-template-columns: minmax(0, 1fr);
            }
        }

        @media (max-width: 575.98px) {
            .dashboard-stats-grid {
                grid-template-columns: minmax(0, 1fr);
            }
        }

.receipt-container {
            max-width: 820px;
            margin: 0 auto;
            padding: 18px 20px 16px;
            border: 1px solid #dfe6ee;
            border-radius: 12px;
            background-color: white;
            box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
            overflow: hidden;
        }

        .receipt-container .doc-stamp {
            display: none !important;
        }

        .receipt-header {
            position: relative;
            text-align: center;
            margin-bottom: 14px;
            border-bottom: 1px solid #e5e7eb;
            padding: 4px 96px 12px;
            min-height: 90px;
        }

        .receipt-header .logo-area {
            height: 0;
            margin: 0 !important;
        }

        .receipt-header .logo-area .logo-display,
        .receipt-header .logo-area .logo-placeholder {
            position: absolute;
            top: 2px;
            width: 72px;
            height: 72px;
            object-fit: contain;
        }

        .receipt-header .logo-area > *:first-child { left: 0; }
        .receipt-header .logo-area > *:last-child { right: 0; }

        .logo-display {
            object-fit: contain;
        }

        .logo-placeholder {
            border: 1px dashed #cbd5e1;
            border-radius: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 10px;
            color: #64748b;
            background: #fff;
        }

        .receipt-header h2 {
            margin: 0;
            font-size: 20px;
            line-height: 1.12;
            font-weight: 700;
        }

        .receipt-header h3 {
            margin: 4px 0 0;
            font-size: 16px;
            line-height: 1.18;
            font-weight: 600;
        }

        .receipt-header p {
            margin: 4px 0 0;
            font-size: 13px;
            line-height: 1.24;
            color: #4b5563;
        }

        .receipt-header h4 {
            margin: 10px 0 0;
            font-size: 15px;
            line-height: 1.2;
            font-weight: 700;
        }

        .receipt-body {
            margin-bottom: 0;
        }

        .receipt-body p {
            margin: 0 0 5px;
            font-size: 13px;
            line-height: 1.38;
        }

        .receipt-meta {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            column-gap: 24px;
            row-gap: 8px;
            align-items: start;
            margin: 0 0 12px !important;
        }

        .receipt-meta-col {
            min-width: 0;
        }

        .receipt-meta-col p:last-child {
            margin-bottom: 0;
        }

        .receipt-body .row {
            margin-bottom: 12px !important;
        }

        .receipt-body .table-responsive {
            margin-bottom: 12px !important;
            overflow: visible;
        }

        .receipt-body .table,
        .receipt-body table.table {
            width: 100%;
            margin-bottom: 0;
            font-size: 13px;
            border-collapse: collapse;
        }

        .receipt-body .table th,
        .receipt-body .table td {
            border: 1px solid #dfe6ee;
            padding: 9px 10px;
            vertical-align: top;
        }

        .receipt-body .table thead th {
            background: #f8fafc;
        }

        .qr-code {
            text-align: center;
            margin: 10px 0 0;
        }

        .qr-code p {
            margin: 0 0 6px;
        }

        .qr-code img {
            opacity: 1 !important;
            background: #fff;
            max-width: 150px;
            width: 100%;
            height: auto;
            border: 1px solid #e5e7eb;
            border-radius: 10px;
            padding: 6px;
        }

        .qr-stamp {
            width: 118px;
            height: 118px;
            object-fit: contain;
            opacity: 0.92;
            background: transparent;
            border: none !important;
            padding: 0 !important;
        }

        .qr-pack {
            display:flex;
            gap:12px;
            align-items:center;
            justify-content:center;
            flex-wrap:wrap;
        }

        @page {
            size: A4;
            margin: 9mm 8mm;
        }

        @media (max-width: 767.98px) {
            .receipt-container {
                padding: 14px 14px 12px;
            }

            .receipt-header {
                padding: 2px 72px 10px;
                min-height: 72px;
            }

            .receipt-header .logo-area .logo-display,
            .receipt-header .logo-area .logo-placeholder {
                width: 52px;
                height: 52px;
            }

            .receipt-header h2 {
                font-size: 17px;
            }

            .receipt-header h3 {
                font-size: 14px;
            }

            .receipt-header p {
                font-size: 12px;
            }

            .receipt-header h4 {
                font-size: 14px;
            }

            .receipt-meta {
                grid-template-columns: 1fr;
                column-gap: 0;
                row-gap: 6px;
            }
        }

        @media print {
            html, body {
                background: #fff !important;
            }

            .card-header,
            .no-print,
            .navbar,
            .sidebar,
            .topbar,
            .main-header,
            .footer {
                display: none !important;
            }

            .card,
            .card-body,
            .container,
            .container-fluid,
            .row,
            [class*="col-"] {
                border: none !important;
                box-shadow: none !important;
            }

            .card-body {
                padding: 0 !important;
            }

            .receipt-container {
                max-width: none;
                border: none;
                box-shadow: none;
                padding: 6mm 5mm 4mm;
            }

            .receipt-header {
                margin-bottom: 10px;
                padding: 2px 84px 10px;
                min-height: 78px;
            }

            .receipt-header .logo-area .logo-display,
            .receipt-header .logo-area .logo-placeholder {
                width: 64px;
                height: 64px;
                top: 1px;
            }

            .receipt-header h2 {
                font-size: 19px;
            }

            .receipt-header h3 {
                font-size: 15px;
            }

            .receipt-header p {
                font-size: 12px;
            }

            .receipt-header h4 {
                font-size: 14px;
                margin-top: 8px;
            }

            .receipt-body p {
                font-size: 12px;
                margin-bottom: 4px;
            }

            .receipt-meta {
                grid-template-columns: repeat(2, minmax(0, 1fr));
                column-gap: 20px;
                row-gap: 6px;
            }

            .receipt-body .table th,
            .receipt-body .table td {
                padding: 7px 8px;
                font-size: 12px;
            }

            .qr-code {
                margin-top: 8px;
            }

            .qr-code img {
                max-width: 118px;
                padding: 4px;
            }

            .qr-stamp {
                width: 96px;
                height: 96px;
            }

            .receipt-body .row,
            .receipt-body .table-responsive,
            .qr-code,
            .receipt-footer {
                page-break-inside: avoid;
                break-inside: avoid-page;
            }
        }

        .receipt-footer {
            margin-top: 14px;
            border-top: 1px solid #e5e7eb;
            padding-top: 10px;
            text-align: center;
        }

        .receipt-footer p {
            margin: 0 0 4px;
            font-size: 12px;
            line-height: 1.35;
            color: #374151;
        }

.admission-letter-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 30px;
        }

        .admission-letter-logo {
            width: 100px;
            height: 100px;
            background-color: #eee;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 10px;
            color: #777;
            border: 1px solid #ccc;
        }

        .document-upload-area {
            border: 2px dashed #ccc;
            border-radius: 5px;
            padding: 20px;
            text-align: center;
            margin-bottom: 15px;
            transition: all 0.3s;
        }

        .document-upload-area:hover {
            border-color: var(--primary-color);
            background-color: rgba(0, 51, 102, 0.05);
        }

        .document-list {
            margin-top: 15px;
        }

        .document-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px;
            border: 1px solid #eee;
            border-radius: 5px;
            margin-bottom: 10px;
        }

        .document-item i {
            color: var(--primary-color);
            margin-right: 10px;
        }

        .passport-photo {
            width: 150px;
            height: 150px;
            object-fit: cover;
            border: 1px solid #ddd;
            border-radius: 5px;
        }

        .fee-breakdown {
            background-color: #f8f9fa;
            border-radius: 5px;
            padding: 15px;
            margin: 20px 0;
        }

        .fee-item {
            display: flex;
            justify-content: space-between;
            padding: 8px 0;
            border-bottom: 1px solid #eee;
        }

        .fee-item:last-child {
            border-bottom: none;
            font-weight: bold;
            font-size: 1.1rem;
        }

        @media print {
            .no-print {
                display: none !important;
            }

            .container {
                max-width: 100%;
                padding: 0;
            }

            .card {
                box-shadow: none;
                border: 1px solid #ddd;
            }
            .sidebar, .navbar-toggle {
                display: none;
            }
            .main-content {
                margin: 0;
                padding: 0;
                width: 100%;
            }
        }

        /* Logo display styles */
        .logo-display {
            max-width: 150px;
            max-height: 150px;
            object-fit: contain;
        }

        .asset-upload-section {
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 20px;
            margin-bottom: 20px;
            background-color: #f8f9fa;
        }

        .edit-warning {
            background-color: #fff3cd;
            border-left: 4px solid #ffc107;
            padding: 15px;
            margin-bottom: 20px;
        }

        .edit-counter {
            font-weight: bold;
            color: #856404;
        }

        /* ID Card Styles */
        .id-card-container {
            width: 3.375in; /* CR80 width */
            height: 2.125in; /* CR80 height */
            margin: 0 auto;
            background-color: white;
            border-radius: 12px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.2);
            overflow: hidden;
            position: relative;
        }

        .id-card-wrapper {
            display: flex;
            gap: 15px;
            justify-content: center;
            flex-wrap: wrap;
        }

        @media print {
            body { background: #fff !important; }
            .id-card-wrapper { gap: 0.2in; }
            .id-card-container { box-shadow: none !important; }
        }


        .id-card-header {
            background: linear-gradient(135deg, var(--primary-color), #004080);
            color: white;
            padding: 15px 20px;
            text-align: center;
        }

        .id-card-body {
            padding: 20px;
            display: flex;
            gap: 20px;
        }

        .id-card-photo {
            width: 150px;
            height: 180px;
            border: 2px solid var(--primary-color);
            border-radius: 10px;
            overflow: hidden;
            display: flex;
            align-items: center;
            justify-content: center;
            background-color: #f8f9fa;
        }

        .id-card-photo img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        .id-card-info {
            flex: 1;
        }

        .id-card-info h3 {
            color: var(--primary-color);
            margin-bottom: 15px;
            font-size: 1.2rem;
        }

        .id-card-info table {
            width: 100%;
            border-collapse: collapse;
        }

        .id-card-info td {
            padding: 5px 0;
            border-bottom: 1px solid #eee;
        }

        .id-card-info td:first-child {
            font-weight: bold;
            width: 40%;
        }

        .id-card-footer {
            background-color: #f8f9fa;
            padding: 10px 20px;
            text-align: center;
            font-size: 0.8rem;
            color: #666;
            border-top: 1px solid #eee;
        }

        .id-card-watermark {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%) rotate(-45deg);
            font-size: 5rem;
            color: rgba(0, 51, 102, 0.1);
            font-weight: bold;
            pointer-events: none;
            z-index: 1;
        }

        .id-card-actions {
            margin-top: 20px;
            text-align: center;
        }

        .id-card-qr {
            position: absolute;
            bottom: 20px;
            right: 20px;
            width: 60px;
            height: 60px;
        }

        .id-card-signature {
            position: absolute;
            bottom: 20px;
            left: 20px;
            width: 100px;
            height: 40px;
            border-bottom: 1px solid #999;
            text-align: center;
            font-size: 0.7rem;
            color: #666;
        }

        /* Application Form Print Styles */
        .application-form-container {
            max-width: 800px;
            margin: 0 auto;
            background-color: white;
            padding: 30px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }

        .application-form-header {
            text-align: center;
            margin-bottom: 30px;
            border-bottom: 2px solid #eee;
            padding-bottom: 20px;
        }

        .application-form-logo-area {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
        }

        .application-form-logo {
            width: 100px;
            height: 100px;
            object-fit: contain;
        }

        .application-form-title {
            font-size: 1.5rem;
            font-weight: bold;
            color: var(--primary-color);
            margin-bottom: 10px;
        }

        .application-form-subtitle {
            font-size: 1.1rem;
            color: #666;
        }

        .application-form-section {
            margin-bottom: 30px;
            padding: 15px;
            border: 1px solid #eee;
            border-radius: 5px;
        }

        .application-form-section h3 {
            color: var(--primary-color);
            margin-bottom: 15px;
            padding-bottom: 5px;
            border-bottom: 1px solid #eee;
        }

        .application-form-row {
            display: flex;
            margin-bottom: 10px;
        }

        .application-form-label {
            width: 200px;
            font-weight: bold;
        }

        .application-form-value {
            flex: 1;
        }

        .application-form-signature {
            margin-top: 20px;
            text-align: center;
        }

        .application-form-signature img {
            max-height: 100px;
            border: 1px solid #eee;
            border-radius: 5px;
            padding: 5px;
            background-color: #f9f9f9;
        }

        .application-form-actions {
            margin-top: 30px;
            text-align: center;
            padding-top: 20px;
            border-top: 1px solid #eee;
        }

        @media print {
            .application-form-container {
                box-shadow: none;
                border: none;
                padding: 0;
            }

            .application-form-actions {
                display: none;
            }
        }

        /* Floating chat-style notifications button (bottom-right) */
        .floating-chat-btn{
            position:fixed;
            right:20px;
            bottom:20px;
            z-index:9999;
            width:56px;
            height:56px;
            border-radius:50%;
            display:flex;
            align-items:center;
            justify-content:center;
            box-shadow:0 10px 25px rgba(0,0,0,0.18);
            text-decoration:none;
        }
        .floating-chat-btn .badge{
            position:absolute;
            top:-6px;
            right:-6px;
            border-radius:999px;
        }
        .notification-bell{ display:none !important; }

        /* Modern ID card (sample-like) */

/* ID Card Template (portrait, sample-inspired) */
.idcard-wrap{display:flex; gap:24px; flex-wrap:wrap; justify-content:center; align-items:flex-start;}
.idcard{
    width:2.125in; height:3.375in;
    border-radius:18px; overflow:hidden;
    background:#fff;
    box-shadow:0 10px 25px rgba(0,0,0,0.15);
    border:1px solid rgba(0,0,0,0.08);
    position:relative;
}
.idcard .waveTop{
    height:32%;
    background: linear-gradient(135deg, #0b5ed7, #0d6efd);
    position:relative;
}
.idcard .waveTop:after{
    content:'';
    position:absolute; left:-20%; bottom:-40%;
    width:140%; height:140%;
    background:#fff;
    border-top-left-radius:70% 55%;
    transform:rotate(-4deg);
}
.idcard .logos{
    position:absolute; top:10px; left:12px; right:12px;
    display:flex; align-items:center; justify-content:space-between;
    z-index:2;
}
.idcard .logos img{height:42px; width:auto; object-fit:contain; filter: drop-shadow(0 2px 2px rgba(0,0,0,.15));}
.idcard .title{
    position:absolute; top:64px; left:0; right:0;
    text-align:center; color:#fff; font-weight:800;
    letter-spacing:.6px; font-size:13px;
    z-index:2; text-transform:uppercase;
    text-shadow:0 2px 8px rgba(0,0,0,.25);
}
.idcard .photo{
    position:absolute; top:88px; left:50%;
    transform:translateX(-50%);
    width:92px; height:92px; border-radius:999px;
    background:#e9ecef;
    border:6px solid #fff;
    box-shadow:0 6px 18px rgba(0,0,0,.18);
    overflow:hidden;
    z-index:3;
}
.idcard .photo img{width:100%; height:100%; object-fit:cover;}
.idcard .body{
    position:absolute; left:0; right:0; top:34%;
    padding:72px 16px 12px 16px;
    text-align:center;
}
.idcard .name{font-weight:900; font-size:18px; margin:0; color:#0b2a4a;}
.idcard .role{margin:0; font-size:12px; color:#0d6efd; font-weight:700;}
.idcard .meta{
    margin-top:10px;
    display:grid;
    grid-template-columns: 1fr 1fr;
    gap:6px 10px;
    text-align:left;
    font-size:11px;
}
.idcard .meta .k{color:#546e7a; font-weight:700;}
.idcard .meta .v{color:#0b2a4a; font-weight:700; word-break:break-word;}

.idcard .studentSig{margin-top:10px; display:flex; flex-direction:column; align-items:center; gap:4px;}
.idcard .studentSig .label{font-size:10px; font-weight:900; color:#546e7a; text-transform:uppercase; letter-spacing:.4px;}
.idcard .studentSig img{height:22px; max-width:160px; width:auto; object-fit:contain;}
.idcard .studentSig .line{width:150px; height:18px; border-bottom:1px solid rgba(0,0,0,.35);}
.idcard .waveBottom{
    position:absolute; left:0; right:0; bottom:0;
    height:18%;
    background: linear-gradient(135deg, #0b5ed7, #0d6efd);
}
.idcard .waveBottom:before{
    content:'';
    position:absolute; left:-15%; top:-55%;
    width:130%; height:140%;
    background:#fff;
    border-bottom-right-radius:70% 55%;
    transform:rotate(-3deg);
}
.idcard-back .body{padding:18px 16px 70px 16px; top:0;}
.idcard-back .tcTitle{font-weight:900; letter-spacing:.5px; color:#0b2a4a; margin:0; font-size:14px;}
.idcard-back .tcLine{margin-top:6px; font-size:11px; color:#334e68; line-height:1.35;}
.idcard-back .dates{margin-top:12px; display:grid; grid-template-columns: 1fr 1fr; gap:8px 10px; font-size:12px;}
.idcard-back .dates .k{font-weight:900; color:#0b2a4a;}
.idcard-back .sigBlock{margin-top:12px; text-align:center;}
.idcard-back .sigBlock img{height:26px; width:auto; object-fit:contain;}
.idcard-back .sigLine{width:140px; height:22px; margin:0 auto; border-bottom:1px solid rgba(0,0,0,.35);}
.idcard-back .sigName{font-weight:900; color:#0b2a4a; font-size:12px; margin-top:6px;}
.idcard-back .sigRole{font-size:11px; color:#546e7a;}
.idcard-back .qr{
    position:absolute; left:0; right:0; bottom:34px;
    display:flex; justify-content:center;
    z-index:3;
}
.idcard-back .qr img{width:96px; height:96px; object-fit:contain; background:#fff; padding:6px; border-radius:10px; border:1px solid rgba(0,0,0,.08);}
.idcard-back .brandFooter{
    position:absolute; left:0; right:0; bottom:8px;
    text-align:center; color:#fff; font-weight:900; letter-spacing:.6px;
    font-size:12px; text-transform:uppercase; z-index:3;
    text-shadow:0 2px 8px rgba(0,0,0,.25);
}
@media print{
    .idcard-wrap{gap:16px;}
    .idcard{box-shadow:none;}
}</style>
<style>
body.page-student_dashboard.student-dashboard-theme-active{
    --primary-color: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['primary_color'] ?? $portalLegacyPrimary)); ?>;
    --accent-color: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['accent_color'] ?? $portalLegacyAccent)); ?>;
    --bg-color: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['background_color'] ?? $portalLegacyBackground)); ?>;
    --white: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['surface_color'] ?? $portalLegacySurface)); ?>;
    --text-color: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['text_color'] ?? $portalLegacyText)); ?>;
    --secondary-color: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['secondary_color'] ?? '#0B5ED7')); ?>;
    --portal-sidebar-bg: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['sidebar_color'] ?? $portalBrandSidebar)); ?>;
    --portal-sidebar-text: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['sidebar_text_color'] ?? $portalBrandSidebarText)); ?>;
    --portal-sidebar-inactive: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['sidebar_inactive_color'] ?? '#D9E2EC')); ?>;
    --portal-header-bg: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['header_color'] ?? $portalBrandHeader)); ?>;
    --portal-header-text: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['header_text_color'] ?? $portalBrandHeaderText)); ?>;
    --portal-button-bg: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['button_color'] ?? $portalBrandButton)); ?>;
    --portal-button-text: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['button_text_color'] ?? $portalBrandButtonText)); ?>;
    --portal-link-color: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['link_color'] ?? $portalBrandLink)); ?>;
    --portal-card-header-bg: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['card_header_color'] ?? $portalBrandCardHeader)); ?>;
    --portal-card-header-text: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['card_header_text_color'] ?? $portalBrandCardHeaderText)); ?>;
    --portal-badge-success: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['badge_success_color'] ?? $portalStudentDashboardRule['badge_color'] ?? $portalBrandBadgeSuccess)); ?>;
    --portal-badge-warning: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['badge_warning_color'] ?? $portalBrandBadgeWarning)); ?>;
    --portal-badge-danger: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['badge_danger_color'] ?? $portalBrandBadgeDanger)); ?>;
    --portal-card-bg: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['card_background_color'] ?? $portalBrandSurface)); ?>;
    --portal-card-border: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['card_border_color'] ?? '#D9E2EC')); ?>;
    --portal-card-title: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['card_title_color'] ?? $portalBrandPrimary)); ?>;
    --portal-heading: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['heading_color'] ?? $portalBrandPrimary)); ?>;
    --portal-muted: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['muted_text_color'] ?? '#64748B')); ?>;
    --portal-stat-bg: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['stat_card_background_color'] ?? ($portalStudentDashboardRule['primary_color'] ?? $portalBrandPrimary))); ?>;
    --portal-stat-number: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['stat_number_color'] ?? '#FFFFFF')); ?>;
    --portal-stat-label: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['stat_label_color'] ?? '#FFFFFF')); ?>;
    --portal-tab-active: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['tab_active_color'] ?? $portalBrandPrimary)); ?>;
    --portal-tab-inactive: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['tab_inactive_color'] ?? '#E8EEF7')); ?>;
    --portal-tab-text: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['tab_text_color'] ?? $portalBrandPrimary)); ?>;
    --portal-table-header-bg: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['table_header_bg_color'] ?? $portalBrandTableHeader)); ?>;
    --portal-table-header-text: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['table_header_text_color'] ?? $portalBrandTableHeaderText)); ?>;
    --portal-alert-info: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['alert_info_color'] ?? $portalBrandSecondary)); ?>;
    --portal-alert-info-text: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['alert_info_text_color'] ?? '#FFFFFF')); ?>;
    --portal-section-header-bg: <?php echo htmlspecialchars((string)($portalStudentDashboardRule['section_header_color'] ?? $portalBrandCardHeader)); ?>;
}
body.page-student_dashboard.student-dashboard-theme-active .sidebar-brand,
body.page-student_dashboard.student-dashboard-theme-active .sidebar-footer,
body.page-student_dashboard.student-dashboard-theme-active .portal-search-input,
body.page-student_dashboard.student-dashboard-theme-active .portal-search-icon,
body.page-student_dashboard.student-dashboard-theme-active .portal-search-input::placeholder{
    color: var(--portal-sidebar-text) !important;
}
body.page-student_dashboard.student-dashboard-theme-active .sidebar-menu a{
    color: var(--portal-sidebar-inactive) !important;
}
body.page-student_dashboard.student-dashboard-theme-active .sidebar-menu a:hover,
body.page-student_dashboard.student-dashboard-theme-active .sidebar-menu a.active{
    color: var(--portal-sidebar-text) !important;
}
.student-dashboard-page-wrap{
    display:flex;
    flex-direction:column;
    gap:18px;
}
.student-dashboard-scope-banner{
    border-radius:20px;
    padding:18px 20px;
    background: linear-gradient(135deg, var(--portal-header-bg), var(--secondary-color));
    color: var(--portal-header-text);
    box-shadow: 0 16px 34px rgba(15,23,42,.12);
}
.student-dashboard-scope-banner .scope-kicker{
    font-size:.74rem;
    text-transform:uppercase;
    letter-spacing:.08em;
    font-weight:800;
    opacity:.9;
}
.student-dashboard-scope-banner .scope-title{
    font-size:1.28rem;
    font-weight:800;
    line-height:1.2;
    margin-top:4px;
}
.student-dashboard-scope-meta{
    display:flex;
    flex-wrap:wrap;
    gap:10px;
    margin-top:12px;
}
.student-dashboard-scope-pill{
    border-radius:999px;
    padding:7px 12px;
    background: rgba(255,255,255,.14);
    border: 1px solid rgba(255,255,255,.16);
    color: inherit;
    font-size:.86rem;
    font-weight:700;
}
.student-dashboard-notice{
    margin-top:12px;
    border-radius:14px;
    padding:10px 12px;
    background: rgba(255,255,255,.12);
    border: 1px solid rgba(255,255,255,.18);
    font-size:.92rem;
}
.student-dashboard-theme-active .card.dashboard-card > .card-header,
.student-dashboard-theme-active .card > .card-header{
    background: var(--portal-section-header-bg);
    color: var(--portal-card-header-text);
}
.student-dashboard-theme-active .card,
.student-dashboard-theme-active .summary-card,
.student-dashboard-theme-active .dashboard-card{
    background: var(--portal-card-bg);
    border-color: var(--portal-card-border);
    color: var(--text-color);
}
.student-dashboard-theme-active .card .card-title,
.student-dashboard-theme-active .card h1,
.student-dashboard-theme-active .card h2,
.student-dashboard-theme-active .card h3,
.student-dashboard-theme-active .card h4,
.student-dashboard-theme-active .card h5,
.student-dashboard-theme-active .card h6,
.student-dashboard-theme-active .dashboard-card h1,
.student-dashboard-theme-active .dashboard-card h2,
.student-dashboard-theme-active .dashboard-card h3,
.student-dashboard-theme-active .dashboard-card h4,
.student-dashboard-theme-active .dashboard-card h5,
.student-dashboard-theme-active .dashboard-card h6{
    color: var(--portal-card-title);
}
.student-dashboard-theme-active .text-muted,
.student-dashboard-theme-active .small.text-muted{
    color: var(--portal-muted) !important;
}
.student-dashboard-theme-active h1,
.student-dashboard-theme-active h2,
.student-dashboard-theme-active h3,
.student-dashboard-theme-active h4,
.student-dashboard-theme-active h5,
.student-dashboard-theme-active h6{
    color: var(--portal-heading);
}
.student-dashboard-theme-active .btn-primary{
    background: var(--portal-button-bg);
    border-color: var(--portal-button-bg);
    color: var(--portal-button-text);
}
.student-dashboard-theme-active .btn-outline-primary{
    color: var(--portal-button-bg);
    border-color: var(--portal-button-bg);
}
.student-dashboard-theme-active a{
    color: var(--portal-link-color);
}
.student-dashboard-theme-active a.stats-card-link,
.student-dashboard-theme-active .student-dashboard-scope-banner a{
    color: inherit;
}
.student-dashboard-theme-active .stats-card{
    background: var(--portal-stat-bg);
    color: var(--portal-stat-number);
    box-shadow: 0 14px 30px <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['stat_card_background_color'] ?? $portalStudentDashboardRule['primary_color'] ?? '#003366'), .22) : 'rgba(0,0,0,.22)'); ?>;
}
.student-dashboard-theme-active .stats-card .stats-label,
.student-dashboard-theme-active .stats-card p,
.student-dashboard-theme-active .stats-card small,
.student-dashboard-theme-active .stats-card span.small{
    color: var(--portal-stat-label) !important;
}
.student-dashboard-theme-active .stats-card .dashboard-stat-value,
.student-dashboard-theme-active .stats-card .dashboard-stat-value-sm,
.student-dashboard-theme-active .stats-card .dashboard-stat-value-xs,
.student-dashboard-theme-active .stats-card h1,
.student-dashboard-theme-active .stats-card h2,
.student-dashboard-theme-active .stats-card h3{
    color: var(--portal-stat-number) !important;
}
.student-dashboard-theme-active .student-dashboard-scope-banner,
body.page-student_dashboard .student-dashboard-scope-banner{
    display:none !important;
}

.student-dashboard-theme-active .student-dashboard-hero{
    border-color: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['accent_color'] ?? '#F0A500'), .18) : 'rgba(240,165,0,.18)'); ?>;
    background: linear-gradient(135deg, <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['accent_color'] ?? '#F0A500'), .08) : 'rgba(240,165,0,.08)'); ?>, rgba(255,255,255,.98));
}
.student-dashboard-theme-active .student-dashboard-minihero-card{
    border-color: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['primary_color'] ?? '#003366'), .14) : 'rgba(0,51,102,.14)'); ?>;
}
.student-dashboard-theme-active .student-dashboard-minihero-label{
    color:#667085;
}
.student-dashboard-theme-active .student-dashboard-minihero-value{
    color: var(--text-color);
}
.student-dashboard-theme-active.dashboard-layout-compact .student-dashboard-scope-banner,
.student-dashboard-theme-active.dashboard-layout-compact .student-dashboard-hero{
    padding:14px 16px;
    border-radius:18px;
}
.student-dashboard-theme-active.dashboard-layout-compact .dashboard-stats-grid{
    gap:12px;
}
.student-dashboard-theme-active.dashboard-layout-compact .stats-card{
    min-height:90px;
    padding:14px;
}
.student-dashboard-theme-active.dashboard-layout-card-heavy .dashboard-stats-grid{
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap:16px;
}
.student-dashboard-theme-active.dashboard-layout-card-heavy .stats-card{
    border-radius:16px;
    min-height:118px;
}
.student-dashboard-theme-active.dashboard-layout-academic-summary .student-dashboard-scope-banner{
    background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
}
.student-dashboard-theme-active.dashboard-layout-academic-summary .dashboard-summary-panel{
    border-color: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['primary_color'] ?? '#003366'), .18) : 'rgba(0,51,102,.18)'); ?>;
    background: <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['primary_color'] ?? '#003366'), .04) : 'rgba(0,51,102,.04)'); ?>;
}
.student-dashboard-theme-active.dashboard-layout-elearning-focused .dashboard-services-card{
    border:1px solid <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['accent_color'] ?? '#F0A500'), .18) : 'rgba(240,165,0,.18)'); ?>;
    box-shadow: 0 16px 28px <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['accent_color'] ?? '#F0A500'), .10) : 'rgba(240,165,0,.10)'); ?>;
}
.student-dashboard-theme-active.dashboard-layout-finance-focused .dashboard-payments-card{
    border:1px solid <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['button_color'] ?? '#003366'), .18) : 'rgba(0,51,102,.18)'); ?>;
    box-shadow: 0 16px 28px <?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['button_color'] ?? '#003366'), .10) : 'rgba(0,51,102,.10)'); ?>;
}
.student-dashboard-theme-active.dashboard-layout-simple .student-dashboard-scope-banner{
    padding:14px 16px;
    box-shadow:none;
}
.student-dashboard-theme-active.dashboard-layout-simple .student-dashboard-hero{
    box-shadow:none;
    border-radius:16px;
}
.student-dashboard-theme-active.dashboard-layout-simple .stats-card{
    border-radius:12px;
    box-shadow:none;
}
.student-dashboard-theme-active.dashboard-layout-banner-cards .student-dashboard-scope-banner{
    padding:22px 24px;
}
.student-dashboard-theme-active.dashboard-layout-banner-cards .dashboard-stats-grid{
    margin-top:2px;
}
.student-dashboard-theme-active .nav-tabs .nav-link{background:var(--portal-tab-inactive);color:var(--portal-tab-text);border:none;}
.student-dashboard-theme-active .nav-tabs .nav-link.active,
.student-dashboard-theme-active .nav-tabs .nav-item.show .nav-link{background:var(--portal-tab-active);color:#fff;}
.student-dashboard-theme-active .table thead th{background:var(--portal-table-header-bg);color:var(--portal-table-header-text);}
.student-dashboard-theme-active .badge.bg-success,
.student-dashboard-theme-active .text-bg-success{background:var(--portal-badge-success) !important;color:<?php echo htmlspecialchars(function_exists('portalContrastColor') ? portalContrastColor((string)($portalStudentDashboardRule['badge_success_color'] ?? $portalBrandBadgeSuccess)) : '#FFFFFF'); ?> !important;}
.student-dashboard-theme-active .badge.bg-warning,
.student-dashboard-theme-active .text-bg-warning{background:var(--portal-badge-warning) !important;color:<?php echo htmlspecialchars(function_exists('portalContrastColor') ? portalContrastColor((string)($portalStudentDashboardRule['badge_warning_color'] ?? $portalBrandBadgeWarning)) : '#FFFFFF'); ?> !important;}
.student-dashboard-theme-active .badge.bg-danger,
.student-dashboard-theme-active .text-bg-danger{background:var(--portal-badge-danger) !important;color:<?php echo htmlspecialchars(function_exists('portalContrastColor') ? portalContrastColor((string)($portalStudentDashboardRule['badge_danger_color'] ?? $portalBrandBadgeDanger)) : '#FFFFFF'); ?> !important;}
.student-dashboard-theme-active .alert-info{background:<?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['alert_info_color'] ?? $portalBrandSecondary), .12) : 'rgba(11,94,215,.12)'); ?>;border-color:<?php echo htmlspecialchars(function_exists('portalHexToRgba') ? portalHexToRgba((string)($portalStudentDashboardRule['alert_info_color'] ?? $portalBrandSecondary), .28) : 'rgba(11,94,215,.28)'); ?>;color:var(--text-color);}
.student-dashboard-theme-active .alert-info .btn-warning{background:var(--portal-badge-warning);border-color:var(--portal-badge-warning);}
.student-dashboard-theme-active .alert-info .btn-outline-warning{color:var(--portal-badge-warning);border-color:var(--portal-badge-warning);}
@media (max-width: 1599.98px){
    .student-dashboard-theme-active.dashboard-layout-card-heavy .dashboard-stats-grid{
        grid-template-columns: repeat(4, minmax(0,1fr));
    }
}
@media (max-width: 991.98px){
    .student-dashboard-scope-banner{
        padding:16px;
    }
    .student-dashboard-theme-active.dashboard-layout-card-heavy .dashboard-stats-grid{
        grid-template-columns: repeat(2, minmax(0,1fr));
    }
}
@media (max-width: 575.98px){
    .student-dashboard-scope-meta{
        gap:8px;
    }
    .student-dashboard-scope-pill{
        width:100%;
        text-align:center;
    }
    .student-dashboard-theme-active.dashboard-layout-card-heavy .dashboard-stats-grid{
        grid-template-columns: minmax(0,1fr);
    }
}
</style>
</head>
<body class="page-<?php echo htmlspecialchars($page ?? ''); ?> theme-<?php echo htmlspecialchars($portalThemeCurrentMode); ?> <?php echo htmlspecialchars(((string)($_SESSION['user_type'] ?? '') === 'admin') ? 'portal-admin' : (((string)($_SESSION['user_type'] ?? '') === 'student') ? 'portal-student' : 'portal-auth')); ?> <?php echo htmlspecialchars($portalStudentDashboardBodyClasses); ?>" data-theme-mode="<?php echo htmlspecialchars($portalThemeCurrentMode); ?>">
    <!-- Mobile Toggle Button -->
    <button class="navbar-toggle" onclick="toggleSidebar()">
        <i class="bi bi-list"></i>
    </button>

    <?php if (isset($_SESSION['user_type']) && in_array($_SESSION['user_type'], ['admin','student'], true)): ?>
        <?php
            $role = currentUserRole();
            $uid  = currentUserId();
            $unreadNotifications = getUnreadNotificationCount($pdo, $role, $uid);
                    $unreadMessages = getUnreadMessageCount($pdo, $role, $uid);
?>
        <a href="?page=notifications" class="notification-bell" title="Notifications">
            <i class="bi bi-bell"></i>
            <?php if ($unreadNotifications > 0): ?>
                <span class="notification-count"><?php echo $unreadNotifications; ?></span>
            <?php endif; ?>
        </a>
    <?php endif; ?>

    <?php if (!empty($portalThemeFeatureEnabled) && isset($_SESSION['user_type']) && in_array($_SESSION['user_type'], ['admin','student'], true)): ?>
        <div class="theme-switcher" id="portalThemeSwitcher" aria-label="Portal theme switcher">
            <button type="button" class="theme-toggle-btn <?php echo $portalThemeCurrentMode === 'light' ? 'active' : ''; ?>" data-theme-mode="light" title="Light Mode" aria-label="Switch to Light Mode" aria-pressed="<?php echo $portalThemeCurrentMode === 'light' ? 'true' : 'false'; ?>">
                <i class="bi bi-sun-fill"></i>
            </button>
            <button type="button" class="theme-toggle-btn <?php echo $portalThemeCurrentMode === 'dark' ? 'active' : ''; ?>" data-theme-mode="dark" title="Dark Mode" aria-label="Switch to Dark Mode" aria-pressed="<?php echo $portalThemeCurrentMode === 'dark' ? 'true' : 'false'; ?>">
                <i class="bi bi-moon-stars-fill"></i>
            </button>
        </div>
    <?php endif; ?>

    <!-- Sidebar -->
    <nav class="sidebar" id="sidebar">
        <div class="sidebar-header">
            <div class="logo-area">
                <?php if ($leftLogoPath): ?>
                <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">


                    </li>

                <?php else: ?>
                <div class="logo-placeholder" style="background:white; color:var(--primary-color);">HONTTECH</div>
                <?php endif; ?>
                <?php if ($rightLogoPath): ?>
                <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                <?php else: ?>
                <div class="logo-placeholder" style="background:white; color:var(--primary-color);">DAHIFT</div>
                <?php endif; ?>
            </div>
            <a href="?page=home" class="sidebar-brand">
                <i class="bi bi-mortarboard-fill"></i> <?php echo htmlspecialchars(setting('portal_title','HontTech System')); ?>
            </a>
        </div>

        <?php portalRenderSidebarSearch($pdo); ?>

        <ul class="sidebar-menu">
            <?php if (isLoggedIn()): ?>
                <?php if (isAdmin()): ?>
                    <li>
                        <a href="?page=admin_dashboard" class="<?php echo $page === 'admin_dashboard' ? 'active' : ''; ?>">
                            <i class="bi bi-speedometer2"></i> Dashboard
                        </a>
                    </li>
                    <?php if (!empty($_SESSION['is_impersonating']) && !empty($_SESSION['superadmin_impersonation_backup'])): ?>
                    <li>
                        <a href="?return_superadmin=1">
                            <i class="bi bi-arrow-counterclockwise"></i> Return to Superadmin
                        </a>
                    </li>
                    <?php endif; ?>
                    <li>
                        <a href="?page=admin_applications" class="<?php echo $page === 'admin_applications' ? 'active' : ''; ?>">
                            <i class="bi bi-file-earmark-text"></i> Applications
                        </a>
                    </li>
                    <li>
                        <a href="?page=admin_add_application" class="<?php echo $page === 'admin_add_application' ? 'active' : ''; ?>">
                            <i class="bi bi-plus-circle"></i> Add Application
                        </a>
                    </li>
                    <li>
                        <a href="?page=admin_students" class="<?php echo $page === 'admin_students' ? 'active' : ''; ?>">
                            <i class="bi bi-people"></i> Students
                        </a>
                    </li>

<li class="mt-2">
    <div style="padding:8px 18px; font-size:12px; opacity:.75; text-transform:uppercase; letter-spacing:.08em;">
        Academics
    </div>
</li>
<li>
    <a href="?page=admin_structure_manager" class="<?php echo $page === 'admin_structure_manager' ? 'active' : ''; ?>">
        <i class="bi bi-diagram-3"></i> Faculty/Dept/Programme
    </a>
</li>
<li>
    <a href="?page=admin_sessions_manager" class="<?php echo $page === 'admin_sessions_manager' ? 'active' : ''; ?>">
        <i class="bi bi-calendar2-week"></i> Academic Session/Promotion Manager
    </a>
</li>
<li>
    <a href="?page=admin_courses_manager" class="<?php echo $page === 'admin_courses_manager' ? 'active' : ''; ?>">
        <i class="bi bi-journal-bookmark"></i> Registrable Courses
    </a>
</li>
<?php if (menuEnabled($pdo, 'menu_numbering_settings')): ?>
<li>
    <a href="?page=admin_numbering_settings" class="<?php echo $page === 'admin_numbering_settings' ? 'active' : ''; ?>">
        <i class="bi bi-hash"></i> Admission / Matric Numbering
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo, 'menu_numbering_settings')): ?>
<li>
    <a href="?page=admin_matric_verifier" class="<?php echo $page === 'admin_matric_verifier' ? 'active' : ''; ?>">
        <i class="bi bi-patch-check"></i> Change Department/Matric No.
    </a>
</li>
<?php endif; ?>

<li>
    <a href="?page=admin_registrations_manager" class="<?php echo $page === 'admin_registrations_manager' ? 'active' : ''; ?>">
        <i class="bi bi-card-checklist"></i> Registered Students
    </a>
</li>

<li>
    <a href="?page=admin_yet_to_register" class="<?php echo $page === 'admin_yet_to_register' ? 'active' : ''; ?>">
        <i class="bi bi-person-x"></i> Yet To Register Students
    </a>
</li>
<li>
    <a href="?page=admin_registration_settings" class="<?php echo $page === 'admin_registration_settings' ? 'active' : ''; ?>">
        <i class="bi bi-sliders"></i> Registration Settings
    </a>
</li>
<li>
    <a href="?page=admin_fees_manager" class="<?php echo ((in_array($page, ['admin_fees_manager','admin_school_fee_settings'], true)) && (trim((string)($_GET['fees_tab'] ?? 'school_fees')) !== 'payment_gateways')) ? 'active' : ''; ?>">
        <i class="bi bi-cash-coin"></i> Fees Manager
    </a>
</li>
<li>
    <a href="?page=admin_fees_manager&fees_tab=payment_gateways" class="<?php echo ($page === 'admin_fees_manager' && trim((string)($_GET['fees_tab'] ?? '')) === 'payment_gateways') ? 'active' : ''; ?>">
        <i class="bi bi-credit-card-2-front"></i> Payment Gateway
    </a>
</li>


<?php if (isAdmin() && menuEnabled($pdo, 'menu_student_attendance_admin') && hasStaffPermission($pdo, 'attendance_manage')): ?>
<li>
    <a href="?page=admin_student_attendance" class="<?php echo $page === 'admin_student_attendance' ? 'active' : ''; ?>">
        <i class="bi bi-calendar-check"></i> Student Attendance
    </a>
</li>
<?php endif; ?>

<?php if (isAdmin() && menuEnabled($pdo, 'menu_previous_admission_record')): ?>
<li>
    <a href="?page=admin_previous_admission_record" class="<?php echo $page === 'admin_previous_admission_record' ? 'active' : ''; ?>">
        <i class="bi bi-clock-history"></i> Previous Admission Record
    </a>
</li>
<?php endif; ?>
<?php if (isAdmin() && menuEnabled($pdo, 'menu_admin_clearance') && portalAdminCanManageClearance($pdo)): ?>
<li>
    <a href="?page=admin_clearance" class="<?php echo $page === 'admin_clearance' ? 'active' : ''; ?>">
        <i class="bi bi-patch-check"></i> Admin Clearance
    </a>
</li>
<?php endif; ?>
<?php if (isAdmin() && menuEnabled($pdo, 'menu_graduated')): ?>
<li>
    <a href="?page=admin_graduated" class="<?php echo $page === 'admin_graduated' ? 'active' : ''; ?>">
        <i class="bi bi-mortarboard-fill"></i> Graduated
    </a>
</li>
<?php endif; ?>

<?php if (isAdmin() && portalCanManageAcademicResultPages($pdo) && (menuEnabled($pdo, 'menu_grading_result_settings') || menuEnabled($pdo, 'menu_results_manager') || menuEnabled($pdo, 'menu_download_scoresheet') || menuEnabled($pdo, 'menu_results_approvals') || menuEnabled($pdo, 'menu_transcript_admin'))): ?>
<li class="mt-2">
    <div style="padding:8px 18px; font-size:12px; opacity:.75; text-transform:uppercase; letter-spacing:.08em;">
        Results
    </div>
</li>
<?php if (menuEnabled($pdo, 'menu_grading_result_settings')): ?>
<li>
    <a href="?page=grading_result_settings" class="<?php echo $page === 'grading_result_settings' ? 'active' : ''; ?>">
        <i class="bi bi-sliders2-vertical"></i> Grading &amp; Result Settings
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo, 'menu_results_manager')): ?>
<li>
    <a href="?page=results_manager" class="<?php echo $page === 'results_manager' ? 'active' : ''; ?>">
        <i class="bi bi-calculator"></i> Manage Results
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo, 'menu_download_scoresheet')): ?>
<li>
    <a href="?page=download_scoresheet" class="<?php echo $page === 'download_scoresheet' ? 'active' : ''; ?>">
        <i class="bi bi-download"></i> Download Scoresheet
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo, 'menu_results_approvals')): ?>
<li>
    <a href="?page=results_approvals" class="<?php echo $page === 'results_approvals' ? 'active' : ''; ?>">
        <i class="bi bi-shield-check"></i> Result Approvals
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo, 'menu_transcript_admin')): ?>
<li>
    <a href="?page=admin_transcript" class="<?php echo $page === 'admin_transcript' ? 'active' : ''; ?>">
        <i class="bi bi-file-earmark-text"></i> Transcript
    </a>
</li>
<?php endif; ?>
<?php endif; ?>

<?php if (isAdmin() && (menuEnabled($pdo, 'menu_staff_users') || menuEnabled($pdo, 'menu_staff_roles'))): ?>
<li class="mt-2">
    <div style="padding:8px 18px; font-size:12px; opacity:.75; text-transform:uppercase; letter-spacing:.08em;">
        Staff / Roles
    </div>
</li>
<?php if (menuEnabled($pdo, 'menu_staff_users')): ?>
<li>
    <a href="?page=staff_users" class="<?php echo $page === 'staff_users' ? 'active' : ''; ?>">
        <i class="bi bi-person-badge"></i> Staff Users
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo, 'menu_staff_roles')): ?>
<li>
    <a href="?page=staff_roles" class="<?php echo $page === 'staff_roles' ? 'active' : ''; ?>">
        <i class="bi bi-sliders2"></i> Roles & Permissions
    </a>
</li>
<?php endif; ?>
<?php endif; ?>



<?php if (isAdmin() && menuEnabled($pdo,'menu_hrm') && hasStaffPermission($pdo, 'hrm_manage')): ?>
<li class="mt-2">
    <div style="padding:8px 18px; font-size:12px; opacity:.75; text-transform:uppercase; letter-spacing:.08em;">
        HRM
    </div>
</li>
<li>
    <a href="?page=hrm_dashboard" class="<?php echo $page === 'hrm_dashboard' ? 'active' : ''; ?>">
        <i class="bi bi-people"></i> HRM Dashboard
    </a>
</li>
<?php foreach (hrmMenuPages() as $slug => $label): if ($slug === 'hrm_dashboard' || !hrmSidebarPageVisible($slug)) continue; ?>
<li>
    <a href="?page=<?php echo e($slug); ?>" class="<?php echo $page === $slug ? 'active' : ''; ?>">
        <i class="bi bi-dot"></i> <?php echo e($label); ?>
    </a>
</li>
<?php endforeach; ?>
<?php endif; ?>

<?php if (isAdmin() && (menuEnabled($pdo,'menu_lms_admin') || menuEnabled($pdo,'menu_library_admin') || menuEnabled($pdo,'menu_cbt_admin'))): ?>
<li class="mt-2">
    <div style="padding:8px 18px; font-size:12px; opacity:.75; text-transform:uppercase; letter-spacing:.08em;">
        Modules
    </div>
</li>
<?php if (menuEnabled($pdo,'menu_lms_admin')): ?>
<li>
    <a href="?page=lms_admin" class="<?php echo $page === 'lms_admin' ? 'active' : ''; ?>">
        <i class="bi bi-mortarboard"></i> LMS
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo,'menu_library_admin')): ?>
<li>
    <a href="?page=library_admin" class="<?php echo $page === 'library_admin' ? 'active' : ''; ?>">
        <i class="bi bi-book"></i> Library
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo,'menu_cbt_admin')): ?>
<li>
    <a href="?page=cbt_admin" class="<?php echo $page === 'cbt_admin' ? 'active' : ''; ?>">
        <i class="bi bi-ui-checks-grid"></i> CBT
    </a>
</li>
<?php endif; ?>
<?php endif; ?>

<?php if (isAdmin() && (menuEnabled($pdo,'menu_notification_settings') || menuEnabled($pdo,'menu_feature_toggles') || menuEnabled($pdo,'menu_pages'))): ?>
<li class="mt-2">
    <div style="padding:8px 18px; font-size:12px; opacity:.75; text-transform:uppercase; letter-spacing:.08em;">
        Messaging & Infrastructure
    </div>
</li>
<?php if (menuEnabled($pdo,'menu_notification_settings')): ?>
<li>
    <a href="?page=notification_settings" class="<?php echo $page === 'notification_settings' ? 'active' : ''; ?>">
        <i class="bi bi-broadcast"></i> Email/SMS Settings
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo,'menu_feature_toggles')): ?>
<li>
    <a href="?page=feature_toggles" class="<?php echo $page === 'feature_toggles' ? 'active' : ''; ?>">
        <i class="bi bi-toggle-on"></i> Feature Toggles
    </a>
</li>
<?php endif; ?>
<?php if (menuEnabled($pdo,'menu_pages')): ?>
<li>
    <a href="?page=admin_pages" class="<?php echo $page === 'admin_pages' ? 'active' : ''; ?>">
        <i class="bi bi-file-earmark-richtext"></i> Pages
    </a>
</li>
<?php endif; ?>

<?php endif; ?>


                    <li>
                        <a href="?page=messages" class="<?php echo $page === 'messages' ? 'active' : ''; ?>">
                            <i class="bi bi-envelope"></i> Messages
                            <?php if (!empty($unreadMessages)): ?>
                                <span class="badge bg-danger ms-2"><?php echo (int)$unreadMessages; ?></span>
                            <?php endif; ?>
                        </a>
                    </li>
                    <li>
                        <a href="?page=notifications" class="<?php echo $page === 'notifications' ? 'active' : ''; ?>">
                            <i class="bi bi-bell"></i> Notifications
                            <?php if (!empty($unreadNotifications)): ?>
                                <span class="badge bg-danger ms-2"><?php echo (int)$unreadNotifications; ?></span>
                            <?php endif; ?>
                        </a>
                    </li>
                    <li>
                        <a href="?page=admin_payments" class="<?php echo $page === 'admin_payments' ? 'active' : ''; ?>">
                            <i class="bi bi-credit-card"></i> Payments
                        </a>
                    </li>
                    <li>
                        <a href="?page=admin_wallets" class="<?php echo $page === 'admin_wallets' ? 'active' : ''; ?>">
                            <i class="bi bi-wallet2"></i> Wallets
                        </a>
                    </li>
                    <li>
                        <a href="?page=admin_settings" class="<?php echo $page === 'admin_settings' ? 'active' : ''; ?>">
                            <i class="bi bi-gear"></i> Settings
                        </a>
                    </li>
                    <li>
                        <a href="?page=theme_branding" class="<?php echo $page === 'theme_branding' ? 'active' : ''; ?>">
                            <i class="bi bi-palette"></i> Theme/Branding
                        </a>
                    </li>
                <?php else: ?>
                    <?php if (studentMenuEnabled($pdo, 'menu_student_dashboard')): ?>
                    <li>
                        <a href="?page=student_dashboard" class="<?php echo $page === 'student_dashboard' ? 'active' : ''; ?>">
                            <i class="bi bi-speedometer2"></i> Dashboard
                        </a>
                    </li>
                    <?php endif; ?>
                    <?php if (isset($_SESSION['impersonator_admin_id'])): ?>
                    <li>
                        <a href="?return_superadmin=1" class="">
                            <i class="bi bi-arrow-left-circle"></i> Return to Superadmin
                        </a>
                    </li>
                    <?php endif; ?>
                    <?php
                    // Check if registration fee is paid
                    $stmt = $pdo->prepare("SELECT registration_fee_status FROM students WHERE id = ?");
                    $stmt->execute([$_SESSION['user_id']]);
                    $regFeeStatus = $stmt->fetchColumn();

                    if ($regFeeStatus === 'Paid'):
                    ?><?php if (studentMenuEnabled($pdo, 'menu_student_application_form') && portalManagedContentEnabled($pdo, 'application_form')): ?>

                        <li>
                            <a href="?page=application_form" class="<?php echo $page === 'application_form' ? 'active' : ''; ?>">
                                <i class="bi bi-file-earmark-text"></i> Application Form
                            </a>
                        </li>
<?php endif; ?><?php if (studentMenuEnabled($pdo, 'menu_student_course_registration')): ?>

                        <li>
                            <a href="?page=student_course_registration" class="<?php echo $page === 'student_course_registration' ? 'active' : ''; ?>">
                                <i class="bi bi-clipboard-check"></i> Course Registration
                            </a>
                        </li>
<?php endif; ?>
                    <?php endif; ?>

                    <?php
                    // Check if application is approved
                    $stmt = $pdo->prepare("SELECT application_status, acceptance_fee_status, id_card_generated, offer_status, registration_fee_status, hostel_fee_status, school_fee_status, accommodation_status, hostel_fee_paid_session, school_fee_paid_session FROM students WHERE id = ?");
                    $stmt->execute([$_SESSION['user_id']]);
                    $appStatus = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
                    try { portalSyncStudentFeeVisibilityState($pdo, (int)($_SESSION['user_id'] ?? 0)); } catch (Throwable $e) {}
                    if (!empty($appStatus)) {
                        if (($appStatus['hostel_fee_paid_session'] ?? '') === '' && (($appStatus['hostel_fee_status'] ?? '') === 'Paid')) $appStatus['hostel_fee_paid_session'] = portalCurrentAcademicSessionName($pdo);
                        if (($appStatus['school_fee_paid_session'] ?? '') === '' && (($appStatus['school_fee_status'] ?? '') === 'Paid')) $appStatus['school_fee_paid_session'] = portalCurrentAcademicSessionName($pdo);
                    }
                    $showRegistrationFeeMenu = portalStudentCanShowFeeMenu($pdo, $appStatus, 'registration');
                    $showAcceptanceFeeMenu = portalStudentCanShowFeeMenu($pdo, $appStatus, 'acceptance');
                    $showHostelFeeMenu = portalStudentCanShowFeeMenu($pdo, $appStatus, 'hostel');
                    $showSchoolFeeMenu = portalStudentCanShowFeeMenu($pdo, $appStatus, 'school');

                    // Acceptance Fee shortcut is provided below under Fee shortcuts (Payment tabs)

                    // Check if acceptance fee is paid
                    if ($appStatus['application_status'] === 'Approved' && ($appStatus['acceptance_fee_status'] === 'Paid' || ($appStatus['offer_status'] ?? 'Pending') === 'Accepted')):
                    ?><?php if (studentMenuEnabled($pdo, 'menu_student_admission_letter') && portalManagedContentEnabled($pdo, 'admission_letter')): ?>

                        <li>
                            <a href="?page=admission_letter" class="<?php echo $page === 'admission_letter' ? 'active' : ''; ?>">
                                <i class="bi bi-file-text"></i> Admission Letter
                            </a>
                        </li>
<?php endif; ?>
                    <?php endif; ?>

                    <?php
                    // Check if ID card is generated
                    if ($appStatus['id_card_generated'] && studentMenuEnabled($pdo, 'menu_student_id_card') && portalManagedContentEnabled($pdo, 'student_id_card')):
                    ?>
                        <li>
                            <a href="?page=student_id_card" class="<?php echo $page === 'student_id_card' ? 'active' : ''; ?>">
                                <i class="bi bi-card-text"></i> Student ID Card
                            </a>
                        </li>
                    <?php endif; ?>

                    <?php if (studentMenuEnabled($pdo, 'menu_student_payments')): ?>
                    <li>
                        <a href="?page=payment" class="<?php echo $page === 'payment' ? 'active' : ''; ?>">
                            <i class="bi bi-credit-card"></i> Payment
                        </a>
                    </li>
                    <?php endif; ?>

                    <?php if (getSystemSettingValue($pdo,'wallet_enabled','enabled') === 'enabled' && studentMenuEnabled($pdo, 'menu_student_wallet')): ?>
                    <li>
                        <a href="?page=wallet" class="<?php echo $page === 'wallet' ? 'active' : ''; ?>">
                            <i class="bi bi-wallet2"></i> Wallet
                        </a>
                    </li>
                    <?php endif; ?>

                    <!-- Fees shortcuts (these open the Payment page tabs where Wallet + Manual transfer are available) -->
                    <?php if (studentMenuEnabled($pdo, 'menu_student_registration_fee') && !empty($showRegistrationFeeMenu)): ?>
                    <li>
                        <a href="?page=payment&tab=registration" class="<?php echo ($page === 'payment' && ($_GET['tab'] ?? 'registration') === 'registration') ? 'active' : ''; ?>">
                            <i class="bi bi-receipt"></i> Registration Fee
                        </a>
                    </li>
                    <?php endif; ?>
                    <?php if (studentMenuEnabled($pdo, 'menu_student_acceptance_fee') && !empty($showAcceptanceFeeMenu)): ?>
                    <li>
                        <a href="?page=payment&tab=acceptance" class="<?php echo ($page === 'payment' && ($_GET['tab'] ?? '') === 'acceptance') ? 'active' : ''; ?>">
                            <i class="bi bi-patch-check"></i> Acceptance Fee
                        </a>
                    </li>
                    <?php endif; ?>
                    <?php if (studentMenuEnabled($pdo, 'menu_student_hostel_fee') && !empty($showHostelFeeMenu)): ?>
                    <li>
                        <a href="?page=payment&tab=hostel" class="<?php echo ($page === 'payment' && ($_GET['tab'] ?? '') === 'hostel') ? 'active' : ''; ?>">
                            <i class="bi bi-house"></i> Hostel Fee
                        </a>
                    </li>
                    <?php endif; ?>
                    <?php if (studentMenuEnabled($pdo, 'menu_student_school_fee') && !empty($showSchoolFeeMenu)): ?>
                    <li>
                        <a href="?page=payment&tab=school" class="<?php echo ($page === 'payment' && ($_GET['tab'] ?? '') === 'school') ? 'active' : ''; ?>">
                            <i class="bi bi-book"></i> School Fee
                        </a>
                    </li>
                    <?php endif; ?>

                    <?php if (studentMenuEnabled($pdo, 'menu_student_payment_history')): ?>
                    <li>
                        <a href="?page=payment_history" class="<?php echo $page === 'payment_history' ? 'active' : ''; ?>">
                            <i class="bi bi-clock-history"></i> Payment History
                        </a>
                    </li>
                    <?php endif; ?>
                    <?php if (studentMenuEnabled($pdo, 'menu_student_clearance_form')): ?>
                    <li>
                        <a href="?page=clearance_form" class="<?php echo $page === 'clearance_form' ? 'active' : ''; ?>">
                            <i class="bi bi-patch-check"></i> Clearance Form
                        </a>
                    </li>
                    <?php endif; ?>

                    <?php if (studentMenuEnabled($pdo, 'menu_student_results')): ?>
                    <li>
                        <a href="?page=student_results" class="<?php echo $page === 'student_results' ? 'active' : ''; ?>">
                            <i class="bi bi-bar-chart-line"></i> Results / CGPA
                        </a>
                    </li>
                    <?php endif; ?>

                    <?php if (studentMenuEnabled($pdo, 'menu_student_attendance')): ?>
                    <li>
                        <a href="?page=student_attendance" class="<?php echo $page === 'student_attendance' ? 'active' : ''; ?>">
                            <i class="bi bi-calendar-check"></i> Attendance
                        </a>
                    </li>
                    <?php endif; ?>

                    <?php if (studentMenuEnabled($pdo, 'menu_student_lms')): ?>
                    <li>
                        <a href="?page=student_lms" class="<?php echo $page === 'student_lms' ? 'active' : ''; ?>">
                            <i class="bi bi-mortarboard"></i> LMS
                        </a>
                    </li>
                    <?php endif; ?>

                    <?php if (studentMenuEnabled($pdo, 'menu_student_library')): ?>
                    <li>
                        <a href="?page=student_library" class="<?php echo $page === 'student_library' ? 'active' : ''; ?>">
                            <i class="bi bi-book"></i> Library
                        </a>
                    </li>
                    <?php endif; ?>

                    <?php if (studentMenuEnabled($pdo, 'menu_student_cbt')): ?>
                    <li>
                        <a href="?page=student_cbt" class="<?php echo $page === 'student_cbt' ? 'active' : ''; ?>">
                            <i class="bi bi-ui-checks-grid"></i> CBT
                        </a>
                    </li>
                    <?php endif; ?>

                    <?php if (studentMenuEnabled($pdo, 'menu_student_messages')): ?>
                    <li>
                        <a href="?page=messages" class="<?php echo $page === 'messages' || $page === 'view_message' ? 'active' : ''; ?>">
                            <i class="bi bi-chat-dots"></i> Messages
                            <?php if (!empty($unreadMessages)): ?>
                                <span class="badge bg-danger ms-2"><?php echo (int)$unreadMessages; ?></span>
                            <?php endif; ?>
                        </a>
                    </li>
                    <?php endif; ?>

                    <?php if (studentMenuEnabled($pdo, 'menu_student_notifications')): ?>
                    <li>
                        <a href="?page=notifications" class="<?php echo $page === 'notifications' ? 'active' : ''; ?>">
                            <i class="bi bi-bell"></i> Notifications
                            <?php if (!empty($unreadNotifications)): ?>
                                <span class="badge bg-danger ms-2"><?php echo (int)$unreadNotifications; ?></span>
                            <?php endif; ?>
                        </a>
                    </li>
                    <?php endif; ?>

                <?php endif; ?>
                <li><hr style="border-color: rgba(255,255,255,0.1);"></li><?php if (studentMenuEnabled($pdo, 'menu_student_profile')): ?>

                <li>
                    <a href="?page=profile">
                        <i class="bi bi-person-circle"></i> <?php echo $_SESSION['full_name']; ?>
                    </a>
                </li>
<?php endif; ?>
                <li>
                    <a href="?action=logout">
                        <i class="bi bi-box-arrow-right"></i> Logout
                    </a>
                </li>
            <?php else: ?>
                <?php if (portalManagedContentEnabled($pdo, 'home')): ?>
                <li>
                    <a href="?page=home" class="<?php echo $page === 'home' ? 'active' : ''; ?>">
                        <i class="bi bi-house-door"></i> Home
                    </a>
                </li>
                <?php endif; ?>
                <?php if (portalManagedContentEnabled($pdo, 'about')): ?>
                <li>
                    <a href="?page=about" class="<?php echo $page === 'about' ? 'active' : ''; ?>">
                        <i class="bi bi-info-circle"></i> About
                    </a>
                </li>
                <?php endif; ?>
                <li>
                    <a href="?page=login" class="<?php echo $page === 'login' ? 'active' : ''; ?>">
                        <i class="bi bi-box-arrow-in-right"></i> Login
                    </a>
                </li>
                <?php $publicRegistrationNavState = portalRegistrationPageAvailability($pdo, ['session_name' => portalCurrentAcademicSessionName($pdo)]); ?>
                <?php if (!empty($publicRegistrationNavState['enabled'])): ?>
                <li>
                    <a href="?page=register" class="<?php echo $page === 'register' ? 'active' : ''; ?>">
                        <i class="bi bi-person-plus"></i> Register
                    </a>
                </li>
                <?php endif; ?>
            <?php endif; ?>
        </ul>

        <div class="sidebar-footer">
            &copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?><br>
            <span class="small">Portal designed &amp; powered by <a href="https://honttech.com" target="_blank" rel="noopener" style="color: rgba(255,255,255,0.85); text-decoration: underline;">Honest Technologies</a></span>
        </div>
    </nav>


<?php
// Hide restricted menu links even if they exist in the sidebar markup
if (isAdmin()) {
    $restrictedPages = [];
    foreach (pageMenuKeyMap() as $p => $k) {
        if (!menuEnabled($pdo, $k)) $restrictedPages[] = $p;
    }
    $restrictedJson = htmlspecialchars(json_encode(array_values(array_unique($restrictedPages))), ENT_QUOTES, 'UTF-8');
    echo '<div id="acl-restricted-pages" data-restricted-pages="' . $restrictedJson . '" style="display:none"></div>';
}
?>
<script>
(function(){
  var el = document.getElementById('acl-restricted-pages');
  if(!el) return;
  var pages = [];
  try { pages = JSON.parse(el.getAttribute('data-restricted-pages') || '[]'); } catch(e){}
  if(!pages || !pages.length) return;
  pages.forEach(function(p){
    // Hide sidebar anchors
    var a = document.querySelector('a[href="?page='+p+'"]');
    if(a){
      var li = a.closest('li');
      if(li) li.remove();
    }
  });
})();
</script>


<script>
(function(){
  function initPortalSearch(root){
    var input = root.querySelector('.portal-search-input');
    var results = root.querySelector('.portal-search-results');
    if (!input || !results) return;
    var items = [];
    try { items = JSON.parse(root.getAttribute('data-portal-search-items') || '[]') || []; } catch(e) { items = []; }

    function esc(v){
      return String(v == null ? '' : v)
        .replace(/&/g,'&amp;')
        .replace(/</g,'&lt;')
        .replace(/>/g,'&gt;')
        .replace(/"/g,'&quot;')
        .replace(/'/g,'&#039;');
    }
    function normalize(v){
      return String(v == null ? '' : v).toLowerCase().replace(/[^a-z0-9]+/g,' ').trim();
    }
    function tokenize(v){
      return normalize(v).split(/\s+/).filter(Boolean);
    }
    function hide(){ results.hidden = true; results.innerHTML = ''; }
    function scoreField(field, term){
      if (!field) return 0;
      if (field === term) return 180;
      if (field.indexOf(term) === 0) return 120;
      if (new RegExp('(^|\\s)' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).test(field)) return 85;
      if (field.indexOf(' ' + term) !== -1) return 75;
      if (field.indexOf(term) !== -1) return 40;
      return 0;
    }
    function scoreItem(item, q){
      var label = normalize(item.label || '');
      var parent = normalize(item.parent || '');
      var keywords = normalize(item.keywords || '');
      var aliases = normalize(item.aliases || '');
      var kind = normalize(item.kind || '');
      var hay = [label, parent, keywords, aliases, kind].join(' ').trim();
      var qn = normalize(q);
      if (!hay || !qn) return -1;
      var terms = tokenize(qn);
      if (!terms.length) return -1;
      var total = 0;
      for (var i = 0; i < terms.length; i++) {
        var term = terms[i];
        var s = 0;
        s = Math.max(s, scoreField(label, term));
        s = Math.max(s, Math.floor(scoreField(parent, term) * 0.55));
        s = Math.max(s, Math.floor(scoreField(aliases, term) * 0.7));
        s = Math.max(s, Math.floor(scoreField(keywords, term) * 0.45));
        if (!s && hay.indexOf(term) !== -1) s = 20;
        if (!s) return -1;
        total += s;
      }
      if (label.indexOf(qn) === 0) total += 40;
      if (label.indexOf(qn) !== -1) total += 18;
      if (parent && parent.indexOf(qn) !== -1) total += 10;
      if ((item.kind || '') === 'Sub Menu') total += 4;
      if ((item.kind || '') === 'Feature/Page') total += 2;
      return total;
    }
    function render(list){
      if (!list.length) {
        results.innerHTML = '<div class="portal-search-empty">No matching menu, sub menu, or feature found.</div>';
        results.hidden = false;
        return;
      }
      results.innerHTML = list.map(function(item){
        var metaParts = [];
        if (item.parent) metaParts.push(esc(item.parent));
        if (item.aliases) metaParts.push('Matches: ' + esc(item.aliases));
        var meta = metaParts.length ? '<small>' + metaParts.join(' • ') + '</small>' : '';
        var badge = item.kind ? '<span class="portal-search-kind">' + esc(item.kind) + '</span>' : '';
        return '<a class="portal-search-result" href="' + esc(item.url) + '"><i class="bi bi-arrow-up-right-circle"></i><span><strong>' + esc(item.label) + '</strong>' + badge + meta + '</span></a>';
      }).join('');
      results.hidden = false;
    }
    input.addEventListener('input', function(){
      var q = String(input.value || '').trim();
      if (!q) { hide(); return; }
      var seen = Object.create(null);
      var list = items.map(function(item){
        return { item: item, score: scoreItem(item, q) };
      }).filter(function(row){
        if (row.score < 0) return false;
        var key = String(row.item.url || '') + '|' + String(row.item.label || '');
        if (seen[key]) return false;
        seen[key] = true;
        return true;
      }).sort(function(a,b){
        if (b.score !== a.score) return b.score - a.score;
        return String(a.item.label || '').localeCompare(String(b.item.label || ''));
      }).slice(0, 14).map(function(row){ return row.item; });
      render(list);
    });
    input.addEventListener('focus', function(){ if (String(input.value || '').trim() !== '') input.dispatchEvent(new Event('input')); });
    document.addEventListener('click', function(ev){ if (!root.contains(ev.target)) hide(); });
    input.addEventListener('keydown', function(ev){ if (ev.key === 'Escape') hide(); });
  }
  document.addEventListener('DOMContentLoaded', function(){ document.querySelectorAll('[data-portal-search-root]').forEach(initPortalSearch); });
})();
</script>

    <!-- Main Content -->
    <div class="main-content" id="main-content">
	        <?php
	        // Flash success/error messages
	        renderFlashes();
        // Display success/error messages
        if (isset($_SESSION['registration_success'])) {
            echo '<div class="alert alert-success">Registration successful! Please proceed to payment.</div>';
            unset($_SESSION['registration_success']);
        }

        if (isset($_SESSION['payment_success'])) {
            echo '<div class="alert alert-success">Payment successful! Your application is now under review.</div>';
            unset($_SESSION['payment_success']);
        }

        if (isset($_SESSION['payment_pending_approval'])) {
            echo '<div class="alert alert-info">Payment received successfully! Your payment is now pending approval by the administration.</div>';
            unset($_SESSION['payment_pending_approval']);
        }

        if (isset($_SESSION['acceptance_payment_success'])) {
            echo '<div class="alert alert-success">Acceptance fee payment successful! Your admission letter is now available.</div>';
            unset($_SESSION['acceptance_payment_success']);
        }

        if (isset($_SESSION['acceptance_payment_pending_approval'])) {
            echo '<div class="alert alert-info">Acceptance fee received successfully! Your payment is now pending approval by the administration.</div>';
            unset($_SESSION['acceptance_payment_pending_approval']);
        }

        if (isset($_SESSION['hostel_payment_success'])) {
            echo '<div class="alert alert-success">Hostel fee payment successful! Your hostel accommodation has been confirmed.</div>';
            unset($_SESSION['hostel_payment_success']);
        }

        if (isset($_SESSION['hostel_payment_pending_approval'])) {
            echo '<div class="alert alert-info">Hostel fee received successfully! Your payment is now pending approval by the administration.</div>';
            unset($_SESSION['hostel_payment_pending_approval']);
        }

        if (isset($_SESSION['school_payment_success'])) {
            echo '<div class="alert alert-success">School fee payment successful!</div>';
            unset($_SESSION['school_payment_success']);
        }

        if (isset($_SESSION['school_payment_pending_approval'])) {
            echo '<div class="alert alert-info">School fee received successfully! Your payment is now pending approval by the administration.</div>';
            unset($_SESSION['school_payment_pending_approval']);
        }

        if (isset($_SESSION['payment_failed'])) {
            echo '<div class="alert alert-danger">Payment failed. Please try again.</div>';
            unset($_SESSION['payment_failed']);
        }

        if (isset($_SESSION['acceptance_payment_failed'])) {
            echo '<div class="alert alert-danger">Acceptance fee payment failed. Please try again.</div>';
            unset($_SESSION['acceptance_payment_failed']);
        }

        if (isset($_SESSION['hostel_payment_failed'])) {
            echo '<div class="alert alert-danger">Hostel fee payment failed. Please try again.</div>';
            unset($_SESSION['hostel_payment_failed']);
        }

        if (isset($_SESSION['school_payment_failed'])) {
            echo '<div class="alert alert-danger">School fee payment failed. Please try again.</div>';
            unset($_SESSION['school_payment_failed']);
        }

        if (isset($_SESSION['status_updated'])) {
            $statusUpdatedMessage = is_string($_SESSION['status_updated']) && trim($_SESSION['status_updated']) !== ''
                ? $_SESSION['status_updated']
                : 'Application status updated successfully.';
            echo '<div class="alert alert-success">' . htmlspecialchars($statusUpdatedMessage) . '</div>';
            unset($_SESSION['status_updated']);
        }

        if (isset($_SESSION['decision_updated'])) {
            echo '<div class="alert alert-success">Admin decision updated successfully.</div>';
            unset($_SESSION['decision_updated']);
        }

        if (isset($_SESSION['settings_updated'])) {
            echo '<div class="alert alert-success">System settings updated successfully.</div>';
            unset($_SESSION['settings_updated']);
        }

        if (isset($_SESSION['student_added'])) {
            echo '<div class="alert alert-success">Student added successfully.</div>';
            unset($_SESSION['student_added']);
        }

        if (isset($_SESSION['student_status_updated'])) {
            echo '<div class="alert alert-success">Student status updated successfully.</div>';
            unset($_SESSION['student_status_updated']);
        }

        if (isset($_SESSION['student_deleted'])) {
            echo '<div class="alert alert-success">Student deleted successfully.</div>';
            unset($_SESSION['student_deleted']);
        }

        if (isset($_SESSION['application_deleted'])) {
            echo '<div class="alert alert-success">Application deleted successfully.</div>';
            unset($_SESSION['application_deleted']);
        }

        if (isset($_SESSION['payment_status_updated'])) {
            echo '<div class="alert alert-success">Payment status updated successfully.</div>';
            unset($_SESSION['payment_status_updated']);
        }

        if (isset($_SESSION['upload_success'])) {
            echo '<div class="alert alert-success">' . $_SESSION['upload_success'] . '</div>';
            unset($_SESSION['upload_success']);
        }

        if (isset($_SESSION['upload_error'])) {
            echo '<div class="alert alert-danger">' . $_SESSION['upload_error'] . '</div>';
            unset($_SESSION['upload_error']);
        }

        // Display errors
        if (isset($loginError) && !in_array((string)($page ?? ''), ['login', 'admin_login'], true)) {
            echo '<div class="alert alert-danger">' . $loginError . '</div>';
        }

        if (isset($registrationError)) {
            echo '<div class="alert alert-danger">' . $registrationError . '</div>';
        }

        if (isset($paymentError)) {
            echo '<div class="alert alert-danger">' . $paymentError . '</div>';
        }
        // Load page content based on $page variable
// ---- Student feature toggles: block access to disabled modules ----
if (isStudent()) {
    $studentPageMenuMap = [
        'application_form' => 'menu_student_application_form',
        'print_form' => 'menu_student_application_form',
        'student_course_registration' => 'menu_student_course_registration',
        'admission_letter' => 'menu_student_admission_letter',
        'student_id_card' => 'menu_student_id_card',
        'payment_history' => 'menu_student_payment_history',
        'wallet' => 'menu_student_wallet',
        'acceptance_payment' => 'menu_student_acceptance_fee',
        'hostel_payment' => 'menu_student_hostel_fee',
        'school_payment' => 'menu_student_school_fee',
        'student_results' => 'menu_student_results',
        'student_attendance' => 'menu_student_attendance',
        'student_lms' => 'menu_student_lms',
        'student_library' => 'menu_student_library',
        'student_cbt' => 'menu_student_cbt',
        'messages' => 'menu_student_messages',
        'view_message' => 'menu_student_messages',
        'notifications' => 'menu_student_notifications',
        'profile' => 'menu_student_profile',
    ];
    if (isset($studentPageMenuMap[$page])) {
        $mk = $studentPageMenuMap[$page];
        if (!studentMenuEnabled($pdo, $mk)) {
            flash('danger', 'This module is currently disabled by the administrator.');
            redirect('?page=student_dashboard');
        }
    }
}


if (!function_exists('portalRegistrationReportBranding')) {
    function portalRegistrationReportBranding(PDO $pdo): array {
        $title = trim((string)(function_exists('setting') ? setting('portal_title', 'HontTech System') : portalSettingValueSafe($pdo, 'portal_title', 'HontTech System')));
        if ($title === '') $title = 'HontTech System';
        $description = trim((string)portalSettingValueSafe($pdo, 'home_affiliation_text', ''));
        if ($description === '') {
            $description = 'Official registration monitoring and reporting sheet.';
        }
        $leftLogo = function_exists('getSystemAsset') ? trim((string)getSystemAsset('left_logo')) : '';
        $rightLogo = function_exists('getSystemAsset') ? trim((string)getSystemAsset('right_logo')) : '';
        if ($leftLogo === '' && function_exists('portalSchoolLogoPath')) {
            $leftLogo = trim((string)portalSchoolLogoPath());
        }
        return [
            'title' => $title,
            'description' => $description,
            'left_logo' => $leftLogo,
            'right_logo' => $rightLogo,
        ];
    }
}

if (!function_exists('portalRegistrationSummaryTableRows')) {
    function portalRegistrationSummaryTableRows(array $rows): array {
        $out = [];
        $sn = 0;
        foreach ($rows as $row) {
            $sn++;
            $name = '';
            if (function_exists('full_name')) {
                $name = trim((string)full_name((array)$row));
            }
            if ($name === '') {
                $name = trim((string)($row['student_name'] ?? ''));
            }
            $matricNo = trim((string)($row['matric_no'] ?? ''));
            if ($matricNo === '') $matricNo = trim((string)($row['registration_no'] ?? ''));
            if ($matricNo === '') $matricNo = trim((string)($row['reg_no'] ?? ''));
            if ($matricNo === '') $matricNo = trim((string)($row['application_no'] ?? ''));
            $department = trim((string)($row['department_name'] ?? ($row['department_text'] ?? ($row['department'] ?? ''))));
            $level = trim((string)($row['level'] ?? ''));
            $out[] = [
                'sn' => $sn,
                'name' => $name !== '' ? $name : 'Student',
                'matric_no' => $matricNo,
                'department' => $department,
                'level' => $level,
            ];
        }
        return $out;
    }
}

if (!function_exists('portalRenderRegistrationSummaryReport')) {
    function portalRenderRegistrationSummaryReport(PDO $pdo, string $reportTitle, array $rows, array $context = [], bool $autoPrint = false, bool $autoDownload = false): void {
        $brand = portalRegistrationReportBranding($pdo);
        $tableRows = portalRegistrationSummaryTableRows($rows);
        $faculty = trim((string)($context['faculty'] ?? 'All Faculties'));
        $department = trim((string)($context['department'] ?? 'All Departments'));
        $sessionName = trim((string)($context['session_name'] ?? 'All Sessions'));
        $semester = trim((string)($context['semester'] ?? 'All Semesters'));
        $level = trim((string)($context['level'] ?? 'All Levels'));
        $scopeNote = trim((string)($context['scope_note'] ?? ''));
        $generatedAt = date('d M Y h:i A');
        $filenameBits = [$reportTitle, $sessionName, $semester, $level];
        $filename = strtolower(trim(preg_replace('/[^A-Za-z0-9]+/', '-', implode('-', array_filter($filenameBits))), '-'));
        if ($filename === '') $filename = 'registration-summary-report';
        $filename .= '.pdf';
        if (!headers_sent()) {
            header('Content-Type: text/html; charset=utf-8');
            header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
            header('Pragma: no-cache');
        }
        ?>
        <!doctype html>
        <html lang="en">
        <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width,initial-scale=1">
            <title><?php echo e($reportTitle); ?></title>
            <style>
                :root{--rp-primary:#0b3f7e;--rp-soft:#eef4fb;--rp-border:#cfd9e5;--rp-ink:#12253a;--rp-muted:#5f6f82}
                *{box-sizing:border-box}
                body{margin:0;background:#f4f7fb;color:var(--rp-ink);font-family:Arial,Helvetica,sans-serif}
                .report-shell{max-width:1000px;margin:18px auto;padding:0 14px}
                .report-actions{display:flex;gap:10px;justify-content:flex-end;margin-bottom:12px}
                .report-btn{border:1px solid #b8c7d8;background:#fff;color:#173a63;border-radius:8px;padding:9px 14px;font-size:14px;cursor:pointer;text-decoration:none}
                .report-paper{background:#fff;border:1px solid var(--rp-border);border-radius:16px;box-shadow:0 12px 32px rgba(15,23,42,.08);padding:18px 22px}
                .report-header{display:grid;grid-template-columns:100px 1fr 100px;gap:16px;align-items:center;padding-bottom:14px;border-bottom:3px solid var(--rp-primary)}
                .report-logo{height:82px;display:flex;align-items:center;justify-content:center}
                .report-logo img{max-width:82px;max-height:82px;object-fit:contain}
                .report-logo-fallback{width:82px;height:82px;border-radius:50%;border:1px dashed var(--rp-border);display:flex;align-items:center;justify-content:center;color:var(--rp-muted);font-size:12px;text-align:center;padding:8px}
                .report-title{text-align:center}
                .report-title h1{margin:0;font-size:26px;color:var(--rp-primary);letter-spacing:.02em}
                .report-title p{margin:6px 0 0;color:var(--rp-muted);font-size:14px}
                .report-title .sub{font-weight:700;color:#1c3f68;margin-top:8px;font-size:16px}
                .report-meta{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:14px}
                .report-meta-card{border:1px solid var(--rp-border);background:var(--rp-soft);border-radius:12px;padding:10px 12px}
                .report-meta-card .label{font-size:11px;text-transform:uppercase;letter-spacing:.12em;font-weight:700;color:var(--rp-muted);margin-bottom:4px}
                .report-meta-card .value{font-size:15px;font-weight:700;color:#173a63;word-break:break-word}
                .report-note{margin-top:10px;border:1px solid #dbe6f3;background:#f8fbff;border-radius:10px;padding:10px 12px;color:#486179;font-size:13px}
                table{width:100%;border-collapse:collapse;margin-top:16px}
                th,td{border:1px solid var(--rp-border);padding:9px 10px;text-align:left;font-size:14px;vertical-align:top}
                thead th{background:#edf3fb;color:#173a63;font-size:12px;letter-spacing:.08em;text-transform:uppercase}
                tbody tr:nth-child(even){background:#fbfdff}
                .report-foot{display:flex;justify-content:space-between;gap:10px;margin-top:12px;font-size:12px;color:var(--rp-muted)}
                .report-total{font-weight:700;color:#173a63}
                @media print{
                    body{background:#fff}
                    .report-shell{max-width:none;margin:0;padding:0}
                    .report-actions{display:none !important}
                    .report-paper{border:none;box-shadow:none;border-radius:0;padding:0}
                    @page{size:A4 portrait;margin:10mm}
                }
            </style>
        </head>
        <body>
            <div class="report-shell">
                <div class="report-actions">
                    <button class="report-btn" type="button" onclick="window.print()">Print</button>
                    <button class="report-btn" type="button" id="reportDownloadBtn">Download PDF</button>
                </div>
                <div class="report-paper" id="registrationSummaryPaper">
                    <div class="report-header">
                        <div class="report-logo">
                            <?php if ($brand['left_logo'] !== ''): ?><img src="<?php echo e($brand['left_logo']); ?>" alt="Left Logo"><?php else: ?><div class="report-logo-fallback">LOGO</div><?php endif; ?>
                        </div>
                        <div class="report-title">
                            <h1><?php echo e($brand['title']); ?></h1>
                            <p><?php echo e($brand['description']); ?></p>
                            <div class="sub"><?php echo e($reportTitle); ?></div>
                        </div>
                        <div class="report-logo">
                            <?php if ($brand['right_logo'] !== ''): ?><img src="<?php echo e($brand['right_logo']); ?>" alt="Right Logo"><?php elseif ($brand['left_logo'] !== ''): ?><img src="<?php echo e($brand['left_logo']); ?>" alt="Logo"><?php else: ?><div class="report-logo-fallback">LOGO</div><?php endif; ?>
                        </div>
                    </div>
                    <div class="report-meta">
                        <div class="report-meta-card"><div class="label">Faculty</div><div class="value"><?php echo e($faculty !== '' ? $faculty : 'All Faculties'); ?></div></div>
                        <div class="report-meta-card"><div class="label">Department</div><div class="value"><?php echo e($department !== '' ? $department : 'All Departments'); ?></div></div>
                        <div class="report-meta-card"><div class="label">Academic Session</div><div class="value"><?php echo e($sessionName !== '' ? $sessionName : 'All Sessions'); ?></div></div>
                        <div class="report-meta-card"><div class="label">Semester</div><div class="value"><?php echo e($semester !== '' ? $semester : 'All Semesters'); ?></div></div>
                        <div class="report-meta-card"><div class="label">Level</div><div class="value"><?php echo e($level !== '' ? $level : 'All Levels'); ?></div></div>
                        <div class="report-meta-card"><div class="label">Generated</div><div class="value"><?php echo e($generatedAt); ?></div></div>
                    </div>
                    <?php if ($scopeNote !== ''): ?><div class="report-note"><?php echo e($scopeNote); ?></div><?php endif; ?>
                    <table>
                        <thead>
                            <tr>
                                <th style="width:70px;">S/N</th>
                                <th>Name</th>
                                <th style="width:220px;">Matric No.</th>
                                <th>Department</th>
                                <th style="width:120px;">Level</th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php if (!$tableRows): ?>
                                <tr><td colspan="5" style="text-align:center;color:#6b7280;">No records matched the selected scope.</td></tr>
                            <?php else: foreach ($tableRows as $item): ?>
                                <tr>
                                    <td><?php echo (int)$item['sn']; ?></td>
                                    <td><?php echo e($item['name']); ?></td>
                                    <td><?php echo e($item['matric_no'] !== '' ? $item['matric_no'] : '—'); ?></td>
                                    <td><?php echo e($item['department'] !== '' ? $item['department'] : '—'); ?></td>
                                    <td><?php echo e($item['level'] !== '' ? $item['level'] : '—'); ?></td>
                                </tr>
                            <?php endforeach; endif; ?>
                        </tbody>
                    </table>
                    <div class="report-foot">
                        <div><?php echo e($brand['title']); ?> • <?php echo e($reportTitle); ?></div>
                        <div class="report-total">Total Record(s): <?php echo count($tableRows); ?></div>
                    </div>
                </div>
            </div>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" referrerpolicy="no-referrer"></script>
            <script>
                (function(){
                    const autoPrint = <?php echo $autoPrint ? 'true' : 'false'; ?>;
                    const autoDownload = <?php echo $autoDownload ? 'true' : 'false'; ?>;
                    const paper = document.getElementById('registrationSummaryPaper');
                    const downloadBtn = document.getElementById('reportDownloadBtn');
                    function downloadPdf(){
                        if(!window.html2pdf || !paper) return;
                        window.html2pdf().set({
                            filename: <?php echo json_encode($filename); ?>,
                            margin: [8,8,8,8],
                            image: {type:'jpeg', quality:0.98},
                            html2canvas: {scale:2, useCORS:true},
                            jsPDF: {unit:'mm', format:'a4', orientation:'portrait'},
                            pagebreak: {mode:['css','legacy']}
                        }).from(paper).save();
                    }
                    if(downloadBtn){ downloadBtn.addEventListener('click', downloadPdf); }
                    window.addEventListener('load', function(){
                        if(autoDownload){ setTimeout(downloadPdf, 350); }
                        else if(autoPrint){ setTimeout(function(){ window.print(); }, 350); }
                    });
                })();
            </script>
        </body>
        </html>
        <?php
    }
}


        switch ($page) {
            case 'public_clearance_verify':
                portalEnsureClearanceSupport($pdo);
                $token = trim((string)($_GET['token'] ?? ''));
                $publicClearance = portalFindClearanceRecordByPublicToken($pdo, $token);
                if (!$publicClearance) {
                    echo '<div class="container py-4"><div class="alert alert-warning">Invalid or unavailable clearance verification record.</div></div>';
                    break;
                }
                $publicSnapshot = portalBuildClearanceSnapshot($pdo, $publicClearance, (string)($publicClearance['academic_session'] ?? ''), (string)($publicClearance['semester'] ?? ''));
                $schoolLogoPath = portalSchoolLogoPath();
                $schoolName = (string)setting('portal_title', 'HontTech System');
                ?>
                <div class="container py-4">
                    <div class="card shadow-sm mx-auto" style="max-width:980px;">
                        <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                            <div><div class="fw-bold"><?php echo e($schoolName); ?></div><div class="text-muted small">Public Clearance Verification</div></div>
                            <span class="badge bg-<?php echo portalClearanceStatusBadgeClass((string)($publicClearance['clearance_status'] ?? 'Pending')); ?>"><?php echo e((string)($publicClearance['clearance_status'] ?? 'Pending')); ?></span>
                        </div>
                        <div class="card-body">
                            <div class="row g-3 align-items-start mb-3">
                                <div class="col-md-2 text-center"><?php if ($schoolLogoPath): ?><img src="<?php echo htmlspecialchars($schoolLogoPath); ?>" alt="School Logo" style="max-width:84px;max-height:84px;object-fit:contain;"><?php endif; ?></div>
                                <div class="col-md-10"><div class="row g-3">
                                    <div class="col-md-6"><strong>Receipt No:</strong> <?php echo e((string)($publicClearance['receipt_no'] ?? portalClearanceReceiptNumber($pdo, (int)($publicClearance['student_id'] ?? 0), (string)($publicClearance['academic_session'] ?? ''), (string)($publicClearance['semester'] ?? '')))); ?></div>
                                    <div class="col-md-6"><strong>Student Name:</strong> <?php echo e(full_name($publicClearance)); ?></div>
                                    <div class="col-md-6"><strong>Matric Number:</strong> <?php echo e(portalReceiptMatricNumber($publicClearance)); ?></div>
                                    <div class="col-md-6"><strong>Session / Semester:</strong> <?php echo e((string)($publicClearance['academic_session'] ?? '')); ?> / <?php echo e((string)($publicClearance['semester'] ?? '')); ?></div>
                                    <div class="col-md-6"><strong>Payment Status:</strong> <?php echo e((string)($publicSnapshot['payment_status'] ?? ($publicClearance['payment_status'] ?? 'Not Paid'))); ?></div>
                                    <div class="col-md-6"><strong>Total Approved Payment:</strong> ₦<?php echo number_format((float)($publicSnapshot['total_amount_paid'] ?? 0), 2); ?></div>
                                </div></div>
                            </div>
                            <div class="table-responsive"><table class="table table-bordered align-middle mb-0"><thead><tr><th>Item</th><th>Status</th><th>Amount</th><th>Date</th><th>Reference</th></tr></thead><tbody>
                            <?php if (empty($publicSnapshot['paid_items'])): ?>
                                <tr><td colspan="5" class="text-center text-muted">No approved payment entries found for this clearance scope.</td></tr>
                            <?php else: foreach ((array)$publicSnapshot['paid_items'] as $item): ?>
                                <tr><td><?php echo e((string)($item['label'] ?? 'Payment')); ?></td><td><?php echo e((string)($item['status'] ?? 'Pending')); ?></td><td>₦<?php echo number_format((float)($item['amount'] ?? 0), 2); ?></td><td><?php echo !empty($item['payment_date']) ? e(date('d M Y', strtotime((string)$item['payment_date']))) : '-'; ?></td><td style="word-break:break-word;"><?php echo e((string)($item['reference'] ?? '')); ?></td></tr>
                            <?php endforeach; endif; ?>
                            </tbody></table></div>
                            <div class="small text-muted mt-3">This page confirms only the selected clearance record and its safe verification details.</div>
                        </div>
                    </div>
                </div>
                <?php
                break;
            case 'public_course_form':
                $token = trim((string)($_GET['token'] ?? ''));
                $publicReg = portalFindCourseRegistrationByPublicToken($pdo, $token);
                if (!$publicReg) {
                    echo '<div class="container py-5"><div class="alert alert-danger">This course form verification link is invalid, expired, or no longer available.</div></div>';
                    break;
                }
                $publicItems = (array)($publicReg['__items'] ?? []);
                unset($publicReg['__items']);
                portalRenderCourseRegistrationPrintable($pdo, $publicReg, $publicItems, false);
                break;
            case 'print_form':
                if (!isStudent()) { redirect('?page=login'); }
                if (!portalManagedContentEnabled($pdo, 'application_form')) { flash('danger', portalManagedContentDisabledMessage($pdo, 'application_form')); redirect('?page=student_dashboard'); }
                $sid = (int)($_SESSION['user_id'] ?? 0);
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $stmt->execute([$sid]);
                $st = $stmt->fetch(PDO::FETCH_ASSOC);
                if (!$st) { echo "<div class='alert alert-danger'>Student not found.</div>"; break; }
                $applicationDoc = portalDocumentContent($pdo, 'application_form');
                $autoDownloadApplication = isset($_GET['download']) && (string)$_GET['download'] === '1';
                $autoPrintApplication = !$autoDownloadApplication && ((string)($_GET['print'] ?? '1') === '1');
                $applicationAdmissionReference = portalApplicationAdmissionReferenceValue((array)$st);
                $applicationAdmissionYear = portalStudentAcademicYear((array)$st);
                $applicationPdfFilename = portalApplicationFormPdfFilename((array)$st);

                // Related data
                $edu = [];
                $exams = [];
                $ref = null;
                try {
                    $stmtE = $pdo->prepare("SELECT * FROM education_history WHERE student_id = ? ORDER BY id ASC");
                    $stmtE->execute([$sid]);
                    $edu = $stmtE->fetchAll(PDO::FETCH_ASSOC);
                } catch (Throwable $e) {}
                try {
                    $stmtX = $pdo->prepare("SELECT * FROM examination_results WHERE student_id = ? ORDER BY id ASC");
                    $stmtX->execute([$sid]);
                    $exams = $stmtX->fetchAll(PDO::FETCH_ASSOC);
                } catch (Throwable $e) {}
                try {
                    $stmtR = $pdo->prepare("SELECT * FROM referees WHERE student_id = ? ORDER BY id DESC LIMIT 1");
                    $stmtR->execute([$sid]);
                    $ref = $stmtR->fetch(PDO::FETCH_ASSOC);
                } catch (Throwable $e) {}

                $adminDecisionPrint = function_exists('getOfficerDecision') ? (getOfficerDecision($pdo, (int)$sid) ?: []) : [];
                $pfHasValue = static function ($value): bool {
                    if (is_array($value)) {
                        foreach ($value as $item) {
                            if (trim((string)$item) !== '') return true;
                        }
                        return false;
                    }
                    return trim((string)$value) !== '';
                };
                $pfNormalizeDate = static function ($value, string $fallback = ''): string {
                    $raw = trim((string)$value);
                    if ($raw === '') return $fallback;
                    $ts = strtotime($raw);
                    return $ts ? date('d M Y', $ts) : $raw;
                };
                $pfFilterRows = static function (array $rows, array $keys) use ($pfHasValue): array {
                    $out = [];
                    foreach ($rows as $row) {
                        foreach ($keys as $key) {
                            if ($pfHasValue($row[$key] ?? '')) {
                                $out[] = $row;
                                break;
                            }
                        }
                    }
                    return $out;
                };

                $pfRef = is_array($ref) ? $ref : [];
                $pfRefName = trim((string)($pfRef['full_name'] ?? ''));
                $pfRefAddress = trim((string)($pfRef['address'] ?? ''));
                $pfRefOccupation = trim((string)($pfRef['occupation'] ?? ''));
                $pfRefPostHeld = trim((string)($pfRef['post_held'] ?? ''));
                $pfRefSignature = trim((string)($pfRef['signature_data'] ?? ''));
                $pfRefDate = $pfNormalizeDate($pfRef['date'] ?? '');
                $pfApplicantDate = $pfNormalizeDate($st['created_at'] ?? 'now', date('d M Y'));
                $pfAdmissionReference = trim((string)$applicationAdmissionReference);
                $pfMatricNumber = trim((string)portalStudentNumberDisplay((array)$st, 'matric'));
                $pfAdmissionYear = trim((string)$applicationAdmissionYear);
                $pfDownloadFilename = trim((string)$applicationPdfFilename);
                $pfStampPath = trim((string)($stampPath ?? ''));
                $pfPrintableEducation = $pfFilterRows((array)$edu, ['institution_name', 'from_date', 'to_date', 'qualification']);
                $pfPrintableExams = $pfFilterRows((array)$exams, ['exam_type', 'exam_date', 'exam_number', 'subjects_grades']);
                $pfQualifiedValue = array_key_exists('qualified', $adminDecisionPrint) ? (int)$adminDecisionPrint['qualified'] : null;
                $pfOfficerName = trim((string)($adminDecisionPrint['officer_name'] ?? ''));
                $pfOfficerSignature = trim((string)($adminDecisionPrint['officer_signature'] ?? ''));
                $pfShowAdminOnly = ($pfQualifiedValue !== null) || $pfHasValue($pfOfficerName) || $pfHasValue($pfOfficerSignature);

                $pfLookupAcademicName = static function (PDO $pdo, string $table, int $id): string {
                    if ($id <= 0) return '';
                    $allowed = ['faculties', 'departments', 'programmes'];
                    if (!in_array($table, $allowed, true)) return '';
                    try {
                        $stmt = $pdo->prepare("SELECT name FROM `{$table}` WHERE id = ? LIMIT 1");
                        $stmt->execute([$id]);
                        return trim((string)($stmt->fetchColumn() ?: ''));
                    } catch (Throwable $e) {
                        return '';
                    }
                };
                $pfInlineDisplayName = trim((string)(function_exists('studentDisplayFullName') ? studentDisplayFullName((array)$st) : str_replace(',', '', full_name((array)$st))));
                if ($pfInlineDisplayName === '') {
                    $pfInlineDisplayName = trim(str_replace(',', '', full_name((array)$st)));
                }
                $pfAcademicDisplay = [
                    'school' => '',
                    'course_of_study' => '',
                    'programme' => '',
                    'mode_of_study' => trim((string)($st['mode_of_study'] ?? portalInferModeOfStudy((array)$st))),
                ];
                $pfAcademicDisplay['school'] = $pfLookupAcademicName($pdo, 'faculties', (int)($st['faculty_id'] ?? 0));
                if ($pfAcademicDisplay['school'] === '') {
                    $pfAcademicDisplay['school'] = trim((string)($st['school'] ?? $st['school_name'] ?? $st['faculty'] ?? ''));
                }
                $pfAcademicDisplay['course_of_study'] = $pfLookupAcademicName($pdo, 'departments', (int)($st['department_id'] ?? 0));
                if ($pfAcademicDisplay['course_of_study'] === '') {
                    $pfAcademicDisplay['course_of_study'] = trim((string)($st['course_of_study'] ?? $st['course_in_view'] ?? $st['department'] ?? ''));
                }
                $pfAcademicDisplay['programme'] = $pfLookupAcademicName($pdo, 'programmes', (int)($st['programme_id'] ?? 0));
                if ($pfAcademicDisplay['programme'] === '') {
                    $pfAcademicDisplay['programme'] = trim((string)($st['program'] ?? $st['programme_name_cache'] ?? $st['programme'] ?? ''));
                }
                if ($pfAcademicDisplay['mode_of_study'] === '') {
                    $pfAcademicDisplay['mode_of_study'] = 'Full-Time';
                }
                $pfSponsorOccupation = trim((string)($st['sponsor_occupation'] ?? $st['sponsor_occ'] ?? ''));
                if ($pfSponsorOccupation !== '' && filter_var($pfSponsorOccupation, FILTER_VALIDATE_EMAIL)) {
                    $pfSponsorOccupation = '';
                }
                if ((int)($st['id'] ?? 0) > 0) {
                    try {
                        $stmt = $pdo->prepare("UPDATE students SET
                            school = COALESCE(?, school),
                            school_name = COALESCE(?, school_name),
                            faculty = COALESCE(?, faculty),
                            course_of_study = COALESCE(?, course_of_study),
                            course_in_view = COALESCE(?, course_in_view),
                            department = COALESCE(?, department),
                            programme = COALESCE(?, programme),
                            program = COALESCE(?, program),
                            programme_name_cache = COALESCE(?, programme_name_cache)
                            WHERE id = ? LIMIT 1");
                        $stmt->execute([
                            $pfAcademicDisplay['school'] !== '' ? $pfAcademicDisplay['school'] : null,
                            $pfAcademicDisplay['school'] !== '' ? $pfAcademicDisplay['school'] : null,
                            $pfAcademicDisplay['school'] !== '' ? $pfAcademicDisplay['school'] : null,
                            $pfAcademicDisplay['course_of_study'] !== '' ? $pfAcademicDisplay['course_of_study'] : null,
                            $pfAcademicDisplay['course_of_study'] !== '' ? $pfAcademicDisplay['course_of_study'] : null,
                            $pfAcademicDisplay['course_of_study'] !== '' ? $pfAcademicDisplay['course_of_study'] : null,
                            $pfAcademicDisplay['programme'] !== '' ? $pfAcademicDisplay['programme'] : null,
                            $pfAcademicDisplay['programme'] !== '' ? $pfAcademicDisplay['programme'] : null,
                            $pfAcademicDisplay['programme'] !== '' ? $pfAcademicDisplay['programme'] : null,
                            (int)$st['id']
                        ]);
                    } catch (Throwable $e) {}
                }

                $pfHeaderRows = [];
                $pfHeaderMap = [
                    'School' => (string)$pfAcademicDisplay['school'],
                    'Course of Study' => (string)$pfAcademicDisplay['course_of_study'],
                    'Program' => (string)$pfAcademicDisplay['programme'],
                    'Mode of Study' => (string)$pfAcademicDisplay['mode_of_study'],
                    'Matric Number' => $pfMatricNumber,
                    'Admission Reference No.' => $pfAdmissionReference,
                    'Application Date' => $pfApplicantDate,
                ];
                foreach ($pfHeaderMap as $label => $value) {
                    if ($pfHasValue($value)) {
                        $pfHeaderRows[] = ['label' => $label, 'value' => $value];
                    }
                }
                if (!$pfHeaderRows) {
                    $pfHeaderRows[] = ['label' => 'Application Date', 'value' => $pfApplicantDate];
                }

                $pfPersonalPairs = [
                    [['label' => 'Surname', 'value' => $st['surname'] ?? ''], ['label' => 'First Name', 'value' => $st['first_name'] ?? '']],
                    [['label' => 'Other Name', 'value' => $st['other_name'] ?? ''], ['label' => 'Sex', 'value' => $st['sex'] ?? '']],
                    [['label' => 'Telephone', 'value' => $st['mobile_phone'] ?? ''], ['label' => 'Maiden Name', 'value' => $st['maiden_name'] ?? '']],
                    [['label' => 'Email Address', 'value' => $st['email'] ?? ''], ['label' => 'Telephone (Residential)', 'value' => $st['residential_phone'] ?? '']],
                    [['label' => 'Nationality', 'value' => $st['nationality'] ?? ''], ['label' => 'Date of Birth', 'value' => $pfNormalizeDate($st['date_of_birth'] ?? '')]],
                    [['label' => 'Local Government Area', 'value' => $st['lga'] ?? ''], ['label' => 'State of Origin', 'value' => $st['state_of_origin'] ?? '']],
                    [['label' => "Sponsor's Telephone Number", 'value' => $st['sponsor_phone'] ?? ''], ['label' => "Sponsor's Occupation", 'value' => $pfSponsorOccupation]],
                ];
                $pfPersonalSingles = [
                    ['label' => 'Permanent Home Address', 'value' => $st['permanent_address'] ?? ''],
                    ['label' => 'Extra Curricular Activities', 'value' => $st['extra_curricular'] ?? ''],
                    ['label' => 'Next of Kin or Parent Name & Address', 'value' => $st['nok_name'] ?? ''],
                    ['label' => 'Next of Kin or Parent Telephone Number', 'value' => $st['nok_phone'] ?? ''],
                    ['label' => "Sponsor's Name & Address", 'value' => $st['sponsor_name'] ?? ''],
                ];
                $pfAcademicRows = [];
                foreach ([
                    ['label' => 'School', 'value' => (string)$pfAcademicDisplay['school']],
                    ['label' => 'Course of Study', 'value' => (string)$pfAcademicDisplay['course_of_study']],
                    ['label' => 'Program', 'value' => (string)$pfAcademicDisplay['programme']],
                    ['label' => 'Mode of Study', 'value' => (string)$pfAcademicDisplay['mode_of_study']],
                ] as $row) {
                    if ($pfHasValue($row['value'])) $pfAcademicRows[] = $row;
                }

                ?>
                <div class="print-page application-form-pdf-shell" style="position:relative;">
<?php
    $pf_passport = $st['passport_photo'] ?? '';
    $pf_qr_data = json_encode([
        'doc' => 'application_form',
        'reg_no' => $st['registration_no'] ?? '',
        'student_id' => $st['id'] ?? '',
        'ts' => date('c')
    ]);
    $pf_qr = generateQRCode($pf_qr_data);
?>
                    <?php if (!$autoDownloadApplication): ?>
                    <div class="d-flex justify-content-between align-items-start mb-3 no-print flex-wrap gap-2">
                        <div>
                            <a href="?page=application_form" class="btn btn-outline-secondary btn-sm"><i class="bi bi-arrow-left"></i> Back</a>
                        </div>
                        <div class="d-flex gap-2 flex-wrap">
                            <button class="btn btn-success btn-sm" type="button" onclick="window.print()"><i class="bi bi-printer"></i> Print</button>
                            <button class="btn btn-primary btn-sm" type="button" id="applicationFormDownloadBtn"><i class="bi bi-download"></i> Download PDF</button>
                        </div>
                    </div>
                    <div id="applicationFormDownloadStatus" class="course-form-download-status d-print-none" aria-live="polite"></div>
                    <?php else: ?>
                    <div id="applicationFormDownloadStatus" class="course-form-download-status show d-print-none" aria-live="polite">Preparing PDF download...</div>
                    <?php endif; ?>

                    <div class="application-print-header mb-3">
                        <div class="application-print-logo">
                            <?php if (!empty($leftLogoPath)): ?>
                                <img src="<?php echo htmlspecialchars($leftLogoPath); ?>" alt="Left Logo" style="max-width:90px; max-height:90px;">
                            <?php endif; ?>
                        </div>
                        <div class="application-print-title text-center">
                            <div class="fw-bold" style="font-size:18px;">Honest Technologies University</div>
                            <div class="fw-bold" style="font-size:18px;">AHMADU BELLO UNIVERSITY, ZARIA</div>
                            <div class="fst-italic" style="font-size:14px;">In affiliation with</div>
                            <div class="fw-bold" style="font-size:18px;">FEDERAL COLLEGE OF EDUCATION (TECHNICAL) R.M. B-1044 ASABA</div>
                            <div class="fw-bold text-danger mt-2" style="font-size:20px;"><?php echo e((string)($applicationDoc['heading'] ?? 'APPLICATION FORM FOR ADMISSION')); ?></div>
                            <?php if ($pfHasValue($applicationDoc['subheading'] ?? '')): ?>
                                <div class="small text-muted mt-1"><?php echo e((string)$applicationDoc['subheading']); ?></div>
                            <?php endif; ?>
                            <?php if ($pfHasValue($applicationDoc['notice_text'] ?? '')): ?>
                                <div class="small text-muted mt-2"><?php echo e((string)$applicationDoc['notice_text']); ?></div>
                            <?php endif; ?>
                        </div>
                        <div class="application-print-logo">
                            <?php if (!empty($rightLogoPath)): ?>
                                <img src="<?php echo htmlspecialchars($rightLogoPath); ?>" alt="Right Logo" style="max-width:90px; max-height:90px;">
                            <?php endif; ?>
                        </div>
                    </div>

                    <div class="row mb-3">
                        <div class="col-12">
                            <table class="table table-bordered mb-0">
                                <?php
                                    $passport = $st['passport_photo'] ?? '';
                                    if (empty($passport) && !empty($st['profile_image'])) { $passport = $st['profile_image']; }
                                    $pfHeaderRowspan = max(1, count($pfHeaderRows));
                                ?>
                                <?php foreach ($pfHeaderRows as $pfHeaderIndex => $pfHeaderRow): ?>
                                    <tr>
                                        <th style="width:30%"><?php echo e($pfHeaderRow['label']); ?>:</th>
                                        <td><?php echo e((string)$pfHeaderRow['value']); ?></td>
                                        <?php if ($pfHeaderIndex === 0): ?>
                                            <td rowspan="<?php echo (int)$pfHeaderRowspan; ?>" class="text-center align-middle" style="width:190px; padding:6px;">
                                                <div style="width:170px;height:210px;border:2px solid #111;border-radius:2px;overflow:hidden;background:#f5f5f5;margin:0 auto;">
                                                    <?php if (!empty($passport)): ?>
                                                        <img src="<?php echo htmlspecialchars($passport); ?>" alt="Passport" style="width:100%;height:100%;object-fit:cover;display:block;">
                                                    <?php else: ?>
                                                        <div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:12px;color:#666;">No Passport</div>
                                                    <?php endif; ?>
                                                </div>
                                            </td>
                                        <?php endif; ?>
                                    </tr>
                                <?php endforeach; ?>
                            </table>
                        </div>
                    </div>

                    <style>
                        @media print{
                            .no-print{ display:none !important; }
                            body{ background:#fff !important; }
                            .main-content{ margin-left:0 !important; }
                            nav.sidebar, .sidebar, .floating-chat-btn{ display:none !important; }
                            .print-page{ padding:0 !important; }
                            @page { size: A4; margin: 8mm; }
                        }
                        .print-page{ background:#fff; padding:6px 8px; font-size:13px; line-height:1.35; }
                        .form-table{ width:100%; border-collapse:collapse; margin-bottom:10px; }
                        .form-table td{ border:1px solid #222; padding:6px 7px; vertical-align:top; }
                        .section-title{ font-weight:700; color:#0d6efd; margin:8px 0 4px; page-break-after:avoid; break-after:avoid-page; }
                        .thin{ font-weight:600; color:#333; }
                        .application-print-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:10px;}
                        .application-print-logo{width:92px;min-width:92px;height:92px;display:flex;align-items:flex-start;justify-content:center;}
                        .application-print-title{flex:1 1 auto;min-width:0;}
                        .application-section-group{margin-bottom:8px;}
                        .application-section-group--declaration,.application-section-group--attestation{page-break-inside:avoid; break-inside:avoid-page;}
                        .application-section-group--attestation{page-break-before:auto; break-before:auto;}
                        .application-section-compact-empty{border:1px solid #222; padding:8px 10px; margin-bottom:10px; color:#6c757d; text-align:center;}
                        .declaration-block,.attestation-block{border:1px solid #222; padding:8px 10px;}
                        .declaration-text{line-height:1.45;}
                        .declaration-inline-name{display:inline-block; min-width:220px; border-bottom:1px dotted #222; padding:0 4px 1px; margin:0 2px; font-weight:600;}
                        .declaration-sign-row{margin-top:12px; display:flex; align-items:flex-end; gap:10px; flex-wrap:wrap;}
                        .declaration-sign-slot{min-width:220px; border-bottom:1px solid #222; padding-bottom:4px;}
                        .declaration-date-slot{min-width:130px; border-bottom:1px solid #222; padding-bottom:4px;}
                        .attestation-shell{display:flex;align-items:flex-start;gap:14px;flex-wrap:nowrap;}
                        .attestation-main{flex:1 1 auto;min-width:0;}
                        .application-form-pdf-shell{background:#fff;}
                        .application-form-pdf-shell.application-form-fit-page{transform:scale(var(--application-form-fit-scale, 1));transform-origin:top center;}
                        .attestation-media-panel{width:250px;min-width:250px;border:1px solid #d1d5db;border-radius:6px;background:#fff;padding:8px;text-align:center;}
                        .attestation-media-row{display:flex;align-items:flex-start;justify-content:center;gap:10px;flex-wrap:nowrap;}
                        .attestation-media-box{flex:1 1 0;min-width:0;}
                        .attestation-media-box img{width:100%;max-width:108px;height:auto;display:block;margin:0 auto;object-fit:contain;}
                        .attestation-media-label{font-size:10px;line-height:1.25;color:#475467;margin-top:6px;font-weight:600;letter-spacing:.02em;}
                        .attestation-grid{display:flex;flex-wrap:wrap;gap:8px 14px;}
                        .attestation-grid-item{flex:1 1 calc(50% - 7px);min-width:220px;}
                        .attestation-grid-item--full{flex-basis:100%;max-width:100%;}
                        .attestation-field-label{font-weight:600;color:#333;}
                        .attestation-qr-note{font-size:10px;line-height:1.25;color:#475467;margin-top:6px;}
                        @media (max-width: 767px){
                            .application-print-header{gap:10px;}
                            .application-print-logo{width:72px;min-width:72px;height:72px;}
                            .attestation-shell{flex-wrap:wrap;}
                            .attestation-media-panel{width:100%;min-width:0;}
                            .attestation-media-row{flex-wrap:wrap;}
                            .attestation-grid-item{flex-basis:100%;min-width:0;}
                            .declaration-inline-name{min-width:0;}
                            .declaration-sign-slot,.declaration-date-slot{min-width:0; width:100%;}
                        }
                    </style>

                    <div class="section-title">1. PERSONAL DATA</div>
                    <table class="form-table mb-3">
                        <?php foreach ($pfPersonalPairs as $pfPair): ?>
                            <?php
                                $pfLeft = $pfPair[0];
                                $pfRight = $pfPair[1];
                                $pfShowLeft = $pfHasValue($pfLeft['value'] ?? '');
                                $pfShowRight = $pfHasValue($pfRight['value'] ?? '');
                                if (!$pfShowLeft && !$pfShowRight) { continue; }
                            ?>
                            <tr>
                                <?php if ($pfShowLeft && $pfShowRight): ?>
                                    <td style="width:50%"><span class="thin"><?php echo e($pfLeft['label']); ?>:</span> <?php echo e((string)$pfLeft['value']); ?></td>
                                    <td><span class="thin"><?php echo e($pfRight['label']); ?>:</span> <?php echo e((string)$pfRight['value']); ?></td>
                                <?php elseif ($pfShowLeft): ?>
                                    <td colspan="2"><span class="thin"><?php echo e($pfLeft['label']); ?>:</span> <?php echo e((string)$pfLeft['value']); ?></td>
                                <?php else: ?>
                                    <td colspan="2"><span class="thin"><?php echo e($pfRight['label']); ?>:</span> <?php echo e((string)$pfRight['value']); ?></td>
                                <?php endif; ?>
                            </tr>
                        <?php endforeach; ?>
                        <?php foreach ($pfPersonalSingles as $pfSingle): ?>
                            <?php if (!$pfHasValue($pfSingle['value'] ?? '')) { continue; } ?>
                            <tr>
                                <td colspan="2"><span class="thin"><?php echo e($pfSingle['label']); ?>:</span> <?php echo e((string)$pfSingle['value']); ?></td>
                            </tr>
                        <?php endforeach; ?>
                    </table>

                    <div class="section-title">2. ACADEMIC STRUCTURE</div>
                    <table class="form-table mb-3">
                        <?php foreach ($pfAcademicRows as $pfAcademicRow): ?>
                            <tr><td><span class="thin"><?php echo e($pfAcademicRow['label']); ?></span></td><td><?php echo e((string)$pfAcademicRow['value']); ?></td></tr>
                        <?php endforeach; ?>
                    </table>

                    <div class="application-section-group">
                        <div class="section-title">3. POST-PRIMARY INSTITUTION ATTENDED WITH DATES</div>
                        <?php if (!$pfPrintableEducation): ?>
                            <div class="application-section-compact-empty">No post-primary institution history was provided.</div>
                        <?php else: ?>
                            <table class="form-table mb-3">
                                <tr>
                                    <td style="width:10%"><span class="thin">#</span></td>
                                    <td style="width:40%"><span class="thin">Institution</span></td>
                                    <td style="width:15%"><span class="thin">From</span></td>
                                    <td style="width:15%"><span class="thin">To</span></td>
                                    <td><span class="thin">Qualifications Obtained</span></td>
                                </tr>
                                <?php foreach ($pfPrintableEducation as $i => $r): ?>
                                    <tr>
                                        <td><?php echo $i + 1; ?>.</td>
                                        <td><?php echo e((string)($r['institution_name'] ?? '')); ?></td>
                                        <td><?php echo e($pfNormalizeDate($r['from_date'] ?? '')); ?></td>
                                        <td><?php echo e($pfNormalizeDate($r['to_date'] ?? '')); ?></td>
                                        <td><?php echo e((string)($r['qualification'] ?? '')); ?></td>
                                    </tr>
                                <?php endforeach; ?>
                            </table>
                        <?php endif; ?>
                    </div>

                    <div class="application-section-group">
                        <div class="section-title">4. SCHOOL CERTIFICATE RESULTS: WAEC/NECO SSCE/NABTEB/OTHERS</div>
                        <table class="form-table mb-3">
                            <tr>
                                <td style="width:25%"><span class="thin">Names Of Examination</span></td>
                                <td style="width:15%"><span class="thin">Date of Examination</span></td>
                                <td style="width:20%"><span class="thin">Examination Number</span></td>
                                <td><span class="thin">Subjects/Grades</span></td>
                                <td style="width:7%"><span class="thin">X</span></td>
                            </tr>
                            <?php if (!$pfPrintableExams): ?>
                                <tr><td colspan="5" class="text-center text-muted">No certificate result entries were provided.</td></tr>
                            <?php else: foreach ($pfPrintableExams as $x): ?>
                                <?php $attached = !empty($x['credentials_attached']) ? 'X' : ''; ?>
                                <tr>
                                    <td><?php echo e((string)($x['exam_type'] ?? '')); ?></td>
                                    <td><?php echo e($pfNormalizeDate($x['exam_date'] ?? '')); ?></td>
                                    <td><?php echo e((string)($x['exam_number'] ?? '')); ?></td>
                                    <td><?php echo e((string)($x['subjects_grades'] ?? '')); ?></td>
                                    <td class="text-center"><?php echo e($attached); ?></td>
                                </tr>
                            <?php endforeach; endif; ?>
                        </table>
                        <?php if ($pfPrintableExams): ?><div class="small text-muted mb-2">* Mark X if credentials are enclosed</div><?php endif; ?>
                    </div>

                    <div class="application-section-group application-section-group--declaration">
                        <div class="section-title">5. DECLARATION</div>
                        <div class="declaration-block mb-2">
                            <div class="declaration-text">
                                I, <span class="declaration-inline-name"><?php echo e($pfInlineDisplayName !== '' ? $pfInlineDisplayName : 'Applicant'); ?></span>, hereby declare that the information given in this form, to the best of my knowledge and belief, is correct. Any false or incomplete information given in this form will automatically disqualify me from being considered for admission to, or continuing with any course of study in Honest Technologies University HontTech Programme affiliated with FEDERAL COLLEGE OF EDUCATION (TECHNICAL) ASABA. I shall accept as final the decision of the HontTech administration about my course(s) of study and placement in the programme.
                            </div>
                            <?php $pf_app_sig = $st['signature_data'] ?? ''; ?>
                            <div class="declaration-sign-row">
                                <div><span class="thin">Applicant’s Signature and Date:</span></div>
                                <div class="declaration-sign-slot">
                                    <?php if (!empty($pf_app_sig)): ?>
                                        <img src="<?php echo htmlspecialchars($pf_app_sig); ?>" alt="Applicant Signature" style="max-height:46px; max-width:240px; object-fit:contain; display:block;">
                                    <?php endif; ?>
                                </div>
                                <div class="declaration-date-slot"><?php echo e($pfApplicantDate); ?></div>
                            </div>
                        </div>
                    </div>

                    <div class="application-section-group application-section-group--attestation">
                        <div class="section-title">6. ATTESTATION</div>
                        <div class="attestation-block">
                            <div class="attestation-shell">
                                <div class="attestation-main">
                                    <div>
                                        <?php if ($pfHasValue($pfRefName)): ?>
                                            I <?php echo e($pfRefName); ?> HEREBY CONFIRM THAT THIS APPLICANT IS KNOWN TO ME. THE INFORMATION SUPPLIED IN HIS OR HER FORM IS, TO THE BEST OF MY KNOWLEDGE, TRUE. THE ATTACHED PASSPORT-SIZED PHOTOGRAPH, ENDORSED BY ME, IS A TRUE RESEMBLANCE OF THE APPLICANT.
                                        <?php else: ?>
                                            I HEREBY CONFIRM THAT THIS APPLICANT IS KNOWN TO ME. THE INFORMATION SUPPLIED IN HIS OR HER FORM IS, TO THE BEST OF MY KNOWLEDGE, TRUE. THE ATTACHED PASSPORT-SIZED PHOTOGRAPH, ENDORSED BY ME, IS A TRUE RESEMBLANCE OF THE APPLICANT.
                                        <?php endif; ?>
                                    </div>

                                    <div class="attestation-grid mt-3">
                                        <?php if ($pfHasValue($pfRefName)): ?>
                                            <div class="attestation-grid-item attestation-grid-item--full"><span class="attestation-field-label">FULL NAME:</span> <?php echo e($pfRefName); ?></div>
                                        <?php endif; ?>
                                        <?php if ($pfHasValue($pfRefAddress)): ?>
                                            <div class="attestation-grid-item attestation-grid-item--full"><span class="attestation-field-label">ADDRESS:</span> <?php echo e($pfRefAddress); ?></div>
                                        <?php endif; ?>
                                        <?php if ($pfHasValue($pfRefOccupation)): ?>
                                            <div class="attestation-grid-item"><span class="attestation-field-label">OCCUPATION:</span> <?php echo e($pfRefOccupation); ?></div>
                                        <?php endif; ?>
                                        <?php if ($pfHasValue($pfRefPostHeld)): ?>
                                            <div class="attestation-grid-item"><span class="attestation-field-label">POST HELD:</span> <?php echo e($pfRefPostHeld); ?></div>
                                        <?php endif; ?>
                                        <?php if ($pfHasValue($pfRefSignature)): ?>
                                            <div class="attestation-grid-item"><span class="attestation-field-label">SIGNATURE:</span> <span style="display:inline-block; min-width:180px; border-bottom:1px solid #222; padding-bottom:2px; vertical-align:bottom;"><img src="<?php echo htmlspecialchars($pfRefSignature); ?>" alt="Sponsor Signature" style="max-height:45px; max-width:180px; object-fit:contain; vertical-align:bottom;"></span></div>
                                        <?php endif; ?>
                                        <?php if ($pfHasValue($pfRefDate)): ?>
                                            <div class="attestation-grid-item"><span class="attestation-field-label">DATE:</span> <?php echo e($pfRefDate); ?></div>
                                        <?php endif; ?>
                                    </div>

                                    <?php if ($pfShowAdminOnly): ?>
                                        <div class="mt-4 text-center text-danger fw-bold">ADMINISTRATIVE USE ONLY</div>
                                        <div class="attestation-grid mt-3">
                                            <?php if ($pfQualifiedValue !== null): ?>
                                                <div class="attestation-grid-item"><span class="attestation-field-label">QUALIFIED:</span> <?php echo $pfQualifiedValue ? 'YES' : 'NO'; ?></div>
                                                <div class="attestation-grid-item"><span class="attestation-field-label">NOT QUALIFIED:</span> <?php echo $pfQualifiedValue ? 'NO' : 'YES'; ?></div>
                                            <?php endif; ?>
                                            <?php if ($pfHasValue($pfOfficerName) || $pfHasValue($pfOfficerSignature)): ?>
                                                <div class="attestation-grid-item attestation-grid-item--full">
                                                    <span class="attestation-field-label">Name &amp; Signature of Officer:</span>
                                                    <?php if ($pfHasValue($pfOfficerSignature)): ?>
                                                        <span style="display:inline-block; min-width:180px; border-bottom:1px solid #222; padding-bottom:2px; vertical-align:bottom; margin-left:6px;"><img src="<?php echo htmlspecialchars($pfOfficerSignature); ?>" alt="Officer Signature" style="max-height:45px; max-width:180px; object-fit:contain; vertical-align:bottom;"></span>
                                                    <?php endif; ?>
                                                    <?php if ($pfHasValue($pfOfficerName)): ?><span style="margin-left:8px;"><?php echo e($pfOfficerName); ?></span><?php endif; ?>
                                                </div>
                                            <?php endif; ?>
                                        </div>
                                    <?php endif; ?>
                                </div>

                                <?php if ($pfHasValue($pf_qr) || $pfHasValue($pfStampPath)): ?>
                                <div class="attestation-media-panel">
                                    <div class="attestation-media-row">
                                        <?php if ($pfHasValue($pf_qr)): ?>
                                            <div class="attestation-media-box">
                                                <img src="<?php echo htmlspecialchars($pf_qr); ?>" alt="Application Verification QR Code">
                                                <div class="attestation-media-label">Verification QR Code</div>
                                            </div>
                                        <?php endif; ?>
                                        <?php if ($pfHasValue($pfStampPath)): ?>
                                            <div class="attestation-media-box">
                                                <img src="<?php echo htmlspecialchars($pfStampPath); ?>" alt="Official Admin Stamp">
                                                <div class="attestation-media-label">Official Stamp</div>
                                            </div>
                                        <?php endif; ?>
                                    </div>
                                    <?php if ($pfHasValue($pf_qr)): ?>
                                        <div class="attestation-qr-note">Scan to verify this application form.</div>
                                    <?php endif; ?>
                                </div>
                                <?php endif; ?>
                            </div>
                        </div>
                    </div>
                </div>
                <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" referrerpolicy="no-referrer"></script>
                <script>
                (function(){
                    const autoDownload = <?php echo $autoDownloadApplication ? 'true' : 'false'; ?>;
                    const autoPrint = <?php echo $autoPrintApplication ? 'true' : 'false'; ?>;
                    const fileName = <?php echo json_encode($pfDownloadFilename !== '' ? $pfDownloadFilename : 'application-form.pdf', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
                    const paper = document.querySelector('.print-page');
                    const statusEl = document.getElementById('applicationFormDownloadStatus');
                    const downloadBtn = document.getElementById('applicationFormDownloadBtn');
                    let previousScale = '';
                    let previousFit = false;
                    let busy = false;

                    function setStatus(message, isError){
                        if(!statusEl) return;
                        const text = String(message || '').trim();
                        statusEl.textContent = text;
                        statusEl.classList.toggle('show', text !== '');
                        statusEl.classList.toggle('error', !!isError);
                    }

                    function preserveFitState(){
                        if(!paper) return;
                        previousScale = paper.style.getPropertyValue('--application-form-fit-scale') || '';
                        previousFit = paper.classList.contains('application-form-fit-page');
                    }

                    function restoreFitState(){
                        if(!paper) return;
                        paper.classList.toggle('application-form-fit-page', !!previousFit);
                        if(previousScale){
                            paper.style.setProperty('--application-form-fit-scale', previousScale);
                        } else {
                            paper.style.removeProperty('--application-form-fit-scale');
                        }
                    }

                    function applySinglePageFit(){
                        if(!paper) return 1;
                        paper.classList.remove('application-form-fit-page');
                        paper.style.removeProperty('--application-form-fit-scale');
                        const pageHeightPx = ((paper.offsetWidth || 794) * 297) / 210;
                        const actualHeightPx = Math.max(paper.scrollHeight || 0, paper.offsetHeight || 0);
                        if(!pageHeightPx || !actualHeightPx || actualHeightPx <= (pageHeightPx + 12)) return 1;
                        const fitScale = Math.max(0.84, Math.min(0.985, (pageHeightPx - 6) / actualHeightPx));
                        if(fitScale < 0.995){
                            paper.style.setProperty('--application-form-fit-scale', String(fitScale));
                            paper.classList.add('application-form-fit-page');
                        }
                        return fitScale;
                    }

                    function waitForImages(root){
                        if(!root) return Promise.resolve();
                        const images = Array.from(root.querySelectorAll('img'));
                        if(!images.length) return Promise.resolve();
                        return Promise.all(images.map(function(img){
                            if(img.complete) return Promise.resolve();
                            return new Promise(function(resolve){
                                const done = function(){ resolve(); };
                                img.addEventListener('load', done, {once:true});
                                img.addEventListener('error', done, {once:true});
                            });
                        }));
                    }

                    async function downloadPdf(){
                        if(busy || !paper || !window.html2pdf) return false;
                        busy = true;
                        setStatus('Preparing PDF download...', false);
                        try{
                            await waitForImages(paper);
                            preserveFitState();
                            applySinglePageFit();
                            await window.html2pdf().set({
                                filename: fileName,
                                margin: [8,8,8,8],
                                image: {type:'jpeg', quality:0.98},
                                html2canvas: {scale:2, useCORS:true, allowTaint:true, backgroundColor:'#ffffff', scrollX:0, scrollY:-window.scrollY},
                                jsPDF: {unit:'mm', format:'a4', orientation:'portrait'},
                                pagebreak: {mode:['css','legacy']}
                            }).from(paper).save();
                            setStatus('PDF downloaded successfully.', false);
                            return true;
                        }catch(err){
                            setStatus((err && err.message) ? err.message : 'Unable to generate the PDF right now.', true);
                            return false;
                        }finally{
                            restoreFitState();
                            busy = false;
                        }
                    }

                    if(downloadBtn){
                        downloadBtn.addEventListener('click', function(ev){
                            if(ev){ ev.preventDefault(); }
                            downloadPdf();
                        });
                    }

                    window.addEventListener('beforeprint', function(){ preserveFitState(); applySinglePageFit(); });
                    window.addEventListener('afterprint', function(){ restoreFitState(); });
                    window.addEventListener('load', function(){
                        setTimeout(function(){ applySinglePageFit(); }, 120);
                        if(autoDownload){ setTimeout(function(){ downloadPdf(); }, 360); }
                        else if(autoPrint){ setTimeout(function(){ try{ window.print(); }catch(e){} }, 320); }
                    });
                })();
                </script>
                <?php
                break;

            case 'home':
                if (!portalManagedContentEnabled($pdo, 'home')) {
                    echo '<div class="row"><div class="col-md-12"><div class="alert alert-warning">' . e(portalManagedContentDisabledMessage($pdo, 'home')) . '</div></div></div>';
                    break;
                }
                $pageContent = portalPageContent($pdo);
                $homeCards = [
                    [
                        'icon' => trim((string)($pageContent['home_card_1_icon'] ?? 'bi bi-mortarboard-fill')) ?: 'bi bi-mortarboard-fill',
                        'title' => (string)($pageContent['home_card_1_title'] ?? ''),
                        'text' => (string)($pageContent['home_card_1_text'] ?? ''),
                    ],
                    [
                        'icon' => trim((string)($pageContent['home_card_2_icon'] ?? 'bi bi-people-fill')) ?: 'bi bi-people-fill',
                        'title' => (string)($pageContent['home_card_2_title'] ?? ''),
                        'text' => (string)($pageContent['home_card_2_text'] ?? ''),
                    ],
                    [
                        'icon' => trim((string)($pageContent['home_card_3_icon'] ?? 'bi bi-building')) ?: 'bi bi-building',
                        'title' => (string)($pageContent['home_card_3_title'] ?? ''),
                        'text' => (string)($pageContent['home_card_3_text'] ?? ''),
                    ],
                ];
                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-body text-center">
                                <div class="logo-area mb-4">
                                    <?php if ($leftLogoPath): ?>
                                    <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                    <?php else: ?>
                                    <div class="logo-placeholder">HONTTECH Logo</div>
                                    <?php endif; ?>
                                    <?php if ($rightLogoPath): ?>
                                    <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                    <?php else: ?>
                                    <div class="logo-placeholder">DAHIFT Logo</div>
                                    <?php endif; ?>
                                </div>
                                <h1><?php echo htmlspecialchars((string)($pageContent['home_banner_title'] ?? '')); ?></h1>
                                <h2><?php echo htmlspecialchars((string)($pageContent['home_banner_subtitle'] ?? '')); ?></h2>
                                <div class="affiliation fw-bold mb-3"><?php echo htmlspecialchars((string)($pageContent['home_affiliation_text'] ?? '')); ?></div>
                                <h3><?php echo htmlspecialchars((string)($pageContent['home_form_title'] ?? '')); ?></h3>

                                <div class="alert alert-info text-start mt-4">
                                    <strong>Instructions:</strong> <?php echo portalRenderText((string)($pageContent['home_instruction_text'] ?? '')); ?>
                                </div>

                                <div class="mt-4">
                                    <a href="<?php echo htmlspecialchars((string)($pageContent['home_apply_button_link'] ?? '?page=register')); ?>" class="btn btn-primary btn-lg me-2"><?php echo htmlspecialchars((string)($pageContent['home_apply_button_text'] ?? 'Apply Now')); ?></a>
                                    <a href="<?php echo htmlspecialchars((string)($pageContent['home_login_button_link'] ?? '?page=login')); ?>" class="btn btn-outline-primary btn-lg"><?php echo htmlspecialchars((string)($pageContent['home_login_button_text'] ?? 'Login')); ?></a>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="row mt-4">
                    <?php foreach ($homeCards as $card): ?>
                        <div class="col-md-4">
                            <div class="card h-100">
                                <div class="card-body text-center">
                                    <i class="<?php echo htmlspecialchars((string)$card['icon']); ?>" style="font-size: 3rem; color: var(--primary-color);"></i>
                                    <h4 class="mt-3"><?php echo htmlspecialchars((string)$card['title']); ?></h4>
                                    <p><?php echo htmlspecialchars((string)$card['text']); ?></p>
                                </div>
                            </div>
                        </div>
                    <?php endforeach; ?>
                </div>
                <?php
                break;

            case 'about':
                if (!portalManagedContentEnabled($pdo, 'about')) {
                    echo '<div class="row"><div class="col-md-12"><div class="alert alert-warning">' . e(portalManagedContentDisabledMessage($pdo, 'about')) . '</div></div></div>';
                    break;
                }
                $pageContent = portalPageContent($pdo);
                $aboutBenefits = portalPageLines((string)($pageContent['about_benefits_list'] ?? ''));
                $aboutRequirements = portalPageLines((string)($pageContent['about_requirements_list'] ?? ''));
                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header">
                                <?php echo htmlspecialchars((string)($pageContent['about_page_title'] ?? 'About')); ?>
                            </div>
                            <div class="card-body doc-stamp-wrap">
                                <?php if (!empty($stampPath)): ?>
                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="doc-stamp" alt="Official Stamp">
                                <?php endif; ?>

                                <h4><?php echo htmlspecialchars((string)($pageContent['about_intro_title'] ?? '')); ?></h4>
                                <p><?php echo portalRenderText((string)($pageContent['about_intro_text'] ?? '')); ?></p>

                                <h4 class="mt-4"><?php echo htmlspecialchars((string)($pageContent['about_programme_title'] ?? '')); ?></h4>
                                <p><?php echo portalRenderText((string)($pageContent['about_programme_text'] ?? '')); ?></p>

                                <h4 class="mt-4"><?php echo htmlspecialchars((string)($pageContent['about_affiliation_title'] ?? '')); ?></h4>
                                <p><?php echo portalRenderText((string)($pageContent['about_affiliation_text'] ?? '')); ?></p>

                                <h4 class="mt-4"><?php echo htmlspecialchars((string)($pageContent['about_benefits_title'] ?? '')); ?></h4>
                                <ul>
                                    <?php foreach ($aboutBenefits as $line): ?>
                                        <li><?php echo htmlspecialchars((string)$line); ?></li>
                                    <?php endforeach; ?>
                                </ul>

                                <h4 class="mt-4"><?php echo htmlspecialchars((string)($pageContent['about_requirements_title'] ?? '')); ?></h4>
                                <ul>
                                    <?php foreach ($aboutRequirements as $line): ?>
                                        <li><?php echo htmlspecialchars((string)$line); ?></li>
                                    <?php endforeach; ?>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

            case 'login':
                ?>
                <?php
                $flashSuccess = $_SESSION['flash_success'] ?? '';
                $flashError = $_SESSION['flash_error'] ?? '';
                unset($_SESSION['flash_success'], $_SESSION['flash_error']);
                $loginMode = (string)($_GET['mode'] ?? 'password');
                ?>
                <style>
                    .student-preview-card{display:none;align-items:center;gap:14px;width:100%;padding:16px 18px;margin:.7rem 0 1rem;border:1px dashed #9ad39f;border-radius:18px;background:#f3f6f3;box-shadow:none;transition:all .2s ease}
                    .student-preview-card.is-visible{display:flex}
                    .student-preview-avatar{width:68px;height:68px;border-radius:50%;overflow:hidden;background:#dde5df;flex:0 0 68px;border:3px solid #ffffff;box-shadow:0 4px 12px rgba(0,0,0,.08)}
                    .student-preview-avatar img{width:100%;height:100%;object-fit:cover;display:block}
                    .student-preview-body{min-width:0;display:flex;flex-direction:column;justify-content:center}
                    .student-preview-label{display:none}
                    .student-preview-name{font-size:1.1rem;font-weight:800;color:#0f5c2c;line-height:1.2;margin:0 0 4px;word-break:break-word}
                    .student-preview-meta{font-size:.98rem;color:#4e5d57;line-height:1.2;word-break:break-word}
                    .student-preview-meta strong{font-weight:500;color:inherit}
                    .student-preview-status{min-height:18px;margin-top:6px;font-size:.86rem;color:#5f6f67}
                    .student-preview-status.is-checking{color:#0d6efd}
                    .student-auth-btn.is-loading{pointer-events:none;opacity:.9}
                    @media (max-width: 575.98px){
                        .student-preview-card{padding:14px 15px;gap:12px;border-radius:16px}
                        .student-preview-avatar{width:64px;height:64px;flex-basis:64px}
                        .student-preview-name{font-size:1rem}
                        .student-preview-meta{font-size:.9rem}
                    }
                </style>
                
                <div class="auth-bg" style="background-image:url('data:image/webp;base64, ');">
                    <div class="auth-inner">
                        <div class="row justify-content-center">
                    <div class="col-md-6">
                        <div class="card">
                            <div class="card-header">
                                Student Login
                            </div>
                            <div class="card-body">
                                <?php if (!empty($flashSuccess)): ?>
                                    <div class="alert alert-success"><?php echo htmlspecialchars($flashSuccess); ?></div>
                                <?php endif; ?>
                                <?php if (!empty($flashError)): ?>
                                    <div class="alert alert-danger"><?php echo htmlspecialchars($flashError); ?></div>
                                <?php endif; ?>
                                <?php if (isset($loginError) && $loginError): ?>
                                    <div class="alert alert-danger"><?php echo htmlspecialchars($loginError); ?></div>
                                <?php endif; ?>

                                <ul class="nav nav-tabs mb-3" role="tablist">
                                    <li class="nav-item" role="presentation">
                                        <button class="nav-link <?php echo ($loginMode !== 'otp') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#login_pw" type="button" role="tab">Password</button>
                                    </li>
                                    <li class="nav-item" role="presentation">
                                        <button class="nav-link <?php echo ($loginMode === 'otp') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#login_otp" type="button" role="tab">OTP</button>
                                    </li>
                                </ul>

                                <div class="tab-content">
                                    <div class="tab-pane fade <?php echo ($loginMode !== 'otp') ? 'show active' : ''; ?>" id="login_pw" role="tabpanel">
                                        <form method="post" action="" class="student-identity-preview-form" data-preview-scope="student-password">
                                            <input type="hidden" name="action" value="login">
                                            <input type="hidden" name="user_type" value="student">

                                            <div class="mb-3">
                                                <label for="username" class="form-label">Matric No. / Matriculation No. / Student ID / Phone No. / Email</label>
                                                <input type="text" class="form-control student-identity-input" id="username" name="username" required autocomplete="username" data-student-identity-input="true" data-preview-scope="student-password">
                                                <div class="form-text">Use the email address or phone number linked to your account, or your Matric No. once assigned.</div>
                                                <div class="student-preview-status" data-student-preview-status aria-live="polite"></div>
                                            </div>

                                            <div class="student-preview-card" data-student-preview hidden aria-live="polite">
                                                <div class="student-preview-avatar"><img src="" alt="Student passport preview" data-student-preview-photo></div>
                                                <div class="student-preview-body">
                                                    <div class="student-preview-label">Student identity preview</div>
                                                    <div class="student-preview-name" data-student-preview-name></div>
                                                    <div class="student-preview-meta"><span data-student-preview-label>Matric No.</span>: <strong data-student-preview-matric></strong></div>
                                                </div>
                                            </div>

                                            <div class="mb-3">
                                                <label for="password" class="form-label">Password</label>
                                                <input type="password" class="form-control" id="password" name="password" required autocomplete="current-password">
                                            </div>

                                            <div class="form-check mb-3">
                                                <input class="form-check-input" type="checkbox" name="remember_me" id="studentRememberMe" value="1">
                                                <label class="form-check-label" for="studentRememberMe">Remember me for 30 days</label>
                                            </div>

                                            <div class="d-grid">
                                                <button type="submit" class="btn btn-primary student-auth-btn" data-loading-text="Logging in...">Login</button>
                                            </div>
                                        </form>
                                    </div>

                                    <div class="tab-pane fade <?php echo ($loginMode === 'otp') ? 'show active' : ''; ?>" id="login_otp" role="tabpanel">
                                        <form method="post" action="" class="mb-3 student-identity-preview-form" data-preview-scope="student-otp">
                                            <input type="hidden" name="action" value="request_login_otp">
                                            <input type="hidden" name="user_type" value="student">
                                            <div class="mb-3">
                                                <label class="form-label">Matric No. / Matriculation No. / Student ID / Phone No. / Email</label>
                                                <input class="form-control student-identity-input" name="identity" required autocomplete="username" data-student-identity-input="true" data-preview-scope="student-otp">
                                                <div class="form-text">Use your Matric No., Matriculation No., Student ID, phone number, or email address. We will send a 6-digit OTP to the email on your account.</div>
                                                <div class="student-preview-status" data-student-preview-status aria-live="polite"></div>
                                            </div>
                                            <div class="student-preview-card" data-student-preview hidden aria-live="polite">
                                                <div class="student-preview-avatar"><img src="" alt="Student passport preview" data-student-preview-photo></div>
                                                <div class="student-preview-body">
                                                    <div class="student-preview-label">Student identity preview</div>
                                                    <div class="student-preview-name" data-student-preview-name></div>
                                                    <div class="student-preview-meta"><span data-student-preview-label>Matric No.</span>: <strong data-student-preview-matric></strong></div>
                                                </div>
                                            </div>
                                            <div class="form-check mb-3">
                                                <input class="form-check-input" type="checkbox" name="remember_me" id="studentOtpRememberMe" value="1">
                                                <label class="form-check-label" for="studentOtpRememberMe">Remember me for 30 days</label>
                                            </div>
                                            <button class="btn btn-outline-primary w-100 student-auth-btn" type="submit" data-loading-text="Sending OTP...">Send OTP</button>
                                        </form>

                                        <?php
                                        $pending = $_SESSION['otp_pending'] ?? null;
                                        $showVerify = is_array($pending) && (($pending['user_type'] ?? '') === 'student');
                                        ?>
                                        <?php if ($showVerify): ?>
                                            <form method="post" action="">
                                                <input type="hidden" name="action" value="verify_login_otp">
                                                <div class="mb-3">
                                                    <label class="form-label">Enter OTP</label>
                                                    <input class="form-control" name="otp" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required>
                                                </div>
                                                <div class="form-check mb-3">
                                                    <input class="form-check-input" type="checkbox" name="remember_me" id="studentOtpVerifyRememberMe" value="1" <?php echo !empty($pending['remember']) ? 'checked' : ''; ?>>
                                                    <label class="form-check-label" for="studentOtpVerifyRememberMe">Remember me for 30 days</label>
                                                </div>
                                                <button class="btn btn-primary w-100 student-auth-btn" type="submit" data-loading-text="Verifying...">Verify &amp; Login</button>
                                            </form>
                                        <?php endif; ?>
                                    </div>
                                </div>

                                <div class="mt-3 text-center">
                                    <p>Don't have an account? <a href="?page=register">Register here</a></p>
                                    <p><a href="?page=forgot_password">Forgot Password?</a> &nbsp;|&nbsp; <a href="?page=change_password">Change Password</a></p>
                                    <hr>
                                    <p class="small mb-0">Staff/Admin? <a href="?page=admin_login">Go to Admin Login</a></p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                </div></div></div>
                <script>
                (function(){
                    const defaultAvatar = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120"><rect width="120" height="120" rx="60" fill="#e9ecef"/><circle cx="60" cy="46" r="24" fill="#adb5bd"/><path d="M24 98c7-19 24-29 36-29s29 10 36 29" fill="#adb5bd"/></svg>');
                    const endpoint = '?ajax=student_identity_preview';
                    const previewCache = new Map();

                    function normalizeIdentityKey(value){
                        value = (value || '').trim();
                        if (value === '') return '';
                        const digits = value.replace(/\D+/g, '');
                        if (digits.length >= 7) {
                            if (digits.indexOf('234') === 0) return digits;
                            if (digits.charAt(0) === '0') return '234' + digits.slice(1);
                            return digits;
                        }
                        return value.toLowerCase();
                    }

                    function isMeaningfulIdentity(value){
                        value = (value || '').trim();
                        if (value === '') return false;
                        if (/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i.test(value)) return true;
                        const digits = value.replace(/\D+/g, '');
                        if (digits.length >= 7) return true;
                        return value.length >= 4;
                    }

                    function setStatus(statusEl, message, checking){
                        if (!statusEl) return;
                        statusEl.textContent = message || '';
                        statusEl.classList.toggle('is-checking', !!checking);
                    }

                    function hidePreview(card){
                        if (!card) return;
                        card.hidden = true;
                        card.classList.remove('is-visible');
                        delete card.dataset.previewKey;
                        const img = card.querySelector('[data-student-preview-photo]');
                        const name = card.querySelector('[data-student-preview-name]');
                        const matric = card.querySelector('[data-student-preview-matric]');
                        const label = card.querySelector('[data-student-preview-label]');
                        if (img) {
                            img.onerror = null;
                            img.src = defaultAvatar;
                        }
                        if (name) name.textContent = '';
                        if (matric) matric.textContent = '';
                        if (label) label.textContent = 'Matric No.';
                    }

                    function showPreview(card, payload, cacheKey){
                        if (!card || !payload || !payload.found) {
                            hidePreview(card);
                            return;
                        }
                        if (cacheKey && card.dataset.previewKey === cacheKey) {
                            card.hidden = false;
                            card.classList.add('is-visible');
                            return;
                        }
                        const img = card.querySelector('[data-student-preview-photo]');
                        const name = card.querySelector('[data-student-preview-name]');
                        const matric = card.querySelector('[data-student-preview-matric]');
                        const label = card.querySelector('[data-student-preview-label]');
                        const photo = (payload.photo || '').trim();
                        if (img) {
                            img.onerror = function(){
                                this.onerror = null;
                                this.src = defaultAvatar;
                            };
                            img.src = photo !== '' ? photo : defaultAvatar;
                        }
                        if (name) name.textContent = payload.fullname || '';
                        if (matric) matric.textContent = payload.identifier_value || payload.matric_number || '';
                        if (label) label.textContent = payload.identifier_label || 'Matric No.';
                        if (cacheKey) card.dataset.previewKey = cacheKey;
                        card.hidden = false;
                        card.classList.add('is-visible');
                    }

                    function bindSubmitLoading(form){
                        if (!form || form.dataset.submitBound === '1') return;
                        form.dataset.submitBound = '1';
                        form.addEventListener('submit', function(){
                            const button = form.querySelector('button[type="submit"].student-auth-btn');
                            if (!button || button.disabled) return;
                            const original = button.dataset.originalText || button.innerHTML;
                            button.dataset.originalText = original;
                            button.disabled = true;
                            button.classList.add('is-loading');
                            button.textContent = button.getAttribute('data-loading-text') || 'Please wait...';
                        });
                    }

                    function bindPreviewForm(form){
                        if (!form || form.dataset.previewBound === '1') return;
                        const input = form.querySelector('.student-identity-input');
                        const card = form.querySelector('[data-student-preview]');
                        const statusEl = form.querySelector('[data-student-preview-status]');
                        if (!input || !card) return;
                        form.dataset.previewBound = '1';

                        let timer = null;
                        let controller = null;
                        let activeRequestKey = '';
                        let requestSerial = 0;
                        hidePreview(card);
                        setStatus(statusEl, '', false);
                        bindSubmitLoading(form);

                        const lookup = function(force){
                            const rawValue = (input.value || '').trim();
                            const cacheKey = normalizeIdentityKey(rawValue);

                            if (!isMeaningfulIdentity(rawValue)) {
                                activeRequestKey = '';
                                if (controller) {
                                    controller.abort();
                                    controller = null;
                                }
                                hidePreview(card);
                                setStatus(statusEl, rawValue === '' ? '' : '');
                                return;
                            }

                            if (!force && cacheKey !== '' && cacheKey === activeRequestKey) {
                                if (previewCache.has(cacheKey)) {
                                    showPreview(card, previewCache.get(cacheKey), cacheKey);
                                    setStatus(statusEl, '', false);
                                }
                                return;
                            }

                            if (cacheKey !== '' && previewCache.has(cacheKey)) {
                                activeRequestKey = cacheKey;
                                showPreview(card, previewCache.get(cacheKey), cacheKey);
                                setStatus(statusEl, '', false);
                                return;
                            }

                            activeRequestKey = cacheKey;
                            setStatus(statusEl, 'Checking...', true);
                            if (controller) controller.abort();
                            controller = new AbortController();
                            const currentRequest = ++requestSerial;

                            fetch(endpoint + '&user_type=student&identity=' + encodeURIComponent(rawValue), {
                                headers: {'X-Requested-With':'XMLHttpRequest', 'Accept':'application/json'},
                                signal: controller.signal,
                                credentials: 'same-origin'
                            })
                            .then(function(res){ return res.ok ? res.json() : {found:false}; })
                            .then(function(data){
                                if (currentRequest !== requestSerial) return;
                                controller = null;
                                if (cacheKey !== '') {
                                    previewCache.set(cacheKey, data || {found:false});
                                }
                                if (data && data.found) {
                                    showPreview(card, data, cacheKey);
                                    setStatus(statusEl, '', false);
                                } else {
                                    hidePreview(card);
                                    setStatus(statusEl, '', false);
                                }
                            })
                            .catch(function(err){
                                if (err && err.name === 'AbortError') return;
                                if (currentRequest !== requestSerial) return;
                                controller = null;
                                hidePreview(card);
                                setStatus(statusEl, '', false);
                            });
                        };

                        input.addEventListener('input', function(){
                            clearTimeout(timer);
                            timer = setTimeout(function(){ lookup(false); }, 120);
                        });
                        input.addEventListener('change', function(){ lookup(true); });
                        input.addEventListener('blur', function(){ lookup(true); });
                        input.addEventListener('paste', function(){
                            clearTimeout(timer);
                            timer = setTimeout(function(){ lookup(true); }, 40);
                        });

                        if (isMeaningfulIdentity((input.value || '').trim())) {
                            setTimeout(function(){ lookup(true); }, 60);
                        }
                    }

                    document.querySelectorAll('.student-identity-preview-form').forEach(bindPreviewForm);
                    document.querySelectorAll('#login_otp form, #login_pw form').forEach(bindSubmitLoading);
                })();
                </script>
                <?php
                break;

            case 'admin_login':
                ?>
                <?php
                $flashSuccess = $_SESSION['flash_success'] ?? '';
                $flashError = $_SESSION['flash_error'] ?? '';
                unset($_SESSION['flash_success'], $_SESSION['flash_error']);
                $loginMode = (string)($_GET['mode'] ?? 'password');
                ?>
                
                <div class="auth-bg" style="background-image:url('data:image/webp;base64, ');">
                    <div class="auth-inner">
                        <div class="row justify-content-center">
                    <div class="col-md-6">
                        <div class="card">
                            <div class="card-header">
                                Admin Login
                            </div>
                            <div class="card-body">
                                <?php if (!empty($flashSuccess)): ?>
                                    <div class="alert alert-success"><?php echo htmlspecialchars($flashSuccess); ?></div>
                                <?php endif; ?>
                                <?php if (!empty($flashError)): ?>
                                    <div class="alert alert-danger"><?php echo htmlspecialchars($flashError); ?></div>
                                <?php endif; ?>
                                <?php if (isset($loginError) && $loginError): ?>
                                    <div class="alert alert-danger"><?php echo htmlspecialchars($loginError); ?></div>
                                <?php endif; ?>

                                <ul class="nav nav-tabs mb-3" role="tablist">
                                    <li class="nav-item" role="presentation">
                                        <button class="nav-link <?php echo ($loginMode !== 'otp') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#admin_pw" type="button" role="tab">Password</button>
                                    </li>
                                    <li class="nav-item" role="presentation">
                                        <button class="nav-link <?php echo ($loginMode === 'otp') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#admin_otp" type="button" role="tab">OTP</button>
                                    </li>
                                </ul>

                                <div class="tab-content">
                                    <div class="tab-pane fade <?php echo ($loginMode !== 'otp') ? 'show active' : ''; ?>" id="admin_pw" role="tabpanel">
                                        <form method="post" action="">
                                            <input type="hidden" name="action" value="login">
                                            <input type="hidden" name="user_type" value="admin">

                                            <div class="mb-3">
                                                <label for="admin_username" class="form-label">Username</label>
                                                <input type="text" class="form-control" id="admin_username" name="username" required autocomplete="username">
                                            </div>

                                            <div class="mb-3">
                                                <label for="admin_password" class="form-label">Password</label>
                                                <input type="password" class="form-control" id="admin_password" name="password" required autocomplete="current-password">
                                            </div>

                                            <div class="d-grid">
                                                <button type="submit" class="btn btn-primary">Login</button>
                                            </div>
                                        </form>
                                    </div>

                                    <div class="tab-pane fade <?php echo ($loginMode === 'otp') ? 'show active' : ''; ?>" id="admin_otp" role="tabpanel">
                                        <form method="post" action="" class="mb-3">
                                            <input type="hidden" name="action" value="request_login_otp">
                                            <input type="hidden" name="user_type" value="admin">
                                            <div class="mb-3">
                                                <label class="form-label">Username or Email</label>
                                                <input class="form-control" name="identity" required autocomplete="username">
                                                <div class="form-text">Use your Matric No., Matriculation No., Student ID, phone number, or email address. We will send a 6-digit OTP to the email on your account.</div>
                                            </div>
                                            <button class="btn btn-outline-primary w-100" type="submit">Send OTP</button>
                                        </form>

                                        <?php
                                        $pending = $_SESSION['otp_pending'] ?? null;
                                        $showVerify = is_array($pending) && (($pending['user_type'] ?? '') === 'admin');
                                        ?>
                                        <?php if ($showVerify): ?>
                                            <form method="post" action="">
                                                <input type="hidden" name="action" value="verify_login_otp">
                                                <div class="mb-3">
                                                    <label class="form-label">Enter OTP</label>
                                                    <input class="form-control" name="otp" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required>
                                                </div>
                                                <button class="btn btn-primary w-100" type="submit">Verify &amp; Login</button>
                                            </form>
                                        <?php endif; ?>
                                    </div>
                                </div>

                                <div class="mt-3 text-center">
                                    <p class="mb-0"><a href="?page=login">Back to Student Login</a></p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                </div></div></div>
                <?php
                break;


case 'change_password':
                $cpFlashSuccess = $_SESSION['flash_success'] ?? '';
                $cpFlashError = $_SESSION['flash_error'] ?? '';
                unset($_SESSION['flash_success'], $_SESSION['flash_error']);
                ?>
                <style>
                    .student-preview-card{display:none;align-items:center;gap:14px;width:100%;padding:16px 18px;margin:.7rem 0 1rem;border:1px dashed #9ad39f;border-radius:18px;background:#f3f6f3;box-shadow:none;transition:all .2s ease}
                    .student-preview-card.is-visible{display:flex}
                    .student-preview-avatar{width:68px;height:68px;border-radius:50%;overflow:hidden;background:#dde5df;flex:0 0 68px;border:3px solid #ffffff;box-shadow:0 4px 12px rgba(0,0,0,.08)}
                    .student-preview-avatar img{width:100%;height:100%;object-fit:cover;display:block}
                    .student-preview-body{min-width:0;display:flex;flex-direction:column;justify-content:center}
                    .student-preview-name{font-size:1.1rem;font-weight:800;color:#0f5c2c;line-height:1.2;margin:0 0 4px;word-break:break-word}
                    .student-preview-meta{font-size:.98rem;color:#4e5d57;line-height:1.2;word-break:break-word}
                </style>
                <div class="row justify-content-center">
                    <div class="col-md-7">
                        <div class="card">
                            <div class="card-header">Change Password</div>
                            <div class="card-body">
                                <?php if ($cpFlashSuccess): ?>
                                    <div class="alert alert-success"><?php echo htmlspecialchars($cpFlashSuccess); ?></div>
                                <?php endif; ?>
                                <?php if ($cpFlashError): ?>
                                    <div class="alert alert-danger"><?php echo htmlspecialchars($cpFlashError); ?></div>
                                <?php endif; ?>

                                <form method="post" action="" class="student-identity-preview-form" data-preview-scope="student-change-password">
                                    <input type="hidden" name="action" value="change_password">
                                    <div class="mb-3">
                                        <label class="form-label">Matric No. / Matriculation No. / Student ID / Phone No. / Email</label>
                                        <input class="form-control student-identity-input" name="identifier" required data-student-identity-input="true" data-preview-scope="student-change-password">
                                    </div>
                                    <div class="student-preview-card" data-student-preview hidden aria-live="polite">
                                        <div class="student-preview-avatar"><img src="" alt="Student passport preview" data-student-preview-photo></div>
                                        <div class="student-preview-body">
                                            <div class="student-preview-name" data-student-preview-name></div>
                                            <div class="student-preview-meta"><span data-student-preview-label>Matric No.</span>: <strong data-student-preview-matric></strong></div>
                                        </div>
                                    </div>
                                    <div class="mb-3">
                                        <label class="form-label">Old Password</label>
                                        <input type="password" class="form-control" name="old_password" required>
                                    </div>
                                    <div class="mb-3">
                                        <label class="form-label">New Password</label>
                                        <input type="password" class="form-control" name="new_password" required>
                                    </div>
                                    <div class="mb-3">
                                        <label class="form-label">Confirm New Password</label>
                                        <input type="password" class="form-control" name="confirm_password" required>
                                    </div>
                                    <div class="form-text mb-3">Password must be at least 8 characters and include uppercase, lowercase, and a number.</div>
                                    <button class="btn btn-primary w-100" type="submit">Change Password</button>
                                </form>

                                <div class="mt-3 text-center">
                                    <a href="?page=login">Back to Login</a>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <script>
                (function(){
                    const defaultAvatar = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120"><rect width="120" height="120" rx="60" fill="#e9ecef"/><circle cx="60" cy="46" r="24" fill="#adb5bd"/><path d="M24 98c7-19 24-29 36-29s29 10 36 29" fill="#adb5bd"/></svg>');
                    const endpoint = '?ajax=student_identity_preview';
                    function hidePreview(card){ if(!card) return; card.hidden = true; card.classList.remove('is-visible'); const img = card.querySelector('[data-student-preview-photo]'); const name = card.querySelector('[data-student-preview-name]'); const matric = card.querySelector('[data-student-preview-matric]'); if(img){ img.onerror = null; img.src = defaultAvatar; } if(name) name.textContent=''; if(matric) matric.textContent=''; }
                    function showPreview(card,payload){ if(!card || !payload || !payload.found){ hidePreview(card); return; } const img = card.querySelector('[data-student-preview-photo]'); const name = card.querySelector('[data-student-preview-name]'); const matric = card.querySelector('[data-student-preview-matric]'); if(img){ img.onerror = function(){ this.onerror = null; this.src = defaultAvatar; }; img.src = (payload.photo || '').trim() !== '' ? payload.photo : defaultAvatar; } if(name) name.textContent = payload.name || payload.fullname || ''; if(matric) matric.textContent = payload.matric_no || ''; card.hidden = false; card.classList.add('is-visible'); }
                    function bindPreview(form){ if(!form) return; const input = form.querySelector('[data-student-identity-input="true"]'); const card = form.querySelector('[data-student-preview]'); if(!input || !card) return; let timer = null; input.addEventListener('input', function(){ const value = input.value.trim(); clearTimeout(timer); if(value.length < 2){ hidePreview(card); return; } timer = setTimeout(function(){ fetch(endpoint + '&identity=' + encodeURIComponent(value), {headers:{'Accept':'application/json'}}).then(r => r.ok ? r.json() : null).then(data => showPreview(card,data)).catch(() => hidePreview(card)); }, 220); }); }
                    bindPreview(document.querySelector('.student-identity-preview-form[data-preview-scope="student-change-password"]'));
                })();
                </script>
                <?php
                break;

case 'forgot_password':
                $notice = $_SESSION['password_reset_notice'] ?? ($_SESSION['flash_success'] ?? '');
                unset($_SESSION['password_reset_notice'], $_SESSION['flash_success']);
                $err = $_SESSION['reset_error'] ?? ($_SESSION['flash_error'] ?? '');
                unset($_SESSION['reset_error'], $_SESSION['flash_error']);
                ?>
                <style>
                    .student-preview-card{display:none;align-items:center;gap:14px;width:100%;padding:16px 18px;margin:.7rem 0 1rem;border:1px dashed #9ad39f;border-radius:18px;background:#f3f6f3;box-shadow:none;transition:all .2s ease}
                    .student-preview-card.is-visible{display:flex}
                    .student-preview-avatar{width:68px;height:68px;border-radius:50%;overflow:hidden;background:#dde5df;flex:0 0 68px;border:3px solid #ffffff;box-shadow:0 4px 12px rgba(0,0,0,.08)}
                    .student-preview-avatar img{width:100%;height:100%;object-fit:cover;display:block}
                    .student-preview-body{min-width:0;display:flex;flex-direction:column;justify-content:center}
                    .student-preview-label{display:none}
                    .student-preview-name{font-size:1.1rem;font-weight:800;color:#0f5c2c;line-height:1.2;margin:0 0 4px;word-break:break-word}
                    .student-preview-meta{font-size:.98rem;color:#4e5d57;line-height:1.2;word-break:break-word}
                    .student-preview-meta strong{font-weight:500;color:inherit}
                    @media (max-width: 575.98px){
                        .student-preview-card{padding:14px 15px;gap:12px;border-radius:16px}
                        .student-preview-avatar{width:64px;height:64px;flex-basis:64px}
                        .student-preview-name{font-size:1rem}
                        .student-preview-meta{font-size:.9rem}
                    }
                </style>
                <div class="row justify-content-center">
                    <div class="col-md-7">
                        <div class="card">
                            <div class="card-header">Forgot Password</div>
                            <div class="card-body">
                                <?php if ($notice): ?>
                                    <div class="alert alert-success"><?php echo htmlspecialchars($notice); ?></div>
                                <?php endif; ?>
                                <?php if ($err): ?>
                                    <div class="alert alert-danger"><?php echo htmlspecialchars($err); ?></div>
                                <?php endif; ?>

                                <p class="text-muted">Enter your Matric No. / Matriculation No. / Student ID / Phone No. / Email (student) or username / email (admin). Students will receive a reset OTP by email, while admins receive a reset link by email.</p>

                                <ul class="nav nav-tabs mb-3" role="tablist">
                                    <li class="nav-item" role="presentation">
                                        <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#fp_student" type="button" role="tab">Student</button>
                                    </li>
                                    <li class="nav-item" role="presentation">
                                        <button class="nav-link" data-bs-toggle="tab" data-bs-target="#fp_admin" type="button" role="tab">Admin</button>
                                    </li>
                                </ul>

                                <div class="tab-content">
                                    <div class="tab-pane fade show active" id="fp_student" role="tabpanel">
                                        <?php
                                        $studentResetIdentity = $_SESSION['student_reset_identity'] ?? '';
                                        $studentResetVerified = (int)($_SESSION['student_reset_verified_id'] ?? 0);
                                        ?>
                                        <form method="post" action="" class="student-identity-preview-form mb-3" data-preview-scope="student-reset">
                                            <input type="hidden" name="action" value="request_password_reset_otp">
                                            <div class="mb-3">
                                                <label class="form-label">Matric No. / Matriculation No. / Student ID / Phone No. / Email</label>
                                                <input class="form-control student-identity-input" name="identity" value="<?php echo htmlspecialchars((string)$studentResetIdentity); ?>" required data-student-identity-input="true" data-preview-scope="student-reset">
                                                <div class="form-text">Use your Matric No., Student ID, phone number, or email address. A reset OTP will be sent to your registered email.</div>
                                            </div>
                                            <div class="student-preview-card" data-student-preview hidden aria-live="polite">
                                                <div class="student-preview-avatar"><img src="" alt="Student passport preview" data-student-preview-photo></div>
                                                <div class="student-preview-body">
                                                    <div class="student-preview-label">Student identity preview</div>
                                                    <div class="student-preview-name" data-student-preview-name></div>
                                                    <div class="student-preview-meta"><span data-student-preview-label>Matric No.</span>: <strong data-student-preview-matric></strong></div>
                                                </div>
                                            </div>
                                            <button class="btn btn-primary w-100" type="submit">Send Reset OTP</button>
                                        </form>

                                        <?php if (!empty($studentResetIdentity) && $studentResetVerified <= 0): ?>
                                            <form method="post" action="" class="mb-3">
                                                <input type="hidden" name="action" value="verify_password_reset_otp">
                                                <input type="hidden" name="identity" value="<?php echo htmlspecialchars((string)$studentResetIdentity); ?>">
                                                <div class="mb-3">
                                                    <label class="form-label">Enter Reset OTP</label>
                                                    <input class="form-control" name="otp" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required>
                                                </div>
                                                <button class="btn btn-outline-primary w-100" type="submit">Verify OTP</button>
                                            </form>
                                        <?php endif; ?>

                                        <?php if ($studentResetVerified > 0 && !empty($studentResetIdentity)): ?>
                                            <form method="post" action="">
                                                <input type="hidden" name="action" value="perform_password_reset_otp">
                                                <input type="hidden" name="identity" value="<?php echo htmlspecialchars((string)$studentResetIdentity); ?>">
                                                <div class="mb-3">
                                                    <label class="form-label">New Password</label>
                                                    <input type="password" class="form-control" name="new_password" required>
                                                </div>
                                                <div class="mb-3">
                                                    <label class="form-label">Confirm New Password</label>
                                                    <input type="password" class="form-control" name="confirm_password" required>
                                                </div>
                                                <div class="form-text mb-3">Password must be at least 8 characters and include uppercase, lowercase, and a number.</div>
                                                <button class="btn btn-success w-100" type="submit">Reset Password</button>
                                            </form>
                                        <?php endif; ?>
                                    </div>
                                    <div class="tab-pane fade" id="fp_admin" role="tabpanel">
                                        <form method="post" action="">
                                            <input type="hidden" name="action" value="request_password_reset">
                                            <input type="hidden" name="user_type" value="admin">
                                            <div class="mb-3">
                                                <label class="form-label">Username or Email</label>
                                                <input class="form-control" name="identity" required>
                                            </div>
                                            <button class="btn btn-primary w-100" type="submit">Send Reset Link</button>
                                        </form>
                                    </div>
                                </div>

                                <div class="mt-3 text-center">
                                    <a href="?page=login">Back to Login</a>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <script>
                (function(){
                    const defaultAvatar = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120"><rect width="120" height="120" rx="60" fill="#e9ecef"/><circle cx="60" cy="46" r="24" fill="#adb5bd"/><path d="M24 98c7-19 24-29 36-29s29 10 36 29" fill="#adb5bd"/></svg>');
                    const endpoint = '?ajax=student_identity_preview';

                    function hidePreview(card){
                        if (!card) return;
                        card.hidden = true;
                        card.classList.remove('is-visible');
                        const img = card.querySelector('[data-student-preview-photo]');
                        const name = card.querySelector('[data-student-preview-name]');
                        const matric = card.querySelector('[data-student-preview-matric]');
                        if (img) {
                            img.onerror = null;
                            img.src = defaultAvatar;
                        }
                        if (name) name.textContent = '';
                        if (matric) matric.textContent = '';
                        if (label) label.textContent = 'Matric No.';
                    }

                    function showPreview(card, payload){
                        if (!card || !payload || !payload.found) {
                            hidePreview(card);
                            return;
                        }
                        const img = card.querySelector('[data-student-preview-photo]');
                        const name = card.querySelector('[data-student-preview-name]');
                        const matric = card.querySelector('[data-student-preview-matric]');
                        const photo = (payload.photo || '').trim();
                        if (img) {
                            img.onerror = function(){
                                this.onerror = null;
                                this.src = defaultAvatar;
                            };
                            img.src = photo !== '' ? photo : defaultAvatar;
                        }
                        if (name) name.textContent = payload.fullname || '';
                        if (matric) matric.textContent = payload.identifier_value || payload.matric_number || '';
                        if (label) label.textContent = payload.identifier_label || 'Matric No.';
                        card.hidden = false;
                        card.classList.add('is-visible');
                    }

                    function bindPreviewForm(form){
                        if (!form || form.dataset.previewBound === '1') return;
                        const input = form.querySelector('.student-identity-input');
                        const card = form.querySelector('[data-student-preview]');
                        if (!input || !card) return;
                        form.dataset.previewBound = '1';

                        let timer = null;
                        let controller = null;
                        hidePreview(card);

                        const lookup = function(){
                            const value = (input.value || '').trim();
                            if (value.length < 2) {
                                hidePreview(card);
                                return;
                            }
                            if (controller) controller.abort();
                            controller = new AbortController();

                            fetch(endpoint + '&user_type=student&identity=' + encodeURIComponent(value), {
                                headers: {'X-Requested-With':'XMLHttpRequest', 'Accept':'application/json'},
                                signal: controller.signal,
                                credentials: 'same-origin'
                            })
                            .then(function(res){ return res.ok ? res.json() : {found:false}; })
                            .then(function(data){ showPreview(card, data); })
                            .catch(function(err){
                                if (err && err.name === 'AbortError') return;
                                hidePreview(card);
                            });
                        };

                        input.addEventListener('input', function(){
                            clearTimeout(timer);
                            timer = setTimeout(lookup, 220);
                        });
                        input.addEventListener('change', lookup);
                        input.addEventListener('blur', lookup);
                        input.addEventListener('paste', function(){
                            clearTimeout(timer);
                            timer = setTimeout(lookup, 80);
                        });

                        if ((input.value || '').trim().length >= 2) {
                            setTimeout(lookup, 120);
                        }
                    }

                    document.querySelectorAll('.student-identity-preview-form').forEach(bindPreviewForm);
                })();
                </script>
                <?php
                break;

            case 'reset_password':
                $token = trim((string)($_GET['token'] ?? ''));
                $err = $_SESSION['reset_error'] ?? '';
                unset($_SESSION['reset_error']);
                ?>
                <div class="row justify-content-center">
                    <div class="col-md-6">
                        <div class="card">
                            <div class="card-header">Reset Password</div>
                            <div class="card-body">
                                <?php if ($err): ?>
                                    <div class="alert alert-danger"><?php echo htmlspecialchars($err); ?></div>
                                <?php endif; ?>

                                <?php
                                $valid = false;
                                if ($token !== '') {
                                    try { $valid = (bool)findValidPasswordReset($pdo, $token); } catch (Throwable $e) { $valid = false; }
                                }
                                ?>

                                <?php if (!$valid): ?>
                                    <div class="alert alert-warning">This reset link is invalid or has expired. Please request a new reset link.</div>
                                    <a class="btn btn-primary w-100" href="?page=forgot_password">Request New Link</a>
                                <?php else: ?>
                                    <form method="post" action="">
                                        <input type="hidden" name="action" value="reset_password">
                                        <input type="hidden" name="token" value="<?php echo htmlspecialchars($token); ?>">
                                        <div class="mb-3">
                                            <label class="form-label">New Password</label>
                                            <input type="password" class="form-control" name="new_password" minlength="6" required>
                                        </div>
                                        <div class="mb-3">
                                            <label class="form-label">Confirm New Password</label>
                                            <input type="password" class="form-control" name="confirm_password" minlength="6" required>
                                        </div>
                                        <button class="btn btn-success w-100" type="submit">Update Password</button>
                                    </form>
                                <?php endif; ?>

                                <div class="mt-3 text-center">
                                    <a href="?page=login">Back to Login</a>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

case 'register':
    portalEnsureApplicationAcademicFieldSupport($pdo);
    $registrationPageAccess = portalRegistrationPageAvailability($pdo, [
        'session_name' => portalCurrentAcademicSessionName($pdo),
        'faculty_id' => (int)($_POST['faculty_id'] ?? $_GET['faculty_id'] ?? 0),
        'department_id' => (int)($_POST['department_id'] ?? $_GET['department_id'] ?? 0),
        'programme_id' => (int)($_POST['programme_id'] ?? $_GET['programme_id'] ?? 0),
        'mode_of_study' => trim((string)($_POST['mode_of_study'] ?? $_GET['mode_of_study'] ?? '')),
    ]);
    $registrationFormEnabled = !empty($registrationPageAccess['enabled']);
    $registrationFormDisabledMessage = trim((string)($registrationPageAccess['message'] ?? 'Student registration is currently disabled by the administrator. Please try again later.'));
    if ($registrationFormDisabledMessage === '') {
        $registrationFormDisabledMessage = 'Student registration is currently disabled by the administrator. Please try again later.';
    }
    $registerFaculties = [];
    try {
        $registerFaculties = $pdo->query("SELECT id, name FROM faculties WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {
        $registerFaculties = [];
    }
    $registerModeOptions = ['Full-Time', 'Part-Time'];
    try {
        foreach (portalFetchDistinctProgrammeOptions($pdo) as $__registerProgrammeRow) {
            $__modeValue = trim((string)($__registerProgrammeRow['mode_of_study'] ?? portalProgrammeModeOfStudyDisplay((array)$__registerProgrammeRow, 'Full-Time')));
            if ($__modeValue !== '' && !in_array($__modeValue, $registerModeOptions, true)) {
                $registerModeOptions[] = $__modeValue;
            }
        }
    } catch (Throwable $e) {}
    ?>
    <div class="row">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header d-flex justify-content-between align-items-center">
                    <span>Student Registration</span>
                    <span class="small text-white">Create your account with your email address and phone number using the same academic structure and validation as the Register Form page.</span>
                </div>
                <div class="card-body">
                    <?php if (!empty($registrationError ?? '')): ?>
                        <div class="alert alert-danger"><?php echo htmlspecialchars((string)$registrationError); ?></div>
                    <?php endif; ?>
                    <?php if (!$registrationFormEnabled): ?>
                        <div class="alert alert-warning border-warning mb-0">
                            <strong>Registration Page Disabled:</strong> <?php echo e($registrationFormDisabledMessage); ?>
                        </div>
                    <?php else: ?>
                    <?php
                        $registerRegistrationFee = (float)(portalManagedFeeRuleForContext($pdo, 'registration', ['session_name' => portalCurrentAcademicSessionName($pdo), 'semester' => portalCurrentAcademicSemesterName($pdo)])['amount'] ?? 0);
                        $registerAcceptanceFee = (float)(portalManagedFeeRuleForContext($pdo, 'acceptance', ['session_name' => portalCurrentAcademicSessionName($pdo), 'semester' => portalCurrentAcademicSemesterName($pdo)])['amount'] ?? 0);
                        $registerHostelFee = (float)(portalManagedFeeRuleForContext($pdo, 'hostel', ['session_name' => portalCurrentAcademicSessionName($pdo), 'semester' => portalCurrentAcademicSemesterName($pdo)])['amount'] ?? 0);
                        $registerSchoolRule = function_exists('portalSchoolFeeRuleForContext')
                            ? portalSchoolFeeRuleForContext($pdo, [
                                'session_name' => portalCurrentAcademicSessionName($pdo),
                                'semester' => portalCurrentAcademicSemesterName($pdo),
                            ])
                            : ['amount' => (float)getSystemSettingValue($pdo, 'school_fee', '0')];
                        $registerSchoolFee = (float)($registerSchoolRule['amount'] ?? getSystemSettingValue($pdo, 'school_fee', '0'));
                    ?>
                    <div class="row mb-3">
                        <div class="col-12">
                            <div class="border rounded p-3 bg-light">
                                <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
                                    <div>
                                        <h6 class="mb-1"><i class="bi bi-cash-coin"></i> Current Fees</h6>
                                        <div class="small text-muted">Displayed from the current fee setup / Fees Manager logic.</div>
                                    </div>
                                    <div class="d-flex flex-wrap gap-3">
                                        <div><strong>Registration Fee:</strong> ₦<?php echo number_format($registerRegistrationFee, 2); ?></div>
                                        <div><strong>Acceptance Fee:</strong> ₦<?php echo number_format($registerAcceptanceFee, 2); ?></div>
                                        <div><strong>Hostel:</strong> ₦<?php echo number_format($registerHostelFee, 2); ?></div>
                                        <div><strong>School:</strong> ₦<?php echo number_format($registerSchoolFee, 2); ?></div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <?php if (!$registrationFormEnabled): ?>
                        <div class="alert alert-warning border-warning">
                            <strong>Registration Form Disabled:</strong> <?php echo e($registrationFormDisabledMessage); ?>
                        </div>
                    <?php endif; ?>

                    <form method="post" action="" enctype="multipart/form-data" id="publicRegisterForm">
                        <input type="hidden" name="action" value="register">

                        <div class="row">
                            <div class="col-md-3 mb-3">
                                <label class="form-label">School</label>
                                <select class="form-select" name="faculty_id" required>
                                    <option value="">Select School</option>
                                    <?php foreach ($registerFaculties as $facultyRow): ?>
                                        <option value="<?php echo (int)($facultyRow['id'] ?? 0); ?>" <?php echo ((int)($_POST['faculty_id'] ?? 0) === (int)($facultyRow['id'] ?? 0)) ? 'selected' : ''; ?>>
                                            <?php echo e((string)($facultyRow['name'] ?? '')); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <div class="form-text">Choose your school first. Only linked course options will be shown.</div>
                            </div>
                            <div class="col-md-3 mb-3">
                                <label class="form-label">Course of Study</label>
                                <select class="form-select" name="department_id" required>
                                    <option value="">Select Course of Study</option>
                                    <?php if ((int)($_POST['department_id'] ?? 0) > 0): ?>
                                        <option value="<?php echo (int)($_POST['department_id'] ?? 0); ?>" selected><?php echo e((string)portalDepartmentNameById($pdo, (int)($_POST['department_id'] ?? 0), 'Current Selection')); ?></option>
                                    <?php endif; ?>
                                </select>
                            </div>
                            <div class="col-md-3 mb-3">
                                <label class="form-label">Program</label>
                                <select class="form-select" name="programme_id" required>
                                    <option value="">Select Program</option>
                                    <?php if ((int)($_POST['programme_id'] ?? 0) > 0): ?>
                                        <option value="<?php echo (int)($_POST['programme_id'] ?? 0); ?>" selected><?php echo e((string)portalProgrammeSelectorLabelById($pdo, (int)($_POST['programme_id'] ?? 0), (int)($_POST['faculty_id'] ?? 0), (int)($_POST['department_id'] ?? 0), 'Current Selection')); ?></option>
                                    <?php endif; ?>
                                </select>
                            </div>
                            <div class="col-md-3 mb-3">
                                <label class="form-label">Mode of Study</label>
                                <?php
                                    $selectedRegisterModeOfStudy = trim((string)($_POST['mode_of_study'] ?? (($registrationAcademic['mode_of_study'] ?? 'Full-Time'))));
                                    if ($selectedRegisterModeOfStudy === '') $selectedRegisterModeOfStudy = 'Full-Time';
                                    if (!in_array($selectedRegisterModeOfStudy, $registerModeOptions, true)) {
                                        $registerModeOptions[] = $selectedRegisterModeOfStudy;
                                    }
                                ?>
                                <select class="form-select" name="mode_of_study" data-mode-of-study-preview="1">
                                    <?php foreach ($registerModeOptions as $__modeOption): ?>
                                        <option value="<?php echo e($__modeOption); ?>" <?php echo ($selectedRegisterModeOfStudy === $__modeOption) ? 'selected' : ''; ?>>
                                            <?php echo e($__modeOption); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <div class="form-text">Default is filled from the selected program. You can change it if needed.</div>
                            </div>

                            <div class="col-md-4 mb-3">
                                <label for="surname" class="form-label">Surname</label>
                                <input type="text" class="form-control" id="surname" name="surname" value="<?php echo htmlspecialchars((string)($_POST['surname'] ?? '')); ?>" required>
                            </div>
                            <div class="col-md-4 mb-3">
                                <label for="firstname" class="form-label">First Name</label>
                                <input type="text" class="form-control" id="firstname" name="firstname" value="<?php echo htmlspecialchars((string)($_POST['firstname'] ?? '')); ?>" required>
                            </div>
                            <div class="col-md-4 mb-3">
                                <label for="othername" class="form-label">Other Name</label>
                                <input type="text" class="form-control" id="othername" name="othername" value="<?php echo htmlspecialchars((string)($_POST['othername'] ?? '')); ?>">
                            </div>

                            <div class="col-md-4 mb-3">
                                <label for="maidenname" class="form-label">Maiden Name</label>
                                <input type="text" class="form-control" id="maidenname" name="maidenname" value="<?php echo htmlspecialchars((string)($_POST['maidenname'] ?? '')); ?>">
                            </div>
                            <div class="col-md-4 mb-3">
                                <label for="sex" class="form-label">Sex</label>
                                <select class="form-select" id="sex" name="sex" required>
                                    <option value="">Select...</option>
                                    <option value="Male" <?php echo (($_POST['sex'] ?? '') === 'Male') ? 'selected' : ''; ?>>Male</option>
                                    <option value="Female" <?php echo (($_POST['sex'] ?? '') === 'Female') ? 'selected' : ''; ?>>Female</option>
                                </select>
                            </div>
                            <div class="col-md-4 mb-3">
                                <label for="dob" class="form-label">Date of Birth</label>
                                <input type="date" class="form-control" id="dob" name="dob" value="<?php echo htmlspecialchars((string)($_POST['dob'] ?? '')); ?>" required>
                            </div>

                            <div class="col-md-4 mb-3">
                                <label for="nationality" class="form-label">Nationality</label>
                                <input type="text" class="form-control" id="nationality" name="nationality" value="<?php echo htmlspecialchars((string)($_POST['nationality'] ?? 'Nigerian')); ?>">
                            </div>
                            <div class="col-md-4 mb-3">
                                <label for="state" class="form-label">State of Origin</label>
                                <input type="text" class="form-control" id="state" name="state" value="<?php echo htmlspecialchars((string)($_POST['state'] ?? '')); ?>" required>
                            </div>
                            <div class="col-md-4 mb-3">
                                <label for="lga" class="form-label">Local Government Area</label>
                                <input type="text" class="form-control" id="lga" name="lga" value="<?php echo htmlspecialchars((string)($_POST['lga'] ?? '')); ?>" required>
                            </div>

                            <div class="col-md-4 mb-3">
                                <label for="phone" class="form-label">Telephone (Mobile)</label>
                                <input type="tel" class="form-control" id="phone" name="phone" value="<?php echo htmlspecialchars((string)($_POST['phone'] ?? '')); ?>" required>
                                <div class="form-text">This phone number will be linked to the student account and can be used for login.</div>
                            </div>
                            <div class="col-md-4 mb-3">
                                <label for="phone_res" class="form-label">Telephone (Residential)</label>
                                <input type="tel" class="form-control" id="phone_res" name="phone_res" value="<?php echo htmlspecialchars((string)($_POST['phone_res'] ?? '')); ?>">
                            </div>
                            <div class="col-md-4 mb-3">
                                <label for="email" class="form-label">Email Address</label>
                                <input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars((string)($_POST['email'] ?? '')); ?>" required>
                                <div class="form-text">Your email address will be linked to the same account for login, OTP, and notifications.</div>
                            </div>

                            <div class="col-12 mb-3">
                                <label for="address" class="form-label">Permanent Home Address</label>
                                <textarea class="form-control" id="address" name="address" rows="3" required><?php echo htmlspecialchars((string)($_POST['address'] ?? '')); ?></textarea>
                            </div>
                            <div class="col-12 mb-3">
                                <label for="activities" class="form-label">Extra Curricular Activities</label>
                                <textarea class="form-control" id="activities" name="activities" rows="3" placeholder="Sports, Clubs, Hobbies, etc."><?php echo htmlspecialchars((string)($_POST['activities'] ?? '')); ?></textarea>
                            </div>

                            <div class="col-md-8 mb-3">
                                <label for="nok_name" class="form-label">Next of Kin/Parent Name & Address</label>
                                <textarea class="form-control" id="nok_name" name="nok_name" rows="3" required><?php echo htmlspecialchars((string)($_POST['nok_name'] ?? '')); ?></textarea>
                            </div>
                            <div class="col-md-4 mb-3">
                                <label for="nok_phone" class="form-label">Next of Kin Telephone</label>
                                <input type="tel" class="form-control" id="nok_phone" name="nok_phone" value="<?php echo htmlspecialchars((string)($_POST['nok_phone'] ?? '')); ?>" required>
                            </div>

                            <div class="col-md-6 mb-3">
                                <label for="sponsor_name" class="form-label">Sponsor's Name & Address</label>
                                <textarea class="form-control" id="sponsor_name" name="sponsor_name" rows="3" required><?php echo htmlspecialchars((string)($_POST['sponsor_name'] ?? '')); ?></textarea>
                            </div>
                            <div class="col-md-3 mb-3">
                                <label for="sponsor_phone" class="form-label">Sponsor's Telephone</label>
                                <input type="tel" class="form-control" id="sponsor_phone" name="sponsor_phone" value="<?php echo htmlspecialchars((string)($_POST['sponsor_phone'] ?? '')); ?>" required>
                            </div>
                            <div class="col-md-3 mb-3">
                                <label for="sponsor_occ" class="form-label">Sponsor's Occupation</label>
                                <input type="text" class="form-control" id="sponsor_occ" name="sponsor_occ" value="<?php echo htmlspecialchars((string)($_POST['sponsor_occ'] ?? '')); ?>" required>
                            </div>

                            <div class="col-md-6 mb-3">
                                <label for="password" class="form-label">Password</label>
                                <input type="password" class="form-control" id="password" name="password" required>
                            </div>
                            <div class="col-md-6 mb-3">
                                <label for="confirm_password" class="form-label">Confirm Password</label>
                                <input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
                            </div>
                        </div>

                        <div class="d-grid">
                            <button type="submit" class="btn btn-primary" <?php echo !$registrationFormEnabled ? 'disabled' : ''; ?>>Register</button>
                        </div>

                        <div class="mt-3 text-center">
                            <p>Already have an account? <a href="?page=login">Login here</a></p>
                        </div>
                    </form>
                    <?php if (!$registrationFormEnabled): ?>
                    <script>
                    (function(){
                        var form = document.getElementById('publicRegisterForm');
                        if (!form) return;
                        Array.prototype.forEach.call(form.querySelectorAll('input, select, textarea, button'), function(el){
                            if (el.name === 'action') return;
                            if (el.type === 'hidden') return;
                            el.disabled = true;
                        });
                    })();
                    </script>
                    <?php endif; ?>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;

            case 'application_form':
                if (!portalManagedContentEnabled($pdo, 'application_form')) { flash('danger', portalManagedContentDisabledMessage($pdo, 'application_form')); redirect('?page=student_dashboard'); }
                // Check if registration fee is paid
                if (isStudent()) {
                    $stmt = $pdo->prepare("SELECT registration_fee_status FROM students WHERE id = ?");
                    $stmt->execute([$_SESSION['user_id']]);
                    $regFeeStatus = $stmt->fetchColumn();

                    if ($regFeeStatus !== 'Paid') {
                        echo '<div class="alert alert-warning">You must pay the registration fee before accessing the application form. <a href="?page=payment" class="btn btn-primary">Pay Now</a></div>';
                        break;
                    }
                } else {
                    redirect('?page=login');
                }

                // Get student information
                $studentId = function_exists('portalResolveStudentViewerId')
                    ? portalResolveStudentViewerId($pdo, false)
                    : (int)($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0);
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC);

                // Get education history
                $stmt = $pdo->prepare("SELECT * FROM education_history WHERE student_id = ?");
                $stmt->execute([$studentId]);
                $educationHistory = $stmt->fetchAll(PDO::FETCH_ASSOC);

                // Get examination results
                $stmt = $pdo->prepare("SELECT * FROM examination_results WHERE student_id = ?");
                $stmt->execute([$studentId]);
                $examResults = $stmt->fetchAll(PDO::FETCH_ASSOC);

                // Get referee information
                $stmt = $pdo->prepare("SELECT * FROM referees WHERE student_id = ?");
                $stmt->execute([$studentId]);
                $referee = $stmt->fetch(PDO::FETCH_ASSOC);

                // Get student documents
                $stmt = $pdo->prepare("SELECT * FROM student_documents WHERE student_id = ? ORDER BY id DESC");
                $stmt->execute([$studentId]);
                $documents = $stmt->fetchAll(PDO::FETCH_ASSOC);
                // Build quick lookup for already-submitted required documents (so we don't re-require them on edit)
                $docByType = [];
                if (!empty($documents)) {
                    foreach ($documents as $d) {
                        $t = $d['document_type'] ?? '';
                        if ($t !== '' && !isset($docByType[$t])) {
                            // because query is ORDER BY id DESC, first seen is the latest
                            $docByType[$t] = $d;
                        }
                    }
                }
                $ninDoc   = $docByType['nin_slip'] ?? null;
                $birthDoc = $docByType['birth_certificate'] ?? null;

                $hasNinDoc   = !empty($ninDoc) && !empty($ninDoc['file_path']);
                $hasBirthDoc = !empty($birthDoc) && !empty($birthDoc['file_path']);


                // Check edit count + lock state (with student-specific grace resubmission support)
                portalEnsureApplicationGraceSupport($pdo);
                $applicationState = portalApplicationResubmissionState($pdo, $student);
                $editCount = (int)($applicationState['used_count'] ?? 0);
                $isSubmitted = !empty($applicationState['submitted']);
                $editsRemaining = (int)($applicationState['remaining_total'] ?? 0);
                $canEdit = !empty($applicationState['can_edit']);
                $appGraceRemaining = (int)($applicationState['grace_remaining'] ?? 0);
                $appGraceUsed = (int)($applicationState['grace_used'] ?? 0);
                $appReopenedForGrace = !empty($applicationState['reopened']);
                $admissionFormLocked = portalStudentAdmissionFormLocked($student);
                if ($admissionFormLocked) {
                    $canEdit = false;
                    $applicationState['message'] = portalStudentAdmissionFormLockMessage($student);
                }
                $applicationDoc = portalDocumentContent($pdo, 'application_form');
                $applicationAdmissionReference = portalApplicationAdmissionReferenceValue((array)$student);
                $applicationAdmissionYear = portalStudentAcademicYear((array)$student);
?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header d-flex justify-content-between align-items-center">
                                <h4 class="mb-0"><?php echo e((string)($applicationDoc['heading'] ?? 'Application Form')); ?></h4>
                                <div class="no-print d-flex gap-2 flex-wrap">
                                    <a class="btn btn-success" href="?page=print_form&print=1">
                                        <i class="bi bi-printer"></i> Print Application
                                    </a>
                                    <a class="btn btn-primary" href="?page=print_form&download=1">
                                        <i class="bi bi-download"></i> Download PDF
                                    </a>
                                </div>
                            </div>
                            <div class="card-body">
                                <?php if (!empty($applicationDoc['subheading'])): ?><p class="text-muted mb-2"><?php echo e((string)$applicationDoc['subheading']); ?></p><?php endif; ?>
                                <?php if (!empty($applicationDoc['body_text'])): ?><p class="mb-3"><?php echo e((string)$applicationDoc['body_text']); ?></p><?php endif; ?>
                                <?php if ($canEdit): ?>
                                <?php if ($appReopenedForGrace): ?>
                                <div class="edit-warning">
                                    <i class="bi bi-unlock-fill"></i>
                                    <strong>Your application has been reopened for resubmission.</strong>
                                    <span class="d-block mt-1">You have <span class="edit-counter"><?php echo (int)$appGraceRemaining; ?></span> grace resubmission remaining.</span>
                                </div>
                                <?php elseif ($editCount > 0): ?>
                                <div class="edit-warning">
                                    <i class="bi bi-exclamation-triangle-fill"></i>
                                    <strong>Notice:</strong> <?php echo e((string)($applicationDoc['notice_text'] ?? '')); ?>
                                    <span class="d-block mt-1">You have already edited your application <span class="edit-counter"><?php echo (int)$editCount; ?></span> time(s). You can edit your application <span class="edit-counter"><?php echo (int)$editsRemaining; ?></span> more time(s).</span>
                                </div>
                                <?php else: ?>
                                <div class="edit-warning">
                                    <i class="bi bi-info-circle-fill"></i>
                                    <strong>Notice:</strong> <?php echo e((string)($applicationDoc['notice_text'] ?? '')); ?>
                                    <span class="d-block mt-1">You can edit your application a maximum of <span class="edit-counter"><?php echo (int)($applicationState['base_limit'] ?? 3); ?></span> times. Please review your information carefully before saving.</span>
                                </div>
                                <?php endif; ?>
                                <?php else: ?>
                                <div class="edit-warning">
                                    <i class="bi bi-lock-fill"></i>
                                    <strong>Locked:</strong> <?php echo e((string)($applicationState['message'] ?: 'Your application is locked for further modifications.')); ?>
                                </div>
                                <?php endif; ?>

                                <?php
                                    portalEnsureApplicationAcademicFieldSupport($pdo);
                                    $__appAcademicState = portalStudentApplicationAcademicState($pdo, $student);
                                    $__appSelectedFacultyId = (int)($__appAcademicState['faculty_id'] ?? 0);
                                    $__appSelectedDepartmentId = (int)($__appAcademicState['department_id'] ?? 0);
                                    $__appSelectedProgrammeId = (int)($__appAcademicState['programme_id'] ?? 0);
                                    $__appFaculties = $__appAcademicState['faculties'] ?? [];
                                    $__appDepartmentCount = is_array($__appAcademicState['departments'] ?? null) ? count($__appAcademicState['departments']) : 0;
                                    $__appProgrammeCount = is_array($__appAcademicState['programmes'] ?? null) ? count($__appAcademicState['programmes']) : 0;
                                ?>
                                <form method="post" action="" enctype="multipart/form-data" id="applicationForm">
                                    <input type="hidden" name="action" id="applicationAction" value="update_application">

                                    <!-- Personal Information -->
                                    <div class="form-section">
                                        <h3>Personal Information</h3>
                                        <div id="settings-general" class="row">
                                            <div class="col-lg-3 col-md-6 mb-3">
                                                <label class="form-label">Registration No.</label>
                                                <input type="text" class="form-control" value="<?php echo htmlspecialchars((string)($student['registration_no'] ?? $student['application_no'] ?? '')); ?>" readonly>
                                                <div class="form-text">Synchronized with your Application / Admission / Matric numbering workflow.</div>
                                            </div>
                                            <div class="col-lg-3 col-md-6 mb-3">
                                                <label class="form-label">Matric Number</label>
                                                <input type="text" class="form-control" value="<?php echo htmlspecialchars(portalStudentNumberDisplay($student, 'matric'), ENT_QUOTES, 'UTF-8'); ?>" readonly>
                                            </div>
                                            <div class="col-lg-3 col-md-6 mb-3">
                                                <label class="form-label">Admission Reference No.</label>
                                                <input type="text" class="form-control" value="<?php echo htmlspecialchars($applicationAdmissionReference !== '' ? $applicationAdmissionReference : '—', ENT_QUOTES, 'UTF-8'); ?>" readonly>
                                                <div class="form-text">Admission workflow reference for print and PDF output.</div>
                                            </div>
                                            <div class="col-lg-3 col-md-6 mb-3">
                                                <label class="form-label">School</label>
                                                <select class="form-select" name="faculty_id" id="applicationSchoolSelect" <?php echo !$canEdit ? 'disabled' : ''; ?> required>
                                                    <option value="">Select School</option>
                                                    <?php foreach ($__appFaculties as $__facultyRow): ?>
                                                        <option value="<?php echo (int)($__facultyRow['id'] ?? 0); ?>" <?php echo (int)($__facultyRow['id'] ?? 0) === $__appSelectedFacultyId ? 'selected' : ''; ?>><?php echo e((string)($__facultyRow['name'] ?? '')); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                                <div class="form-text">Choose your school first. Only linked course options will be shown.</div>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Course of Study</label>
                                                <select class="form-select" name="department_id" id="applicationCourseOfStudySelect" <?php echo !$canEdit ? 'disabled' : ''; ?> required>
                                                    <option value="">Select Course of Study</option>
                                                    <?php if ($__appSelectedDepartmentId > 0): ?>
                                                        <option value="<?php echo $__appSelectedDepartmentId; ?>" selected><?php echo e((string)($__appAcademicState['course_of_study'] ?? '')); ?></option>
                                                    <?php endif; ?>
                                                </select>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Program</label>
                                                <select class="form-select" name="programme_id" id="applicationProgramSelect" <?php echo !$canEdit ? 'disabled' : ''; ?> required>
                                                    <option value="">Select Program</option>
                                                    <?php if ($__appSelectedProgrammeId > 0): ?>
                                                        <option value="<?php echo $__appSelectedProgrammeId; ?>" selected><?php echo e((string)($__appAcademicState['programme'] ?? '')); ?></option>
                                                    <?php endif; ?>
                                                </select>
                                                <?php if (empty($__appFaculties) || $__appDepartmentCount === 0 || $__appProgrammeCount === 0): ?>
                                                    <div class="form-text text-danger">Academic structure data is incomplete. Please contact the portal administrator if the options do not load.</div>
                                                <?php endif; ?>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Surname</label>
                                                <input type="text" class="form-control" name="surname" value="<?php echo $student['surname']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">First Name</label>
                                                <input type="text" class="form-control" name="first_name" value="<?php echo $student['first_name']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Other Name</label>
                                                <input type="text" class="form-control" name="other_name" value="<?php echo $student['other_name']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Maiden Name</label>
                                                <input type="text" class="form-control" name="maiden_name" value="<?php echo $student['maiden_name']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Sex</label>
                                                <select class="form-select" name="sex" <?php echo !$canEdit ? 'disabled' : ''; ?> required>
                                                    <option value="Male" <?php echo $student['sex'] === 'Male' ? 'selected' : ''; ?>>Male</option>
                                                    <option value="Female" <?php echo $student['sex'] === 'Female' ? 'selected' : ''; ?>>Female</option>
                                                </select>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Date of Birth</label>
                                                <input type="date" class="form-control" name="date_of_birth" value="<?php echo $student['date_of_birth']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Nationality</label>
                                                <input type="text" class="form-control" name="nationality" value="<?php echo $student['nationality']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">State of Origin</label>
                                                <input type="text" class="form-control" name="state_of_origin" value="<?php echo $student['state_of_origin']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Local Government Area</label>
                                                <input type="text" class="form-control" name="lga" value="<?php echo $student['lga']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Mobile Phone</label>
                                                <input type="tel" class="form-control" name="mobile_phone" value="<?php echo $student['mobile_phone']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Residential Phone</label>
                                                <input type="tel" class="form-control" name="residential_phone" value="<?php echo $student['residential_phone']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>>
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label class="form-label">Email</label>
                                                <input type="email" class="form-control" name="email" value="<?php echo $student['email']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                            <div class="col-md-12 mb-3">
                                                <label class="form-label">Permanent Address</label>
                                                <textarea class="form-control" name="permanent_address" rows="3" <?php echo !$canEdit ? 'readonly' : ''; ?> required><?php echo $student['permanent_address']; ?></textarea>
                                            </div>
                                            <div class="col-md-12 mb-3">
                                                <label class="form-label">Extra Curricular Activities</label>
                                                <textarea class="form-control" name="extra_curricular" rows="2" <?php echo !$canEdit ? 'readonly' : ''; ?>><?php echo $student['extra_curricular']; ?></textarea>
                                            </div>
                                        </div>
                                    </div>

                                    <!-- Profile Image -->
                                    <div class="form-section">
                                        <h3>Profile Image</h3>
                                        <div class="row">
                                            <div class="col-md-6 mb-3">
                                                <label class="form-label">Upload Profile Image</label>
                                                <input type="file" class="form-control" id="profile_image" name="profile_image" accept="image/*" <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                                <div class="form-text">Allowed formats: JPG, JPEG, PNG. Maximum size: 5MB.</div>
                                            </div>
                                            <div class="col-md-6 mb-3">
                                                <?php if ($student['profile_image']): ?>
                                                    <img src="<?php echo $student['profile_image']; ?>" alt="Profile Image" style="max-width: 150px; max-height: 150px; border-radius: 5px;">
                                                <?php endif; ?>
                                            </div>
                                        </div>
                                    </div>

                                    <!-- Passport Photo -->
                                    <div class="form-section">
                                        <h3>Passport Photograph</h3>
                                        <div class="row">
                                            <div class="col-md-6 mb-3">
                                                <label class="form-label">Upload Passport Photograph</label>
                                                <input type="file" class="form-control" id="passport_photo" name="passport_photo" accept="image/*" <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                                <div class="form-text">Allowed formats: JPG, JPEG, PNG. Maximum size: 5MB.</div>
                                            </div>
                                            <div class="col-md-6 mb-3">
                                                <?php if ($student['passport_photo']): ?>
                                                    <img src="<?php echo $student['passport_photo']; ?>" alt="Passport Photo" style="max-width: 150px; max-height: 150px; border-radius: 5px;">
                                                <?php endif; ?>
                                            </div>
                                        </div>
                                    </div>

                                    <!-- Document Uploads -->
                                    <div class="form-section">
                                        <h3>Supporting Documents</h3>
                                        <div class="row">
                                            <div class="col-md-12 mb-3">


<div class="document-upload-area" id="document_upload_area" <?php echo !$canEdit ? 'style="pointer-events: none; opacity: 0.7;"' : ''; ?>>
    <i class="bi bi-cloud-upload" style="font-size: 3rem; color: var(--primary-color);"></i>
    <h4>Upload Documents</h4>
    <p>You can upload up to <strong>10</strong> supporting documents.</p>
    <p class="mb-2">Required: <strong>NIN Slip</strong> and <strong>Birth Certificate/Affidavit</strong>. Others are optional.</p>
    <p class="text-muted mb-3">Allowed formats: JPG, JPEG, PNG, PDF, DOC, DOCX. Maximum size: 5MB per file.</p>


    <div id="docs_repeater">

        <!-- Required 1: NIN -->
        <div class="row g-2 align-items-end doc-row" data-required="1" data-fixed="nin_slip">
            <div class="col-md-4">
                <label class="form-label mb-1">Document Type</label>
                <select class="form-select" name="document_type[]">
                    <option value="nin_slip" selected>NIN Slip (Required)</option>
                </select>
                <input type="hidden" name="document_name[]" value="NIN Slip">
            </div>
            <div class="col-md-6">
                <label class="form-label mb-1">Choose File</label>
                <input type="file" class="form-control" name="documents[]" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"
                    <?php echo !$canEdit ? 'disabled' : ''; ?>
                    <?php echo (!$hasNinDoc ? 'required' : ''); ?>>
                <?php if ($hasNinDoc): ?>
                    <div class="form-text">Already submitted. Upload again only if you want to replace it.</div>
                <?php endif; ?>
            </div>
            <div class="col-md-2 text-end">
                <?php if ($hasNinDoc): ?>
                    <a href="<?php echo htmlspecialchars($ninDoc['file_path']); ?>" target="_blank" class="btn btn-sm btn-outline-primary mb-1">View</a>
                    <span class="badge bg-secondary">Submitted</span>
                <?php else: ?>
                    <span class="badge bg-success">Required</span>
                <?php endif; ?>
            </div>
        </div>


        <!-- Required 2: Birth -->
        <div class="row g-2 align-items-end doc-row mt-2" data-required="1" data-fixed="birth_certificate">
            <div class="col-md-4">
                <label class="form-label mb-1">Document Type</label>
                <select class="form-select" name="document_type[]">
                    <option value="birth_certificate" selected>Birth Certificate/Affidavit (Required)</option>
                </select>
                <input type="hidden" name="document_name[]" value="Birth Certificate/Affidavit">
            </div>
            <div class="col-md-6">
                <label class="form-label mb-1">Choose File</label>
                <input type="file" class="form-control" name="documents[]" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"
                    <?php echo !$canEdit ? 'disabled' : ''; ?>
                    <?php echo (!$hasBirthDoc ? 'required' : ''); ?>>
                <?php if ($hasBirthDoc): ?>
                    <div class="form-text">Already submitted. Upload again only if you want to replace it.</div>
                <?php endif; ?>
            </div>
            <div class="col-md-2 text-end">
                <?php if ($hasBirthDoc): ?>
                    <a href="<?php echo htmlspecialchars($birthDoc['file_path']); ?>" target="_blank" class="btn btn-sm btn-outline-primary mb-1">View</a>
                    <span class="badge bg-secondary">Submitted</span>
                <?php else: ?>
                    <span class="badge bg-success">Required</span>
                <?php endif; ?>
            </div>
        </div>

    </div>


    <div class="mt-3 d-flex justify-content-between align-items-center flex-wrap gap-2">
        <button type="button" class="btn btn-outline-primary" id="btn_add_doc" <?php echo !$canEdit ? 'disabled' : ''; ?>>
            <i class="bi bi-plus-circle"></i> Add Optional Document
        </button>
        <div class="form-text">
            After selecting files, click <strong>Save Changes</strong> to upload them.
        </div>
    </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function(){
    const wrap = document.getElementById('docs_repeater');
    const addBtn = document.getElementById('btn_add_doc');
    if (!wrap || !addBtn) return;

    const maxDocs = 10;

    function docTypeOptionsHtml(){
        return `
            <option value="neco">NECO</option>
            <option value="waec">WACE/WAEC</option>
            <option value="nabteb">NABTEB</option>
            <option value="gce">GCE</option>
            <option value="other">Other</option>
        `;
    }

    function addRow(){
        const rows = wrap.querySelectorAll('.doc-row').length;
        if (rows >= maxDocs) return;

        const row = document.createElement('div');
        row.className = 'row g-2 align-items-end doc-row mt-2';
        row.innerHTML = `
            <div class="col-md-4">
                <label class="form-label mb-1">Document Type</label>
                <select class="form-select doc-type" name="document_type[]">
                    ${docTypeOptionsHtml()}
                </select>
            </div>
            <div class="col-md-4">
                <label class="form-label mb-1">Document Name (for Other)</label>
                <input type="text" class="form-control doc-name" name="document_name[]" placeholder="Auto-filled unless Other">
            </div>
            <div class="col-md-3">
                <label class="form-label mb-1">Choose File</label>
                <input type="file" class="form-control" name="documents[]" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx" <?php echo !$canEdit ? 'disabled' : ''; ?>>
            </div>
            <div class="col-md-1 text-end">
                <button type="button" class="btn btn-outline-danger btn-sm btn-remove" title="Remove">
                    <i class="bi bi-x-lg"></i>
                </button>
            </div>
        `;
        wrap.appendChild(row);

        // initialize name based on selected type
        const sel = row.querySelector('.doc-type');
        const name = row.querySelector('.doc-name');
        const syncName = () => {
            const v = sel.value;
            const map = {neco:'NECO', waec:'WACE/WAEC', nabteb:'NABTEB', gce:'GCE'};
            if (v === 'other') {
                name.placeholder = 'Enter document name';
                name.value = name.value || '';
                name.required = true;
                name.removeAttribute('readonly');
                name.focus();
            } else {
                name.value = map[v] || 'Document';
                name.required = false;
                name.setAttribute('readonly','readonly');
            }
        };
        sel.addEventListener('change', syncName);
        syncName();

        // remove
        const rm = row.querySelector('.btn-remove');
        rm.addEventListener('click', () => row.remove());
    }

    addBtn.addEventListener('click', addRow);
});
</script>
<div class="document-list" id="document_list">
                                            <?php if (!empty($documents)): ?>
                                                <?php foreach ($documents as $doc): ?>
                                                    <div class="document-item">
                                                        <div>
                                                            <i class="bi bi-file-earmark"></i>
                                                            <span><?php echo $doc['document_name']; ?></span>
                                                            <span class="badge bg-secondary ms-2"><?php echo $doc['document_type']; ?></span>
                                                        </div>
                                                        <div>
                                                            <a href="<?php echo $doc['file_path']; ?>" target="_blank" class="btn btn-sm btn-outline-primary">View Upload</a>
                                                            <?php if ($editCount < 3): ?>
                                                            <button type="button" class="btn btn-sm btn-outline-danger ms-1" onclick="deleteDocument(<?php echo $doc['id']; ?>)">Delete</button>
                                                            <?php endif; ?>
                                                        </div>
                                                    </div>
                                                <?php endforeach; ?>
                                            <?php endif; ?>
                                        </div>
                                    </div>

                                    <!-- Next of Kin & Sponsor -->
                                    <div class="form-section">
                                        <h3>Next of Kin & Sponsor</h3>
                                        <div class="row">
                                            <div class="col-md-12 mb-3">
                                                <label class="form-label">Next of Kin/Parent Name & Address</label>
                                                <textarea class="form-control" name="nok_name" rows="3" <?php echo !$canEdit ? 'readonly' : ''; ?> required><?php echo $student['nok_name']; ?></textarea>
                                            </div>
                                            <div class="col-md-6 mb-3">
                                                <label class="form-label">Next of Kin Telephone</label>
                                                <input type="tel" class="form-control" name="nok_phone" value="<?php echo $student['nok_phone']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                            <div class="col-md-12 mb-3">
                                                <label class="form-label">Sponsor's Name & Address</label>
                                                <textarea class="form-control" name="sponsor_name" rows="3" <?php echo !$canEdit ? 'readonly' : ''; ?> required><?php echo $student['sponsor_name']; ?></textarea>
                                            </div>
                                            <div class="col-md-6 mb-3">
                                                <label class="form-label">Sponsor's Telephone</label>
                                                <input type="tel" class="form-control" name="sponsor_phone" value="<?php echo $student['sponsor_phone']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                            <div class="col-md-6 mb-3">
                                                <label class="form-label">Sponsor's Occupation</label>
                                                <input type="text" class="form-control" name="sponsor_occupation" value="<?php echo $student['sponsor_occupation']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?> required>
                                            </div>
                                        </div>
                                    </div>

                                    <!-- Choice of Course/Subject (A' Level) -->
                                    <div class="form-section">
                                        <h3>Choice of Course/Subject (A' Level)</h3>
                                        <div class="table-responsive">
                                            <table class="table table-bordered" id="subjectsTable">
                                                <thead>
                                                    <tr>
                                                        <th>Subject</th>
                                                        <th>Grade</th>
                                                        <th>Action</th>
                                                    </tr>
                                                </thead>
                                                <tbody>
                                                    <tr>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="subject_name[]" <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                                                <option value="Mathematics" <?php echo ($student['subject1'] ?? '') === 'Mathematics' ? 'selected' : ''; ?>>Mathematics</option>
                                                                <option value="English Language" <?php echo ($student['subject1'] ?? '') === 'English Language' ? 'selected' : ''; ?>>English Language</option>
                                                                <option value="Biology" <?php echo ($student['subject1'] ?? '') === 'Biology' ? 'selected' : ''; ?>>Biology</option>
                                                                <option value="Chemistry" <?php echo ($student['subject1'] ?? '') === 'Chemistry' ? 'selected' : ''; ?>>Chemistry</option>
                                                                <option value="Physics" <?php echo ($student['subject1'] ?? '') === 'Physics' ? 'selected' : ''; ?>>Physics</option>
                                                                <option value="Economics" <?php echo ($student['subject1'] ?? '') === 'Economics' ? 'selected' : ''; ?>>Economics</option>
                                                                <option value="Geography" <?php echo ($student['subject1'] ?? '') === 'Geography' ? 'selected' : ''; ?>>Geography</option>
                                                                <option value="Government" <?php echo ($student['subject1'] ?? '') === 'Government' ? 'selected' : ''; ?>>Government</option>
                                                                <option value="Literature in English" <?php echo ($student['subject1'] ?? '') === 'Literature in English' ? 'selected' : ''; ?>>Literature in English</option>
                                                                <option value="History" <?php echo ($student['subject1'] ?? '') === 'History' ? 'selected' : ''; ?>>History</option>
                                                                <option value="Christian Religious Studies" <?php echo ($student['subject1'] ?? '') === 'Christian Religious Studies' ? 'selected' : ''; ?>>Christian Religious Studies</option>
                                                                <option value="Islamic Religious Studies" <?php echo ($student['subject1'] ?? '') === 'Islamic Religious Studies' ? 'selected' : ''; ?>>Islamic Religious Studies</option>
                                                                <option value="Accounting" <?php echo ($student['subject1'] ?? '') === 'Accounting' ? 'selected' : ''; ?>>Accounting</option>
                                                                <option value="Commerce" <?php echo ($student['subject1'] ?? '') === 'Commerce' ? 'selected' : ''; ?>>Commerce</option>
                                                                <option value="Agricultural Science" <?php echo ($student['subject1'] ?? '') === 'Agricultural Science' ? 'selected' : ''; ?>>Agricultural Science</option>
                                                                <option value="Further Mathematics" <?php echo ($student['subject1'] ?? '') === 'Further Mathematics' ? 'selected' : ''; ?>>Further Mathematics</option>
                                                            </select>
                                                        </td>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="subject_grade[]" <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                                                <option value="A1">A1</option>
                                                                <option value="B2">B2</option>
                                                                <option value="B3">B3</option>
                                                                <option value="C4">C4</option>
                                                                <option value="C5">C5</option>
                                                                <option value="C6">C6</option>
                                                                <option value="D7">D7</option>
                                                                <option value="E8">E8</option>
                                                                <option value="F9">F9</option>
                                                            </select>
                                                        </td>
                                                        <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()" <?php echo !$canEdit ? 'disabled' : ''; ?>>X</button></td>
                                                    </tr>
                                                    <tr>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="subject_name[]" <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                                                <option value="Mathematics" <?php echo ($student['subject2'] ?? '') === 'Mathematics' ? 'selected' : ''; ?>>Mathematics</option>
                                                                <option value="English Language" <?php echo ($student['subject2'] ?? '') === 'English Language' ? 'selected' : ''; ?>>English Language</option>
                                                                <option value="Biology" <?php echo ($student['subject2'] ?? '') === 'Biology' ? 'selected' : ''; ?>>Biology</option>
                                                                <option value="Chemistry" <?php echo ($student['subject2'] ?? '') === 'Chemistry' ? 'selected' : ''; ?>>Chemistry</option>
                                                                <option value="Physics" <?php echo ($student['subject2'] ?? '') === 'Physics' ? 'selected' : ''; ?>>Physics</option>
                                                                <option value="Economics" <?php echo ($student['subject2'] ?? '') === 'Economics' ? 'selected' : ''; ?>>Economics</option>
                                                                <option value="Geography" <?php echo ($student['subject2'] ?? '') === 'Geography' ? 'selected' : ''; ?>>Geography</option>
                                                                <option value="Government" <?php echo ($student['subject2'] ?? '') === 'Government' ? 'selected' : ''; ?>>Government</option>
                                                                <option value="Literature in English" <?php echo ($student['subject2'] ?? '') === 'Literature in English' ? 'selected' : ''; ?>>Literature in English</option>
                                                                <option value="History" <?php echo ($student['subject2'] ?? '') === 'History' ? 'selected' : ''; ?>>History</option>
                                                                <option value="Christian Religious Studies" <?php echo ($student['subject2'] ?? '') === 'Christian Religious Studies' ? 'selected' : ''; ?>>Christian Religious Studies</option>
                                                                <option value="Islamic Religious Studies" <?php echo ($student['subject2'] ?? '') === 'Islamic Religious Studies' ? 'selected' : ''; ?>>Islamic Religious Studies</option>
                                                                <option value="Accounting" <?php echo ($student['subject2'] ?? '') === 'Accounting' ? 'selected' : ''; ?>>Accounting</option>
                                                                <option value="Commerce" <?php echo ($student['subject2'] ?? '') === 'Commerce' ? 'selected' : ''; ?>>Commerce</option>
                                                                <option value="Agricultural Science" <?php echo ($student['subject2'] ?? '') === 'Agricultural Science' ? 'selected' : ''; ?>>Agricultural Science</option>
                                                                <option value="Further Mathematics" <?php echo ($student['subject2'] ?? '') === 'Further Mathematics' ? 'selected' : ''; ?>>Further Mathematics</option>
                                                            </select>
                                                        </td>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="subject_grade[]" <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                                                <option value="A1">A1</option>
                                                                <option value="B2">B2</option>
                                                                <option value="B3">B3</option>
                                                                <option value="C4">C4</option>
                                                                <option value="C5">C5</option>
                                                                <option value="C6">C6</option>
                                                                <option value="D7">D7</option>
                                                                <option value="E8">E8</option>
                                                                <option value="F9">F9</option>
                                                            </select>
                                                        </td>
                                                        <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()" <?php echo !$canEdit ? 'disabled' : ''; ?>>X</button></td>
                                                    </tr>
                                                    <tr>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="subject_name[]" <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                                                <option value="Mathematics" <?php echo ($student['subject3'] ?? '') === 'Mathematics' ? 'selected' : ''; ?>>Mathematics</option>
                                                                <option value="English Language" <?php echo ($student['subject3'] ?? '') === 'English Language' ? 'selected' : ''; ?>>English Language</option>
                                                                <option value="Biology" <?php echo ($student['subject3'] ?? '') === 'Biology' ? 'selected' : ''; ?>>Biology</option>
                                                                <option value="Chemistry" <?php echo ($student['subject3'] ?? '') === 'Chemistry' ? 'selected' : ''; ?>>Chemistry</option>
                                                                <option value="Physics" <?php echo ($student['subject3'] ?? '') === 'Physics' ? 'selected' : ''; ?>>Physics</option>
                                                                <option value="Economics" <?php echo ($student['subject3'] ?? '') === 'Economics' ? 'selected' : ''; ?>>Economics</option>
                                                                <option value="Geography" <?php echo ($student['subject3'] ?? '') === 'Geography' ? 'selected' : ''; ?>>Geography</option>
                                                                <option value="Government" <?php echo ($student['subject3'] ?? '') === 'Government' ? 'selected' : ''; ?>>Government</option>
                                                                <option value="Literature in English" <?php echo ($student['subject3'] ?? '') === 'Literature in English' ? 'selected' : ''; ?>>Literature in English</option>
                                                                <option value="History" <?php echo ($student['subject3'] ?? '') === 'History' ? 'selected' : ''; ?>>History</option>
                                                                <option value="Christian Religious Studies" <?php echo ($student['subject3'] ?? '') === 'Christian Religious Studies' ? 'selected' : ''; ?>>Christian Religious Studies</option>
                                                                <option value="Islamic Religious Studies" <?php echo ($student['subject3'] ?? '') === 'Islamic Religious Studies' ? 'selected' : ''; ?>>Islamic Religious Studies</option>
                                                                <option value="Accounting" <?php echo ($student['subject3'] ?? '') === 'Accounting' ? 'selected' : ''; ?>>Accounting</option>
                                                                <option value="Commerce" <?php echo ($student['subject3'] ?? '') === 'Commerce' ? 'selected' : ''; ?>>Commerce</option>
                                                                <option value="Agricultural Science" <?php echo ($student['subject3'] ?? '') === 'Agricultural Science' ? 'selected' : ''; ?>>Agricultural Science</option>
                                                                <option value="Further Mathematics" <?php echo ($student['subject3'] ?? '') === 'Further Mathematics' ? 'selected' : ''; ?>>Further Mathematics</option>
                                                            </select>
                                                        </td>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="subject_grade[]" <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                                                <option value="A1">A1</option>
                                                                <option value="B2">B2</option>
                                                                <option value="B3">B3</option>
                                                                <option value="C4">C4</option>
                                                                <option value="C5">C5</option>
                                                                <option value="C6">C6</option>
                                                                <option value="D7">D7</option>
                                                                <option value="E8">E8</option>
                                                                <option value="F9">F9</option>
                                                            </select>
                                                        </td>
                                                        <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()" <?php echo !$canEdit ? 'disabled' : ''; ?>>X</button></td>
                                                    </tr>
                                                </tbody>
                                            </table>
                                        </div>
                                        <?php if ($editCount < 3): ?>
                                        <button type="button" class="btn btn-sm btn-secondary" onclick="addSubjectRow()">Add More</button>
                                        <?php endif; ?>
                                    </div>

                                    <!-- Education History -->
                                    <div class="form-section">
                                        <h3>Post-Primary Institution Attended with Dates</h3>
                                        <div class="table-responsive">
                                            <table class="table table-bordered" id="schoolsTable">
                                                <thead>
                                                    <tr>
                                                        <th>Name of Institution</th>
                                                        <th>From (Date)</th>
                                                        <th>To (Date)</th>
                                                        <th>Qualifications Obtained</th>
                                                        <th>Action</th>
                                                    </tr>
                                                </thead>
                                                <tbody>
                                                    <?php foreach ($educationHistory as $edu): ?>
                                                    <tr>
                                                        <td><input type="text" class="form-control form-control-sm" name="school_name[]" value="<?php echo $edu['institution_name']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><input type="month" class="form-control form-control-sm" name="school_from[]" value="<?php echo date('Y-m', strtotime($edu['from_date'])); ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><input type="month" class="form-control form-control-sm" name="school_to[]" value="<?php echo date('Y-m', strtotime($edu['to_date'])); ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><input type="text" class="form-control form-control-sm" name="school_qual[]" value="<?php echo $edu['qualification']; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()" <?php echo !$canEdit ? 'disabled' : ''; ?>>X</button></td>
                                                    </tr>
                                                    <?php endforeach; ?>
                                                    <?php if (empty($educationHistory)): ?>
                                                    <tr>
                                                        <td><input type="text" class="form-control form-control-sm" name="school_name[]" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><input type="month" class="form-control form-control-sm" name="school_from[]" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><input type="month" class="form-control form-control-sm" name="school_to[]" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><input type="text" class="form-control form-control-sm" name="school_qual[]" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()" <?php echo !$canEdit ? 'disabled' : ''; ?>>X</button></td>
                                                    </tr>
                                                    <?php endif; ?>

                                                </tbody>
                                            </table>
                                        </div>
                                        <?php if ($editCount < 3): ?>
                                        <button type="button" class="btn btn-sm btn-secondary" onclick="addSchoolRow()">Add More</button>
                                        <?php endif; ?>
                                    </div>

                                    <!-- Examination Results -->
                                    <div class="form-section">
                                        <h3>School Certificate Results</h3>
                                        <div class="table-responsive">
                                            <table class="table table-bordered" id="resultsTable">
                                                <thead>
                                                    <tr>
                                                        <th>Names of Examination</th>
                                                        <th>Date</th>
                                                        <th>Exam Number</th>
                                                        <th>Subjects & Grades</th>
                                                        <th>Credentials Enclosed?</th>
                                                        <th>Action</th>
                                                    </tr>
                                                </thead>
                                                <tbody>
                                                    <?php foreach ($examResults as $exam): ?>
                                                    <tr>
                                                        <td>
    <select class="form-select form-select-sm exam-type-select" name="exam_type[]" <?php echo !$canEdit ? 'disabled' : ''; ?>>
        <option value="WAEC" <?php echo ($exam['exam_type'] ?? '') === 'WAEC' ? 'selected' : ''; ?>>WAEC</option>
        <option value="NECO" <?php echo ($exam['exam_type'] ?? '') === 'NECO' ? 'selected' : ''; ?>>NECO</option>
        <option value="NABTEB" <?php echo ($exam['exam_type'] ?? '') === 'NABTEB' ? 'selected' : ''; ?>>NABTEB/NAPTEB</option>
        <option value="GCE" <?php echo ($exam['exam_type'] ?? '') === 'GCE' ? 'selected' : ''; ?>>GCE</option>
        <option value="OTHER" <?php echo ($exam['exam_type'] ?? '') === 'OTHER' ? 'selected' : ''; ?>>OTHER</option>
    </select>
    <input type="text" class="form-control form-control-sm mt-1 exam-type-other" name="exam_type_other[]"
           value="<?php echo e($exam['exam_type_other'] ?? ''); ?>" placeholder="Enter examination name"
           style="<?php echo (($exam['exam_type'] ?? '') === 'OTHER') ? '' : 'display:none;'; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>>
</td>
                                                        <td><input type="month" class="form-control form-control-sm" name="exam_date[]" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><input type="text" class="form-control form-control-sm" name="exam_no[]" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td>
    <div class="exam-sg">
        <select class="form-select form-select-sm exam-subjects" multiple <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                <option value="English Language">English Language</option>
                                <option value="Mathematics">Mathematics</option>
                                <option value="Biology">Biology</option>
                                <option value="Chemistry">Chemistry</option>
                                <option value="Physics">Physics</option>
                                <option value="Agricultural Science">Agricultural Science</option>
                                <option value="Economics">Economics</option>
                                <option value="Government">Government</option>
                                <option value="Geography">Geography</option>
                                <option value="Literature in English">Literature in English</option>
                                <option value="Civic Education">Civic Education</option>
                                <option value="Christian Religious Studies">Christian Religious Studies</option>
                                <option value="Islamic Religious Studies">Islamic Religious Studies</option>
                                <option value="Commerce">Commerce</option>
                                <option value="Financial Accounting">Financial Accounting</option>
                                <option value="Further Mathematics">Further Mathematics</option>
                                <option value="Data Processing">Data Processing</option>
                                <option value="Computer Studies">Computer Studies</option>
                                <option value="Technical Drawing">Technical Drawing</option>
                                <option value="Home Economics">Home Economics</option>
                                <option value="Fine Arts">Fine Arts</option>
                                <option value="French">French</option>
                                <option value="History">History</option>
        </select>
        <div class="exam-grade-rows mt-2"></div>
        <input type="hidden" name="exam_grades[]" class="exam-grades-hidden" value="<?php echo e($exam['subjects_grades'] ?? ''); ?>">
        <div class="form-text">Select one or more subjects, then choose grades (A1 - F9) for each selected subject.</div>
    </div>
</td>
                                                        <td><div class="form-check mb-0"><input class="form-check-input" type="checkbox" name="exam_attached[]" checked <?php echo !$canEdit ? 'disabled' : ''; ?>></div></td>
                                                        <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()" <?php echo !$canEdit ? 'disabled' : ''; ?>>X</button></td>
                                                                                                        </tr>
                                                    <?php endforeach; ?>
                                                    <?php if (empty($examResults)): ?>
                                                    <tr>
                                                        <td>
    <select class="form-select form-select-sm exam-type-select" name="exam_type[]" <?php echo !$canEdit ? 'disabled' : ''; ?>>
        <option value="WAEC">WAEC</option>
        <option value="NECO">NECO</option>
        <option value="NABTEB">NABTEB/NAPTEB</option>
        <option value="GCE">GCE</option>
        <option value="OTHER">OTHER</option>
    </select>
    <input type="text" class="form-control form-control-sm mt-1 exam-type-other" name="exam_type_other[]" placeholder="Enter examination name"
           style="display:none;" <?php echo !$canEdit ? 'readonly' : ''; ?>>
</td>
                                                        <td><input type="month" class="form-control form-control-sm" name="exam_date[]" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td><input type="text" class="form-control form-control-sm" name="exam_no[]" <?php echo !$canEdit ? 'readonly' : ''; ?>></td>
                                                        <td>
                                                            <div class="exam-sg">
                                                                <select class="form-select form-select-sm exam-subjects" multiple <?php echo !$canEdit ? 'disabled' : ''; ?>>
                                <option value="English Language">English Language</option>
                                <option value="Mathematics">Mathematics</option>
                                <option value="Biology">Biology</option>
                                <option value="Chemistry">Chemistry</option>
                                <option value="Physics">Physics</option>
                                <option value="Agricultural Science">Agricultural Science</option>
                                <option value="Economics">Economics</option>
                                <option value="Government">Government</option>
                                <option value="Geography">Geography</option>
                                <option value="Literature in English">Literature in English</option>
                                <option value="Civic Education">Civic Education</option>
                                <option value="Christian Religious Studies">Christian Religious Studies</option>
                                <option value="Islamic Religious Studies">Islamic Religious Studies</option>
                                <option value="Commerce">Commerce</option>
                                <option value="Financial Accounting">Financial Accounting</option>
                                <option value="Further Mathematics">Further Mathematics</option>
                                <option value="Data Processing">Data Processing</option>
                                <option value="Computer Studies">Computer Studies</option>
                                <option value="Technical Drawing">Technical Drawing</option>
                                <option value="Home Economics">Home Economics</option>
                                <option value="Fine Arts">Fine Arts</option>
                                <option value="French">French</option>
                                <option value="History">History</option>
                                                                </select>
                                                                <div class="exam-grade-rows mt-2"></div>
                                                                <input type="hidden" name="exam_grades[]" class="exam-grades-hidden" value="">
                                                                <div class="form-text">Select one or more subjects, then choose grades (A1 - F9) for each selected subject.</div>
                                                            </div>
                                                        </td>
                                                        <td><div class="form-check"><input class="form-check-input" type="checkbox" name="exam_attached[]" checked <?php echo !$canEdit ? 'disabled' : ''; ?>></div></td>
                                                        <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()" <?php echo !$canEdit ? 'disabled' : ''; ?>>X</button></td>
                                                    </tr>
                                                    <?php endif; ?>
                                                </tbody>
                                            </table>
                                        </div>
                                        <?php if ($editCount < 3): ?>
                                        <button type="button" class="btn btn-sm btn-secondary" onclick="addResultRow()">Add More</button>
                                        <?php endif; ?>
                                    </div>

                                    <!-- Referee Information -->
                                    <div class="form-section">
                                        <h3>Referee Information</h3>
                                        <div class="row">
                                            <div class="col-md-6 mb-3">
                                                <label class="form-label">Referee Full Name</label>
                                                <input type="text" class="form-control" name="ref_name" value="<?php echo $referee['full_name'] ?? ''; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>>
                                            </div>
                                            <div class="col-md-6 mb-3">
                                                <label class="form-label">Occupation</label>
                                                <input type="text" class="form-control" name="ref_occ" value="<?php echo $referee['occupation'] ?? ''; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>>
                                            </div>
                                            <div class="col-md-6 mb-3">
                                                <label class="form-label">Post Held</label>
                                                <input type="text" class="form-control" name="ref_post" value="<?php echo $referee['post_held'] ?? ''; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>>
                                            </div>
                                            <div class="col-md-6 mb-3">
                                                <label class="form-label">Date</label>
                                                <input type="date" class="form-control" name="ref_date" value="<?php echo $referee['date'] ?? ''; ?>" <?php echo !$canEdit ? 'readonly' : ''; ?>>
                                            </div>
                                            <div class="col-md-12 mb-3">
                                                <label class="form-label">Address</label>
                                                <textarea class="form-control" name="ref_address" rows="3" <?php echo !$canEdit ? 'readonly' : ''; ?>><?php echo $referee['address'] ?? ''; ?></textarea>
                                            </div>
                                            <div class="col-md-12 mb-3">
                                                <label class="form-label">Referee's Signature</label>
                                                <div class="signature-wrapper border p-2 bg-light">
                                                    <canvas id="referee_signature" class="signature-canvas"></canvas>
                                                    <div class="d-flex justify-content-between mt-2">
                                                        <span>Clear</span>
                                                        <button type="button" class="btn btn-secondary btn-sm" id="referee_signature_clear" <?php echo isset($editCount) && !$canEdit ? 'disabled' : ''; ?>>Clear Pad</button>
                                                    </div>
                                                    <input type="hidden" id="ref_signature_data" name="ref_signature_data" value="<?php echo $referee['signature_data'] ?? ''; ?>">
                                                </div>
                                            </div>
                                        </div>
                                    </div>

                                    <!-- Applicant Declaration -->
                                    <div class="form-section">
                                        <h3>Applicant's Declaration</h3>
                                        <p>I, <strong><span id="declarantName"><?php echo e(studentFullNameUpper($student)); ?></span></strong>, hereby declare that the information given above is true and correct.</p>

                                        <div class="row">
                                            <div class="col-md-12 mb-3">
                                                <label class="form-label">Applicant's Signature</label>
                                                <div class="signature-wrapper border p-2 bg-light">
                                                    <canvas id="applicant_signature" class="signature-canvas"></canvas>
                                                    <div class="d-flex justify-content-between mt-2">
                                                        <span>Clear</span>
                                                        <button type="button" class="btn btn-secondary btn-sm" id="applicant_signature_clear" <?php echo isset($editCount) && !$canEdit ? 'disabled' : ''; ?>>Clear Pad</button>
                                                    </div>
                                                    <input type="hidden" id="applicant_signature_data" name="applicant_signature_data" value="<?php echo $student['signature_data'] ?? ''; ?>">
                                                </div>
                                            </div>
                                        </div>
                                    </div>

                                    <div class="d-flex flex-wrap gap-2 align-items-start">
<?php if ($canEdit): ?>
                                            <!-- NOTE: We also set name/value on the button so it submits even if JS fails -->
                                            <button type="submit" class="btn btn-primary" name="action" value="update_application"
                                                onclick="document.getElementById('applicationAction').value=this.value;">
                                                <i class="bi bi-save"></i> Save Changes
                                            </button>
                                            <button type="submit" class="btn btn-success" name="action" value="final_submit_application"
                                                onclick="if(!confirm('Final submit will LOCK your application and you will NOT be able to edit again. Continue?')) return false; document.getElementById('applicationAction').value=this.value;">
                                                <i class="bi bi-check2-circle"></i> Final Submit (Lock)
                                            </button>

                                        <?php else: ?>
                                            <div class="alert alert-info mb-0 numbering-help-block">Application is locked (submitted) or you have used up your allowed edits.</div>
                                        <?php endif; ?>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>


                <!-- Application Preview Modal -->
                <div class="modal fade" id="applicationPreviewModal" tabindex="-1" aria-hidden="true">
                  <div class="modal-dialog modal-xl modal-dialog-scrollable">
                    <div class="modal-content">
                      <div class="modal-header">
                        <h5 class="modal-title">Application Preview</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                      </div>
                      <div class="modal-body" id="applicationPreviewBody"></div><div class="mt-4 application-signature-block">
  <div><strong>Signature:</strong></div>
  <?php
      $appSig = null;
      try {
          $stmtAppS = $pdo->prepare("SELECT file_path FROM system_assets WHERE asset_type='application_signature' ORDER BY id DESC LIMIT 1");
          $stmtAppS->execute();
          $appSig = $stmtAppS->fetchColumn();
      } catch (Throwable $e) {}
      $appName = trim(getSystemSettingValue($pdo,'application_signatory_name',''));
  ?>
  <div class="mt-2">
      <?php if (!empty($appSig)): ?>
          <img src="<?php echo htmlspecialchars($appSig); ?>" alt="Application Signature" style="max-height: 70px; max-width: 240px; object-fit: contain;">
      <?php else: ?>
          <div style="height:40px; border-bottom:1px solid rgba(0,0,0,0.6); width:240px;"></div>
      <?php endif; ?>
      <?php if (!empty($appName)): ?><div class="small mt-1"><strong><?php echo htmlspecialchars($appName); ?></strong></div><?php endif; ?>
  </div>
</div>

                      <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                        <button type="button" class="btn btn-primary" onclick="printApplicationPreview()"><i class="bi bi-printer"></i> Print</button>
                      </div>
                    </div>
                  </div>
                </div>

<script>
                function openPrintableForm(){
    // Open server-rendered printable form in the current tab so printing stays in one tab
    window.location.href = '?page=print_form';
}

// Legacy modal preview (kept for compatibility)
function openApplicationPreview(){
    // Build a simple preview table from the current form values and show it in the Bootstrap modal.
    const form = document.getElementById('applicationForm');
    if(!form){ alert('Application form not found on this page.'); return; }

    const fd = new FormData(form);
    const rows = [];

    for (const [k,v] of fd.entries()){
        // Skip internal fields
        if(k === 'action' || k === 'applicationAction' || k === 'csrf_token') continue;

        // Skip file blobs; show just file name when present
        if (v instanceof File){
            if(v && v.name){ rows.push([k, v.name]); }
            continue;
        }

        const val = String(v ?? '').trim();
        if(!val) continue;

        // Reduce noise for repeated arrays
        rows.push([k, val]);
    }

    const esc = (s) => String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
    const tableRows = rows.map(([k,v]) => `
        <tr>
            <td class="fw-semibold" style="width:40%;">${esc(k)}</td>
            <td>${esc(v)}</td>
        </tr>
    `).join('');

    const bodyEl = document.getElementById('applicationPreviewBody');
    if(!bodyEl){ alert('Preview container not found.'); return; }

    bodyEl.innerHTML = `
        <div class="d-flex flex-wrap justify-content-between align-items-start gap-2 mb-3">
            <div>
                <h5 class="mb-1">Application Preview</h5>
                <div class="text-muted small">Review your details, then click <strong>Save Changes</strong> or <strong>Final Submit</strong> on the main page.</div>
            </div>
            <div class="d-flex gap-2 no-print">
                <button type="button" class="btn btn-outline-primary" onclick="printApplicationPreview()">
                    <i class="bi bi-printer"></i> Print
                </button>
            </div>
        </div>
        <div class="table-responsive">
            <table class="table table-bordered table-sm align-middle">
                <thead class="table-light">
                    <tr><th>Field</th><th>Value</th></tr>
                </thead>
                <tbody>
                    ${tableRows || '<tr><td colspan="2" class="text-muted">No previewable fields found.</td></tr>'}
                </tbody>
            </table>
        </div>
    `;

    // Show modal (preferred: avoids popup blockers)
    try{
        if (window.bootstrap && bootstrap.Modal){
            const modalEl = document.getElementById('applicationPreviewModal');
            const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
            modal.show();
            return;
        }
    }catch(e){ /* ignore and fallback */ }

    // Fallback: open a new window if Bootstrap isn't available
    const w = window.open('', '_blank', 'width=980,height=720');
    if(!w){ alert('Popup blocked. Please allow popups for preview/print.'); return; }
    w.document.open();
    w.document.write(`<html><head><title>Application Preview</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
        </head><body class="p-3">${bodyEl.innerHTML}</body></html>`);
    w.document.close();
    w.focus();
}

function printApplicationPreview(){
                    const bodyEl = document.getElementById('applicationPreviewBody');
                    if(!bodyEl) return;
                    const w = window.open('', '_blank');
                    w.document.write('<html><head><title>Application Preview</title>');
                    w.document.write('<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">');
                    w.document.write('</head><body class="p-3">');
                    w.document.write(bodyEl.innerHTML);
                    w.document.write('</body></html>');
                    w.document.close();
                    w.focus();
                    w.print();
                }
                document.addEventListener('DOMContentLoaded', function() {
                    // Handle document uploads
                    const documentFiles = document.getElementById('document_files');
                    const documentList = document.getElementById('document_list');
                    const uploadArea = document.getElementById('document_upload_area');

                    function renderSelectedDocs(fileList) {
                        if (!documentList) return;
                        documentList.innerHTML = '';
                        if (!fileList || fileList.length === 0) {
                            documentList.innerHTML = '<div class="text-muted">No files selected.</div>';
                            return;
                        }
                        const types = [
                            {v:'nin', t:'NIN'},
                            {v:'neco', t:'NECO'},
                            {v:'wace', t:'WACE'},
                            {v:'gce', t:'GCE'},
                            {v:'nabteb', t:'NABTEB'},
                            {v:'birth_certificate', t:'Birth Certificate'},
                            {v:'attestation_letter', t:'Attestation Letter'},
                            {v:'other', t:'Other'}
                        ];
                        Array.from(fileList).forEach((f, idx) => {
                            const row = document.createElement('div');
                            row.className = 'd-flex align-items-center justify-content-between border rounded p-2 mb-2';

                            const left = document.createElement('div');
                            left.innerHTML = '<div class="fw-semibold">' + f.name + '</div><div class="small text-muted">' + Math.round((f.size/1024)*10)/10 + ' KB</div>';

                            const sel = document.createElement('select');
                            sel.className = 'form-select form-select-sm';
                            sel.name = 'document_type[' + idx + ']';
                            types.forEach(o => {
                                const opt = document.createElement('option');
                                opt.value = o.v;
                                opt.textContent = o.t;
                                sel.appendChild(opt);
                            });

                            row.appendChild(left);
                            row.appendChild(sel);
                            documentList.appendChild(row);
                        });
                    }

                    if (documentFiles) {
                        documentFiles.addEventListener('change', function() {
                            renderSelectedDocs(documentFiles.files);
                        });
                    }

                    // If upload area is not present on this page, skip drag/drop wiring
                    if (!uploadArea || !documentFiles) { return; }

                    // Drag and drop functionality
                    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
                        uploadArea.addEventListener(eventName, preventDefaults, false);
                    });

                    function preventDefaults(e) {
                        e.preventDefault();
                        e.stopPropagation();
                    }

                    ['dragenter', 'dragover'].forEach(eventName => {
                        uploadArea.addEventListener(eventName, highlight, false);
                    });

                    ['dragleave', 'drop'].forEach(eventName => {
                        uploadArea.addEventListener(eventName, unhighlight, false);
                    });

                    function highlight() {
                        uploadArea.classList.add('highlight');
                    }

                    function unhighlight() {
                        uploadArea.classList.remove('highlight');
                    }

                    uploadArea.addEventListener('drop', handleDrop, false);

                    function handleDrop(e) {
                        const dt = e.dataTransfer;
                        const files = dt.files;
                        handleFiles(files);
                    }

                    if (documentFiles) documentFiles.addEventListener('change', function() {
                        handleFiles(this.files);
                    });

                    function handleFiles(files) {
                        ([...files]).forEach(uploadFile);
                    }

                    function uploadFile(file) {
                        // Check file type
                        const validTypes = ['image/jpeg', 'image/png', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
                        if (!validTypes.includes(file.type)) {
                            alert('Invalid file type. Please upload JPG, JPEG, PNG, PDF, DOC, or DOCX files.');
                            return;
                        }

                        // Check file size
                        if (file.size > 5242880) { // 5MB
                            alert('File is too large. Maximum size is 5MB.');
                            return;
                        }

                        // Create document item
                        const documentItem = document.createElement('div');
                        documentItem.className = 'document-item';

                        // Get file extension
                        const extension = file.name.split('.').pop().toLowerCase();

                        // Create document type selector
                        const typeSelector = document.createElement('select');
                        typeSelector.className = 'form-select form-select-sm';
                        typeSelector.name = 'document_type[]';

                        // Add options based on file type
                        if (['jpg', 'jpeg', 'png'].includes(extension)) {
                            typeSelector.innerHTML = `
                                <option value="nin">NIN</option>
                                <option value="neco">NECO</option>
                                <option value="waec_wassce">WAEC/WASSCE</option>
                                <option value="gce">GCE</option>
                                <option value="nabteb">NABTEB/NAPTEB</option>
                                <option value="birth_certificate">Birth Certificate</option>
                                <option value="attestation_letter">Attestation Letter</option>
                                <option value="other">Other</option>
                            `;
                        } else if (extension === 'pdf') {
                            typeSelector.innerHTML = `
                                <option value="nin">NIN</option>
                                <option value="neco">NECO</option>
                                <option value="waec_wassce">WAEC/WASSCE</option>
                                <option value="gce">GCE</option>
                                <option value="nabteb">NABTEB/NAPTEB</option>
                                <option value="birth_certificate">Birth Certificate</option>
                                <option value="attestation_letter">Attestation Letter</option>
                                <option value="other">Other</option>
                            `;
                        } else {
                            typeSelector.innerHTML = `
                                <option value="nin">NIN</option>
                                <option value="neco">NECO</option>
                                <option value="waec_wassce">WAEC/WASSCE</option>
                                <option value="gce">GCE</option>
                                <option value="nabteb">NABTEB/NAPTEB</option>
                                <option value="birth_certificate">Birth Certificate</option>
                                <option value="attestation_letter">Attestation Letter</option>
                                <option value="other">Other</option>
                            `;
                        }

                        documentItem.innerHTML = `
                            <div>
                                <i class="bi bi-file-earmark"></i>
                                <span>${file.name}</span>
                            </div>
                            <div>
                                <button type="button" class="btn btn-sm btn-outline-danger" onclick="this.parentElement.parentElement.remove()">Remove</button>
                            </div>
                        `;

                        // Insert type selector after file name
                        const fileNameDiv = documentItem.querySelector('div');
                        fileNameDiv.appendChild(typeSelector);

                        // Add to document list
                        documentList.appendChild(documentItem);
                    }

                    // Delete document function
                    window.deleteDocument = function(docId) {
                        if (confirm('Are you sure you want to delete this document?')) {
                            const xhr = new XMLHttpRequest();
                            xhr.open('POST', '', true);
                            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                            xhr.onreadystatechange = function() {
                                if (xhr.readyState === 4 && xhr.status === 200) {
                                    if (xhr.responseText === 'success') {
                                        location.reload();
                                    } else {
                                        alert('Error: ' + xhr.responseText);
                                    }
                                }
                            };
                            xhr.send('action=delete_document&doc_id=' + docId);
                        }
                    };

                    // Download application as PDF
                    window.downloadApplication = function() {
                        // Pre-render logo HTML from PHP settings
                        const leftLogoHtml = <?php echo json_encode($leftLogoPath ? '<img src="' . htmlspecialchars($leftLogoPath, ENT_QUOTES) . '" alt="HONTTECH Logo" class="application-form-logo">' : '<div class="application-form-logo">HONTTECH Logo</div>'); ?>;
                        const rightLogoHtml = <?php echo json_encode($rightLogoPath ? '<img src="' . htmlspecialchars($rightLogoPath, ENT_QUOTES) . '" alt="DAHIFT Logo" class="application-form-logo">' : '<div class="application-form-logo">DAHIFT Logo</div>'); ?>;

                        // Create a new window for printing
                        const printWindow = window.open('', '_blank');

                        // Get the HTML content
                        const applicationContent = document.querySelector('.card-body').innerHTML;

                        // Write the HTML to the new window
                        printWindow.document.write(`
                            <!DOCTYPE html>
                            <html>
                            <head>
                                <title>HontTech Application Form</title>
                                <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
                                <style>
                                    body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
                                    .form-section { margin-bottom: 30px; padding: 15px; border: 1px solid #eee; border-radius: 5px; }
                                    .form-section h3 { color: #003366; margin-bottom: 15px; padding-bottom: 5px; border-bottom: 1px solid #eee; }
                                    .application-form-logo-area { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
                                    .application-form-logo { width: 100px; height: 100px; object-fit: contain; }
                                    .application-form-title { font-size: 1.5rem; font-weight: bold; color: #003366; margin-bottom: 10px; }
                                    .application-form-subtitle { font-size: 1.1rem; color: #666; }
                                    .application-form-row { display: flex; margin-bottom: 10px; }
                                    .application-form-label { width: 200px; font-weight: bold; }
                                    .application-form-value { flex: 1; }
                                    .application-form-signature { margin-top: 20px; text-align: center; }
                                    .application-form-signature img { max-height: 100px; border: 1px solid #eee; border-radius: 5px; padding: 5px; background-color: #f9f9f9; }
                                    @media print { .no-print { display: none !important; } }
                                </style>
                            </head>
                            <body>
                                <div class="application-form-container">
                                    <div class="application-form-header">
                                        <div class="application-form-logo-area">
                                            ${leftLogoHtml}
                                            <div class="text-center">
                                                <div class="application-form-title">Honest Technologies University</div>
                                                <div class="application-form-subtitle">Ahmadu Bello University, Zaria</div>
                                                <div class="application-form-subtitle">In affiliation with Dahift Global Ltd</div>
                                                <div class="application-form-subtitle">Application Form for Admission</div>
                                            </div>
                                            ${rightLogoHtml}
                                        </div>
                                    </div>
                                    ${applicationContent}
                                    <div class="application-form-actions no-print">
                                        <button onclick="window.print()" class="btn btn-primary">Print</button>
                                        <button onclick="window.close()" class="btn btn-secondary">Close</button>
                                    </div>
                                </div>
                                <script>
                                    // Auto-print when the page loads
                                    window.onload = function() {
                                        setTimeout(function() {
                                            window.print();
                                        }, 500);
                                    };
                                </script>
                            </body>
                            </html>

                </script>
                <?php
                break;


            case 'payment':
                // Check if user is logged in
                if (!isStudent()) { redirect('?page=login'); }

                $studentId = (int)($_SESSION['user_id'] ?? 0);
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC);

                $fees = [
                    'registration' => ['label' => 'Registration Fee', 'amount' => (float)getSystemSettingValue($pdo,'registration_fee','0'), 'status' => 'registration_fee_status', 'typeName' => 'Registration Fee'],
                    'acceptance'   => ['label' => 'Acceptance Fee',   'amount' => (float)getSystemSettingValue($pdo,'acceptance_fee','0'),   'status' => 'acceptance_fee_status',   'typeName' => 'Acceptance Fee'],
                    'hostel'       => ['label' => 'Hostel Fee',       'amount' => (float)portalStudentManagedFeeAmount($pdo, 'hostel', (array)$student, (string)($student['session'] ?? ''), (string)($student['semester'] ?? '')),       'status' => 'hostel_fee_status',       'typeName' => 'Hostel Fee'],
                    'school'       => ['label' => 'School Fee',       'amount' => (float)portalStudentSchoolFeeAmount($pdo, (array)$student, (string)($student['session'] ?? ''), (string)($student['semester'] ?? '')),       'status' => 'school_fee_status',       'typeName' => 'School Fee'],
                    'other'        => ['label' => 'Other Payments',   'amount' => 0.0, 'status' => '', 'typeName' => 'Other Payments'],
                ];
                $activeTab = $_GET['tab'] ?? 'registration';
                if (!isset($fees[$activeTab])) $activeTab = 'registration';

                $paymentTabToggleMap = [
                    'registration' => 'menu_student_registration_fee',
                    'acceptance' => 'menu_student_acceptance_fee',
                    'hostel' => 'menu_student_hostel_fee',
                    'school' => 'menu_student_school_fee',
                    'other' => 'menu_student_payments',
                ];
                if (isset($paymentTabToggleMap[$activeTab]) && !studentMenuEnabled($pdo, $paymentTabToggleMap[$activeTab])) {
                    $fallbackTab = null;
                    foreach (['registration','acceptance','hostel','school','other'] as $candidateTab) {
                        $candidateKey = $paymentTabToggleMap[$candidateTab] ?? 'menu_student_payments';
                        if (studentMenuEnabled($pdo, $candidateKey)) {
                            $fallbackTab = $candidateTab;
                            break;
                        }
                    }
                    if ($fallbackTab === null) {
                        flash('danger', 'Payment pages are currently disabled by the administrator.');
                        redirect('?page=student_dashboard');
                    }
                    if ($fallbackTab !== $activeTab) {
                        flash('warning', 'That payment page is currently disabled by the administrator.');
                        redirect('?page=payment&tab=' . urlencode($fallbackTab));
                    }
                }

                // Enforce acceptance fee prerequisite for School/Hostel fees
                $acceptPaid = payment_status_is_paid((string)($student['acceptance_fee_status'] ?? ''));
                if (!$acceptPaid && in_array($activeTab, ['school','hostel'], true)) {
                    flash('warning', 'Please pay the Acceptance Fee before paying for School Fee or Hostel Fee.');
                    $activeTab = 'acceptance';
                }


                $gws = portalSortGatewayOptionsByDefault(
                    portalEnabledGatewayOptions($pdo, false),
                    (string)getSystemSettingValue($pdo,'default_gateways',getSystemSettingValue($pdo,'default_gateway','paystack'))
                );
                if (empty($gws)) { $gws['paystack'] = 'Paystack'; }
                $def = strtolower(trim((string)array_key_first($gws)));
                $manualEnabled = getSystemSettingValue($pdo,'manual_payment_enabled','enabled') === 'enabled';

                $accommodationStudentId = (int)($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0);
                if ($_SERVER['REQUEST_METHOD'] === 'POST' && (($_POST['action'] ?? '') === 'save_accommodation_status') && $accommodationStudentId > 0) {
                    $selectedAccommodation = (($_POST['accommodation_status'] ?? 'Campus') === 'Off-Campus') ? 'Off-Campus' : 'Campus';
                    try {
                        if (portalStudentAccommodationLockedForSession($pdo, $student)) {
                            flash('warning', 'Accommodation status is locked for the current session because the hostel fee has already been paid. You can change it again in a new session.');
                        } else {
                            $stmt = $pdo->prepare("UPDATE students SET accommodation_status = ? WHERE id = ?");
                            $stmt->execute([$selectedAccommodation, $accommodationStudentId]);
                            $student['accommodation_status'] = $selectedAccommodation;
                            try { portalSynchronizeClearanceForStudent($pdo, $student, portalCurrentAcademicSessionName($pdo), ''); } catch (Throwable $e) {}
                            flash('success', 'Accommodation status updated successfully.');
                        }
                    } catch (Throwable $e) {
                        flash('danger', 'Unable to update accommodation status.');
                    }
                    redirect('?page=payment&tab=hostel');
                }
                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header">PAYMENT</div>
                            <div class="card-body">
                                <ul class="nav nav-tabs mb-3" role="tablist">
                                    <?php foreach ($fees as $k => $meta): ?>
                                        <?php $tabToggleKey = $paymentTabToggleMap[$k] ?? 'menu_student_payments'; ?>
                                        <?php if (!studentMenuEnabled($pdo, $tabToggleKey)) continue; ?>
                                        <li class="nav-item" role="presentation">
                                            <?php
                                                $restricted = (!$acceptPaid && in_array($k, ['school','hostel'], true));
                                                $tabHref = $restricted ? '?page=payment&tab=acceptance' : ('?page=payment&tab=' . e($k));
                                            ?>
                                            <a class="nav-link <?php echo ($activeTab===$k)?'active':''; ?> <?php echo $restricted?'disabled text-muted':''; ?>" href="<?php echo $tabHref; ?>" <?php echo $restricted?'title="Pay Acceptance Fee first"':''; ?>>

                                                <?php echo e($meta['label']); ?>
                                            </a>
                                        </li>
                                    <?php endforeach; ?>
                                </ul>

                                <div class="tab-content">
                                    <?php foreach ($fees as $k => $meta): ?>
                                        <div class="tab-pane fade <?php echo ($activeTab===$k)?'show active':''; ?>" id="tab-<?php echo e($k); ?>" role="tabpanel">
                                            <?php if ($k !== 'other'): ?>
                                                <?php
                                                    $amount = (float)$meta['amount'];
                                                    $statusField = (string)$meta['status'];
                                                    $statusVal = $statusField && isset($student[$statusField]) ? (string)$student[$statusField] : '';
                                                    $isPaid = payment_status_is_paid($statusVal);
                                                    $hostelRequirementState = ($k === 'hostel' && function_exists('portalStudentHostelRequirementSummary')) ? portalStudentHostelRequirementSummary($pdo, $student) : ['requires_hostel_fee' => true, 'label' => '', 'message' => ''];
                                                    $hostelOffCampus = ($k === 'hostel' && empty($hostelRequirementState['requires_hostel_fee']));
                                                    $accommodationLocked = ($k === 'hostel') ? portalStudentAccommodationLockedForSession($pdo, $student) : false;
                                                    $displayStatusLabel = ($k === 'hostel') ? portalStudentHostelStatusLabel($student) : payment_status_label($statusVal);
                                                    $statusBadgeClass = $hostelOffCampus ? 'bg-info text-dark' : ('bg-' . ($isPaid ? 'success' : 'secondary'));
                                                ?>
                                                <?php if ($k === 'hostel'): ?>
                                                    <?php renderFlashes(); ?>
                                                <?php endif; ?>
                                                <h4 class="mb-2"><?php echo e($meta['label']); ?> Payment</h4>
                                                <div class="alert alert-info mb-3"><i class="bi bi-info-circle"></i> Automatic payments are processed through enabled gateways. Manual bank transfer is available when enabled by admin.</div>

                                                <div class="row g-3">
                                                    <!-- LEFT: Automatic gateway + wallet -->
                                                    <div class="col-md-6">
                                                        <div class="card">
                                                            <div class="card-body">
                                                                <h5 class="mb-3">Automatic Payment</h5>
                                                                <table class="table">
                                                                    <tr><td><?php echo e($meta['label']); ?>:</td><td>₦<?php echo number_format($amount); ?></td></tr>
                                                                    <tr><td>Status:</td><td><span class="badge <?php echo e($statusBadgeClass); ?>"><?php echo e($displayStatusLabel); ?></span></td></tr>
                                                                    <tr><td>Total Amount:</td><td><strong>₦<?php echo number_format($amount); ?></strong></td></tr>
                                                                </table>

                                                                <?php if ($hostelOffCampus): ?>
                                                                    <div class="alert alert-info mb-0">
                                                                        <i class="bi bi-house-door"></i> <?php echo e((string)($hostelRequirementState['message'] ?? 'Hostel fee is not required for your current accommodation status.')); ?>
                                                                    </div>
                                                                <?php elseif ($isPaid): ?>
                                                                    <div class="alert alert-success mb-0">
                                                                        <i class="bi bi-check-circle"></i> You have already paid this fee.
                                                                        <?php
    // Open the actual receipt for this fee (not payment history)
    $txType = ($k === 'registration') ? 'Registration Fee' :
              (($k === 'acceptance') ? 'Acceptance Fee' :
              (($k === 'hostel') ? 'Hostel Fee' :
              (($k === 'school') ? 'School Fee' : (string)($meta['label'] ?? 'Payment'))));
    $rp = 'payment_receipt';
    if ($txType === 'Acceptance Fee') $rp = 'acceptance_payment_receipt';
    elseif ($txType === 'Hostel Fee') $rp = 'hostel_payment_receipt';
    elseif ($txType === 'School Fee') $rp = 'school_payment_receipt';

    $receiptTxId = 0;
    try {
        $st = $pdo->prepare("SELECT id FROM payment_transactions
                             WHERE student_id = ? AND transaction_type = ?
                               AND status IN ('Approved','Successful')
                             ORDER BY id DESC LIMIT 1");
        $st->execute([(int)$studentId, $txType]);
        $receiptTxId = (int)$st->fetchColumn();
    } catch (Throwable $e) { $receiptTxId = 0; }

    $receiptUrl = ($receiptTxId > 0) ? ("?page=" . $rp . "&transaction_id=" . $receiptTxId) : "?page=payment_history";
?>
<a href="<?php echo $receiptUrl; ?>" class="btn btn-sm btn-primary ms-2">View Receipt</a>
                                                                    </div>
                                                                <?php else: ?>

<?php if ($k === 'school'): ?>
    <?php
        $schoolTotal = (float)$amount;
        $installmentEnabled = true;
        $installmentAmount = ($schoolTotal > 0 ? ($schoolTotal / 2.0) : 0.0);
        $paidInstallmentsSchool = 0;
        try {
            $stx = $pdo->prepare("SELECT COUNT(*) FROM payment_transactions WHERE student_id = ? AND transaction_type = 'School Fee' AND status = 'Approved' AND COALESCE(amount,0) > 0 AND total_installments > 1");
            $stx->execute([(int)$studentId]);
            $paidInstallmentsSchool = (int)$stx->fetchColumn();
        } catch (Throwable $e) { $paidInstallmentsSchool = 0; }
        $disableOneTimeSchool = ($paidInstallmentsSchool > 0);
        $nextInstallmentSchool = min(2, max(1, $paidInstallmentsSchool + 1));
        $installmentBtnText = ($nextInstallmentSchool === 2) ? 'Pay Second Installment' : 'Pay First Installment';

        $selectedSchoolSession = trim((string)($_GET['session_name'] ?? $_POST['payment_session'] ?? ''));
        $selectedSchoolSemester = trim((string)($_GET['semester'] ?? $_POST['payment_semester'] ?? ''));
        if ($selectedSchoolSession === '' && function_exists('portalCurrentAcademicSessionName')) {
            try { $selectedSchoolSession = (string)portalCurrentAcademicSessionName($pdo); } catch (Throwable $e) { $selectedSchoolSession = ''; }
        }
        if ($selectedSchoolSemester === '') {
            $selectedSchoolSemester = (string)(($student['semester'] ?? '') ?: 'First');
        }

        $schoolSummary = [];
        $schoolPrincipalOutstanding = max(0.0, $schoolTotal);
        $schoolLateFeeBalance = 0.0;
        $schoolTotalDue = $schoolTotal;
        if (function_exists('portalSchoolFeePeriodSummary')) {
            try {
                $schoolSummary = (array)portalSchoolFeePeriodSummary($pdo, (int)$studentId, $selectedSchoolSession, $selectedSchoolSemester, (array)($student ?? []));
                $schoolPrincipalOutstanding = (float)($schoolSummary['principal_outstanding'] ?? $schoolPrincipalOutstanding);
                $schoolLateFeeBalance = (float)($schoolSummary['late_fee_balance'] ?? 0);
                $schoolTotalDue = (float)($schoolSummary['total_due'] ?? ($schoolPrincipalOutstanding + $schoolLateFeeBalance));
            } catch (Throwable $e) {
                $schoolSummary = [];
            }
        }

        $schoolPayNowOneTime = $schoolTotalDue > 0 ? $schoolTotalDue : $schoolTotal;
        $schoolPayNowInstallment = ((int)($schoolSummary['approved_installments'] ?? 0) > 0)
            ? max(0.0, $schoolTotalDue)
            : max(0.0, ((float)$installmentAmount + $schoolLateFeeBalance));
        if ($schoolPayNowInstallment <= 0) {
            $schoolPayNowInstallment = max(0.0, (float)$installmentAmount);
        }
    ?>
    <div class="mb-2">
        <label class="form-label">Choose School Fee Plan</label>
        <div class="form-check">
            <input class="form-check-input" type="radio" name="school_plan" id="school_plan_one" value="one" <?php echo $disableOneTimeSchool ? 'disabled' : 'checked'; ?>>
            <label class="form-check-label" for="school_plan_one">One-time Payment - ₦<?php echo number_format($schoolTotal); ?></label>
        </div>
        <div class="form-check">
            <input class="form-check-input" type="radio" name="school_plan" id="school_plan_inst" value="inst" <?php echo $disableOneTimeSchool ? 'checked' : ''; ?>>
            <label class="form-check-label" for="school_plan_inst">Installment Payment - next payable ₦<?php echo number_format($schoolPayNowInstallment); ?></label>
        </div>
        <?php if ($disableOneTimeSchool): ?>
            <div class="small text-muted mt-1"><i class="bi bi-info-circle"></i> You already started installments; one-time payment is disabled. Please pay the next installment.</div>
        <?php endif; ?>
        <div class="small text-muted mt-1">Wallet/manual installments are also available on the School Fee page: <a href="?page=school_payment">open School Fee payment</a>.</div>
    </div>

    <form method="post" class="mb-3" id="school_one_time_form" style="<?php echo $disableOneTimeSchool ? 'display:none;' : ''; ?>">
        <input type="hidden" name="action" value="initiate_payment">
        <input type="hidden" name="payment_type" value="school"><input type="hidden" name="payment_session" value="<?php echo e($selectedSchoolSession); ?>"><input type="hidden" name="payment_semester" value="<?php echo e($selectedSchoolSemester); ?>">
        <input type="hidden" name="installment_number" value="1">
        <input type="hidden" name="total_installments" value="1">
        <div class="mb-2">
            <label class="form-label">Choose Payment Gateway</label>
            <select name="gateway" class="form-select">
                <?php foreach ($gws as $gwK => $gwLabel): ?>
                    <option value="<?php echo e($gwK); ?>" <?php echo ($def===$gwK)?'selected':''; ?>><?php echo e($gwLabel); ?></option>
                <?php endforeach; ?>
            </select>
        </div>
        <button type="submit" class="btn btn-primary w-100">Pay One-time</button>
    </form>

    <form method="post" class="mb-3" id="school_installment_form" style="<?php echo $disableOneTimeSchool ? '' : 'display:none;'; ?>">
        <input type="hidden" name="action" value="initiate_payment">
        <input type="hidden" name="payment_type" value="school">
        <input type="hidden" name="installment_number" value="<?php echo (int)$nextInstallmentSchool; ?>">
        <input type="hidden" name="total_installments" value="2">
        <div class="mb-2">
            <label class="form-label">Choose Payment Gateway</label>
            <select name="gateway" class="form-select">
                <?php foreach ($gws as $gwK => $gwLabel): ?>
                    <option value="<?php echo e($gwK); ?>" <?php echo ($def===$gwK)?'selected':''; ?>><?php echo e($gwLabel); ?></option>
                <?php endforeach; ?>
            </select>
        </div>
        <button type="submit" class="btn btn-primary w-100"><?php echo e($installmentBtnText); ?></button>
    </form>

    <script>
    document.addEventListener('DOMContentLoaded', function(){
        const r1 = document.getElementById('school_plan_one');
        const r2 = document.getElementById('school_plan_inst');
        const f1 = document.getElementById('school_one_time_form');
        const f2 = document.getElementById('school_installment_form');
        function toggle(){
            if(!r1 || !r2 || !f1 || !f2) return;
            const isOne = r1.checked;
            f1.style.display = isOne ? 'block' : 'none';
            f2.style.display = isOne ? 'none' : 'block';
            const w1 = document.getElementById('wallet_school_one_time_tab');
            const w2 = document.getElementById('wallet_school_installment_tab');
            const m1 = document.getElementById('manual_school_one_time_tab');
            const m2 = document.getElementById('manual_school_installment_tab');
            if (w1) w1.style.display = isOne ? 'block' : 'none';
            if (w2) w2.style.display = isOne ? 'none' : 'block';
            if (m1) m1.style.display = isOne ? 'block' : 'none';
            if (m2) m2.style.display = isOne ? 'none' : 'block';
        }
        if(r1) r1.addEventListener('change', toggle);
        if(r2) r2.addEventListener('change', toggle);
        toggle();
    });
    </script>
<?php else: ?>
    <form method="post" class="mb-3">
        <input type="hidden" name="action" value="initiate_payment">
        <input type="hidden" name="payment_type" value="<?php echo e($k); ?>">
        <div class="mb-2">
            <label class="form-label">Choose Payment Gateway</label>
            <select name="gateway" class="form-select">
                <?php foreach ($gws as $gwK => $gwLabel): ?>
                    <option value="<?php echo e($gwK); ?>" <?php echo ($def===$gwK)?'selected':''; ?>><?php echo e($gwLabel); ?></option>
                <?php endforeach; ?>
            </select>
        </div>
        <button type="submit" class="btn btn-primary w-100">Pay Now</button>
    </form>
<?php endif; ?>

<!-- Wallet under automatic gateway -->
                                                                    <?php if ($k === 'school'): ?>
                                                                        <?php
                                                                            $schoolTotalForWallet = (float)$amount;
                                                                            $schoolInstallmentAmtForWallet = ($schoolTotalForWallet > 0 ? ($schoolTotalForWallet / 2.0) : 0.0);
                                                                            // $paidInstallmentsSchool, $disableOneTimeSchool, $nextInstallmentSchool are computed above in the School Fee block
                                                                            $nextInstAmt = $schoolInstallmentAmtForWallet;
                                                                            $oneTimeAmt = $schoolTotalForWallet;
                                                                        ?>
                                                                        <div class="p-3 border rounded" id="wallet_fund">
                                                                            <h6 class="mb-2"><i class="bi bi-wallet2"></i> Pay with Wallet</h6>
                                                                            <?php $walletBal = getWalletBalance($pdo, (int)$studentId); ?>
                                                                            <div class="small text-muted mb-2">Wallet Balance: <strong>₦<?php echo number_format((float)$walletBal, 2); ?></strong></div>

                                                                            <?php if (getSystemSettingValue($pdo,'wallet_enabled','enabled') !== 'enabled'): ?>
                                                                                <div class="alert alert-warning mb-0">Wallet is currently disabled by admin.</div>
                                                                            <?php else: ?>

                                                                                <!-- One-time wallet -->
                                                                                <div id="wallet_school_one_time_tab" style="<?php echo (!empty($disableOneTimeSchool) ? 'display:none;' : ''); ?>">
                                                                                    <?php if ($walletBal + 0.00001 >= $oneTimeAmt): ?>
                                                                                        <form method="post" class="mb-0">
                                                                                            <input type="hidden" name="action" value="pay_with_wallet">
                                                                                            <input type="hidden" name="payment_type" value="school">
                                                                                            <input type="hidden" name="installment_number" value="1">
                                                                                            <input type="hidden" name="total_installments" value="1">
                                                                                            <input type="hidden" name="wallet_payment_confirmed" value="0">
                                                                                            <input type="hidden" name="wallet_fee_name" value="School Fee">
                                                                                            <input type="hidden" name="wallet_fee_amount" value="<?php echo e(number_format((float)$oneTimeAmt, 2, '.', '')); ?>">
                                                                                            <input type="hidden" name="wallet_balance_snapshot" value="<?php echo e(number_format((float)$walletBal, 2, '.', '')); ?>">
                                                                                            <button type="submit" class="btn btn-success w-100">Pay ₦<?php echo number_format((float)$oneTimeAmt); ?> with Wallet (One-time)</button>
                                                                                        </form>
                                                                                    <?php else: ?>
                                                                                        <div class="alert alert-warning mb-0">Insufficient wallet balance for one-time payment. <a href="?page=wallet">Fund Wallet</a> to pay.</div>
                                                                                    <?php endif; ?>
                                                                                </div>

                                                                                <!-- Installment wallet -->
                                                                                <div id="wallet_school_installment_tab" style="<?php echo (!empty($disableOneTimeSchool) ? '' : 'display:none;'); ?>">
                                                                                    <?php if (!empty($paidInstallmentsSchool) && (int)$paidInstallmentsSchool >= 2): ?>
                                                                                        <div class="alert alert-success mb-0">School fee installments have been fully paid.</div>
                                                                                    <?php else: ?>
                                                                                        <?php if ($walletBal + 0.00001 >= $nextInstAmt): ?>
                                                                                            <form method="post" class="mb-0">
                                                                                                <input type="hidden" name="action" value="pay_with_wallet">
                                                                                                <input type="hidden" name="payment_type" value="school">
                                                                                                <input type="hidden" name="installment_number" value="<?php echo (int)$nextInstallmentSchool; ?>">
                                                                                                <input type="hidden" name="total_installments" value="2">
                                                                                                <input type="hidden" name="wallet_payment_confirmed" value="0">
                                                                                                <input type="hidden" name="wallet_fee_name" value="School Fee">
                                                                                                <input type="hidden" name="wallet_fee_amount" value="<?php echo e(number_format((float)$nextInstAmt, 2, '.', '')); ?>">
                                                                                                <input type="hidden" name="wallet_balance_snapshot" value="<?php echo e(number_format((float)$walletBal, 2, '.', '')); ?>">
                                                                                                <button type="submit" class="btn btn-success w-100">Pay ₦<?php echo number_format((float)$nextInstAmt); ?> with Wallet (Installment <?php echo (int)$nextInstallmentSchool; ?> of 2)</button>
                                                                                            </form>
                                                                                        <?php else: ?>
                                                                                            <div class="alert alert-warning mb-0">Insufficient wallet balance for installment payment. <a href="?page=wallet">Fund Wallet</a> to pay.</div>
                                                                                        <?php endif; ?>
                                                                                    <?php endif; ?>
                                                                                </div>

                                                                            <?php endif; ?>
                                                                        </div>
                                                                    <?php else: ?>
                                                                        <div class="p-3 border rounded" id="wallet_fund">
                                                                            <h6 class="mb-2"><i class="bi bi-wallet2"></i> Pay with Wallet</h6>
                                                                            <?php $walletBal = getWalletBalance($pdo, (int)$studentId); ?>
                                                                            <div class="small text-muted mb-2">Wallet Balance: <strong>₦<?php echo number_format((float)$walletBal, 2); ?></strong></div>
                                                                            <?php if (getSystemSettingValue($pdo,'wallet_enabled','enabled') !== 'enabled'): ?>
                                                                                <div class="alert alert-warning mb-0">Wallet is currently disabled by admin.</div>
                                                                            <?php elseif ($walletBal + 0.00001 >= $amount): ?>
                                                                                <form method="post">
                                    <input type="hidden" name="id" value="0">
                                                                                    <input type="hidden" name="action" value="pay_with_wallet">
                                                                                    <input type="hidden" name="payment_type" value="<?php echo e($k); ?>">
                                                                                    <input type="hidden" name="wallet_payment_confirmed" value="0">
                                                                                    <input type="hidden" name="wallet_fee_name" value="<?php echo e($label ?? ucwords(str_replace('_', ' ', (string)$k))); ?>">
                                                                                    <input type="hidden" name="wallet_fee_amount" value="<?php echo e(number_format((float)$amount, 2, '.', '')); ?>">
                                                                                    <input type="hidden" name="wallet_balance_snapshot" value="<?php echo e(number_format((float)$walletBal, 2, '.', '')); ?>">
                                                                                    <button type="submit" class="btn btn-success w-100">Pay ₦<?php echo number_format($amount); ?> with Wallet</button>
                                                                                </form>
                                                                            <?php else: ?>
                                                                                <div class="alert alert-warning mb-0">Insufficient wallet balance. <a href="?page=wallet">Fund Wallet</a> to pay.</div>
                                                                            <?php endif; ?>
                                                                        </div>
                                                                    <?php endif; ?>

                                                                <?php endif; ?>
                                                            </div>
                                                        </div>
                                                    </div>

                                                    <!-- RIGHT: Manual bank transfer / accommodation -->
                                                    <div class="col-md-6">
                                                        <div class="card">
                                                            <div class="card-body">
                                                                <?php if ($k === 'hostel'): ?>
                                                                    <h5 class="mb-3">Accommodation Status</h5>
                                                                    <form method="post" class="row g-2 align-items-end mb-3">
                                                                        <input type="hidden" name="action" value="save_accommodation_status">
                                                    <input type="hidden" name="return_to" value="<?php echo e((string)($_SERVER['REQUEST_URI'] ?? '?page=hostel_payment')); ?>">
                                                                        <div class="col-md-8">
                                                                            <label class="form-label">Accommodation Option</label>
                                                                            <select class="form-select" name="accommodation_status" <?php echo $accommodationLocked ? 'disabled' : ''; ?>>
                                                                                <option value="Campus" <?php echo ((string)($student['accommodation_status'] ?? 'Campus') === 'Campus') ? 'selected' : ''; ?>>Campus Accommodation</option>
                                                                                <option value="Off-Campus" <?php echo ((string)($student['accommodation_status'] ?? 'Campus') === 'Off-Campus') ? 'selected' : ''; ?>>Off-Campus</option>
                                                                            </select>
                                                                            <?php if ($accommodationLocked): ?>
                                                                                <input type="hidden" name="accommodation_status" value="<?php echo e((string)($student['accommodation_status'] ?? 'Campus')); ?>">
                                                                            <?php endif; ?>
                                                                        </div>
                                                                        <div class="col-md-4">
                                                                            <button type="submit" class="btn btn-outline-primary w-100" <?php echo $accommodationLocked ? 'disabled aria-disabled="true"' : ''; ?>>Update</button>
                                                                        </div>
                                                                        <div class="col-12"><div class="small text-muted">Select Off-Campus if you will not stay in campus accommodation. You can switch back to Campus any time before hostel fee is paid for the current session.</div></div>
                                                                    </form>
                                                                    <?php if ($accommodationLocked): ?>
                                                                        <div class="alert alert-secondary">
                                                                            <i class="bi bi-lock"></i> Hostel fee has already been paid for the current session. Accommodation status cannot be changed again until the next session.
                                                                        </div>
                                                                    <?php endif; ?>
                                                                    <h5 class="mb-3">Manual Bank Transfer</h5>
                                                                <?php else: ?>
                                                                    <h5 class="mb-3">Manual Bank Transfer</h5>
                                                                <?php endif; ?>

                                                                <?php if ($k === 'hostel' && $hostelOffCampus): ?>
                                                                    <div class="alert alert-info mb-0">
                                                                        <i class="bi bi-house-door"></i> <?php echo e((string)($hostelRequirementState['message'] ?? 'Hostel fee is not required for your current accommodation status.')); ?>
                                                                    </div>
                                                                <?php elseif ($manualEnabled && !$isPaid): ?>
                                                                    <div class="small text-muted mb-2"><?php echo nl2br(e(getSystemSettingValue($pdo,'manual_payment_instructions',''))); ?></div>
                                                                    <?php if ($k === 'school'): ?>
    <div class="small text-muted mb-2">
        <i class="bi bi-info-circle"></i> Choose <strong>One-time</strong> or <strong>Installment</strong> before uploading proof. If you already started installments, one-time upload is disabled automatically.
    </div>

    <!-- One-time manual -->
    <div id="manual_school_one_time_tab" style="<?php echo (!empty($disableOneTimeSchool) ? 'display:none;' : ''); ?>">
        <form method="post" enctype="multipart/form-data">
            <input type="hidden" name="action" value="submit_manual_payment">
            <input type="hidden" name="payment_type" value="school">
            <input type="hidden" name="installment_number" value="1">
            <input type="hidden" name="total_installments" value="1">
            <div class="mb-2">
                <label class="form-label">Amount Paid (₦)</label>
                <input type="number" name="amount_paid" class="form-control" min="0.01" step="0.01" required>
            </div>
            <div class="mb-2">
                <label class="form-label">Payer Name (optional)</label>
                <input type="text" name="payer_name" class="form-control" placeholder="Account name used for payment">
            </div>
            <div class="mb-2">
                <label class="form-label">Upload Teller / Receipt (JPG/PNG/PDF)</label>
                <input type="file" name="manual_proof" class="form-control" accept=".jpg,.jpeg,.png,.pdf" required>
            </div>
            <div class="mb-2">
                <label class="form-label">Notes (optional)</label>
                <textarea name="manual_notes" class="form-control" rows="2"></textarea>
            </div>
            <button class="btn btn-outline-primary w-100" type="submit">Submit One-time Manual Payment</button>
        </form>
    </div>

    <!-- Installment manual -->
    <div id="manual_school_installment_tab" style="<?php echo (!empty($disableOneTimeSchool) ? '' : 'display:none;'); ?>">
        <form method="post" enctype="multipart/form-data">
            <input type="hidden" name="action" value="submit_manual_payment">
            <input type="hidden" name="payment_type" value="school">
            <input type="hidden" name="installment_number" value="<?php echo (int)$nextInstallmentSchool; ?>">
            <input type="hidden" name="total_installments" value="2">
            <div class="mb-2">
                <label class="form-label">Amount Paid (₦)</label>
                <input type="number" name="amount_paid" class="form-control" min="0.01" step="0.01" required>
            </div>
            <div class="mb-2">
                <label class="form-label">Payer Name (optional)</label>
                <input type="text" name="payer_name" class="form-control" placeholder="Account name used for payment">
            </div>
            <div class="mb-2">
                <label class="form-label">Upload Teller / Receipt (JPG/PNG/PDF)</label>
                <input type="file" name="manual_proof" class="form-control" accept=".jpg,.jpeg,.png,.pdf" required>
            </div>
            <div class="mb-2">
                <label class="form-label">Notes (optional)</label>
                <textarea name="manual_notes" class="form-control" rows="2"></textarea>
            </div>
            <button class="btn btn-outline-primary w-100" type="submit">Submit Installment <?php echo (int)$nextInstallmentSchool; ?> Manual Payment</button>
        </form>
    </div>
<?php else: ?>
    <form method="post" enctype="multipart/form-data">
        <input type="hidden" name="action" value="submit_manual_payment">
        <input type="hidden" name="payment_type" value="<?php echo e($k); ?>">
        <div class="mb-2">
                <label class="form-label">Amount Paid (₦)</label>
                <input type="number" name="amount_paid" class="form-control" min="0.01" step="0.01" required>
            </div>
            <div class="mb-2">
            <label class="form-label">Payer Name (optional)</label>
            <input type="text" name="payer_name" class="form-control" placeholder="Account name used for payment">
        </div>
        <div class="mb-2">
            <label class="form-label">Upload Teller / Receipt (JPG/PNG/PDF)</label>
            <input type="file" name="manual_proof" class="form-control" accept=".jpg,.jpeg,.png,.pdf" required>
        </div>
        <div class="mb-2">
            <label class="form-label">Notes (optional)</label>
            <textarea name="manual_notes" class="form-control" rows="2"></textarea>
        </div>
        <button class="btn btn-outline-primary w-100" type="submit">Submit Manual Payment</button>
    </form>
<?php endif; ?>

                                                                <?php elseif (!$manualEnabled): ?>
                                                                    <div class="alert alert-secondary mb-0">Manual bank transfer is currently disabled by admin.</div>
                                                                <?php else: ?>
                                                                    <div class="alert alert-success mb-0">This fee is already paid.</div>
                                                                <?php endif; ?>

                                                                <div class="alert alert-warning mt-3 mb-0">
                                                                    <i class="bi bi-exclamation-triangle"></i> Your application is processed after successful payment approval.
                                                                </div>
                                                            </div>
                                                        </div>
                                                    </div>
                                                </div>
                                            <?php else: ?>
                                                <h4 class="mb-2">Other Payments</h4>
                                                <p class="text-muted">Assigned and miscellaneous payments will appear here.</p>

                                                <?php
                                                    $stmt = $pdo->prepare("SELECT * FROM assigned_payments WHERE student_id = ? AND status <> 'Paid' ORDER BY id DESC");
                                                    $stmt->execute([(int)$studentId]);
                                                    $assigned = $stmt->fetchAll(PDO::FETCH_ASSOC);
                                                    // Auto-sync: if admin has approved a payment for a custom/assigned fee, mark it as paid and hide it here
                                                    if (!empty($assigned)) {
                                                        foreach ($assigned as $k => $apRow) {
                                                            $apRef = (string)($apRow['reference'] ?? '');
                                                            if ($apRef !== '') {
                                                                $chk = $pdo->prepare("SELECT COUNT(*) FROM payment_transactions WHERE reference = ? AND status = 'Approved'");
                                                                $chk->execute([$apRef]);
                                                                if ((int)$chk->fetchColumn() > 0) {
                                                                    $upd = $pdo->prepare("UPDATE assigned_payments SET status = 'Paid' WHERE id = ? AND student_id = ?");
                                                                    $upd->execute([(int)$apRow['id'], (int)$studentId]);
                                                                    unset($assigned[$k]);
                                                                }
                                                            }
                                                        }
                                                        // reindex
                                                        $assigned = array_values($assigned);
                                                    }
                                                ?>
                                                <?php if (empty($assigned)): ?>
                                                    <div class="alert alert-secondary">No assigned payments at the moment.</div>
                                                <?php else: ?>
                                                    <?php foreach ($assigned as $ap): ?>
                                                        <div class="card mb-2">
                                                            <div class="card-body">
                                                                <div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
                                                                    <div>
                                                                        <div class="fw-bold"><?php echo e($ap['title']); ?></div>
                                                                        <div class="small text-muted">Status: <?php echo e($ap['status']); ?></div>
                                                                    </div>
                                                                    <div class="fw-bold">₦<?php echo number_format((float)$ap['amount']); ?></div>
                                                                </div>
                                                                <div class="row g-2 mt-2">
                                                                    <div class="col-md-6">
                                                                        <form method="post" class="mb-2">
                                                                            <input type="hidden" name="action" value="initiate_payment">
                                                                            <input type="hidden" name="payment_type" value="assigned">
                                                                            <input type="hidden" name="assigned_id" value="<?php echo (int)$ap['id']; ?>">
                                                                            <div class="mb-2">
                                                                                <label class="form-label">Automatic Payment Gateway</label>
                                                                                <select name="gateway" class="form-select">
                                                                                    <?php foreach ($gws as $gwK => $gwLabel): ?>
                                                                                        <option value="<?php echo e($gwK); ?>" <?php echo ($def===$gwK)?'selected':''; ?>><?php echo e($gwLabel); ?></option>
                                                                                    <?php endforeach; ?>
                                                                                </select>
                                                                            </div>
                                                                            <button type="submit" class="btn btn-primary w-100">Pay Now</button>
                                                                        </form>

                                                                        <div class="p-3 border rounded mt-2">
                                                                            <h6 class="mb-2"><i class="bi bi-wallet2"></i> Pay with Wallet</h6>
                                                                            <?php $walletBal = getWalletBalance($pdo, (int)$studentId); ?>
                                                                            <div class="small text-muted mb-2">Wallet Balance: <strong>₦<?php echo number_format((float)$walletBal, 2); ?></strong></div>
                                                                            <?php if (getSystemSettingValue($pdo,'wallet_enabled','enabled') !== 'enabled'): ?>
                                                                                <div class="alert alert-warning mb-0">Wallet is currently disabled.</div>
                                                                            <?php else: ?>
                                                                                <?php if ($walletBal >= (float)$ap['amount']): ?>
                                                                                    <form method="post" class="mb-0 wallet-payment-form">
                                                                                        <input type="hidden" name="action" value="pay_with_wallet">
                                                                                        <input type="hidden" name="payment_type" value="assigned">
                                                                                        <input type="hidden" name="assigned_id" value="<?php echo (int)$ap['id']; ?>">
                                                                                        <input type="hidden" name="wallet_payment_confirmed" value="0">
                                                                                        <input type="hidden" name="wallet_fee_name" value="<?php echo e((string)($ap['title'] ?? 'Assigned Payment')); ?>">
                                                                                        <input type="hidden" name="wallet_fee_amount" value="<?php echo e(number_format((float)$ap['amount'], 2, '.', '')); ?>">
                                                                                        <input type="hidden" name="wallet_balance_snapshot" value="<?php echo e(number_format((float)$walletBal, 2, '.', '')); ?>">
                                                                                        <button class="btn btn-success w-100" type="submit">Pay with Wallet</button>
                                                                                    </form>
                                                                                <?php else: ?>
                                                                                    <div class="alert alert-warning mb-0">Insufficient wallet balance. <a href="?page=wallet" class="alert-link">Fund Wallet</a> to pay.</div>
                                                                                <?php endif; ?>
                                                                            <?php endif; ?>
                                                                        </div>
                                                                    </div>
                                                                    <div class="col-md-6">
                                                                        <?php if ($manualEnabled): ?>
                                                                            <div class="p-3 border rounded h-100">
                                                                                <div class="fw-semibold mb-1"><i class="bi bi-bank"></i> Manual Bank Transfer</div>
                                                                                <form method="post" enctype="multipart/form-data">
                                                                                    <input type="hidden" name="action" value="submit_manual_payment">
                                                                                    <input type="hidden" name="payment_type" value="assigned">
                                                                                    <input type="hidden" name="assigned_id" value="<?php echo (int)$ap['id']; ?>">
                                                                                    <input type="file" name="manual_proof" class="form-control mb-2" accept=".jpg,.jpeg,.png,.pdf" required>
                                                                                    <button class="btn btn-outline-primary w-100" type="submit">Upload Proof</button>
                                                                                </form>
                                                                            </div>
                                                                        <?php else: ?>
                                                                            <div class="alert alert-secondary mb-0">Manual transfer disabled.</div>
                                                                        <?php endif; ?>
                                                                    </div>
                                                                </div>
                                                            </div>
                                                        </div>
                                                    <?php endforeach; ?>
                                                <?php endif; ?>
                                            <?php endif; ?>
                                        </div>
                                    <?php endforeach; ?>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

            case 'payment_receipt':
                $receiptToken = trim((string)($_GET['receipt_token'] ?? ''));
                $transactionId = (int)($_GET['transaction_id'] ?? $_SESSION['transaction_id'] ?? 0);
                $publicReceiptMode = false;
                $transaction = null;

                if ($receiptToken !== '') {
                    $transaction = portalFindReceiptTransactionByPublicToken($pdo, $receiptToken);
                    $publicReceiptMode = $transaction ? true : false;
                }

                if (!$transaction) {
                    if ($receiptToken !== '' && !isStudent() && !isAdmin()) {
                        echo '<div class="alert alert-danger">This receipt link is invalid or has expired.</div>';
                        break;
                    }
                    if (!isStudent() && !isAdmin()) { redirect('?page=login'); }
                    if ($transactionId > 0) {
                        if (isAdmin()) {
                            $stmt = $pdo->prepare("SELECT pt.*, s.first_name, s.surname, s.other_name, s.registration_no, s.application_no, s.admission_no, s.matric_no, s.level FROM payment_transactions pt
                                JOIN students s ON pt.student_id = s.id
                                WHERE pt.id = ?");
                            $stmt->execute([$transactionId]);
                        } else {
                            $stmt = $pdo->prepare("SELECT pt.*, s.first_name, s.surname, s.other_name, s.registration_no, s.application_no, s.admission_no, s.matric_no, s.level FROM payment_transactions pt
                                JOIN students s ON pt.student_id = s.id
                                WHERE pt.id = ? AND pt.student_id = ?");
                            $stmt->execute([$transactionId, (int)($_SESSION['user_id'] ?? 0)]);
                        }
                        $transaction = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
                    }
                }

                if ($transaction) {

                        $embed = (isset($_GET['embed']) && (string)$_GET['embed'] === '1');
                        if ($embed) {
                            header('Content-Type: text/html; charset=utf-8');
                            ?><!doctype html>
                            <html>
                            <head>
                                <meta charset="utf-8">
                                <meta name="viewport" content="width=device-width, initial-scale=1">
                                <title>Payment Receipt</title>
                                <style><?php echo portalReceiptEmbeddedStyleBlock(); ?></style>
                            </head>
                            <body>
                                <div class="wrap">
                                    <div class="receipt-container doc-stamp-wrap">
                                        <?php if (!empty($stampPath)): ?>
                                            <img src="<?php echo htmlspecialchars($stampPath); ?>" class="doc-stamp" alt="Official Stamp">
                                        <?php endif; ?>
                                        <div class="receipt-header">
                                            <div class="logo-area mb-3">
                                                <?php if ($leftLogoPath): ?>
                                                <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">HONTTECH Logo</div>
                                                <?php endif; ?>
                                                <?php if ($rightLogoPath): ?>
                                                <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">DAHIFT Logo</div>
                                                <?php endif; ?>
                                            </div>
                                            <h2>Honest Technologies University</h2>
                                            <h3>Ahmadu Bello University, Zaria</h3>
                                            <p>In affiliation with Dahift Global Ltd</p>
                                            <h4>Payment Receipt</h4>
                                        </div>

                                        <div class="receipt-body">
                                            <div class="receipt-meta">
                                                <div class="receipt-meta-col">
                                                    <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                                    <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                                    <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                                    <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                                </div>
                                                <div class="receipt-meta-col">
                                                    <p><strong>Student Name:</strong> <?php echo e(full_name($transaction)); ?></p>
                                                    <p><strong>Admission No.:</strong> <?php echo e(portalReceiptAdmissionNumber($transaction)); ?></p>
                                                    <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                                    <p><strong>Application No.:</strong> <?php echo e(portalReceiptApplicationNumber($transaction)); ?></p>
                                                    <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                    <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                    <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                                    <p><strong>Payment Status:</strong> <?php echo e((string)($transaction['status'] ?? 'Approved')); ?></p>
                                                    <p><strong>Wallet Payment:</strong> <?php echo (strtolower((string)($transaction['payment_method'] ?? '')) === 'wallet' || stripos((string)($transaction['reference'] ?? ''), 'wallet_') === 0) ? 'Yes' : 'No'; ?></p>
                                                    <?php if ((int)($transaction['total_installments'] ?? 1) > 1): ?>
                                                        <p><strong>Installment:</strong> <?php echo (int)($transaction['installment_number'] ?? 1); ?> of <?php echo (int)($transaction['total_installments'] ?? 1); ?></p>
                                                    <?php endif; ?>
                                                </div>
                                            </div>

                                            <div class="table-responsive mb-3">
                                                <table class="table">
                                                    <thead>
                                                        <tr>
                                                            <th>Description</th>
                                                            <th>Amount</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <?php $receiptBaseAmount = (float)($transaction['school_fee_base_amount'] ?? 0); $receiptLateFeeAmount = (float)($transaction['school_fee_late_fee_amount'] ?? 0); ?>
                                                        <?php if ($receiptBaseAmount > 0): ?>
                                                        <tr>
                                                            <td>School Fee Amount</td>
                                                            <td>₦<?php echo number_format($receiptBaseAmount, 2); ?></td>
                                                        </tr>
                                                        <?php endif; ?>
                                                        <?php if ($receiptLateFeeAmount > 0): ?>
                                                        <tr>
                                                            <td>Late Fee Amount</td>
                                                            <td>₦<?php echo number_format($receiptLateFeeAmount, 2); ?></td>
                                                        </tr>
                                                        <?php endif; ?>
                                                        <tr>
                                                            <td><?php echo $transaction['transaction_type']; ?></td>
                                                            <td>₦<?php echo number_format($transaction['amount'], 2); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <th>Total Paid</th>
                                                            <th>₦<?php echo number_format($transaction['amount'], 2); ?></th>
                                                        </tr>
                                                    </tbody>
                                                </table>
                                            </div>

                                            <div class="qr-code">
                                                <p><strong>QR Code:</strong></p>
                                                <div class="qr-pack">
                                                    <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                                <?php if (!empty($stampPath)): ?>
                                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>

                                        <div class="receipt-footer">
                                            <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                            <p>For inquiries, please contact the admissions office.</p>
                                            <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                                        </div>
                                    </div>
                                </div>
                            </body>
                            </html><?php
                            exit;
                        }
                        $receiptDownloadFilename = portalPaymentReceiptDownloadFilename($transaction);
                        $receiptDocumentTitle = preg_replace('/\.pdf$/i', '', $receiptDownloadFilename) ?: 'payment-receipt';
                        echo "<script>document.title=" . json_encode($receiptDocumentTitle) . ";</script>";
                        ?>
                        <div class="row">
                            <div class="col-md-12">
                                <div class="card">
                                    <div class="card-header d-flex justify-content-between align-items-center">
                                        <h4 class="mb-0">Payment Receipt</h4>
                                        <div class="no-print d-flex gap-2 align-items-center flex-wrap">
                                            <button class="btn btn-secondary" type="button" onclick="window.print()">
                                                <i class="bi bi-printer"></i> Print
                                            </button>
                                            <button class="btn btn-outline-secondary" type="button" id="downloadReceiptBtn" data-download-filename="<?php echo e($receiptDownloadFilename); ?>">
                                                <i class="bi bi-download"></i> Download
                                            </button>
                                            <?php if (!$publicReceiptMode): ?>
                                                <?php if (isAdmin()): ?>
                                                    <a href="?page=admin_dashboard" class="btn btn-primary">
                                                        <i class="bi bi-house"></i> Dashboard
                                                    </a>
                                                <?php else: ?>
                                                    <a href="?page=student_dashboard" class="btn btn-primary">
                                                        <i class="bi bi-house"></i> Dashboard
                                                    </a>
                                                <?php endif; ?>
                                            <?php endif; ?>
                                            <div id="receiptDownloadStatus" class="course-form-download-status d-none mb-0" aria-live="polite"></div>
                                        </div>
                                    </div>
                                    <div class="card-body">
                                        <div class="receipt-container doc-stamp-wrap">
                                            <?php if (!empty($stampPath)): ?>
                                                <img src="<?php echo htmlspecialchars($stampPath); ?>" class="doc-stamp" alt="Official Stamp">
                                            <?php endif; ?>

                                            <div class="receipt-header">
                                                <div class="logo-area mb-3">
                                                    <?php if ($leftLogoPath): ?>
                                                    <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                                    <?php else: ?>
                                                    <div class="logo-placeholder">HONTTECH Logo</div>
                                                    <?php endif; ?>
                                                    <?php if ($rightLogoPath): ?>
                                                    <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                                    <?php else: ?>
                                                    <div class="logo-placeholder">DAHIFT Logo</div>
                                                    <?php endif; ?>
                                                </div>
                                                <h2>Honest Technologies University</h2>
                                                <h3>Ahmadu Bello University, Zaria</h3>
                                                <p>In affiliation with Dahift Global Ltd</p>
                                                <h4>Payment Receipt</h4>
                                            </div>

                                            <div class="receipt-body">
                                                <div class="receipt-meta">
                                                    <div class="receipt-meta-col">
                                                        <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                                        <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                                        <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                                        <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                                    </div>
                                                    <div class="receipt-meta-col">
                                                        <p><strong>Student Name:</strong> <?php echo e(full_name($transaction)); ?></p>
                                                        <p><strong>Admission No.:</strong> <?php echo e(portalReceiptAdmissionNumber($transaction)); ?></p>
                                                        <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                                        <p><strong>Application No.:</strong> <?php echo e(portalReceiptApplicationNumber($transaction)); ?></p>
                                                        <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                        <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                        <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                                        <p><strong>Payment Status:</strong> <?php echo e((string)($transaction['status'] ?? 'Approved')); ?></p>
                                                        <p><strong>Wallet Payment:</strong> <?php echo (strtolower((string)($transaction['payment_method'] ?? '')) === 'wallet' || stripos((string)($transaction['reference'] ?? ''), 'wallet_') === 0) ? 'Yes' : 'No'; ?></p>
                                                        <?php if ((int)($transaction['total_installments'] ?? 1) > 1): ?>
                                                            <p><strong>Installment:</strong> <?php echo (int)($transaction['installment_number'] ?? 1); ?> of <?php echo (int)($transaction['total_installments'] ?? 1); ?></p>
                                                        <?php endif; ?>
                                                    </div>
                                                </div>

                                                <div class="table-responsive mb-3">
                                                    <table class="table table-bordered">
                                                        <thead>
                                                            <tr>
                                                                <th>Description</th>
                                                                <th>Amount</th>
                                                            </tr>
                                                        </thead>
                                                        <tbody>
                                                            <?php $receiptBaseAmount = (float)($transaction['school_fee_base_amount'] ?? 0); $receiptLateFeeAmount = (float)($transaction['school_fee_late_fee_amount'] ?? 0); ?>
                                                            <?php if ($receiptBaseAmount > 0): ?>
                                                            <tr>
                                                                <td>School Fee Amount</td>
                                                                <td>₦<?php echo number_format($receiptBaseAmount, 2); ?></td>
                                                            </tr>
                                                            <?php endif; ?>
                                                            <?php if ($receiptLateFeeAmount > 0): ?>
                                                            <tr>
                                                                <td>Late Fee Amount</td>
                                                                <td>₦<?php echo number_format($receiptLateFeeAmount, 2); ?></td>
                                                            </tr>
                                                            <?php endif; ?>
                                                            <tr>
                                                                <td><?php echo $transaction['transaction_type']; ?></td>
                                                                <td>₦<?php echo number_format($transaction['amount'], 2); ?></td>
                                                            </tr>
                                                            <tr>
                                                                <th>Total Paid</th>
                                                                <th>₦<?php echo number_format($transaction['amount'], 2); ?></th>
                                                            </tr>
                                                        </tbody>
                                                    </table>
                                                </div>

                                                <div class="qr-code">
                                                    <p><strong>QR Code:</strong></p>
                                                    <div class="qr-pack">
                                                        <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                                    <?php if (!empty($stampPath)): ?>
                                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                                    <?php endif; ?>
                                                </div>
                                                </div>
                                            </div>

                                            <div class="receipt-footer">
                                                <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                                <p>For inquiries, please contact the admissions office.</p>
                                                <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" referrerpolicy="no-referrer"></script>
                        <script>
                        (function(){
                            const receiptEl = document.querySelector('.receipt-container');
                            const downloadBtn = document.getElementById('downloadReceiptBtn');
                            const statusEl = document.getElementById('receiptDownloadStatus');
                            const fileName = <?php echo json_encode($receiptDownloadFilename, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
                            let busy = false;

                            function setStatus(message, isError){
                                if(!statusEl) return;
                                const text = String(message || '').trim();
                                statusEl.textContent = text;
                                statusEl.classList.toggle('d-none', text === '');
                                statusEl.classList.toggle('show', text !== '');
                                statusEl.classList.toggle('error', !!isError);
                            }

                            function waitForImages(root){
                                if(!root) return Promise.resolve();
                                const images = Array.from(root.querySelectorAll('img'));
                                if(!images.length) return Promise.resolve();
                                return Promise.all(images.map(function(img){
                                    if(img.complete && img.naturalWidth > 0) return Promise.resolve();
                                    return new Promise(function(resolve){
                                        const done = function(){ resolve(); };
                                        img.addEventListener('load', done, {once:true});
                                        img.addEventListener('error', done, {once:true});
                                    });
                                }));
                            }

                            function ensurePdfLibrary(){
                                if(window.html2pdf) return Promise.resolve(window.html2pdf);
                                return new Promise(function(resolve, reject){
                                    const existing = document.querySelector('script[data-receipt-pdf-lib="1"]');
                                    if(existing){
                                        existing.addEventListener('load', function(){ window.html2pdf ? resolve(window.html2pdf) : reject(new Error('PDF library failed to load.')); }, {once:true});
                                        existing.addEventListener('error', function(){ reject(new Error('PDF library failed to load.')); }, {once:true});
                                        return;
                                    }
                                    const script = document.createElement('script');
                                    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js';
                                    script.async = true;
                                    script.setAttribute('data-receipt-pdf-lib', '1');
                                    script.onload = function(){ window.html2pdf ? resolve(window.html2pdf) : reject(new Error('PDF library failed to load.')); };
                                    script.onerror = function(){ reject(new Error('PDF library failed to load.')); };
                                    document.head.appendChild(script);
                                });
                            }

                            async function downloadReceiptPdf(){
                                if(busy) return false;
                                if(!receiptEl){
                                    setStatus('Receipt content could not be found for download.', true);
                                    return false;
                                }
                                busy = true;
                                if(downloadBtn){
                                    downloadBtn.classList.add('disabled');
                                    downloadBtn.setAttribute('aria-disabled', 'true');
                                }
                                setStatus('Preparing receipt download...', false);
                                try{
                                    await ensurePdfLibrary();
                                    await waitForImages(receiptEl);
                                    document.body.classList.add('pdf-export-active');
                                    await window.html2pdf().set({
                                        filename: fileName || 'payment-receipt.pdf',
                                        margin: 0,
                                        image: { type: 'jpeg', quality: 0.98 },
                                        html2canvas: {
                                            scale: 2,
                                            useCORS: true,
                                            allowTaint: true,
                                            backgroundColor: '#ffffff',
                                            logging: false,
                                            scrollX: 0,
                                            scrollY: -window.scrollY
                                        },
                                        jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
                                        pagebreak: { mode: ['css', 'legacy'] }
                                    }).from(receiptEl).save();
                                    setStatus('Receipt downloaded successfully.', false);
                                    return true;
                                }catch(err){
                                    const message = (err && err.message) ? String(err.message) : 'Unable to generate the receipt PDF right now.';
                                    setStatus(message, true);
                                    return false;
                                }finally{
                                    document.body.classList.remove('pdf-export-active');
                                    busy = false;
                                    if(downloadBtn){
                                        downloadBtn.classList.remove('disabled');
                                        downloadBtn.removeAttribute('aria-disabled');
                                    }
                                }
                            }

                            if(downloadBtn){
                                downloadBtn.addEventListener('click', function(e){
                                    e.preventDefault();
                                    downloadReceiptPdf();
                                });
                            }
                        })();
                        </script>
                        <?php
                    } else {
                        echo '<div class="alert alert-danger">' . ($receiptToken !== '' ? 'This receipt link is invalid or has expired.' : (($transactionId > 0) ? 'Transaction not found.' : 'Invalid transaction.')) . '</div>';
                    }
                break;

            case 'reports':
                if (!isAdmin()) { redirect('?page=admin_login'); }

                $report = $_GET['report'] ?? 'students';
                $from = $_GET['from'] ?? '';
                $to = $_GET['to'] ?? '';

                $fromSql = $from ? date('Y-m-d 00:00:00', strtotime($from)) : null;
                $toSql = $to ? date('Y-m-d 23:59:59', strtotime($to)) : null;

                $params = [];
                $where = [];
                if ($fromSql) { $where[] = 'created_at >= ?'; $params[] = $fromSql; }
                if ($toSql) { $where[] = 'created_at <= ?'; $params[] = $toSql; }
                $whereSql = $where ? (' WHERE ' . implode(' AND ', $where)) : '';

                // KPIs
                $totalStudents = (int)$pdo->query("SELECT COUNT(*) FROM students")->fetchColumn();
                $pendingApps = (int)$pdo->query("SELECT COUNT(*) FROM students WHERE application_status = 'Pending'")->fetchColumn();
                $approvedApps = (int)$pdo->query("SELECT COUNT(*) FROM students WHERE application_status = 'Approved'")->fetchColumn();
                $totalPayments = (int)$pdo->query("SELECT COUNT(*) FROM payment_transactions")->fetchColumn();

                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header d-flex align-items-center justify-content-between">
                                <span><i class="bi bi-graph-up"></i> Reports</span>
                                <a class="btn btn-outline-primary btn-sm" href="?page=reports&report=<?php echo urlencode($report); ?>&from=<?php echo urlencode($from); ?>&to=<?php echo urlencode($to); ?>&export=csv">
                                    <i class="bi bi-download"></i> Export CSV
                                </a>
                            </div>
                            <div class="card-body">

                                <div class="row g-3 mb-3">
                                    <div class="col-md-3">
                                        <div class="p-3 border rounded bg-light">
                                            <div class="text-muted small">Total Students</div>
                                            <div class="fs-4 fw-bold"><?php echo number_format($totalStudents); ?></div>
                                        </div>
                                    </div>
                                    <div class="col-md-3">
                                        <div class="p-3 border rounded bg-light">
                                            <div class="text-muted small">Pending Applications</div>
                                            <div class="fs-4 fw-bold"><?php echo number_format($pendingApps); ?></div>
                                        </div>
                                    </div>
                                    <div class="col-md-3">
                                        <div class="p-3 border rounded bg-light">
                                            <div class="text-muted small">Approved Applications</div>
                                            <div class="fs-4 fw-bold"><?php echo number_format($approvedApps); ?></div>
                                        </div>
                                    </div>
                                    <div class="col-md-3">
                                        <div class="p-3 border rounded bg-light">
                                            <div class="text-muted small">Total Payments</div>
                                            <div class="fs-4 fw-bold"><?php echo number_format($totalPayments); ?></div>
                                        </div>
                                    </div>
                                </div>

                                <form class="row g-2 align-items-end mb-3" method="get" action="">
                                    <input type="hidden" name="page" value="reports">
                                    <div class="col-md-4">
                                        <label class="form-label">Report Type</label>
                                        <select class="form-select" name="report">
                                            <option value="students" <?php echo $report==='students'?'selected':''; ?>>Students</option>
                                            <option value="applications" <?php echo $report==='applications'?'selected':''; ?>>Applications</option>
                                            <option value="payments" <?php echo $report==='payments'?'selected':''; ?>>Payments</option>
                                        </select>
                                    </div>
                                    <div class="col-md-3">
                                        <label class="form-label">From</label>
                                        <input type="date" class="form-control" name="from" value="<?php echo htmlspecialchars($from); ?>">
                                    </div>
                                    <div class="col-md-3">
                                        <label class="form-label">To</label>
                                        <input type="date" class="form-control" name="to" value="<?php echo htmlspecialchars($to); ?>">
                                    </div>
                                    <div class="col-md-2">
                                        <button class="btn btn-primary w-100" type="submit"><i class="bi bi-funnel"></i> Filter</button>
                                    </div>
                                </form>

                                <div class="d-flex justify-content-end mb-2 no-print">
                                    <button class="btn btn-outline-secondary" onclick="window.print()"><i class="bi bi-printer"></i> Print</button>
                                </div>

                                <div class="table-responsive">
                                    <table class="table table-striped table-bordered table-sm">
                                        <thead>
                                        <?php
                                        if ($report === 'payments') {
                                            echo '<tr><th>Transaction ID</th><th>Application No.</th><th>Name</th><th>Type</th><th>Amount</th><th>Status</th><th>Date</th></tr>';
                                        } elseif ($report === 'applications') {
                                            echo '<tr><th>Matric No.</th><th>Name</th><th>Email</th><th>App Status</th><th>Offer Status</th><th>Date</th></tr>';
                                        } else {
                                            echo '<tr><th>Matric No.</th><th>Name</th><th>Email</th><th>Phone</th><th>Account</th><th>Reg Fee</th><th>Acceptance</th><th>Hostel</th><th>School</th><th>Date</th></tr>';
                                        }
                                        ?>
                                        </thead>
                                        <tbody>
                                        <?php
                                        $rows = [];
                                        if ($report === 'payments') {
                                            $sql = "SELECT pt.transaction_id, pt.transaction_type, pt.amount, pt.status, pt.created_at, s.matric_no, s.registration_no, s.surname, s.first_name, s.other_name
                                                    FROM payment_transactions pt
                                                    JOIN students s ON pt.student_id = s.id" . $whereSql . " ORDER BY pt.created_at DESC LIMIT 200";
                                            $stmt = $pdo->prepare($sql);
                                            $stmt->execute($params);
                                            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
                                            foreach ($rows as $r) {
                                                echo '<tr>';
                                                echo '<td>' . htmlspecialchars((string)$r['transaction_id']) . '</td>';
                                                echo '<td>' . htmlspecialchars(portalReceiptMatricNumber($r)) . '</td>';
                                                echo '<td>' . htmlspecialchars(trim($r['surname'] . ', ' . $r['first_name'] . ' ' . ($r['other_name'] ?? ''))) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['transaction_type']) . '</td>';
                                                echo '<td>₦' . number_format((float)$r['amount']) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['status']) . '</td>';
                                                echo '<td>' . date('d M Y', strtotime((string)$r['created_at'])) . '</td>';
                                                echo '</tr>';
                                            }
                                        } elseif ($report === 'applications') {
                                            $sql = "SELECT matric_no, registration_no, surname, first_name, other_name, email, application_status, offer_status, created_at
                                                    FROM students" . $whereSql . " ORDER BY created_at DESC LIMIT 200";
                                            $stmt = $pdo->prepare($sql);
                                            $stmt->execute($params);
                                            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
                                            foreach ($rows as $r) {
                                                echo '<tr>';
                                                echo '<td>' . htmlspecialchars(portalReceiptMatricNumber($r)) . '</td>';
                                                echo '<td>' . htmlspecialchars(trim($r['surname'] . ', ' . $r['first_name'] . ' ' . ($r['other_name'] ?? ''))) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['email']) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['application_status']) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['offer_status']) . '</td>';
                                                echo '<td>' . date('d M Y', strtotime((string)$r['created_at'])) . '</td>';
                                                echo '</tr>';
                                            }
                                        } else {
                                            $sql = "SELECT matric_no, registration_no, surname, first_name, other_name, email, mobile_phone, account_status, accommodation_status, registration_fee_status, acceptance_fee_status, hostel_fee_status, school_fee_status, created_at
                                                    FROM students" . $whereSql . " ORDER BY created_at DESC LIMIT 200";
                                            $stmt = $pdo->prepare($sql);
                                            $stmt->execute($params);
                                            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
                                            foreach ($rows as $r) {
                                                echo '<tr>';
                                                echo '<td>' . htmlspecialchars(portalReceiptMatricNumber($r)) . '</td>';
                                                echo '<td>' . htmlspecialchars(trim($r['surname'] . ', ' . $r['first_name'] . ' ' . ($r['other_name'] ?? ''))) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['email']) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['mobile_phone']) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['account_status']) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['registration_fee_status']) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['acceptance_fee_status']) . '</td>';
                                                echo '<td>' . htmlspecialchars(portalStudentHostelStatusLabel($r)) . '</td>';
                                                echo '<td>' . htmlspecialchars((string)$r['school_fee_status']) . '</td>';
                                                echo '<td>' . date('d M Y', strtotime((string)$r['created_at'])) . '</td>';
                                                echo '</tr>';
                                            }
                                        }

                                        if (empty($rows)) {
                                            echo '<tr><td colspan="20" class="text-center text-muted">No records found for this filter.</td></tr>';
                                        }
                                        ?>
                                        </tbody>
                                    </table>
                                    <div class="text-muted small">Showing up to 200 latest rows in-app. Use <strong>Export CSV</strong> to download full report.</div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;


            case 'wallet':
                if (!isStudent()) { redirect('?page=login'); }
                $studentId = (int)($_SESSION['user_id'] ?? 0);
                if (getSystemSettingValue($pdo,'wallet_enabled','enabled') !== 'enabled') {
                    echo '<div class="alert alert-warning">Wallet is currently disabled by admin.</div>';
                    break;
                }
                ensureWallet($pdo, $studentId);
                $balance = getWalletBalance($pdo, $studentId);

                // wallet transactions
                $wtx = [];
                try {
                    $st = $pdo->prepare("SELECT * FROM wallet_transactions WHERE student_id = ? ORDER BY id DESC LIMIT 50");
                    $st->execute([$studentId]);
                    $wtx = $st->fetchAll(PDO::FETCH_ASSOC);
                } catch (Throwable $e) {}

                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header d-flex justify-content-between align-items-center">
                                <span>Wallet</span>

                            </div>
                            <div class="card-body">
                                <?php renderFlashes(); ?>

                                <div class="row g-3">
                                    <!-- 1) Wallet Balance (Left) -->
                                    <div class="col-lg-4">
                                        <div class="balance-hero mb-0">
                                            <div class="balance-hero-inner">
                                                <div class="balance-hero-title">Wallet Balance</div>
                                                <div class="balance-hero-amount">₦<?php echo number_format((float)$balance, 2); ?></div>
                                                <div class="balance-hero-sub">Use your wallet to pay any fee instantly.</div>
                                                <div class="balance-hero-actions">
                                                    <a href="?page=payment" class="btn btn-light btn-sm">Pay Fees</a>
                                                    <a href="#wallet_fund" class="btn btn-outline-light btn-sm">Fund Wallet</a>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

                                    <!-- 2) Online Funding (Middle) -->
                                    <div class="col-lg-4" id="wallet_fund">
                                        <div class="p-3 border rounded h-100">
                                            <h5 class="mb-2"><i class="bi bi-plus-circle"></i> Fund Wallet</h5>
                                            <div class="small text-muted mb-2">Fund your wallet using any available payment gateway. You can then pay any fee using wallet.</div>

                                            <?php if (getSystemSettingValue($pdo,'wallet_funding_enabled','enabled') !== 'enabled'): ?>
                                                <div class="alert alert-info mb-2">Online wallet funding is disabled. You can still use manual funding (if enabled) or contact admin.</div>
                                            <?php endif; ?>

                                            <?php if (getSystemSettingValue($pdo,'wallet_funding_enabled','enabled') === 'enabled'): ?>
                                                <form method="post">
                                                    <input type="hidden" name="action" value="initiate_payment">
                                                    <input type="hidden" name="payment_type" value="wallet_funding">

                                                    <div class="mb-2">
                                                        <label class="form-label">Amount (₦)</label>
                                                        <input type="number" name="amount" class="form-control" min="100" step="50" required>
                                                    </div>

                                                    <?php
                                                        // Enabled gateways for wallet funding
                                                        $gws = portalSortGatewayOptionsByDefault(
                                                            portalEnabledGatewayOptions($pdo, true),
                                                            (string)getSystemSettingValue($pdo, 'default_gateways', getSystemSettingValue($pdo,'default_gateway','paystack'))
                                                        );
                                                        if (empty($gws)) { $gws['paystack'] = 'Paystack'; }
                                                        $def = strtolower(trim((string)array_key_first($gws)));
                                                        if (!isset($gws[$def])) { $def = array_key_first($gws); }
                                                    ?>

                                                    <div class="mb-2">
                                                        <label class="form-label">Choose Payment Gateway</label>
                                                        <select name="gateway" class="form-select">
                                                            <?php foreach ($gws as $gwK => $gwLabel): ?>
                                                                <option value="<?php echo e($gwK); ?>" <?php echo ($def===$gwK)?'selected':''; ?>><?php echo e($gwLabel); ?></option>
                                                            <?php endforeach; ?>
                                                        </select>
                                                    </div>

                                                    <button class="btn btn-primary w-100" type="submit">Proceed to Pay</button>
                                                </form>
                                            <?php endif; ?>
                                        </div>
                                    </div>

                                    <!-- 3) Manual Bank Transfer (Right) -->
                                    <div class="col-lg-4">
                                        <?php if (getSystemSettingValue($pdo,'manual_payment_enabled','enabled') === 'enabled'): ?>
                                            <div class="p-3 border rounded h-100">
                                                <h6 class="mb-2"><i class="bi bi-bank"></i> Manual Wallet Funding (Upload Proof)</h6>
                                                <div class="small text-muted mb-2"><?php echo nl2br(htmlspecialchars(getSystemSettingValue($pdo,'manual_payment_instructions',''))); ?></div>
                                                <div class="alert alert-info py-2 small">You can upload proof of payment multiple times. Your latest pending upload will be reviewed by admin.</div>
                                                <form method="post" enctype="multipart/form-data">
                                                    <input type="hidden" name="action" value="submit_manual_payment">
                                                    <input type="hidden" name="payment_type" value="wallet_funding">
                                                    <div class="mb-2">
                <label class="form-label">Amount Paid (₦)</label>
                <input type="number" name="amount_paid" class="form-control" min="0.01" step="0.01" required>
            </div>
            <div class="mb-2">
                                                        <label class="form-label">Payer Name (optional)</label>
                                                        <input type="text" name="payer_name" class="form-control">
                                                    </div>
                                                    <div class="mb-2">
                                                        <label class="form-label">Upload Teller / Receipt (JPG/PNG/PDF)</label>
                                                        <input type="file" name="manual_proof" class="form-control" accept=".jpg,.jpeg,.png,.pdf" required>
                                                    </div>
                                                    <div class="mb-2">
                                                        <label class="form-label">Notes (optional)</label>
                                                        <textarea name="manual_notes" class="form-control" rows="2"></textarea>
                                                    </div>
                                                    <button class="btn btn-outline-primary w-100" type="submit">Submit Manual Funding</button>
                                                </form>
                                            </div>
                                        <?php else: ?>
                                            <div class="alert alert-warning mb-0">Manual wallet funding is disabled by admin.</div>
                                        <?php endif; ?>
                                    </div>
                                </div>

                                <hr class="my-4">

                                <h5>Recent Wallet Transactions</h5>
                                <div class="table-responsive">
                                    <table class="table table-striped">
                                        <thead>
                                            <tr>
                                                <th>Date</th>
                                                <th>Type</th>
                                                <th>Amount</th>
                                                <th>Previous Balance</th>
                                                <th>New Balance</th>
                                                <th>Related Ref</th>
                                                <th>Narration</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <?php if (!$wtx): ?>
                                                <tr><td colspan="7" class="text-muted">No wallet transactions yet.</td></tr>
                                            <?php else: foreach ($wtx as $x): ?>
                                                <tr>
                                                    <td><?php echo e($x['created_at'] ?? ''); ?></td>
                                                    <td><?php echo e(strtoupper((string)$x['type'])); ?></td>
                                                    <td>₦<?php echo number_format((float)$x['amount'], 2); ?></td>
                                                    <td>₦<?php echo number_format((float)($x['balance_before'] ?? 0), 2); ?></td>
                                                    <td>₦<?php echo number_format((float)($x['balance_after'] ?? 0), 2); ?></td>
                                                    <td><?php echo e((string)($x['related_payment_reference'] ?? $x['related_transaction_id'] ?? '')); ?></td>
                                                    <td><?php echo e($x['narration'] ?? ''); ?></td>
                                                </tr>
                                            <?php endforeach; endif; ?>
                                        </tbody>
                                    </table>
                                </div>

                                <?php
                                    $walletFundingRows = [];
                                    try {
                                        $wf = $pdo->prepare("SELECT * FROM payment_transactions WHERE student_id = ? AND transaction_type = 'Wallet Funding' ORDER BY id DESC LIMIT 20");
                                        $wf->execute([$studentId]);
                                        $walletFundingRows = $wf->fetchAll(PDO::FETCH_ASSOC) ?: [];
                                    } catch (Throwable $e) {
                                        $walletFundingRows = [];
                                    }
                                ?>
                                <hr class="my-4">
                                <h5>Recent Wallet Funding History</h5>
                                <div class="table-responsive">
                                    <table class="table table-striped">
                                        <thead>
                                            <tr>
                                                <th>Date</th>
                                                <th>Method</th>
                                                <th>Amount Paid</th>
                                                <th>Approved / Credited</th>
                                                <th>Proof</th>
                                                <th>Status</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <?php if (!$walletFundingRows): ?>
                                                <tr><td colspan="6" class="text-muted">No wallet funding history yet.</td></tr>
                                            <?php else: foreach ($walletFundingRows as $fundingRow): ?>
                                                <tr>
                                                    <td><?php echo e(!empty($fundingRow['payment_date']) ? (string)$fundingRow['payment_date'] : (string)($fundingRow['created_at'] ?? '')); ?></td>
                                                    <td><?php echo e((string)($fundingRow['payment_method'] ?? '')); ?></td>
                                                    <td>₦<?php echo number_format((float)portalPaymentEnteredAmount($fundingRow), 2); ?></td>
                                                    <td>₦<?php echo number_format((float)portalPaymentApprovedAmount($fundingRow), 2); ?></td>
                                                    <td>
                                                        <?php if (!empty($fundingRow['manual_proof_path'])): ?>
                                                            <a href="<?php echo htmlspecialchars((string)$fundingRow['manual_proof_path']); ?>" class="btn btn-sm btn-outline-primary js-payment-doc" data-doc-title="Proof of Payment" data-doc-url="<?php echo htmlspecialchars((string)$fundingRow['manual_proof_path']); ?>">View Proof</a>
                                                        <?php else: ?>
                                                            <span class="text-muted">—</span>
                                                        <?php endif; ?>
                                                    </td>
                                                    <td><span class="status-badge status-<?php echo e(portalPaymentDisplayStatusClass($fundingRow)); ?>"><?php echo e(portalPaymentApprovalStatusLabel($fundingRow)); ?></span></td>
                                                </tr>
                                            <?php endforeach; endif; ?>
                                        </tbody>
                                    </table>
                                </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

case 'payment_history':
                // Check if user is logged in
                if (!isStudent()) {
                    redirect('?page=login');
                }

                // Get student payment history
                $studentId = $_SESSION['user_id'];
                $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE student_id = ? ORDER BY created_at DESC");
                $stmt->execute([$studentId]);
                $transactions = $stmt->fetchAll(PDO::FETCH_ASSOC);
                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header">
                                Payment History
                            </div>
                            <div class="card-body">
                                <div class="table-responsive">
                                    <table class="table table-striped">
                                        <thead>
                                            <tr>
                                                <th>Transaction ID</th>
                                                <th>Payment Type</th>
                                                <th>Amount Paid</th>
                                                <th>Approved / Credited</th>
                                                <th>Reference</th>
                                                <th>View Upload</th>
                                                <th>Proof Status</th>
                                                <th>Status</th>
                                                <th>Payment Date</th>
                                                <th>Actions</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <?php foreach ($transactions as $transaction): ?>
                                            <tr>
                                                <td><?php echo $transaction['transaction_id']; ?></td>
                                                <td><?php echo e(bestPaymentType($transaction)); ?></td>
                                                <td>₦<?php echo number_format((float)portalPaymentEnteredAmount($transaction), 2); ?></td>
                                                <td>₦<?php echo number_format((float)portalPaymentApprovedAmount($transaction), 2); ?></td>
                                                <td><?php echo $transaction['reference']; ?></td>
                                                <td>
                                                    <?php if (!empty($transaction['manual_proof_path'])): ?>
                                                        <a href="<?php echo htmlspecialchars($transaction['manual_proof_path']); ?>" class="btn btn-sm btn-outline-primary js-payment-doc" data-doc-title="Proof of Payment" data-doc-url="<?php echo htmlspecialchars($transaction['manual_proof_path']); ?>">View Proof</a>
                                                    <?php else: ?>
                                                        <?php if (in_array(($transaction['status'] ?? ''), ['Approved','Successful'], true)): ?>
                                                            <?php
                                                                $rp = 'payment_receipt';
                                                                if (($transaction['transaction_type'] ?? '') === 'Acceptance Fee') $rp = 'acceptance_payment_receipt';
                                                                elseif (($transaction['transaction_type'] ?? '') === 'Hostel Fee') $rp = 'hostel_payment_receipt';
                                                                elseif (($transaction['transaction_type'] ?? '') === 'School Fee') $rp = 'school_payment_receipt';
                                                            ?>
                                                            <a href="?page=<?php echo $rp; ?>&transaction_id=<?php echo (int)$transaction['id']; ?>" class="btn btn-sm btn-outline-success js-payment-doc" data-doc-title="Payment Receipt" data-doc-url="?page=<?php echo $rp; ?>&transaction_id=<?php echo (int)$transaction['id']; ?>">Receipt</a>
                                                        <?php else: ?>
                                                            <span class="text-muted">View Upload</span>
                                                        <?php endif; ?>
                                                    <?php endif; ?>
                                                </td>
                                                <td>
                                                    <?php $proofLabel = portalPaymentProofStatusLabel($transaction); ?>
                                                    <span class="badge bg-<?php echo e(portalManualPaymentStatusClass($proofLabel)); ?>"><?php echo e($proofLabel); ?></span>
                                                </td>
                                                <td>
                                                    <span class="status-badge status-<?php echo e(portalPaymentDisplayStatusClass($transaction)); ?>">
                                                        <?php echo e(portalPaymentApprovalStatusLabel($transaction)); ?>
                                                    </span>
                                                </td>
                                                <td>
                                                    <?php
                                                    if ($transaction['payment_date']) {
                                                        echo date('d M Y', strtotime($transaction['payment_date']));
                                                    } else {
                                                        $d = $transaction['created_at'] ?? null;
                                                        if ($d) echo date('d M Y', strtotime($d));
                                                        else echo date('d M Y');
                                                    }
                                                    ?>
                                                </td>
                                                <td>
                                                    <?php if ($transaction['status'] === 'Approved'): ?>
                                                        <?php
                                                            $rp = 'payment_receipt';
                                                            if ($transaction['transaction_type'] === 'Acceptance Fee') $rp = 'acceptance_payment_receipt';
                                                            elseif ($transaction['transaction_type'] === 'Hostel Fee') $rp = 'hostel_payment_receipt';
                                                            elseif ($transaction['transaction_type'] === 'School Fee') $rp = 'school_payment_receipt';
                                                        ?>
                                                        <a href="?page=<?php echo $rp; ?>&transaction_id=<?php echo $transaction['id']; ?>" class="btn btn-sm btn-primary">View Receipt</a>
                                                        <?php if (strcasecmp((string)($transaction['payment_method'] ?? ''), 'Manual') === 0): ?>
                                                            <a href="?page=<?php echo portalStudentPaymentPageFromTransaction($transaction); ?>" class="btn btn-sm btn-outline-primary">Upload Another Proof</a>
                                                        <?php endif; ?>
                                                    <?php elseif (strcasecmp((string)($transaction['payment_method'] ?? ''), 'Manual') === 0): ?>
                                                        <a href="?page=<?php echo portalStudentPaymentPageFromTransaction($transaction); ?>" class="btn btn-sm btn-outline-primary">Replace Proof</a>
                                                    <?php else: ?>
                                                        <span class="text-muted">—</span>
                                                    <?php endif; ?>
                                                </td>
                                            </tr>
                                            <?php endforeach; ?>
                                        </tbody>
                                    </table>
                                </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

            case 'acceptance_payment':
                // Check if user is logged in
                if (!isStudent()) {
                    redirect('?page=login');
                }

                // Get student information
                $studentId = $_SESSION['user_id'];
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC);

                // Check if application is approved
                if ($student['application_status'] !== 'Approved') {
                    echo '<div class="alert alert-warning">Your application has not been approved yet. You cannot pay the acceptance fee until your application is approved.</div>';
                    break;
                }

                // Get acceptance fee amount
                $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'acceptance_fee'");
                $stmt->execute();
                $acceptanceFee = $stmt->fetchColumn();
                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header">
                                Acceptance Fee Payment
                            </div>
                            <div class="card-body">
                                <h4>Acceptance Fee Payment</h4>
                                <p>Congratulations on your admission! Please pay the acceptance fee to access your admission letter.</p>

                                <div class="alert alert-info">
                                    <i class="bi bi-info-circle"></i> Payment is secure and processed through Paystack.
                                </div>
                                <?php if (!$hostelFeeConfigured || $hostelFee <= 0): ?>
                                    <div class="alert alert-warning">
                                        No matching hostel fee found for the new session scope.
                                        <div class="small mt-2">Current scope: <strong><?php echo e($hostelSession !== '' ? $hostelSession : 'Not Set'); ?></strong><?php if ($hostelSemester !== ''): ?> / <strong><?php echo e($hostelSemester); ?></strong><?php endif; ?>.</div>
                                    </div>
                                <?php endif; ?>

                                <div class="row">
                                    <div class="col-md-6">
                                        <div class="card">
                                            <div class="card-body">
                                                <h5>Payment Details</h5>
                                                <table class="table">
                                                    <tr>
                                                        <td>Acceptance Fee:</td>
                                                        <td>₦<?php echo number_format($acceptanceFee); ?></td>
                                                    </tr>
                                                    <tr>
                                                        <td>Payment Method:</td>
                                                        <td>Paystack</td>
                                                    </tr>
                                                    <tr>
                                                        <td>Total Amount:</td>
                                                        <td><strong>₦<?php echo number_format($acceptanceFee); ?></strong></td>
                                                    </tr>
                                                </table>

                                                <?php if ($student['acceptance_fee_status'] === 'Paid'): ?>
                                                    <div class="alert alert-success">
                                                        <i class="bi bi-check-circle"></i> You have already paid the acceptance fee.
                                                        <?php
    // Open the actual receipt for this fee (not payment history)
    $txType = ($k === 'registration') ? 'Registration Fee' :
              (($k === 'acceptance') ? 'Acceptance Fee' :
              (($k === 'hostel') ? 'Hostel Fee' :
              (($k === 'school') ? 'School Fee' : (string)($meta['label'] ?? 'Payment'))));
    $rp = 'payment_receipt';
    if ($txType === 'Acceptance Fee') $rp = 'acceptance_payment_receipt';
    elseif ($txType === 'Hostel Fee') $rp = 'hostel_payment_receipt';
    elseif ($txType === 'School Fee') $rp = 'school_payment_receipt';

    $receiptTxId = 0;
    try {
        $st = $pdo->prepare("SELECT id FROM payment_transactions
                             WHERE student_id = ? AND transaction_type = ?
                               AND status IN ('Approved','Successful')
                             ORDER BY id DESC LIMIT 1");
        $st->execute([(int)$studentId, $txType]);
        $receiptTxId = (int)$st->fetchColumn();
    } catch (Throwable $e) { $receiptTxId = 0; }

    $receiptUrl = ($receiptTxId > 0) ? ("?page=" . $rp . "&transaction_id=" . $receiptTxId) : "?page=payment_history";
?>
<a href="<?php echo $receiptUrl; ?>" class="btn btn-sm btn-primary ms-2">View Receipt</a>
                                                    </div>
                                                <?php elseif (!$hostelFeeConfigured || $hostelFee <= 0): ?>
                                                    <div class="alert alert-warning">No matching hostel fee found for your current session scope.</div>
                                                <?php else: ?>
                                                    <form method="post" action="">
                                                        <input type="hidden" name="action" value="initiate_payment">
                                                        <input type="hidden" name="payment_type" value="acceptance">
                                                        <button type="submit" class="btn btn-primary w-100">Pay Now</button>
                                                    </form>
                                                    <div class="mt-3 p-3 border rounded">
                                                        <h6 class="mb-2"><i class="bi bi-wallet2"></i> Pay with Wallet</h6>
                                                        <?php $walletBal = getWalletBalance($pdo, (int)$studentId); ?>
                                                        <div class="small text-muted mb-2">Wallet Balance: <strong>₦<?php echo number_format((float)$walletBal, 2); ?></strong></div>
                                                        <?php if ($walletBal >= (float)$acceptanceFee): ?>
                                                            <form method="post">
                                                                <input type="hidden" name="action" value="pay_with_wallet">
                                                                <input type="hidden" name="payment_type" value="acceptance">
                                                                <input type="hidden" name="wallet_payment_confirmed" value="0">
                                                                <input type="hidden" name="wallet_fee_name" value="Acceptance Fee">
                                                                <input type="hidden" name="wallet_fee_amount" value="<?php echo e(number_format((float)$acceptanceFee, 2, '.', '')); ?>">
                                                                <input type="hidden" name="wallet_balance_snapshot" value="<?php echo e(number_format((float)$walletBal, 2, '.', '')); ?>">
                                                                <button type="submit" class="btn btn-success w-100">Pay ₦<?php echo number_format((float)$acceptanceFee); ?> with Wallet</button>
                                                            </form>
                                                        <?php else: ?>
                                                            <div class="alert alert-warning mb-0">Insufficient wallet balance. <a href="?page=wallet">Fund Wallet</a> to pay.</div>
                                                        <?php endif; ?>
                                                    </div>

                                                    <?php if (getSystemSettingValue($pdo,'manual_payment_enabled','enabled') === 'enabled'): ?>
                                                        <div class="mt-3 p-3 border rounded">
                                                            <h6 class="mb-2"><i class="bi bi-bank"></i> Manual Bank Transfer (Upload Proof)</h6>
                                                            <div class="small text-muted mb-2"><?php echo nl2br(htmlspecialchars(getSystemSettingValue($pdo,'manual_payment_instructions',''))); ?></div>
                                                            <form method="post" enctype="multipart/form-data">
                                                                <input type="hidden" name="action" value="submit_manual_payment">
                                                                <input type="hidden" name="payment_type" value="acceptance">
                                                                <div class="mb-2">
                <label class="form-label">Amount Paid (₦)</label>
                <input type="number" name="amount_paid" class="form-control" min="0.01" step="0.01" required>
            </div>
            <div class="mb-2">
                                                                    <label class="form-label">Payer Name (optional)</label>
                                                                    <input type="text" name="payer_name" class="form-control" placeholder="Account name used for payment">
                                                                </div>
                                                                <div class="mb-2">
                                                                    <label class="form-label">Upload Teller / Receipt (JPG/PNG/PDF)</label>
                                                                    <input type="file" name="manual_proof" class="form-control" accept=".jpg,.jpeg,.png,.pdf" required>
                                                                </div>
                                                                <div class="mb-2">
                                                                    <label class="form-label">Notes (optional)</label>
                                                                    <textarea name="manual_notes" class="form-control" rows="2"></textarea>
                                                                </div>
                                                                <button class="btn btn-outline-primary w-100" type="submit">Submit Manual Payment</button>
                                                            </form>
                                                        </div>
                                                    <?php endif; ?>

                                                <?php endif; ?>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-md-6">
                                        <div class="card">
                                            <div class="card-body">
                                                <h5>Payment Instructions</h5>
                                                <ol>
                                                    <li>Click on the "Pay Now" button to proceed to payment</li>
                                                    <li>You will be redirected to a secure payment page</li>
                                                    <li>Enter your card details or select other payment options</li>
                                                    <li>Complete the payment process</li>
                                                    <li>You will receive a confirmation email after successful payment</li>
                                                </ol>

                                                <div class="alert alert-warning mt-3">
                                                    <i class="bi bi-exclamation-triangle"></i> Your admission letter will be available after successful payment.
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

            case 'acceptance_payment_receipt':
                // Check if user is logged in
                if (!isStudent() && !isAdmin()) { redirect('?page=login'); }

                // Get transaction details
                $transactionId = $_GET['transaction_id'] ?? $_SESSION['transaction_id'] ?? 0;
                if ($transactionId > 0) {
                    $stmt = $pdo->prepare("SELECT pt.*, s.first_name, s.surname, s.registration_no, s.application_no, s.admission_no, s.matric_no, s.session AS student_session, s.semester AS student_semester, COALESCE(NULLIF(TRIM(s.faculty),''),'Not Set') AS faculty_name, COALESCE(NULLIF(TRIM(s.department),''),'Not Set') AS department_name, COALESCE(NULLIF(TRIM(s.programme),''),'Not Set') AS programme_name, COALESCE(NULLIF(TRIM(s.level),''),'Not Set') AS level_name FROM payment_transactions pt
                        JOIN students s ON pt.student_id = s.id
                        WHERE pt.id = ?");
                    $stmt->execute([$transactionId]);
                    $transaction = $stmt->fetch(PDO::FETCH_ASSOC);

                    if ($transaction) {

                        $embed = (isset($_GET['embed']) && (string)$_GET['embed'] === '1');
                        if ($embed) {
                            header('Content-Type: text/html; charset=utf-8');
                            ?><!doctype html>
                            <html>
                            <head>
                                <meta charset="utf-8">
                                <meta name="viewport" content="width=device-width, initial-scale=1">
                                <title>Acceptance Fee Payment Receipt</title>
                                <style><?php echo portalReceiptEmbeddedStyleBlock(); ?></style>
                            </head>
                            <body>
                                <div class="wrap">
                                    <div class="receipt-container doc-stamp-wrap">
                                        <?php if (!empty($stampPath)): ?>
                                            <img src="<?php echo htmlspecialchars($stampPath); ?>" class="doc-stamp" alt="Official Stamp">
                                        <?php endif; ?>
                                        <div class="receipt-header">
                                            <div class="logo-area mb-3">
                                                <?php if ($leftLogoPath): ?>
                                                <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">HONTTECH Logo</div>
                                                <?php endif; ?>
                                                <?php if ($rightLogoPath): ?>
                                                <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">DAHIFT Logo</div>
                                                <?php endif; ?>
                                            </div>
                                            <h2>Honest Technologies University</h2>
                                            <h3>Ahmadu Bello University, Zaria</h3>
                                            <p>In affiliation with Dahift Global Ltd</p>
                                            <h4>Acceptance Fee Payment Receipt</h4>
                                        </div>

                                        <div class="receipt-body">
                                            <div class="receipt-meta">
                                                <div class="receipt-meta-col">
                                                    <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                                    <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                                    <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                                    <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                                </div>
                                                <div class="receipt-meta-col">
                                                    <p><strong>Student Name:</strong> <?php echo e(full_name($transaction)); ?></p>
                                                    <p><strong>Admission No.:</strong> <?php echo e(portalReceiptAdmissionNumber($transaction)); ?></p>
                                                    <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                                    <p><strong>Application No.:</strong> <?php echo e(portalReceiptApplicationNumber($transaction)); ?></p>
                                                    <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                    <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                    <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                                </div>
                                            </div>

                                            <div class="table-responsive mb-3">
                                                <table class="table">
                                                    <thead>
                                                        <tr>
                                                            <th>Description</th>
                                                            <th>Amount</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <tr>
                                                            <td><?php echo $transaction['transaction_type']; ?></td>
                                                            <td>₦<?php echo number_format($transaction['amount']); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <th>Total</th>
                                                            <th>₦<?php echo number_format($transaction['amount']); ?></th>
                                                        </tr>
                                                    </tbody>
                                                </table>
                                            </div>

                                            <div class="qr-code">
                                                <p><strong>QR Code:</strong></p>
                                                <div class="qr-pack">
                                                    <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                                <?php if (!empty($stampPath)): ?>
                                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>

                                        <div class="receipt-footer">
                                            <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                            <p>Your admission letter is now available in your dashboard.</p>
<p>For inquiries, please contact the admissions office.</p>
                                            <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                                        </div>
                                    </div>
                                </div>
                            </body>
                            </html><?php
                            exit;
                        }
                        ?>
                        <div class="row">
                            <div class="col-md-12">
                                <div class="card">
                                    <div class="card-header d-flex justify-content-between align-items-center">
                                        <h4 class="mb-0">Acceptance Fee Payment Receipt</h4>
                                        <div class="no-print">
                                            <button class="btn btn-secondary" onclick="window.print()">
                                                <i class="bi bi-printer"></i> Print
                                            </button>
                                            <?php if (isAdmin()): ?>
                                            <a href="?page=admin_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php else: ?>
                                            <?php if (isAdmin()): ?>
                                            <a href="?page=admin_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php else: ?>
                                            <?php if (isAdmin()): ?>
                                            <a href="?page=admin_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php else: ?>
                                            <a href="?page=student_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php endif; ?>
                                            <?php endif; ?>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                    <div class="card-body">
                                        <div class="receipt-container doc-stamp-wrap">
                                            <div class="receipt-header">
                                                <div class="logo-area mb-3">
                                                    <?php if ($leftLogoPath): ?>
                                                    <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                                    <?php else: ?>
                                                    <div class="logo-placeholder">HONTTECH Logo</div>
                                                    <?php endif; ?>
                                                    <?php if ($rightLogoPath): ?>
                                                    <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                                    <?php else: ?>
                                                    <div class="logo-placeholder">DAHIFT Logo</div>
                                                    <?php endif; ?>
                                                </div>
                                                <h2>Honest Technologies University</h2>
                                                <h3>Ahmadu Bello University, Zaria</h3>
                                                <p>In affiliation with Dahift Global Ltd</p>
                                                <h4>Acceptance Fee Payment Receipt</h4>
                                            </div>

                                            <div class="receipt-body">
                                                <div class="receipt-meta">
                                                    <div class="receipt-meta-col">
                                                        <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                                        <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                                        <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                                        <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                                    </div>
                                                    <div class="receipt-meta-col">
                                                        <p><strong>Student Name:</strong> <?php echo e(full_name($transaction)); ?></p>
                                                        <p><strong>Admission No.:</strong> <?php echo e(portalReceiptAdmissionNumber($transaction)); ?></p>
                                                        <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                                        <p><strong>Application No.:</strong> <?php echo e(portalReceiptApplicationNumber($transaction)); ?></p>
                                                        <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                    <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                    <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                                    </div>
                                                </div>

                                                <div class="table-responsive mb-3">
                                                    <table class="table table-bordered">
                                                        <thead>
                                                            <tr>
                                                                <th>Description</th>
                                                                <th>Amount</th>
                                                            </tr>
                                                        </thead>
                                                        <tbody>
                                                            <tr>
                                                                <td><?php echo $transaction['transaction_type']; ?></td>
                                                                <td>₦<?php echo number_format($transaction['amount']); ?></td>
                                                            </tr>
                                                            <tr>
                                                                <th>Total</th>
                                                                <th>₦<?php echo number_format($transaction['amount']); ?></th>
                                                            </tr>
                                                        </tbody>
                                                    </table>
                                                </div>

                                                <div class="qr-code">
                                                    <p><strong>QR Code:</strong></p>
                                                    <div class="qr-pack">
                                                        <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                                    <?php if (!empty($stampPath)): ?>
                                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                                    <?php endif; ?>
                                                </div>
                                                </div>
                                            </div>

                                            <div class="receipt-footer">
                                                <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                                <p>Your admission letter is now available in your dashboard.</p>
                                                <p>For inquiries, please contact the admissions office.</p>
                                                <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <?php
                    } else {
                        echo '<div class="alert alert-danger">Transaction not found.</div>';
                    }
                } else {
                    echo '<div class="alert alert-danger">Invalid transaction.</div>';
                }
                break;

            case 'hostel_payment':
                // Check if user is logged in
                if (!isStudent()) {
                    redirect('?page=login');
                }

                // Get student information
                $studentId = $_SESSION['user_id'];
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC);

                // Check if application is approved and acceptance fee is paid
                if (($student['application_status'] ?? '') !== 'Approved') {
                    echo '<div class="alert alert-warning">Your application has not been approved yet.</div>';
                    break;
                }
                if (($student['offer_status'] ?? 'Pending') !== 'Accepted') {
                    echo '<div class="alert alert-warning">You must <strong>ACCEPT</strong> your admission offer in your portal before you can access the admission letter.</div>';
                    break;
                }
                if (($student['acceptance_fee_status'] ?? 'Pending') !== 'Paid') {
                    echo '<div class="alert alert-warning">You must have an approved application and have paid the acceptance fee before you can pay for hostel accommodation.</div>';
                    break;
                }

                // Get hostel fee amount for the student's current academic scope
                $hostelSession = portalNormalizeSessionNameSafe((string)($student['session'] ?? portalCurrentAcademicSessionName($pdo)));
                $hostelSemester = portalNormalizeSemesterName((string)($student['semester'] ?? portalCurrentAcademicSemesterName($pdo)));
                $hostelFee = (float)portalStudentManagedFeeAmount($pdo, 'hostel', (array)$student, $hostelSession, $hostelSemester);
                $hostelFeeRule = portalManagedFeeRuleForContext($pdo, 'hostel', array_merge(portalStudentContext($pdo, (array)$student), ['session_name' => $hostelSession, 'semester' => $hostelSemester]));
                $hostelFeeConfigured = ($hostelFee > 0.0001) || !empty($hostelFeeRule['matched']);
                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header">
                                Hostel Fee Payment
                            </div>
                            <div class="card-body">
                                <h4>Hostel Fee Payment</h4>
                                <p>Secure your accommodation in the hostel by paying the hostel fee.</p>

                                <div class="alert alert-info">
                                    <i class="bi bi-info-circle"></i> Payment is secure and processed through Paystack.
                                </div>

                                <div class="row">
                                    <div class="col-md-6">
                                        <div class="card">
                                            <div class="card-body">
                                                <h5>Payment Details</h5>
                                                <table class="table">
                                                    <tr>
                                                        <td>Hostel Fee:</td>
                                                        <td>₦<?php echo number_format($hostelFee); ?></td>
                                                    </tr>
                                                    <tr>
                                                        <td>Payment Method:</td>
                                                        <td>Paystack</td>
                                                    </tr>
                                                    <tr>
                                                        <td>Total Amount:</td>
                                                        <td><strong>₦<?php echo number_format($hostelFee); ?></strong></td>
                                                    </tr>
                                                </table>

                                                <?php
                                                $accommodationStudentId = (int)($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0);
                                                if ($_SERVER['REQUEST_METHOD'] === 'POST' && (($_POST['action'] ?? '') === 'save_accommodation_status') && $accommodationStudentId > 0) {
                                                    $selectedAccommodation = (($_POST['accommodation_status'] ?? 'Campus') === 'Off-Campus') ? 'Off-Campus' : 'Campus';
                                                    try {
                                                        if (portalStudentAccommodationLockedForSession($pdo, $student)) {
                                                            flash('warning', 'Accommodation status is locked for the current session because the hostel fee has already been paid. You can change it again in a new session.');
                                                        } else {
                                                            $stmt = $pdo->prepare("UPDATE students SET accommodation_status = ? WHERE id = ?");
                                                            $stmt->execute([$selectedAccommodation, $accommodationStudentId]);
                                                            $student['accommodation_status'] = $selectedAccommodation;
                                                            flash('success', 'Accommodation status updated successfully.');
                                                        }
                                                    } catch (Throwable $e) {
                                                        flash('danger', 'Unable to update accommodation status.');
                                                    }
                                                }
                                                renderFlashes();
                                                ?>
                                                <?php $accommodationLocked = portalStudentAccommodationLockedForSession($pdo, $student); ?>
                                                <form method="post" class="row g-2 align-items-end mb-3">
                                                    <input type="hidden" name="action" value="save_accommodation_status">
                                                    <input type="hidden" name="return_to" value="<?php echo e((string)($_SERVER['REQUEST_URI'] ?? '?page=hostel_payment')); ?>">
                                                    <div class="col-md-8">
                                                        <label class="form-label">Accommodation Option</label>
                                                        <select class="form-select" name="accommodation_status" <?php echo $accommodationLocked ? 'disabled' : ''; ?>>
                                                            <option value="Campus" <?php echo ((string)($student['accommodation_status'] ?? 'Campus') === 'Campus') ? 'selected' : ''; ?>>Campus Accommodation</option>
                                                            <option value="Off-Campus" <?php echo ((string)($student['accommodation_status'] ?? 'Campus') === 'Off-Campus') ? 'selected' : ''; ?>>Off-Campus</option>
                                                        </select>
                                                        <?php if ($accommodationLocked): ?>
                                                            <input type="hidden" name="accommodation_status" value="<?php echo e((string)($student['accommodation_status'] ?? 'Campus')); ?>">
                                                        <?php endif; ?>
                                                    </div>
                                                    <div class="col-md-4">
                                                        <button class="btn btn-outline-primary w-100" type="submit" <?php echo $accommodationLocked ? 'disabled aria-disabled="true"' : ''; ?>>Update</button>
                                                    </div>
                                                    <div class="col-12"><div class="small text-muted">Select Off-Campus to show Off-Campus anywhere hostel fee status is displayed. You can switch back to Campus any time before hostel fee is paid for the current session.</div></div>
                                                </form>
                                                <?php if ($accommodationLocked): ?>
                                                    <div class="alert alert-secondary"><i class="bi bi-lock"></i> Hostel fee has already been paid for the current session. Accommodation status cannot be changed again until the next session.</div>
                                                <?php endif; ?>

                                                <?php $hostelRequirementState = function_exists('portalStudentHostelRequirementSummary') ? portalStudentHostelRequirementSummary($pdo, $student, $hostelSession ?? '', $hostelSemester ?? '') : ['requires_hostel_fee' => true, 'label' => '', 'message' => '']; ?>
                                                <?php if (empty($hostelRequirementState['requires_hostel_fee'])): ?>
                                                    <div class="alert alert-info">
                                                        <i class="bi bi-house-door"></i> <?php echo e((string)($hostelRequirementState['message'] ?? 'Hostel fee is not required for your current accommodation status.')); ?>
                                                    </div>
                                                <?php elseif ($student['hostel_fee_status'] === 'Paid'): ?>
                                                    <div class="alert alert-success">
                                                        <i class="bi bi-check-circle"></i> You have already paid the hostel fee.
                                                        <?php
    // Open the actual receipt for this fee (not payment history)
    $txType = ($k === 'registration') ? 'Registration Fee' :
              (($k === 'acceptance') ? 'Acceptance Fee' :
              (($k === 'hostel') ? 'Hostel Fee' :
              (($k === 'school') ? 'School Fee' : (string)($meta['label'] ?? 'Payment'))));
    $rp = 'payment_receipt';
    if ($txType === 'Acceptance Fee') $rp = 'acceptance_payment_receipt';
    elseif ($txType === 'Hostel Fee') $rp = 'hostel_payment_receipt';
    elseif ($txType === 'School Fee') $rp = 'school_payment_receipt';

    $receiptTxId = 0;
    try {
        $st = $pdo->prepare("SELECT id FROM payment_transactions
                             WHERE student_id = ? AND transaction_type = ?
                               AND status IN ('Approved','Successful')
                             ORDER BY id DESC LIMIT 1");
        $st->execute([(int)$studentId, $txType]);
        $receiptTxId = (int)$st->fetchColumn();
    } catch (Throwable $e) { $receiptTxId = 0; }

    $receiptUrl = ($receiptTxId > 0) ? ("?page=" . $rp . "&transaction_id=" . $receiptTxId) : "?page=payment_history";
?>
<a href="<?php echo $receiptUrl; ?>" class="btn btn-sm btn-primary ms-2">View Receipt</a>
                                                    </div>
                                                <?php else: ?>
                                                    <form method="post" action="">
                                                        <input type="hidden" name="action" value="initiate_payment">
                                                        <input type="hidden" name="payment_type" value="hostel">
                                                        <button type="submit" class="btn btn-primary w-100">Pay Now</button>
                                                    </form>
                                                    <div class="mt-3 p-3 border rounded">
                                                        <h6 class="mb-2"><i class="bi bi-wallet2"></i> Pay with Wallet</h6>
                                                        <?php $walletBal = getWalletBalance($pdo, (int)$studentId); ?>
                                                        <div class="small text-muted mb-2">Wallet Balance: <strong>₦<?php echo number_format((float)$walletBal, 2); ?></strong></div>
                                                        <?php if ($walletBal >= (float)$hostelFee): ?>
                                                            <form method="post">
                                                                <input type="hidden" name="action" value="pay_with_wallet">
                                                                <input type="hidden" name="payment_type" value="hostel">
                                                                <input type="hidden" name="wallet_payment_confirmed" value="0">
                                                                <input type="hidden" name="wallet_fee_name" value="Hostel Fee">
                                                                <input type="hidden" name="wallet_fee_amount" value="<?php echo e(number_format((float)$hostelFee, 2, '.', '')); ?>">
                                                                <input type="hidden" name="wallet_balance_snapshot" value="<?php echo e(number_format((float)$walletBal, 2, '.', '')); ?>">
                                                                <button type="submit" class="btn btn-success w-100">Pay ₦<?php echo number_format((float)$hostelFee); ?> with Wallet</button>
                                                            </form>
                                                        <?php else: ?>
                                                            <div class="alert alert-warning mb-0">Insufficient wallet balance. <a href="?page=wallet">Fund Wallet</a> to pay.</div>
                                                        <?php endif; ?>
                                                    </div>

                                                    <?php if (getSystemSettingValue($pdo,'manual_payment_enabled','enabled') === 'enabled'): ?>
                                                        <div class="mt-3 p-3 border rounded">
                                                            <h6 class="mb-2"><i class="bi bi-bank"></i> Manual Bank Transfer (Upload Proof)</h6>
                                                            <div class="small text-muted mb-2"><?php echo nl2br(htmlspecialchars(getSystemSettingValue($pdo,'manual_payment_instructions',''))); ?></div>
                                                            <form method="post" enctype="multipart/form-data">
                                                                <input type="hidden" name="action" value="submit_manual_payment">
                                                                <input type="hidden" name="payment_type" value="hostel">
                                                                <div class="mb-2">
                <label class="form-label">Amount Paid (₦)</label>
                <input type="number" name="amount_paid" class="form-control" min="0.01" step="0.01" required>
            </div>
            <div class="mb-2">
                                                                    <label class="form-label">Payer Name (optional)</label>
                                                                    <input type="text" name="payer_name" class="form-control" placeholder="Account name used for payment">
                                                                </div>
                                                                <div class="mb-2">
                                                                    <label class="form-label">Upload Teller / Receipt (JPG/PNG/PDF)</label>
                                                                    <input type="file" name="manual_proof" class="form-control" accept=".jpg,.jpeg,.png,.pdf" required>
                                                                </div>
                                                                <div class="mb-2">
                                                                    <label class="form-label">Notes (optional)</label>
                                                                    <textarea name="manual_notes" class="form-control" rows="2"></textarea>
                                                                </div>
                                                                <button class="btn btn-outline-primary w-100" type="submit">Submit Manual Payment</button>
                                                            </form>
                                                        </div>
                                                    <?php endif; ?>

                                                <?php endif; ?>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-md-6">
                                        <div class="card">
                                            <div class="card-body">
                                                <h5>Payment Instructions</h5>
                                                <ol>
                                                    <li>Click on the "Pay Now" button to proceed to payment</li>
                                                    <li>You will be redirected to a secure payment page</li>
                                                    <li>Enter your card details or select other payment options</li>
                                                    <li>Complete the payment process</li>
                                                    <li>You will receive a confirmation email after successful payment</li>
                                                </ol>

                                                <div class="alert alert-warning mt-3">
                                                    <i class="bi bi-exclamation-triangle"></i> Hostel accommodation is subject to availability. You will be notified of your room allocation after payment confirmation.
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

            case 'hostel_payment_receipt':
                // Check if user is logged in
                if (!isStudent() && !isAdmin()) { redirect('?page=login'); }

                // Get transaction details
                $transactionId = $_GET['transaction_id'] ?? $_SESSION['transaction_id'] ?? 0;
                if ($transactionId > 0) {
                    $stmt = $pdo->prepare("SELECT pt.*, s.first_name, s.surname, s.registration_no, s.application_no, s.admission_no, s.matric_no, s.session AS student_session, s.semester AS student_semester, COALESCE(NULLIF(TRIM(s.faculty),''),'Not Set') AS faculty_name, COALESCE(NULLIF(TRIM(s.department),''),'Not Set') AS department_name, COALESCE(NULLIF(TRIM(s.programme),''),'Not Set') AS programme_name, COALESCE(NULLIF(TRIM(s.level),''),'Not Set') AS level_name FROM payment_transactions pt
                        JOIN students s ON pt.student_id = s.id
                        WHERE pt.id = ?");
                    $stmt->execute([$transactionId]);
                    $transaction = $stmt->fetch(PDO::FETCH_ASSOC);

                    if ($transaction) {

                        $embed = (isset($_GET['embed']) && (string)$_GET['embed'] === '1');
                        if ($embed) {
                            header('Content-Type: text/html; charset=utf-8');
                            ?><!doctype html>
                            <html>
                            <head>
                                <meta charset="utf-8">
                                <meta name="viewport" content="width=device-width, initial-scale=1">
                                <title>Hostel Fee Payment Receipt</title>
                                <style><?php echo portalReceiptEmbeddedStyleBlock(); ?></style>
                            </head>
                            <body>
                                <div class="wrap">
                                    <div class="receipt-container doc-stamp-wrap">
                                        <?php if (!empty($stampPath)): ?>
                                            <img src="<?php echo htmlspecialchars($stampPath); ?>" class="doc-stamp" alt="Official Stamp">
                                        <?php endif; ?>
                                        <div class="receipt-header">
                                            <div class="logo-area mb-3">
                                                <?php if ($leftLogoPath): ?>
                                                <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">HONTTECH Logo</div>
                                                <?php endif; ?>
                                                <?php if ($rightLogoPath): ?>
                                                <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">DAHIFT Logo</div>
                                                <?php endif; ?>
                                            </div>
                                            <h2>Honest Technologies University</h2>
                                            <h3>Ahmadu Bello University, Zaria</h3>
                                            <p>In affiliation with Dahift Global Ltd</p>
                                            <h4>Hostel Fee Payment Receipt</h4>
                                        </div>

                                        <div class="receipt-body">
                                            <div class="receipt-meta">
                                                <div class="receipt-meta-col">
                                                    <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                                    <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                                    <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                                    <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                                </div>
                                                <div class="receipt-meta-col">
                                                    <p><strong>Student Name:</strong> <?php echo e(full_name($transaction)); ?></p>
                                                    <p><strong>Admission No.:</strong> <?php echo e(portalReceiptAdmissionNumber($transaction)); ?></p>
                                                    <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                                    <p><strong>Application No.:</strong> <?php echo e(portalReceiptApplicationNumber($transaction)); ?></p>
                                                    <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                    <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                    <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                                </div>
                                            </div>

                                            <div class="table-responsive mb-3">
                                                <table class="table">
                                                    <thead>
                                                        <tr>
                                                            <th>Description</th>
                                                            <th>Amount</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <tr>
                                                            <td><?php echo $transaction['transaction_type']; ?></td>
                                                            <td>₦<?php echo number_format($transaction['amount']); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <th>Total</th>
                                                            <th>₦<?php echo number_format($transaction['amount']); ?></th>
                                                        </tr>
                                                    </tbody>
                                                </table>
                                            </div>

                                            <div class="qr-code">
                                                <p><strong>QR Code:</strong></p>
                                                <div class="qr-pack">
                                                    <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                                <?php if (!empty($stampPath)): ?>
                                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>

                                        <div class="receipt-footer">
                                            <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                            <p>For inquiries, please contact the admissions office.</p>
                                            <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                                        </div>
                                    </div>
                                </div>
                            </body>
                            </html><?php
                            exit;
                        }
                        ?>
                        <div class="row">
                            <div class="col-md-12">
                                <div class="card">
                                    <div class="card-header d-flex justify-content-between align-items-center">
                                        <h4 class="mb-0">Hostel Fee Payment Receipt</h4>
                                        <div class="no-print">
                                            <button class="btn btn-secondary" onclick="window.print()">
                                                <i class="bi bi-printer"></i> Print
                                            </button>
                                            <?php if (isAdmin()): ?>
                                            <a href="?page=admin_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php else: ?>
                                            <?php if (isAdmin()): ?>
                                            <a href="?page=admin_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php else: ?>
                                            <a href="?page=student_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php endif; ?>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                    <div class="card-body">
                                        <div class="receipt-container doc-stamp-wrap">
                                            <div class="receipt-header">
                                                <div class="logo-area mb-3">
                                                    <?php if ($leftLogoPath): ?>
                                                    <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                                    <?php else: ?>
                                                    <div class="logo-placeholder">HONTTECH Logo</div>
                                                    <?php endif; ?>
                                                    <?php if ($rightLogoPath): ?>
                                                    <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                                    <?php else: ?>
                                                    <div class="logo-placeholder">DAHIFT Logo</div>
                                                    <?php endif; ?>
                                                </div>
                                                <h2>Honest Technologies University</h2>
                                                <h3>Ahmadu Bello University, Zaria</h3>
                                                <p>In affiliation with Dahift Global Ltd</p>
                                                <h4>Hostel Fee Payment Receipt</h4>
                                            </div>

                                            <div class="receipt-body">
                                                <div class="receipt-meta">
                                                    <div class="receipt-meta-col">
                                                        <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                                        <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                                        <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                                        <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                                    </div>
                                                    <div class="receipt-meta-col">
                                                        <p><strong>Student Name:</strong> <?php echo $transaction['first_name'] . ' ' . $transaction['surname']; ?></p>
                                                        <p><strong>Admission No.:</strong> <?php echo e(portalReceiptAdmissionNumber($transaction)); ?></p>
                                                        <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                                        <p><strong>Application No.:</strong> <?php echo e(portalReceiptApplicationNumber($transaction)); ?></p>
                                                        <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                    <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                    <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                                    </div>
                                                </div>

                                                <div class="table-responsive mb-3">
                                                    <table class="table table-bordered">
                                                        <thead>
                                                            <tr>
                                                                <th>Description</th>
                                                                <th>Amount</th>
                                                            </tr>
                                                        </thead>
                                                        <tbody>
                                                            <tr>
                                                                <td><?php echo $transaction['transaction_type']; ?></td>
                                                                <td>₦<?php echo number_format($transaction['amount']); ?></td>
                                                            </tr>
                                                            <tr>
                                                                <th>Total</th>
                                                                <th>₦<?php echo number_format($transaction['amount']); ?></th>
                                                            </tr>
                                                        </tbody>
                                                    </table>
                                                </div>

                                                <div class="qr-code">
                                                    <p><strong>QR Code:</strong></p>
                                                    <div class="qr-pack">
                                                        <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                                    <?php if (!empty($stampPath)): ?>
                                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                                    <?php endif; ?>
                                                </div>
                                                </div>
                                            </div>

                                            <div class="receipt-footer">
                                                <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                                <p>Your hostel accommodation has been confirmed. You will receive further details about your room allocation.</p>
                                                <p>For inquiries, please contact the admissions office.</p>
                                                <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <?php
                    } else {
                        echo '<div class="alert alert-danger">Transaction not found.</div>';
                    }
                } else {
                    echo '<div class="alert alert-danger">Invalid transaction.</div>';
                }
                break;

            case 'school_payment':
                // Check if user is logged in
                if (!isStudent()) {
                    redirect('?page=login');
                }

                // Get student information
                $studentId = $_SESSION['user_id'];
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC);

                // Check if application is approved and acceptance fee is paid
                if (($student['application_status'] ?? '') !== 'Approved') {
                    echo '<div class="alert alert-warning">Your application has not been approved yet.</div>';
                    break;
                }
                if (($student['offer_status'] ?? 'Pending') !== 'Accepted') {
                    echo '<div class="alert alert-warning">You must <strong>ACCEPT</strong> your admission offer in your portal before you can access the admission letter.</div>';
                    break;
                }
                if (($student['acceptance_fee_status'] ?? 'Pending') !== 'Paid') {
                    echo '<div class="alert alert-warning">You must have an approved application and have paid the acceptance fee before you can pay school fees.</div>';
                    break;
                }

                [$selectedSchoolSession, $selectedSchoolSemester] = portalSelectedSessionSemester($pdo, $student, 'session', 'semester');
                $selectedSchoolSessions = portalAvailableSessionOptions($pdo, $student);

                // Get school fee amount and installment settings
                $schoolFee = (float)portalStudentSchoolFeeAmount($pdo, (array)$student, $selectedSchoolSession, $selectedSchoolSemester);

                $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'installment_enabled'");
                $stmt->execute();
                // Always offer installment option (2 parts)
                $installmentEnabled = true;

                // Calculate installment amounts
                $installmentAmount = $installmentEnabled ? ($schoolFee / 2) : $schoolFee;

                // If the student has started school fee installment, disable one-time option automatically
                $disableOneTimeSchool = false;
                $paidInstallmentsSchool = 0;
                if ($installmentEnabled) {
                    $stmt = $pdo->prepare("SELECT
                            SUM(CASE WHEN status = 'Approved' THEN 1 ELSE 0 END) AS approved_cnt,
                            COUNT(*) AS total_cnt,
                            MAX(total_installments) AS max_total
                        FROM payment_transactions
                        WHERE student_id = ? AND transaction_type = 'School Fee' AND total_installments > 1");
                    $stmt->execute([$studentId]);
                    $row = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
                    $paidInstallmentsSchool = (int)($row['approved_cnt'] ?? 0);
                    $maxTotal = (int)($row['max_total'] ?? 0);
                    $hasStarted = ((int)($row['total_cnt'] ?? 0) > 0) || ($paidInstallmentsSchool > 0);
                    if ($hasStarted && $maxTotal > 1) {
                        $disableOneTimeSchool = true;
                    }
                }


// Next installment to pay (if installment option is used)
                $nextInstallmentSchool = 1;
                if ($installmentEnabled) {
                    $nextInstallmentSchool = min(2, max(1, $paidInstallmentsSchool + 1));
                }
                $installmentBtnText = ($nextInstallmentSchool === 2) ? 'Pay Second Installment' : 'Pay First Installment';
                $schoolSummary = portalSchoolFeePeriodSummary($pdo, $studentId, $selectedSchoolSession, $selectedSchoolSemester, $student);
                $schoolFeeStatusLabel = (string)($schoolSummary['status_label'] ?? 'Not Paid');
                $schoolPrincipalOutstanding = (float)($schoolSummary['principal_outstanding'] ?? max(0, $schoolFee - (float)($schoolSummary['total_paid'] ?? 0)));
                $schoolLateFeeBalance = (float)($schoolSummary['late_fee_balance'] ?? 0);
                $schoolTotalDue = (float)($schoolSummary['total_due'] ?? ($schoolPrincipalOutstanding + $schoolLateFeeBalance));
                $schoolPayNowOneTime = $schoolTotalDue > 0 ? $schoolTotalDue : $schoolFee;
                $schoolPayNowInstallment = $schoolSummary['approved_installments'] > 0 ? $schoolTotalDue : ((float)$installmentAmount + $schoolLateFeeBalance);
                $schoolPlanPermission = portalStudentPaymentPlanPermission($student);
                $schoolFeeRule = portalStudentSchoolFeeRule($pdo, (array)$student, $selectedSchoolSession, $selectedSchoolSemester);
                $schoolFeeConfigured = ($schoolFee > 0.0001) || !empty($schoolFeeRule['matched']) || $schoolTotalDue > 0.0001;
                $allowOneTimeSchool = portalStudentPaymentPlanAllowed($student, 1);
                $allowInstallmentSchool = portalStudentPaymentPlanAllowed($student, 2);
                if (!$allowOneTimeSchool) { $disableOneTimeSchool = true; }

                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header">
                                School Fee Payment
                            </div>
                            <div class="card-body">
                                <h4>School Fee Payment</h4>
                                <p>Pay your school fees to continue your studies.</p>

                                <form method="get" class="row g-3 align-items-end mb-3">
                                    <input type="hidden" name="page" value="school_payment">
                                    <div class="col-md-4">
                                        <label class="form-label">Session</label>
                                        <select name="session" class="form-select">
                                            <?php foreach ($selectedSchoolSessions as $opt): ?>
                                                <option value="<?php echo e($opt); ?>" <?php echo strcasecmp($selectedSchoolSession, $opt) === 0 ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-4">
                                        <label class="form-label">Semester</label>
                                        <select name="semester" class="form-select">
                                            <?php foreach (portalAvailableSemesterOptions() as $opt): ?>
                                                <option value="<?php echo e($opt); ?>" <?php echo strcasecmp($selectedSchoolSemester, $opt) === 0 ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-4 d-grid">
                                        <button type="submit" class="btn btn-outline-primary">Load Semester</button>
                                    </div>
                                </form>

                                <div class="alert alert-info">
                                    <i class="bi bi-info-circle"></i> Viewing <strong><?php echo e($selectedSchoolSession); ?></strong> - <strong><?php echo e($selectedSchoolSemester); ?></strong>. School fee status for this semester: <strong><?php echo e($schoolFeeStatusLabel); ?></strong>.
                                    <div class="mt-2"><strong>Allowed Payment Plan:</strong> <?php echo e($schoolPlanPermission); ?></div>
                                    <div class="mt-2"><strong>Outstanding School Fee:</strong> ₦<?php echo number_format($schoolPrincipalOutstanding, 2); ?></div>
                                    <?php if ((float)($schoolSummary['carry_forward_credit'] ?? 0) > 0): ?>
                                        <div><strong>Previous Covered Amount Applied:</strong> ₦<?php echo number_format((float)($schoolSummary['carry_forward_credit'] ?? 0), 2); ?> from <?php echo e((string)($schoolSummary['carry_forward_source_session'] ?? 'previous session')); ?></div>
                                    <?php endif; ?>
                                    <?php if (!empty($schoolSummary['carry_forward_message'])): ?><div class="small text-muted mt-1"><?php echo e((string)$schoolSummary['carry_forward_message']); ?></div><?php endif; ?>
                                    <?php if ($schoolLateFeeBalance > 0): ?>
                                        <div><strong>Late Fee:</strong> ₦<?php echo number_format($schoolLateFeeBalance, 2); ?></div>
                                        <div><strong>Total Due:</strong> ₦<?php echo number_format($schoolTotalDue, 2); ?></div>
                                    <?php endif; ?>
                                </div>
                                <?php $schoolPreviousDebt = portalStudentOutstandingPreviousSchoolFee($pdo, (array)$student, $selectedSchoolSession, $selectedSchoolSemester); ?>
                                <?php if (!empty($schoolPreviousDebt['has_outstanding'])): ?>
                                    <div class="alert alert-danger">
                                        <i class="bi bi-exclamation-octagon"></i> You still have previous school fee debt from <strong><?php echo e((string)($schoolPreviousDebt['session_name'] ?? 'a previous session')); ?></strong>.
                                        Outstanding amount: <strong>₦<?php echo number_format((float)(($schoolPreviousDebt['summary']['total_due'] ?? $schoolPreviousDebt['summary']['outstanding_amount'] ?? 0)), 2); ?></strong>.
                                    </div>
                                <?php endif; ?>
                                <?php if (!$schoolFeeConfigured): ?>
                                    <div class="alert alert-warning">No matching school fee found for the new student scope.</div>
                                <?php endif; ?>
                                <?php if ($schoolLateFeeBalance > 0): ?>
                                    <div class="alert alert-warning">
                                        <i class="bi bi-exclamation-triangle"></i> Late fee has been applied for <strong><?php echo e($selectedSchoolSession); ?></strong> / <strong><?php echo e($selectedSchoolSemester); ?></strong>.
                                        <div class="mt-2">Reason: <?php echo e((string)($schoolSummary['late_fee_notice'] ?? 'Late school fee charged after deadline.')); ?></div>
                                        <div>Original Outstanding Amount: ₦<?php echo number_format($schoolPrincipalOutstanding, 2); ?></div>
                                        <div>Late Fee Amount: ₦<?php echo number_format($schoolLateFeeBalance, 2); ?></div>
                                        <div>Total Due: ₦<?php echo number_format($schoolTotalDue, 2); ?></div>
                                    </div>
                                <?php endif; ?>

                                <div class="row">
                                    <div class="col-md-6">
                                        <div class="card">
                                            <div class="card-body">
                                                <h5>Payment Options</h5>

                                                <?php if ($schoolFeeStatusLabel === 'Paid'): ?>
                                                    <div class="alert alert-success">
                                                        <i class="bi bi-check-circle"></i> You have already paid the school fee.
                                                        <?php
    // Open the actual receipt for this fee (not payment history)
    $txType = ($k === 'registration') ? 'Registration Fee' :
              (($k === 'acceptance') ? 'Acceptance Fee' :
              (($k === 'hostel') ? 'Hostel Fee' :
              (($k === 'school') ? 'School Fee' : (string)($meta['label'] ?? 'Payment'))));
    $rp = 'payment_receipt';
    if ($txType === 'Acceptance Fee') $rp = 'acceptance_payment_receipt';
    elseif ($txType === 'Hostel Fee') $rp = 'hostel_payment_receipt';
    elseif ($txType === 'School Fee') $rp = 'school_payment_receipt';

    $receiptTxId = 0;
    try {
        $st = $pdo->prepare("SELECT id FROM payment_transactions
                             WHERE student_id = ? AND transaction_type = ?
                               AND status IN ('Approved','Successful')
                             ORDER BY id DESC LIMIT 1");
        $st->execute([(int)$studentId, $txType]);
        $receiptTxId = (int)$st->fetchColumn();
    } catch (Throwable $e) { $receiptTxId = 0; }

    $receiptUrl = ($receiptTxId > 0) ? ("?page=" . $rp . "&transaction_id=" . $receiptTxId) : "?page=payment_history";
?>
<a href="<?php echo $receiptUrl; ?>" class="btn btn-sm btn-primary ms-2">View Receipt</a>
                                                    </div>
                                                <?php else: ?>
                                                    <?php if ($installmentEnabled): ?>
                                                        <div class="mb-3">
                                                            <label class="form-label">Choose Payment Plan:</label>
                                                            <div class="form-check">
                                                                <input class="form-check-input" type="radio" name="payment_plan" id="one_time" value="one_time" <?php echo $disableOneTimeSchool ? 'disabled' : ''; ?> <?php echo $disableOneTimeSchool ? '' : 'checked'; ?> >
                                                                <label class="form-check-label" for="one_time">
                                                                    One-time Payment - ₦<?php echo number_format($schoolPayNowOneTime); ?>
                                                                </label>
                                                            </div>
                                                            <div class="form-check">
                                                                <input class="form-check-input" type="radio" name="payment_plan" id="installment" value="installment" <?php echo (!$allowInstallmentSchool ? 'disabled' : ($disableOneTimeSchool ? 'checked' : '')); ?> >
                                                                <label class="form-check-label" for="installment">
                                                                    Installment Payment - 2 installments of ₦<?php echo number_format($installmentAmount); ?> each
                                                                </label>
                                                            </div>
                                                        </div>

                                                        <div id="installment_details" style="display: none;">
                                                            <div class="alert alert-info">
                                                                <h6>Installment Details:</h6>
                                                                <p>Next Installment Principal: ₦<?php echo number_format($schoolSummary['approved_installments'] > 0 ? $schoolPrincipalOutstanding : (float)$installmentAmount); ?><?php echo $schoolLateFeeBalance > 0 ? ' + Late Fee ₦' . number_format($schoolLateFeeBalance) : ''; ?> (Pay now)</p>
                                                                <p>Second Installment: ₦<?php echo number_format($installmentAmount); ?> (Pay before mid-semester)</p>
                                                            </div>
                                                        </div>

                                                        <div class="d-grid gap-2">
                                                            <form method="post" action="" id="one_time_form" style="<?php echo $disableOneTimeSchool ? 'display:none;' : ''; ?>">
                                                                <input type="hidden" name="action" value="initiate_payment">
                                                                <input type="hidden" name="payment_type" value="school">
                                                                <input type="hidden" name="installment_number" value="1">
                                                                <input type="hidden" name="total_installments" value="1">
                                                                <input type="hidden" name="payment_session" value="<?php echo e($selectedSchoolSession); ?>"><input type="hidden" name="payment_semester" value="<?php echo e($selectedSchoolSemester); ?>"><button type="submit" class="btn btn-primary">Pay One-time ₦<?php echo number_format($schoolPayNowOneTime); ?></button>
                                                            </form>

                                                            <form method="post" action="" id="installment_form" style="display: none;">
                                                                <input type="hidden" name="action" value="initiate_payment">
                                                                <input type="hidden" name="payment_type" value="school">
                                                                <input type="hidden" name="installment_number" value="<?php echo (int)$nextInstallmentSchool; ?>">
                                                                <input type="hidden" name="total_installments" value="2">
                                                                <input type="hidden" name="payment_session" value="<?php echo e($selectedSchoolSession); ?>"><input type="hidden" name="payment_semester" value="<?php echo e($selectedSchoolSemester); ?>"><button type="submit" class="btn btn-primary"><?php echo e($installmentBtnText); ?> ₦<?php echo number_format($schoolPayNowInstallment); ?></button>
                                                            </form>
                                                        </div>

                                                        <!-- Wallet + Manual Payment options for School Fees (One-time or Installments) -->
                                                        <div id="wallet_school_one_time" class="mt-3 p-3 border rounded" style="<?php echo $disableOneTimeSchool ? 'display:none;' : ''; ?>">
                                                            <h6 class="mb-2"><i class="bi bi-wallet2"></i> Pay with Wallet</h6>
                                                            <?php $walletBal = getWalletBalance($pdo, (int)$studentId); ?>
                                                            <div class="small text-muted mb-2">Wallet Balance: <strong>₦<?php echo number_format((float)$walletBal, 2); ?></strong></div>
                                                            <?php if ($walletBal >= (float)$schoolPayNowOneTime): ?>
                                                                <form method="post">
                                                                    <input type="hidden" name="action" value="pay_with_wallet">
                                                                    <input type="hidden" name="payment_type" value="school">
                                                                    <input type="hidden" name="installment_number" value="1">
                                                                    <input type="hidden" name="total_installments" value="1">
                                                                    <input type="hidden" name="wallet_payment_confirmed" value="0">
                                                                    <input type="hidden" name="wallet_fee_name" value="School Fee">
                                                                    <input type="hidden" name="wallet_fee_amount" value="<?php echo e(number_format((float)$schoolPayNowOneTime, 2, '.', '')); ?>">
                                                                    <input type="hidden" name="wallet_balance_snapshot" value="<?php echo e(number_format((float)$walletBal, 2, '.', '')); ?>">
                                                                    <button type="submit" class="btn btn-success w-100"><input type="hidden" name="payment_session" value="<?php echo e($selectedSchoolSession); ?>"><input type="hidden" name="payment_semester" value="<?php echo e($selectedSchoolSemester); ?>">Pay ₦<?php echo number_format((float)$schoolPayNowOneTime); ?> with Wallet</button>
                                                                </form>
                                                            <?php else: ?>
                                                                <div class="alert alert-warning mb-0">Insufficient wallet balance. <a href="?page=wallet">Fund Wallet</a> to pay.</div>
                                                            <?php endif; ?>
                                                        </div>

                                                        <div id="wallet_school_installment" class="mt-3 p-3 border rounded" style="display:none;">
                                                            <h6 class="mb-2"><i class="bi bi-wallet2"></i> Pay with Wallet</h6>
                                                            <?php $walletBal2 = getWalletBalance($pdo, (int)$studentId); ?>
                                                            <div class="small text-muted mb-2">Wallet Balance: <strong>₦<?php echo number_format((float)$walletBal2, 2); ?></strong></div>
                                                            <?php if ($walletBal2 >= (float)$schoolPayNowInstallment): ?>
                                                                <form method="post">
                                                                    <input type="hidden" name="action" value="pay_with_wallet">
                                                                    <input type="hidden" name="payment_type" value="school">
                                                                    <input type="hidden" name="installment_number" value="<?php echo (int)$nextInstallmentSchool; ?>">
                                                                    <input type="hidden" name="total_installments" value="2">
                                                                    <input type="hidden" name="wallet_payment_confirmed" value="0">
                                                                    <input type="hidden" name="wallet_fee_name" value="School Fee">
                                                                    <input type="hidden" name="wallet_fee_amount" value="<?php echo e(number_format((float)$schoolPayNowInstallment, 2, '.', '')); ?>">
                                                                    <input type="hidden" name="wallet_balance_snapshot" value="<?php echo e(number_format((float)$walletBal2, 2, '.', '')); ?>">
                                                                    <button type="submit" class="btn btn-success w-100"><input type="hidden" name="payment_session" value="<?php echo e($selectedSchoolSession); ?>"><input type="hidden" name="payment_semester" value="<?php echo e($selectedSchoolSemester); ?>">Pay ₦<?php echo number_format((float)$schoolPayNowInstallment); ?> with Wallet (<?php echo (int)$nextInstallmentSchool === 2 ? 'Second' : 'First'; ?> Installment)</button>
                                                                </form>
                                                            <?php else: ?>
                                                                <div class="alert alert-warning mb-0">Insufficient wallet balance. <a href="?page=wallet">Fund Wallet</a> to pay.</div>
                                                            <?php endif; ?>
                                                        </div>

                                                        <?php if (getSystemSettingValue($pdo,'manual_payment_enabled','enabled') === 'enabled'): ?>
                                                                                                                        <div id="manual_school_box" class="mt-3 p-3 border rounded">
                                                                <h6 class="mb-2"><i class="bi bi-bank"></i> Manual Bank Transfer (Upload Proof)</h6>
                                                                <div class="small text-muted mb-2"><?php echo nl2br(htmlspecialchars(getSystemSettingValue($pdo,'manual_payment_instructions',''))); ?></div>

                                                                <form method="post" enctype="multipart/form-data" id="manual_school_form">
                                                                    <input type="hidden" name="action" value="submit_manual_payment">
                                                                    <input type="hidden" name="payment_type" value="school">
                                                                    <input type="hidden" name="installment_number" id="manual_installment_number" value="<?php echo (int)($disableOneTimeSchool ? $nextInstallmentSchool : 1); ?>">
                                                                    <input type="hidden" name="total_installments" id="manual_total_installments" value="<?php echo (int)($disableOneTimeSchool ? 2 : 1); ?>"><input type="hidden" name="payment_session" value="<?php echo e($selectedSchoolSession); ?>"><input type="hidden" name="payment_semester" value="<?php echo e($selectedSchoolSemester); ?>">

                                                                    <div class="mb-2">
                                                                        <label class="form-label small">Choose Payment Option</label>
                                                                        <select class="form-select" id="manual_school_plan">
                                                                            <option value="one_time" <?php echo $disableOneTimeSchool ? 'disabled' : 'selected'; ?>>
                                                                                One-time Payment — ₦<?php echo number_format((float)$schoolPayNowOneTime); ?>
                                                                            </option>
                                                                            <option value="installment" <?php echo (!$allowInstallmentSchool ? 'disabled' : ($disableOneTimeSchool ? 'selected' : '')); ?>>
                                                                                2 Installments — next payable ₦<?php echo number_format((float)$schoolPayNowInstallment); ?>
                                                                            </option>
                                                                        </select>
                                                                        <div class="form-text">
                                                                            If you start installment payment, one-time payment will be disabled and you'll continue with the next installment.
                                                                        </div>
                                                                    </div>

                                                                    <div id="manual_school_installment_note" class="alert alert-info py-2 small" style="display:none;"></div>

                                                                    <div class="mb-2">
                                                                        <label class="form-label small">Amount Paid (₦)</label>
                                                                        <input type="number" name="amount_paid" class="form-control" min="0.01" step="0.01" required>
                                                                    </div>
                                                                    <div class="mb-2">
                                                                        <label class="form-label small">Payer Name (optional)</label>
                                                                        <input type="text" name="payer_name" class="form-control" placeholder="Account name used for transfer">
                                                                    </div>

                                                                    <div class="mb-2">
                                                                        <label class="form-label small">Upload Proof of Payment (JPG/PNG/PDF)</label>
                                                                        <input type="file" name="manual_proof" class="form-control" accept=".jpg,.jpeg,.png,.pdf" required>
                                                                    </div>

                                                                    <div class="mb-2">
                                                                        <label class="form-label small">Notes (optional)</label>
                                                                        <textarea name="manual_notes" class="form-control" rows="2" placeholder="Any notes"></textarea>
                                                                    </div>

                                                                    <button type="submit" class="btn btn-outline-primary w-100" id="manual_school_submit_btn">Submit Manual Payment</button>
                                                                </form>
                                                            </div>

                                                            <script>
                                                            (function(){
                                                                const plan = document.getElementById('manual_school_plan');
                                                                const note = document.getElementById('manual_school_installment_note');
                                                                const instNum = document.getElementById('manual_installment_number');
                                                                const totalInst = document.getElementById('manual_total_installments');
                                                                const btn = document.getElementById('manual_school_submit_btn');

                                                                if (!plan || !instNum || !totalInst) return;

                                                                const oneTimeAmount = <?php echo json_encode((float)$schoolPayNowOneTime); ?>;
                                                                const instAmount = <?php echo json_encode((float)$schoolPayNowInstallment); ?>;
                                                                const nextInst = <?php echo json_encode((int)$nextInstallmentSchool); ?>;
                                                                const disableOneTime = <?php echo $disableOneTimeSchool ? 'true' : 'false'; ?>;

                                                                function money(n){ try { return Number(n).toLocaleString(); } catch(e){ return n; } }

                                                                function update(){
                                                                    const v = plan.value;
                                                                    if (v === 'installment') {
                                                                        totalInst.value = '2';
                                                                        instNum.value = String(nextInst);

                                                                        if (note) {
                                                                            note.style.display = 'block';
                                                                            note.textContent = 'Submitting proof for ' + (Number(nextInst) === 2 ? 'Second' : 'First') + ' installment: ₦' + money(instAmount);
                                                                        }
                                                                        if (btn) {
                                                                            btn.textContent = 'Submit Proof for ₦' + money(instAmount) + ' (' + (Number(nextInst) === 2 ? 'Second' : 'First') + ' Installment)';
                                                                        }
                                                                    } else {
                                                                        totalInst.value = '1';
                                                                        instNum.value = '1';

                                                                        if (note) note.style.display = 'none';
                                                                        if (btn) btn.textContent = 'Submit Proof for ₦' + money(oneTimeAmount);
                                                                    }
                                                                }

                                                                if (disableOneTime) plan.value = 'installment';
                                                                update();
                                                                plan.addEventListener('change', update);
                                                            })();
                                                            </script>
<script>
(function(){
    const forms = document.querySelectorAll('form');
    forms.forEach(function(form){
        const type = form.querySelector('input[name="payment_type"]');
        if (!type || type.value !== 'school') return;
        const ensureField = function(name, value){
            let input = form.querySelector('input[name="' + name + '"]');
            if (!input) {
                input = document.createElement('input');
                input.type = 'hidden';
                input.name = name;
                form.appendChild(input);
            }
            input.value = value;
        };
        ensureField('payment_session', <?php echo json_encode($selectedSchoolSession); ?>);
        ensureField('payment_semester', <?php echo json_encode($selectedSchoolSemester); ?>);
    });
})();
</script>
<?php endif; ?>
                                                    <?php else: ?>
                                                        <table class="table">
                                                            <tr>
                                                                <td>School Fee:</td>
                                                                <td>₦<?php echo number_format($schoolFee); ?></td>
                                                            </tr>
                                                            <tr>
                                                                <td>Payment Method:</td>
                                                                <td>Paystack</td>
                                                            </tr>
                                                            <tr>
                                                                <td>Total Amount:</td>
                                                                <td><strong>₦<?php echo number_format($schoolFee); ?></strong></td>
                                                            </tr>
                                                        </table>

                                                        <form method="post" action="">
                                                            <input type="hidden" name="action" value="initiate_payment">
                                                            <input type="hidden" name="payment_type" value="school">
                                                            <input type="hidden" name="installment_number" value="1">
                                                            <input type="hidden" name="total_installments" value="1">
                                                            <button type="submit" class="btn btn-primary w-100">Pay Now</button>
                                                        </form>
                                                    <div class="mt-3 p-3 border rounded">
                                                        <h6 class="mb-2"><i class="bi bi-wallet2"></i> Pay with Wallet</h6>
                                                        <?php $walletBal = getWalletBalance($pdo, (int)$studentId); ?>
                                                        <div class="small text-muted mb-2">Wallet Balance: <strong>₦<?php echo number_format((float)$walletBal, 2); ?></strong></div>
                                                        <?php if ($walletBal >= (float)$schoolFee): ?>
                                                            <form method="post">
                                                                <input type="hidden" name="action" value="pay_with_wallet">
                                                                <input type="hidden" name="payment_type" value="school">
                                                                <input type="hidden" name="wallet_payment_confirmed" value="0">
                                                                <input type="hidden" name="wallet_fee_name" value="School Fee">
                                                                <input type="hidden" name="wallet_fee_amount" value="<?php echo e(number_format((float)$schoolFee, 2, '.', '')); ?>">
                                                                <input type="hidden" name="wallet_balance_snapshot" value="<?php echo e(number_format((float)$walletBal, 2, '.', '')); ?>">
                                                                <button type="submit" class="btn btn-success w-100">Pay ₦<?php echo number_format((float)$schoolFee); ?> with Wallet</button>
                                                            </form>
                                                        <?php else: ?>
                                                            <div class="alert alert-warning mb-0">Insufficient wallet balance. <a href="?page=wallet">Fund Wallet</a> to pay.</div>
                                                        <?php endif; ?>
                                                    </div>

                                                    <?php if (getSystemSettingValue($pdo,'manual_payment_enabled','enabled') === 'enabled'): ?>
                                                        <div class="mt-3 p-3 border rounded">
                                                            <h6 class="mb-2"><i class="bi bi-bank"></i> Manual Bank Transfer (Upload Proof)</h6>
                                                            <div class="small text-muted mb-2"><?php echo nl2br(htmlspecialchars(getSystemSettingValue($pdo,'manual_payment_instructions',''))); ?></div>
                                                            <form method="post" enctype="multipart/form-data">
                                                                <input type="hidden" name="action" value="submit_manual_payment">
                                                                <input type="hidden" name="payment_type" value="school">
                                                                <div class="mb-2">
                <label class="form-label">Amount Paid (₦)</label>
                <input type="number" name="amount_paid" class="form-control" min="0.01" step="0.01" required>
            </div>
            <div class="mb-2">
                                                                    <label class="form-label">Payer Name (optional)</label>
                                                                    <input type="text" name="payer_name" class="form-control" placeholder="Account name used for payment">
                                                                </div>
                                                                <div class="mb-2">
                                                                    <label class="form-label">Upload Teller / Receipt (JPG/PNG/PDF)</label>
                                                                    <input type="file" name="manual_proof" class="form-control" accept=".jpg,.jpeg,.png,.pdf" required>
                                                                </div>
                                                                <div class="mb-2">
                                                                    <label class="form-label">Notes (optional)</label>
                                                                    <textarea name="manual_notes" class="form-control" rows="2"></textarea>
                                                                </div>
                                                                <button class="btn btn-outline-primary w-100" type="submit">Submit Manual Payment</button>
                                                            </form>
                                                        </div>
                                                    <?php endif; ?>

                                                    <?php endif; ?>
                                                <?php endif; ?>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-md-6">
                                        <div class="card">
                                            <div class="card-body">
                                                <h5>Payment Instructions</h5>
                                                <ol>
                                                    <li>Choose your preferred payment plan</li>
                                                    <li>Click on the appropriate payment button</li>
                                                    <li>You will be redirected to a secure payment page</li>
                                                    <li>Enter your card details or select other payment options</li>
                                                    <li>Complete the payment process</li>
                                                    <li>You will receive a confirmation email after successful payment</li>
                                                </ol>

                                                <div class="alert alert-warning mt-3">
                                                    <i class="bi bi-exclamation-triangle"></i> If you choose the installment plan, ensure you pay the second installment before the deadline to avoid penalties.
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <script>
                if (typeof window.openApplicationPreview !== 'function') window.openApplicationPreview = function(){
                    const form = document.getElementById('applicationForm') || document.querySelector('form[action=""]');
                    const modalEl = document.getElementById('applicationPreviewModal');
                    const bodyEl = document.getElementById('applicationPreviewBody');
                    if(!form || !modalEl || !bodyEl) return;
                    const fd = new FormData(form);
                    const rows = [];
                    for (const [k,v] of fd.entries()){
                        if(k==='action' || k.startsWith('document_type') || k==='documents[]' || k==='documents') continue;
                        if(typeof v==='string' && v.length>0 && !k.endsWith('[]')){
                            rows.push([k, v]);
                        }
                    }
                    let html = '<div class="container-fluid">';
                    html += '<div class="row"><div class="col-md-12">';
                    html += '<h4 class="mb-2">Application Summary</h4>';
                    html += '<p class="text-muted">Preview your application. Use Print to save a hard copy.</p>';
                    html += '<div class="table-responsive"><table class="table table-sm table-bordered"><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody>';
                    rows.forEach(([k,v])=>{ html += `<tr><td>${k}</td><td>${String(v).replace(/</g,'&lt;')}</td></tr>`; });
                    html += '</tbody></table></div>';
                    html += '</div></div></div>';
                    bodyEl.innerHTML = html;
                    if (typeof bootstrap === 'undefined' || !bootstrap.Modal) {
                        // Fallback: open printable preview in a new window if Bootstrap JS is unavailable
                        printApplicationPreview();
                        return;
                    }
                    const modal = new bootstrap.Modal(modalEl);
                    modal.show();
                }
                if (typeof window.printApplicationPreview !== 'function') window.printApplicationPreview = function(){
                    const bodyEl = document.getElementById('applicationPreviewBody');
                    if(!bodyEl) return;
                    const w = window.open('', '_blank');
                    w.document.write('<html><head><title>Application Preview</title>');
                    w.document.write('<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">');
                    w.document.write('</head><body class="p-3">');
                    w.document.write(bodyEl.innerHTML);
                    w.document.write('</body></html>');
                    w.document.close();
                    w.focus();
                    w.print();
                }
                document.addEventListener('DOMContentLoaded', function() {
                    const oneTimeRadio = document.getElementById('one_time');
                    const installmentRadio = document.getElementById('installment');
                    const oneTimeForm = document.getElementById('one_time_form');
                    const installmentForm = document.getElementById('installment_form');
                    const installmentDetails = document.getElementById('installment_details');
                    const walletOneTime = document.getElementById('wallet_school_one_time');
                    const walletInstallment = document.getElementById('wallet_school_installment');
                    const manualOneTime = document.getElementById('manual_school_one_time');
                    const manualInstallment = document.getElementById('manual_school_installment');

                    function showPlan(plan){
                        if (!oneTimeForm || !installmentForm || !installmentDetails) return;
                        const isOneTime = (plan === 'one_time');
                        oneTimeForm.style.display = isOneTime ? 'block' : 'none';
                        installmentForm.style.display = isOneTime ? 'none' : 'block';
                        installmentDetails.style.display = isOneTime ? 'none' : 'block';
                        if (walletOneTime) walletOneTime.style.display = isOneTime ? 'block' : 'none';
                        if (walletInstallment) walletInstallment.style.display = isOneTime ? 'none' : 'block';
                        if (manualOneTime) manualOneTime.style.display = isOneTime ? 'block' : 'none';
                        if (manualInstallment) manualInstallment.style.display = isOneTime ? 'none' : 'block';
                    }

                    if(oneTimeRadio && installmentRadio && oneTimeForm && installmentForm && installmentDetails){
                        // Initialize correct view on load
                        if (installmentRadio.checked) {
                            showPlan('installment');
                        } else {
                            showPlan('one_time');
                        }

                        oneTimeRadio.addEventListener('change', function() {
                            if (this.checked) showPlan('one_time');
                        });

                        installmentRadio.addEventListener('change', function() {
                            if (this.checked) showPlan('installment');
                        });
                    }
                });
                </script>
                <?php
                break;

            case 'school_payment_receipt':
                // Check if user is logged in
                if (!isStudent() && !isAdmin()) { redirect('?page=login'); }

                // Get transaction details
                $transactionId = $_GET['transaction_id'] ?? $_SESSION['transaction_id'] ?? 0;
                if ($transactionId > 0) {
                    $stmt = $pdo->prepare("SELECT pt.*, s.first_name, s.surname, s.registration_no, s.application_no, s.admission_no, s.matric_no, s.session AS student_session, s.semester AS student_semester, COALESCE(NULLIF(TRIM(s.faculty),''),'Not Set') AS faculty_name, COALESCE(NULLIF(TRIM(s.department),''),'Not Set') AS department_name, COALESCE(NULLIF(TRIM(s.programme),''),'Not Set') AS programme_name, COALESCE(NULLIF(TRIM(s.level),''),'Not Set') AS level_name FROM payment_transactions pt
                        JOIN students s ON pt.student_id = s.id
                        WHERE pt.id = ?");
                    $stmt->execute([$transactionId]);
                    $transaction = $stmt->fetch(PDO::FETCH_ASSOC);

                    if ($transaction) {

                        $embed = (isset($_GET['embed']) && (string)$_GET['embed'] === '1');
                        if ($embed) {
                            header('Content-Type: text/html; charset=utf-8');
                            ?><!doctype html>
                            <html>
                            <head>
                                <meta charset="utf-8">
                                <meta name="viewport" content="width=device-width, initial-scale=1">
                                <title>School Fee Payment Receipt</title>
                                <style><?php echo portalReceiptEmbeddedStyleBlock(); ?></style>
                            </head>
                            <body>
                                <div class="wrap">
                                    <div class="receipt-container doc-stamp-wrap">
                                        <?php if (!empty($stampPath)): ?>
                                            <img src="<?php echo htmlspecialchars($stampPath); ?>" class="doc-stamp" alt="Official Stamp">
                                        <?php endif; ?>
                                        <div class="receipt-header">
                                            <div class="logo-area mb-3">
                                                <?php if ($leftLogoPath): ?>
                                                <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">HONTTECH Logo</div>
                                                <?php endif; ?>
                                                <?php if ($rightLogoPath): ?>
                                                <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">DAHIFT Logo</div>
                                                <?php endif; ?>
                                            </div>
                                            <h2>Honest Technologies University</h2>
                                            <h3>Ahmadu Bello University, Zaria</h3>
                                            <p>In affiliation with Dahift Global Ltd</p>
                                            <h4>School Fee Payment Receipt</h4>
                                        </div>

                                        <div class="receipt-body">
                                            <div class="receipt-meta">
                                                <div class="receipt-meta-col">
                                                    <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                                    <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                                    <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                                    <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                                </div>
                                                <div class="receipt-meta-col">
                                                    <p><strong>Student Name:</strong> <?php echo e(full_name($transaction)); ?></p>
                                                    <p><strong>Admission No.:</strong> <?php echo e(portalReceiptAdmissionNumber($transaction)); ?></p>
                                                    <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                                    <p><strong>Application No.:</strong> <?php echo e(portalReceiptApplicationNumber($transaction)); ?></p>
                                                    <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                    <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                    <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                                </div>
                                            </div>

                                            <div class="table-responsive mb-3">
                                                <table class="table">
                                                    <thead>
                                                        <tr>
                                                            <th>Description</th>
                                                            <th>Amount</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <tr>
                                                            <td><?php echo $transaction['transaction_type']; ?></td>
                                                            <td>₦<?php echo number_format($transaction['amount']); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <th>Total</th>
                                                            <th>₦<?php echo number_format($transaction['amount']); ?></th>
                                                        </tr>
                                                    </tbody>
                                                </table>
                                            </div>

                                            <div class="qr-code">
                                                <p><strong>QR Code:</strong></p>
                                                <div class="qr-pack">
                                                    <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                                <?php if (!empty($stampPath)): ?>
                                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>

                                        <div class="receipt-footer">
                                            <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                            <p>For inquiries, please contact the admissions office.</p>
                                            <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                                        </div>
                                    </div>
                                </div>
                            </body>
                            </html><?php
                            exit;
                        }
                        ?>
                        <div class="row">
                            <div class="col-md-12">
                                <div class="card">
                                    <div class="card-header d-flex justify-content-between align-items-center">
                                        <h4 class="mb-0">School Fee Payment Receipt</h4>
                                        <div class="no-print">
                                            <button class="btn btn-secondary" onclick="window.print()">
                                                <i class="bi bi-printer"></i> Print
                                            </button>
                                            <?php if (isAdmin()): ?>
                                            <a href="?page=admin_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php else: ?>
                                            <?php if (isAdmin()): ?>
                                            <a href="?page=admin_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php else: ?>
                                            <a href="?page=student_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php endif; ?>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                    <div class="card-body">
                                        <div class="receipt-container doc-stamp-wrap">
                                            <div class="receipt-header">
                                                <div class="logo-area mb-3">
                                                    <?php if ($leftLogoPath): ?>
                                                    <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                                    <?php else: ?>
                                                    <div class="logo-placeholder">HONTTECH Logo</div>
                                                    <?php endif; ?>
                                                    <?php if ($rightLogoPath): ?>
                                                    <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                                    <?php else: ?>
                                                    <div class="logo-placeholder">DAHIFT Logo</div>
                                                    <?php endif; ?>
                                                </div>
                                                <h2>Honest Technologies University</h2>
                                                <h3>Ahmadu Bello University, Zaria</h3>
                                                <p>In affiliation with Dahift Global Ltd</p>
                                                <h4>School Fee Payment Receipt</h4>
                                            </div>

                                            <div class="receipt-body">
                                                <div class="receipt-meta">
                                                    <div class="receipt-meta-col">
                                                        <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                                        <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                                        <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                                        <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                                        <?php if ($transaction['total_installments'] > 1): ?>
                                                            <p><strong>Installment:</strong> <?php echo $transaction['installment_number']; ?> of <?php echo $transaction['total_installments']; ?></p>
                                                        <?php endif; ?>
                                                    </div>
                                                    <div class="receipt-meta-col">
                                                        <p><strong>Student Name:</strong> <?php echo $transaction['first_name'] . ' ' . $transaction['surname']; ?></p>
                                                        <p><strong>Admission No.:</strong> <?php echo e(portalReceiptAdmissionNumber($transaction)); ?></p>
                                                        <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                                        <p><strong>Application No.:</strong> <?php echo e(portalReceiptApplicationNumber($transaction)); ?></p>
                                                        <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                    <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                    <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                                    </div>
                                                </div>

                                                <div class="table-responsive mb-3">
                                                    <table class="table table-bordered">
                                                        <thead>
                                                            <tr>
                                                                <th>Description</th>
                                                                <th>Amount</th>
                                                            </tr>
                                                        </thead>
                                                        <tbody>
                                                            <?php $receiptBaseAmount = (float)($transaction['school_fee_base_amount'] ?? 0); $receiptLateFeeAmount = (float)($transaction['school_fee_late_fee_amount'] ?? 0); ?>
                                                            <?php if ($receiptBaseAmount > 0): ?>
                                                            <tr>
                                                                <td>School Fee Amount<?php if ($transaction['total_installments'] > 1): ?> - Installment <?php echo $transaction['installment_number']; ?> of <?php echo $transaction['total_installments']; ?><?php endif; ?></td>
                                                                <td>₦<?php echo number_format($receiptBaseAmount, 2); ?></td>
                                                            </tr>
                                                            <?php endif; ?>
                                                            <?php if ($receiptLateFeeAmount > 0): ?>
                                                            <tr>
                                                                <td>Late Fee Amount</td>
                                                                <td>₦<?php echo number_format($receiptLateFeeAmount, 2); ?></td>
                                                            </tr>
                                                            <?php endif; ?>
                                                            <tr>
                                                                <th>Total Paid</th>
                                                                <th>₦<?php echo number_format($transaction['amount'], 2); ?></th>
                                                            </tr>
                                                        </tbody>
                                                    </table>
                                                </div>

                                                <div class="qr-code">
                                                    <p><strong>QR Code:</strong></p>
                                                    <div class="qr-pack">
                                                        <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                                    <?php if (!empty($stampPath)): ?>
                                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                                    <?php endif; ?>
                                                </div>
                                                </div>
                                            </div>

                                            <div class="receipt-footer">
                                                <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                                <?php if ($transaction['total_installments'] > 1 && $transaction['installment_number'] < $transaction['total_installments']): ?>
                                                    <p>This is installment <?php echo $transaction['installment_number']; ?> of <?php echo $transaction['total_installments']; ?>. Please ensure you pay the remaining installments on time.</p>
                                                <?php elseif ($transaction['total_installments'] > 1 && $transaction['installment_number'] == $transaction['total_installments']): ?>
                                                    <p>All installments have been paid. Your school fee is now fully paid.</p>
                                                <?php else: ?>
                                                    <p>Your school fee has been fully paid.</p>
                                                <?php endif; ?>
                                                <p>For inquiries, please contact the admissions office.</p>
                                                <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <?php
                    } else {
                        echo '<div class="alert alert-danger">Transaction not found.</div>';
                    }
                } else {
                    echo '<div class="alert alert-danger">Invalid transaction.</div>';
                }
                break;

            case 'admission_letter':
                if (!portalManagedContentEnabled($pdo, 'admission_letter')) { flash('danger', portalManagedContentDisabledMessage($pdo, 'admission_letter')); redirect('?page=student_dashboard'); }
                // Check if user is logged in
                if (!isStudent()) {
                    redirect('?page=login');
                }

                // Get student information
                $studentId = $_SESSION['user_id'];
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC);

                // Check if student can access admission letter
                if (($student['application_status'] ?? '') !== 'Approved') {
                    echo '<div class="alert alert-warning">Your application has not been approved yet.</div>';
                    break;
                }
                if (($student['offer_status'] ?? 'Pending') !== 'Accepted') {
                    echo '<div class="alert alert-warning">You must <strong>ACCEPT</strong> your admission offer in your portal before you can access the admission letter.</div>';
                    break;
                }
                if (($student['acceptance_fee_status'] ?? 'Pending') !== 'Paid') {
                    echo '<div class="alert alert-warning">You cannot access the admission letter until your application is approved and you have paid the acceptance fee.</div>';
                    break;
                }

                // Get fee information for the admission letter
                $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'acceptance_fee'");
                $stmt->execute();
                $acceptanceFee = $stmt->fetchColumn();

$hostelFee = portalStudentManagedFeeAmount($pdo, 'hostel', (array)$student, (string)($student['session'] ?? ''), (string)($student['semester'] ?? ''));

                $schoolFee = portalStudentSchoolFeeAmount($pdo, (array)$student, (string)($student['session'] ?? ''), (string)($student['semester'] ?? ''));

                // Check if student has paid hostel fee
                $hostelFeePaid = $student['hostel_fee_status'] === 'Paid';

                // Check if student has paid school fee
                $schoolFeePaid = $student['school_fee_status'] === 'Paid';

                // Calculate total fees
                $totalFees = $acceptanceFee;
                if ($hostelFeePaid) {
                    $totalFees += $hostelFee;
                }
                if ($schoolFeePaid) {
                    $totalFees += $schoolFee;
                }
                $admissionDoc = portalDocumentContent($pdo, 'admission_letter');
                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header d-flex justify-content-between align-items-center">
                                <h4 class="mb-0"><?php echo e((string)($admissionDoc['heading'] ?? 'Admission Letter')); ?></h4>
                                <button class="btn btn-secondary no-print" onclick="window.print()">
                                    <i class="bi bi-printer"></i> Print
                                </button>
                            </div>
                            <div class="card-body doc-stamp-wrap">
                                <?php if (!empty($stampPath)): ?>
                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="doc-stamp" alt="Official Stamp">
                                <?php endif; ?>


                                <div class="admission-letter-header">
                                    <div class="admission-letter-logo">
                                        <?php if ($leftLogoPath): ?>
                                        <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                        <?php else: ?>
                                        <div class="logo-placeholder">HONTTECH Logo</div>
                                        <?php endif; ?>
                                    </div>
                                    <div class="text-center">
                                        <h3>AHMADU BELLO UNIVERSITY, ZARIA</h3>
                                        <p>In affiliation with Dahift Global Ltd</p>
                                        <h4><?php echo e((string)($admissionDoc['subheading'] ?? 'Honest Technologies University EXAMINATION')); ?></h4>
                                        <p><?php echo e((string)($admissionDoc['heading'] ?? 'ADMISSION LETTER')); ?></p>
                                    </div>
                                    <div class="admission-letter-logo">
                                        <?php if ($rightLogoPath): ?>
                                        <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                        <?php else: ?>
                                        <div class="logo-placeholder">DAHIFT Logo</div>
                                        <?php endif; ?>
                                    </div>
                                </div>

                                <div class="row mt-4">
                                    <div class="col-md-8">
                                        <table class="table table-bordered">
                                            <tr>
                                                <th width="30%">Name</th>
                                                <td><?php echo htmlspecialchars(studentFullName($student)); ?></td>
                                            </tr>
                                            <tr>
                                                <th width="30%">Course</th>
                                                <td>HontTech Programme</td>
                                            </tr>
                                            <tr>
                                                <th width="30%">Admission Number</th>
                                                <td><?php echo e(portalStudentNumberDisplay($student, 'admission')); ?></td>
                                            </tr>
                                            <tr>
                                                <th width="30%">Matric Number</th>
                                                <td><?php echo htmlspecialchars(portalStudentNumberDisplay($student, 'matric'), ENT_QUOTES, 'UTF-8'); ?></td>
                                            </tr>
                                            <tr>
                                                <th width="30%">Academic Session</th>
                                                <td>
                                                    <?php
                                                    $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'academic_session'");
                                                    $stmt->execute();
                                                    echo $stmt->fetchColumn();
                                                    ?>
                                                </td>
                                            </tr>
                                            <tr>
                                                <th width="30%">Admission Status</th>
                                                <td><span class="badge bg-success">ADMITTED</span></td>
                                            </tr>
                                        </table>
                                    </div>
                                    <div class="col-md-4">
                                        <?php if ($student['passport_photo']): ?>
                                            <img src="<?php echo $student['passport_photo']; ?>" alt="Passport Photo" class="passport-photo">
                                        <?php else: ?>
                                            <div class="passport-photo d-flex align-items-center justify-content-center bg-light">
                                                <span>No Passport Photo</span>
                                            </div>
                                        <?php endif; ?>
                                    </div>
                                </div>

                                <?php if (!empty($admissionDoc['body_text'])): ?><p class="mb-3"><?php echo e((string)$admissionDoc['body_text']); ?></p><?php endif; ?>
                                <div class="fee-breakdown mt-4">
                                    <h5>Fee Breakdown</h5>
                                    <div class="fee-item">
                                        <span>Acceptance Fee</span>
                                        <span>₦<?php echo number_format($acceptanceFee); ?></span>
                                    </div>
                                    <?php if ($hostelFeePaid): ?>
                                    <div class="fee-item">
                                        <span>Hostel Fee</span>
                                        <span>₦<?php echo number_format($hostelFee); ?></span>
                                    </div>
                                    <?php endif; ?>
                                    <?php if ($schoolFeePaid): ?>
                                    <div class="fee-item">
                                        <span>School Fee</span>
                                        <span>₦<?php echo number_format($schoolFee); ?></span>
                                    </div>
                                    <?php endif; ?>
                                    <div class="fee-item">
                                        <span>Total Amount Paid</span>
                                        <span>₦<?php echo number_format($totalFees); ?></span>
                                    </div>
                                </div>

                                <div class="mt-5">
                                    <h5>This is to certify that</h5>
                                    <p><strong><?php echo htmlspecialchars(studentFullName($student)); ?></strong> has been offered provisional admission into the HontTech programme for the <?php
                                        $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'academic_session'");
                                        $stmt->execute();
                                        echo $stmt->fetchColumn();
                                    ?> academic session.</p>

                                    <p><?php echo e((string)($admissionDoc['footer_text'] ?? 'This admission is subject to the candidate fulfilling all admission requirements including payment of the required fees.')); ?></p>

                                    <div class="mt-4">
                                        <p><strong>Date:</strong> <?php echo date('d F Y'); ?></p>
<?php
                                            $admission_qr_data = json_encode([
                                                'doc' => 'admission_letter',
                                                'reg_no' => $student['registration_no'] ?? '',
                                                'student_id' => $student['id'] ?? '',
                                                'ts' => date('c')
                                            ]);
                                            $admission_qr = generateQRCode($admission_qr_data);
                                        ?>
                                        <div id="admission_qr_box" style="float:right; text-align:center; margin-top:-10px;">
                                            <img src="<?php echo htmlspecialchars($admission_qr); ?>" alt="QR Code" style="width:110px;height:110px;">
                                            <?php /* Stamp is applied once as a watermark on the document (avoid duplicates) */ ?>

                                            <div style="font-size:10px; margin-top:4px;">Scan to verify</div>
                                        </div>
                                        <p><strong><?php echo e((string)($admissionDoc['signature_title'] ?? 'Signature')); ?>:</strong></p>
                                        <div class="mt-2">
                                            <?php
                                            $decision = getOfficerDecision($pdo, (int)$studentId);
                                            $offSig = $decision['officer_signature'] ?? '';
                                            $offName = $decision['officer_name'] ?? '';
            // Override with admin-configured signature/name if set
            $cfgSig = null;
            try {
                $stmtS = $pdo->prepare("SELECT file_path FROM system_assets WHERE asset_type='idcard_signature' ORDER BY id DESC LIMIT 1");
                $stmtS->execute();
                $cfgSig = $stmtS->fetchColumn();
            } catch (Throwable $e) {}
            $cfgName = trim(getSystemSettingValue($pdo,'idcard_signatory_name',''));
            if (!empty($cfgSig)) { $offSig = $cfgSig; }
            if (!empty($cfgName)) { $offName = $cfgName; }
            if (!empty($studentIdDoc['signature_name'])) { $offName = (string)$studentIdDoc['signature_name']; }
if (!empty($admissionDoc['signature_name'])) { $offName = (string)$admissionDoc['signature_name']; }

// Override with admin-configured signature/name if set
$cfgSig = null;
try {
    $stmtS = $pdo->prepare("SELECT file_path FROM system_assets WHERE asset_type='admission_signature' ORDER BY id DESC LIMIT 1");
    $stmtS->execute();
    $cfgSig = $stmtS->fetchColumn();
} catch (Throwable $e) {}
$cfgName = trim(getSystemSettingValue($pdo,'admission_signatory_name',''));
if (!empty($cfgSig)) { $offSig = $cfgSig; }
if (!empty($cfgName)) { $offName = $cfgName; }

                                            ?>
                                            <?php if (!empty($offSig)): ?>
                                                <img src="<?php echo htmlspecialchars($offSig); ?>" alt="Officer Signature" style="max-height: 80px; max-width: 240px; object-fit: contain;">
                                            <?php else: ?>
                                                <div style="height:45px; border-bottom:1px solid rgba(0,0,0,0.6); width:240px;"></div>
                                            <?php endif; ?>
                                            <?php if (!empty($offName)): ?>
                                                <div class="small mt-1"><strong><?php echo htmlspecialchars($offName); ?></strong></div>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

            case 'student_id_card':
                if (!portalManagedContentEnabled($pdo, 'student_id_card')) { flash('danger', portalManagedContentDisabledMessage($pdo, 'student_id_card')); redirect('?page=student_dashboard'); }
                // Check if user is logged in
                if (!isStudent()) {
                    redirect('?page=login');
                }

                // Get student information
                $studentId = $_SESSION['user_id'];
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC);

                // Check if ID card is generated
                if (!$student['id_card_generated']) {
                    echo '<div class="alert alert-warning">Your ID card has not been generated yet. This happens after your application is approved.</div>';
                    break;
                }

                // Get academic session
                $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'academic_session'");
                $stmt->execute();
                $academicSession = $stmt->fetchColumn();
                $studentIdDoc = portalDocumentContent($pdo, 'student_id_card');
                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header d-flex justify-content-between align-items-center">
                                <h4 class="mb-0"><?php echo e((string)($studentIdDoc['heading'] ?? 'Student ID Card')); ?></h4>
                                <div class="no-print">
                                    <button class="btn btn-secondary" onclick="window.print()">
                                        <i class="bi bi-printer"></i> Print ID Card
                                    </button>
                                    <?php if (isAdmin()): ?>
                                            <a href="?page=admin_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php else: ?>
                                            <a href="?page=student_dashboard" class="btn btn-primary">
                                                <i class="bi bi-house"></i> Dashboard
                                            </a>
                                            <?php endif; ?>
                                </div>
                            </div>
                            <div class="card-body">
                                <div class="idcard-wrap">
        <?php
            $decision = getOfficerDecision($pdo, (int)$studentId);
            $offSig = $decision['officer_signature'] ?? '';
            $offName = $decision['officer_name'] ?? '';

            // Student signature from application form (students.signature_data)
            $studentSig = $student['signature_data'] ?? '';

            // Override Authorized Signatory (ID Card) with admin-configured signature/name if set
            try {
                $stmtS = $pdo->prepare("SELECT file_path FROM system_assets WHERE asset_type='idcard_signature' ORDER BY id DESC LIMIT 1");
                $stmtS->execute();
                $cfgSig = $stmtS->fetchColumn();
                if (!empty($cfgSig)) { $offSig = $cfgSig; }
            } catch (Throwable $e) {}
            $cfgName = trim((string)getSystemSettingValue($pdo,'idcard_signatory_name',''));
            if (!empty($cfgName)) { $offName = $cfgName; }

            $portalTitle = (string)(getSystemSettingValue($pdo, 'portal_title', 'HontTech') ?? 'HontTech');
            $leftLogo = getSystemAsset('left_logo') ?: '';
            $rightLogo = getSystemAsset('right_logo') ?: '';
            $fullName = studentFullName($student);

            $joinRaw = (string)($student['id_card_join_date'] ?? $student['created_at'] ?? date('Y-m-d'));
            $joinTs = strtotime($joinRaw) ?: time();
            $expTs = strtotime('+1 year', $joinTs);
            $joinDate = date('d/m/Y', $joinTs);
            $expDate = date('d/m/Y', $expTs);

            $phone = (string)($student['mobile_phone'] ?? $student['phone'] ?? $student['nok_phone'] ?? '');
            $email = (string)($student['email'] ?? '');
            $dob = (string)($student['dob'] ?? $student['date_of_birth'] ?? '');
        ?>

        <!-- FRONT -->
        <div class="idcard">
            <div class="waveTop">
                <div class="logos">
                    <?php if (!empty($leftLogo)): ?>
                        <img src="<?php echo htmlspecialchars($leftLogo); ?>" alt="Logo">
                    <?php else: ?>
                        <div></div>
                    <?php endif; ?>

                    <?php if (!empty($rightLogo)): ?>
                        <img src="<?php echo htmlspecialchars($rightLogo); ?>" alt="Logo">
                    <?php else: ?>
                        <div></div>
                    <?php endif; ?>
                </div>
                <div class="title"><?php echo htmlspecialchars((string)($studentIdDoc['subheading'] ?? $portalTitle)); ?></div>

                <div class="photo">
                    <?php if (!empty($student['passport_photo'])): ?>
                        <img src="<?php echo htmlspecialchars($student['passport_photo']); ?>" alt="Photo">
                    <?php else: ?>
                        <div class="d-flex align-items-center justify-content-center w-100 h-100 text-muted" style="font-size:10px;">No Photo</div>
                    <?php endif; ?>
                </div>
            </div>

            <div class="body">
                <p class="name"><?php echo htmlspecialchars($fullName); ?></p>
                <p class="role"><?php echo e((string)($studentIdDoc['body_text'] ?? 'Student')); ?></p>

                <div class="meta">
                    <div class="k">ID No</div><div class="v"><?php echo htmlspecialchars((string)$student['id']); ?></div>
                    <div class="k">Matric No.</div><div class="v"><?php echo htmlspecialchars((string)($student['matric_no'] ?? '')); ?></div>
                    <div class="k">Matric No</div><div class="v"><?php echo htmlspecialchars(portalStudentNumberDisplay($student, 'matric'), ENT_QUOTES, 'UTF-8'); ?></div>

                    <div class="k">DOB</div><div class="v"><?php echo htmlspecialchars($dob); ?></div>
                    <div class="k">Phone</div><div class="v"><?php echo htmlspecialchars($phone); ?></div>

                    <div class="k">Email</div><div class="v"><?php echo htmlspecialchars($email); ?></div>
                    <div class="k">Join</div><div class="v"><?php echo htmlspecialchars($joinDate); ?></div>

                    <div class="k">Expire</div><div class="v"><?php echo htmlspecialchars($expDate); ?></div>
                </div>
                <div class="studentSig">
                    <div class="label">Student Signature</div>
                    <?php if (!empty($studentSig)): ?>
                        <img src="<?php echo htmlspecialchars($studentSig); ?>" alt="Student Signature">
                    <?php else: ?>
                        <div class="line"></div>
                    <?php endif; ?>
                </div>

            </div>

            <div class="waveBottom"></div>
        </div>

        <!-- BACK -->
        <div class="idcard idcard-back">
            <div class="waveTop"></div>

            <div class="body">
                <p class="tcTitle"><?php echo e((string)($studentIdDoc['heading'] ?? 'TERMS AND CONDITIONS')); ?></p>
                <div class="tcLine"><?php echo e((string)($studentIdDoc['notice_text'] ?? ('This card is the property of ' . $portalTitle . '.'))); ?></div>
                <div class="tcLine"><?php echo e((string)($studentIdDoc['footer_text'] ?? 'Please adhere to all school rules and regulations.')); ?></div>

                <div class="dates">
                    <div class="k">Joined</div><div class="v"><?php echo htmlspecialchars($joinDate); ?></div>
                    <div class="k">Expire</div><div class="v"><?php echo htmlspecialchars($expDate); ?></div>
                </div>

                <div class="sigBlock">
                    <?php if (!empty($offSig)): ?>
                        <img src="<?php echo htmlspecialchars($offSig); ?>" alt="Officer Signature">
                    <?php else: ?>
                        <div class="sigLine"></div>
                    <?php endif; ?>

                    <?php if (!empty($offName)): ?>
                        <div class="sigName"><?php echo htmlspecialchars($offName); ?></div>
                        <div class="sigRole"><?php echo e((string)($studentIdDoc['signature_title'] ?? 'Authorized Signatory')); ?></div>
                    <?php else: ?>
                        <div class="sigRole mt-2"><?php echo e((string)($studentIdDoc['signature_title'] ?? 'Authorized Signatory')); ?></div>
                    <?php endif; ?>
                </div>
            </div>

            <div class="qr">
                <img src="https://api.qrserver.com/v1/create-qr-code/?size=140x140&data=<?php echo urlencode((string)(($student['matric_no'] ?? '') !== '' ? $student['matric_no'] : ($student['registration_no'] ?? $student['id'] ?? ''))); ?>" alt="QR Code">
            </div>

            <div class="waveBottom"></div>
            <div class="brandFooter"><?php echo htmlspecialchars($portalTitle); ?></div>
        </div>
    </div>

<div class="id-card-actions no-print mt-3">
                                    <p class="text-muted small">This ID card was generated on <?php echo date('d F Y', strtotime($student['id_card_generated_date'])); ?></p>
                                    <p class="text-muted small">For verification purposes, please present this ID card at all times while on campus.</p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

            case 'student_dashboard':
                if (isStudent()) {
                    // Get student information
                    $studentId = $_SESSION['user_id'];
                    $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                    $stmt->execute([$studentId]);
                    $student = $stmt->fetch(PDO::FETCH_ASSOC);

                    // Get education history
                    $stmt = $pdo->prepare("SELECT * FROM education_history WHERE student_id = ?");
                    $stmt->execute([$studentId]);
                    $educationHistory = $stmt->fetchAll(PDO::FETCH_ASSOC);

                    // Get examination results
                    $stmt = $pdo->prepare("SELECT * FROM examination_results WHERE student_id = ?");
                    $stmt->execute([$studentId]);
                    $examResults = $stmt->fetchAll(PDO::FETCH_ASSOC);

                    // Get referee information
                    $stmt = $pdo->prepare("SELECT * FROM referees WHERE student_id = ?");
                    $stmt->execute([$studentId]);
                    $referee = $stmt->fetch(PDO::FETCH_ASSOC);

                    // Get payment history
                    $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE student_id = ? ORDER BY created_at DESC LIMIT 3");
                    $stmt->execute([$studentId]);
                    $recentPayments = $stmt->fetchAll(PDO::FETCH_ASSOC);

                    $dashboardSummary = portalDashboardSummary($pdo, $student);
                    $studentAdmissionNo = portalStudentAdmissionNumberValue($student);
                    $studentMatricNo = portalStudentMatricNumberValue($student);

                    $showLms = studentMenuEnabled($pdo, 'menu_student_lms');
                    $showLib = studentMenuEnabled($pdo, 'menu_student_library');
                    $showCbt = studentMenuEnabled($pdo, 'menu_student_cbt');
                    $lmsCount = 0;
                    $libCount = 0;
                    $cbtCount = 0;
                    $dashFacultyId = (int)($student['faculty_id'] ?? 0);
                    $dashDepartmentId = (int)($student['department_id'] ?? 0);
                    $dashProgrammeId = (int)($student['programme_id'] ?? 0);
                    $dashLevel = (string)($student['level'] ?? '');
                    $dashSessionName = (string)($dashboardSummary['session_name'] ?? setting('academic_session', ''));

                    if ($showLms) {
                        try {
                            $st = $pdo->prepare("SELECT COUNT(*) FROM lms_materials
                                WHERE (faculty_id IS NULL OR faculty_id=0 OR faculty_id=?)
                                  AND (department_id IS NULL OR department_id=0 OR department_id=?)
                                  AND (programme_id IS NULL OR programme_id=0 OR programme_id=?)
                                  AND (level IS NULL OR level='' OR level=?)
                                  AND (session_name IS NULL OR session_name='' OR session_name=?)");
                            $st->execute([$dashFacultyId, $dashDepartmentId, $dashProgrammeId, $dashLevel, $dashSessionName]);
                            $lmsCount = (int)($st->fetchColumn() ?: 0);
                        } catch (Throwable $e) {
                            $lmsCount = 0;
                        }
                    }

                    if ($showLib) {
                        try {
                            $st = $pdo->prepare("SELECT COUNT(*) FROM library_loans WHERE student_id=? AND status='borrowed'");
                            $st->execute([(int)$studentId]);
                            $libCount = (int)($st->fetchColumn() ?: 0);
                        } catch (Throwable $e) {
                            $libCount = 0;
                        }
                    }

                    if ($showCbt) {
                        try {
                            $publishedClause = '';
                            try {
                                if (function_exists('portalColumnExists') && portalColumnExists($pdo, 'cbt_tests', 'published')) {
                                    $publishedClause = " AND (published=1)";
                                }
                            } catch (Throwable $e) {
                                $publishedClause = '';
                            }
                            $sql = "SELECT COUNT(*) FROM cbt_tests
                                WHERE status='active'" . $publishedClause . "
                                  AND (faculty_id IS NULL OR faculty_id=0 OR faculty_id=?)
                                  AND (department_id IS NULL OR department_id=0 OR department_id=?)
                                  AND (programme_id IS NULL OR programme_id=0 OR programme_id=?)
                                  AND (level IS NULL OR level='' OR level=?)
                                  AND (session_name IS NULL OR session_name='' OR session_name=?)";
                            $st = $pdo->prepare($sql);
                            $st->execute([$dashFacultyId, $dashDepartmentId, $dashProgrammeId, $dashLevel, $dashSessionName]);
                            $cbtCount = (int)($st->fetchColumn() ?: 0);
                        } catch (Throwable $e) {
                            $cbtCount = 0;
                        }
                    }

                    $studentDashboardTheme = (isset($portalStudentDashboardRule) && is_array($portalStudentDashboardRule) && $portalStudentDashboardRule)
                        ? $portalStudentDashboardRule
                        : (function_exists('portalResolveStudentDashboardDesignRule') ? portalResolveStudentDashboardDesignRule($pdo, $student) : []);
                    $studentDashboardLayout = (string)($studentDashboardTheme['layout_type'] ?? 'standard');
                    $studentDashboardNotice = trim((string)($studentDashboardTheme['notice_banner_text'] ?? ''));
                    $studentDashboardScope = function_exists('portalStudentDashboardScope') ? portalStudentDashboardScope($pdo, $student) : [];
                    $studentDashboardProgrammeName = trim((string)($studentDashboardScope['programme_name'] ?? ($student['program'] ?? $student['programme_name_cache'] ?? $student['programme'] ?? 'Programme')));
                    $studentDashboardMode = trim((string)($studentDashboardScope['mode_of_study'] ?? ($student['mode_of_study'] ?? '')));
                    $studentDashboardLevel = trim((string)($studentDashboardScope['level'] ?? ($student['level'] ?? '')));
                    $studentDashboardShowHostel = (int)($studentDashboardTheme['show_hostel'] ?? 1) === 1;
                    $studentDashboardShowClearance = (int)($studentDashboardTheme['show_clearance'] ?? 1) === 1;
                    $studentDashboardShowCbt = (int)($studentDashboardTheme['show_cbt'] ?? 1) === 1;
                    $studentDashboardShowLms = (int)($studentDashboardTheme['show_lms'] ?? 1) === 1;
                    $studentDashboardShowLibrary = (int)($studentDashboardTheme['show_library'] ?? 1) === 1;
                    ?>
                    <div class="student-dashboard-page-wrap">
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header d-flex justify-content-between align-items-center">
                                    <h4 class="mb-0">Student Dashboard</h4>
                                    <span class="badge bg-<?php echo $student['application_status'] === 'Approved' ? 'success' : ($student['application_status'] === 'Rejected' ? 'danger' : 'warning'); ?>">
                                        <?php echo $student['application_status']; ?>
                                    </span>
                                </div>
                                <div class="card-body">
                                    <?php /* Student dashboard scope banner intentionally hidden globally per current UI requirement. */ ?>
                                    <div class="student-dashboard-hero">
    <div class="student-dashboard-welcome">
        <h4 class="mb-1">Welcome, <?php echo e(studentFullName($student)); ?>!</h4>
        <div class="text-muted">Here's an overview of your HontTech registration application.</div>
    </div>

    <?php
        $studentHeroMatric = portalStudentNumberDisplay($student, 'matric');
        $studentHeroDepartment = trim((string)($dashboardSummary['department_name'] ?? ''));
        if ($studentHeroDepartment === '') $studentHeroDepartment = 'Not Set';
        $studentHeroLevel = trim((string)($dashboardSummary['level'] ?? ''));
        if ($studentHeroLevel === '') $studentHeroLevel = trim((string)($student['level'] ?? 'Not Set'));
        if ($studentHeroLevel === '') $studentHeroLevel = 'Not Set';
        $pp = !empty($student['profile_image']) ? $student['profile_image'] : (!empty($student['passport_photo']) ? $student['passport_photo'] : '');
    ?>
    <div class="student-dashboard-minihero">
        <div class="student-dashboard-minihero-info">
            <div class="student-dashboard-minihero-grid">
                <div class="student-dashboard-minihero-card student-dashboard-minihero-card--matric">
                    <span class="student-dashboard-minihero-label">Matric Number</span>
                    <span class="student-dashboard-minihero-value"><?php echo e($studentHeroMatric); ?></span>
                </div>
                <div class="student-dashboard-minihero-card">
                    <span class="student-dashboard-minihero-label">Department</span>
                    <span class="student-dashboard-minihero-value"><?php echo e($studentHeroDepartment); ?></span>
                </div>
                <div class="student-dashboard-minihero-card student-dashboard-minihero-card--level">
                    <span class="student-dashboard-minihero-label">Level</span>
                    <span class="student-dashboard-minihero-value"><?php echo e($studentHeroLevel); ?></span>
                </div>
            </div>
        </div>
        <div class="student-dashboard-minihero-photo">
            <?php if (!empty($pp)): ?>
                <img src="<?php echo htmlspecialchars($pp); ?>" alt="Student Profile Photo">
            <?php else: ?>
                <div class="student-dashboard-minihero-avatar"><i class="bi bi-person"></i></div>
            <?php endif; ?>
        </div>
    </div>
</div>

<?php $studentDashboardActionNotice = function_exists('portalStudentDashboardActionNotice') ? portalStudentDashboardActionNotice($pdo, $student) : ['show' => false, 'items' => []]; ?>
<?php if (!empty($studentDashboardActionNotice['show'])): ?>
    <div class="alert alert-warning d-flex flex-wrap justify-content-between align-items-center gap-3 mt-3">
        <div>
            <div class="fw-semibold mb-1"><i class="bi bi-exclamation-triangle"></i> Action Required</div>
            <ul class="mb-0 ps-3">
                <?php foreach ((array)($studentDashboardActionNotice['items'] ?? []) as $noticeItem): ?>
                    <li><?php echo e((string)$noticeItem); ?></li>
                <?php endforeach; ?>
            </ul>
        </div>
        <div class="d-flex flex-wrap gap-2">
            <a href="?page=school_payment" class="btn btn-warning"><i class="bi bi-book"></i> Pay School Fee</a>
            <a href="?page=hostel_payment" class="btn btn-outline-warning"><i class="bi bi-house"></i> Pay Hostel Fee</a>
            <a href="?page=hostel_payment" class="btn btn-outline-primary"><i class="bi bi-house-gear"></i> Update Accommodation Status</a>
            <a href="?page=payment_history" class="btn btn-outline-secondary"><i class="bi bi-clock-history"></i> View Payment History</a>
        </div>
    </div>
    <div class="modal fade" id="studentActionRequiredModal" tabindex="-1" aria-hidden="true">
      <div class="modal-dialog modal-dialog-centered">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title">Payment / Accommodation Action Required</h5>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
          </div>
          <div class="modal-body">
            <p class="mb-2">Please complete the following action(s):</p>
            <ul class="mb-0">
              <?php foreach ((array)($studentDashboardActionNotice['items'] ?? []) as $noticeItem): ?>
                  <li><?php echo e((string)$noticeItem); ?></li>
              <?php endforeach; ?>
            </ul>
          </div>
          <div class="modal-footer d-flex justify-content-between flex-wrap gap-2">
            <div class="d-flex gap-2 flex-wrap">
              <a href="?page=school_payment" class="btn btn-warning">Pay School Fee</a>
              <a href="?page=hostel_payment" class="btn btn-outline-warning">Pay Hostel Fee</a>
            </div>
            <div class="d-flex gap-2 flex-wrap">
              <a href="?page=hostel_payment" class="btn btn-outline-primary">Update Accommodation</a>
              <a href="?page=payment_history" class="btn btn-outline-secondary">Payment History</a>
            </div>
          </div>
        </div>
      </div>
    </div>
    <script>
    document.addEventListener('DOMContentLoaded', function(){
        var modalEl = document.getElementById('studentActionRequiredModal');
        if (!modalEl || typeof bootstrap === 'undefined') return;
        try { (new bootstrap.Modal(modalEl)).show(); } catch (e) {}
    });
    </script>
<?php endif; ?>

<?php
        $showDashboardOfferInfo = false;
        if ($showDashboardOfferInfo && (($student['offer_status'] ?? '') === 'Accepted') && !empty($student['offer_decision_date'])) {
            try {
                $dashboardOfferDecisionTs = strtotime((string)$student['offer_decision_date']);
                if ($dashboardOfferDecisionTs !== false && ((time() - $dashboardOfferDecisionTs) >= 604800)) {
                    $showDashboardOfferInfo = false;
                }
            } catch (Throwable $e) {
                $showDashboardOfferInfo = true;
            }
        }
    ?>


<?php if (($student['application_status'] ?? '') === 'Approved' && (($student['offer_status'] ?? 'Pending') === 'Pending')): ?>
<!-- Offer Decision Modal -->
<div class="modal fade" id="offerDecisionModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Admission Offer Decision</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div class="alert alert-warning">
          Please decide. Once you accept or reject, you cannot change it.
        </div>
        <form method="post" class="d-flex flex-wrap gap-2">
          <button type="submit" name="offer_decision" value="accept" class="btn btn-success flex-grow-1">
            <i class="bi bi-hand-thumbs-up"></i> Accept Offer
          </button>
          <button type="submit" name="offer_decision" value="reject" class="btn btn-danger flex-grow-1"
            onclick="return confirm('Are you sure you want to reject this offer?');">
            <i class="bi bi-hand-thumbs-down"></i> Reject Offer
          </button>
        </form>
      </div>
    </div>
  </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function(){
  try{
    var m = new bootstrap.Modal(document.getElementById('offerDecisionModal'));
    m.show();
  }catch(e){}
});
</script>
<?php endif; ?>


<?php
    // Assigned payment popup (shows only when there are unpaid assigned payments)
    $stmt = $pdo->prepare("SELECT id,title,amount,status FROM assigned_payments WHERE student_id = ? AND status <> 'Paid' ORDER BY id DESC");
    $stmt->execute([(int)$studentId]);
    $popupAssigned = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<?php if (!empty($popupAssigned)): ?>
<div class="modal fade" id="assignedPaymentModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title"><i class="bi bi-cash-coin"></i> New / Pending Payments</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div class="alert alert-info mb-3">
          You have payment(s) assigned by the admin. Kindly complete them under <strong>Payment → Other Payments</strong>.
          This popup will auto-close after 1 minute.
        </div>

        <?php foreach ($popupAssigned as $ap): ?>
          <div class="d-flex justify-content-between align-items-center border rounded p-2 mb-2 flex-wrap gap-2">
            <div>
              <div class="fw-semibold"><?php echo htmlspecialchars($ap['title']); ?></div>
              <div class="small text-muted">Status: <?php echo htmlspecialchars($ap['status']); ?></div>
            </div>
            <div class="fw-bold">₦<?php echo number_format((float)$ap['amount']); ?></div>
            <div>
              <a class="btn btn-primary btn-sm" href="?page=payment&tab=other">
                Pay Now
              </a>
            </div>
          </div>
        <?php endforeach; ?>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
        <a href="?page=payment&tab=other" class="btn btn-success">Open Other Payments</a>
      </div>
    </div>
  </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function(){
  try{
    var el = document.getElementById('assignedPaymentModal');
    var m = new bootstrap.Modal(el);
    m.show();
    setTimeout(function(){ try{ m.hide(); }catch(e){} }, 60000); // auto-hide after 1 minute
  }catch(e){}
});
</script>
<?php endif; ?>

<?php
                                    $dashboardSessionText = (string)($dashboardSummary['session_name'] ?? portalCurrentAcademicSessionName($pdo));
                                    $dashboardSemesterText = portalNormalizeSemesterName((string)($dashboardSummary['semester'] ?? ($student['semester'] ?? 'First Semester')));
                                    $dashboardProgrammeText = (string)($dashboardSummary['programme_name'] ?? 'Not Set');
                                    $dashboardLevelText = (string)($dashboardSummary['level'] ?? 'Not Set');
                                    $dashboardSchoolFeeStatus = portalSchoolFeeStatusForPeriod($pdo, $student, $dashboardSessionText, $dashboardSemesterText);
                                    $dashboardClearanceSnapshot = portalBuildClearanceSnapshot($pdo, $student, $dashboardSessionText, $dashboardSemesterText);
                                    $dashboardClearanceRecord = portalUpsertClearanceRecord($pdo, $student, $dashboardClearanceSnapshot, ['updated_by' => (int)($_SESSION['user_id'] ?? 0)]);
                                    $dashboardClearanceStatus = (string)($dashboardClearanceRecord['clearance_status'] ?? 'Pending');
                                    $dashboardOfferStatus = trim((string)($student['offer_status'] ?? ''));
                                    if ($dashboardOfferStatus === '') {
                                        $dashboardOfferStatus = (($student['application_status'] ?? '') === 'Approved') ? 'Pending' : 'Awaiting Approval';
                                    }
                                    $showDashboardOfferCounter = true;
                                    if (($dashboardOfferStatus === 'Accepted') && !empty($student['offer_decision_date'])) {
                                        try {
                                            $dashboardOfferCounterTs = strtotime((string)$student['offer_decision_date']);
                                            if ($dashboardOfferCounterTs !== false && ((time() - $dashboardOfferCounterTs) >= 604800)) {
                                                $showDashboardOfferCounter = false;
                                            }
                                        } catch (Throwable $e) {
                                            $showDashboardOfferCounter = true;
                                        }
                                    }
                                    $dashboardStatsCards = [
                                        [
                                            'href' => '?page=application_form',
                                            'value' => portalReceiptApplicationNumber($student),
                                            'label' => 'Application Number',
                                            'note' => 'Primary application ID',
                                        ],
                                        [
                                            'href' => '?page=application_form',
                                            'value' => portalReceiptAdmissionNumber($student),
                                            'label' => 'Admission Number',
                                            'note' => 'Admission record number',
                                        ],
                                        [
                                            'href' => '?page=application_form',
                                            'value' => portalReceiptMatricNumber($student),
                                            'label' => 'Matric Number',
                                            'note' => ($studentMatricNo !== '' ? 'Student matric record' : 'Processing'),
                                        ],
                                        [
                                            'href' => '?page=payment',
                                            'value' => (string)($student['registration_fee_status'] ?? 'Not Paid'),
                                            'label' => 'Registration Fee',
                                            'note' => 'Payment status',
                                        ],
                                        [
                                            'href' => '?page=acceptance_payment',
                                            'value' => (string)($student['acceptance_fee_status'] ?? 'Not Paid'),
                                            'label' => 'Acceptance Fee',
                                            'note' => 'Payment status',
                                        ],
                                        [
                                            'href' => '?page=profile',
                                            'value' => (string)($student['account_status'] ?? 'Active'),
                                            'label' => 'Account Status',
                                            'note' => 'Portal profile state',
                                        ],
                                        [
                                            'href' => '?page=student_course_registration',
                                            'value' => $dashboardSessionText !== '' ? $dashboardSessionText : 'Not Set',
                                            'label' => 'Session',
                                            'note' => 'Current academic session',
                                        ],
                                        [
                                            'href' => '?page=student_course_registration',
                                            'value' => $dashboardSemesterText !== '' ? $dashboardSemesterText : 'First Semester',
                                            'label' => 'Semester',
                                            'note' => 'Current study period',
                                        ],
                                        [
                                            'href' => '?page=hostel_payment',
                                            'value' => portalStudentHostelStatusLabel($student),
                                            'label' => 'Hostel Fee',
                                            'note' => 'Accommodation payment',
                                        ],
                                        [
                                            'href' => '?page=school_payment',
                                            'value' => $dashboardSchoolFeeStatus,
                                            'label' => 'School Fee',
                                            'note' => $dashboardSessionText . ' • ' . $dashboardSemesterText,
                                        ],
                                        [
                                            'href' => '?page=payment_history',
                                            'value' => (string)($student['payment_plan'] ?? 'Installment'),
                                            'label' => 'Payment Plan',
                                            'note' => 'Current payment option',
                                        ],
                                        [
                                            'href' => '?page=application_form',
                                            'value' => (string)($student['application_status'] ?? 'Pending'),
                                            'label' => 'Application Status',
                                            'note' => 'Admission workflow',
                                        ],
                                    ];
                                    if ($showDashboardOfferCounter) {
                                        $dashboardStatsCards[] = [
                                            'href' => '?page=admission_letter',
                                            'value' => $dashboardOfferStatus,
                                            'label' => 'Admission Offer',
                                            'note' => (($student['application_status'] ?? '') === 'Approved') ? 'Accept or reject offer' : 'Awaiting admission approval',
                                        ];
                                    }
                                    $dashboardStatsCards = array_merge($dashboardStatsCards, [
                                        [
                                            'href' => '?page=student_course_registration',
                                            'value' => $dashboardLevelText !== '' ? $dashboardLevelText : 'Not Set',
                                            'label' => 'Level',
                                            'note' => 'Current academic level',
                                        ],
                                        [
                                            'href' => '?page=student_course_registration',
                                            'value' => $dashboardProgrammeText !== '' ? $dashboardProgrammeText : 'Not Set',
                                            'label' => 'Programme',
                                            'note' => 'Student programme',
                                        ],
                                        [
                                            'href' => '?page=clearance_form',
                                            'value' => $dashboardClearanceStatus,
                                            'label' => 'Clearance Status',
                                            'note' => $dashboardSessionText . ' • ' . $dashboardSemesterText,
                                        ],
                                    ]);
                                    if ($studentDashboardLayout === 'finance-focused' && function_exists('portalDashboardReorderStatCards')) {
                                        $dashboardStatsCards = portalDashboardReorderStatCards($dashboardStatsCards, ['School Fee', 'Registration Fee', 'Acceptance Fee', 'Payment Plan', 'Hostel Fee', 'Clearance Status']);
                                    } elseif ($studentDashboardLayout === 'academic-summary' && function_exists('portalDashboardReorderStatCards')) {
                                        $dashboardStatsCards = portalDashboardReorderStatCards($dashboardStatsCards, ['Programme', 'Level', 'Session', 'Semester', 'Application Status', 'Admission Offer']);
                                    } elseif ($studentDashboardLayout === 'card-heavy' && function_exists('portalDashboardReorderStatCards')) {
                                        $dashboardStatsCards = portalDashboardReorderStatCards($dashboardStatsCards, ['Application Number', 'Admission Number', 'Matric Number', 'Programme', 'Level']);
                                    }
                                    ?>

                                    <div class="mt-4 dashboard-stats-grid">
                                        <?php foreach ($dashboardStatsCards as $dashboardCard): ?>
                                            <?php
                                            $dashboardCardValue = trim((string)($dashboardCard['value'] ?? ''));
                                            if ($dashboardCardValue === '') {
                                                $dashboardCardValue = 'Not Set';
                                            }
                                            $dashboardValueClass = 'dashboard-stat-value';
                                            $dashboardValueLength = function_exists('mb_strlen') ? mb_strlen($dashboardCardValue) : strlen($dashboardCardValue);
                                            if ($dashboardValueLength >= 18) $dashboardValueClass .= ' dashboard-stat-value-sm';
                                            if ($dashboardValueLength >= 34) $dashboardValueClass .= ' dashboard-stat-value-xs';
                                            ?>
                                            <a href="<?php echo e((string)($dashboardCard['href'] ?? '#')); ?>" class="stats-card stats-card-link">
                                                <div class="<?php echo e($dashboardValueClass); ?>"><?php echo e($dashboardCardValue); ?></div>
                                                <div class="stats-label"><?php echo e((string)($dashboardCard['label'] ?? '')); ?></div>
                                            </a>
                                        <?php endforeach; ?>
                                    </div>

                                    <?php if ($studentDashboardShowHostel || $studentDashboardShowClearance): ?>
                                    <div class="row mt-4 dashboard-utility-row">
                                        <?php if ($studentDashboardShowHostel): ?>
                                        <div class="col-md-6">
                                            <div class="card dashboard-card">        
                                                <div class="card-header">Accommodation Status</div>
                                                <div class="card-body">
                                                    <?php
                                                    $accommodationStudentId = (int)($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0);
                                                    if ($_SERVER['REQUEST_METHOD'] === 'POST' && (($_POST['action'] ?? '') === 'save_accommodation_status') && $accommodationStudentId > 0) {
                                                        $selectedAccommodation = (($_POST['accommodation_status'] ?? 'Campus') === 'Off-Campus') ? 'Off-Campus' : 'Campus';
                                                        try {
                                                            if (portalStudentAccommodationLockedForSession($pdo, $student)) {
                                                                flash('warning', 'Accommodation status is locked for the current session because the hostel fee has already been paid. You can change it again in a new session.');
                                                            } else {
                                                                $stmt = $pdo->prepare("UPDATE students SET accommodation_status = ? WHERE id = ?");
                                                                $stmt->execute([$selectedAccommodation, $accommodationStudentId]);
                                                                $student['accommodation_status'] = $selectedAccommodation;
                                                                flash('success', 'Accommodation status updated successfully.');
                                                            }
                                                        } catch (Throwable $e) {
                                                            flash('danger', 'Unable to update accommodation status.');
                                                        }
                                                    }
                                                    $accommodationLocked = portalStudentAccommodationLockedForSession($pdo, $student);
                                                    ?>
                                                    <?php renderFlashes(); ?>
                                                    <form method="post" class="row g-2 align-items-end">
                                                        <input type="hidden" name="action" value="save_accommodation_status">
                                                    <input type="hidden" name="return_to" value="<?php echo e((string)($_SERVER['REQUEST_URI'] ?? '?page=hostel_payment')); ?>">
                                                        <div class="col-md-8">
                                                            <label class="form-label">Select accommodation option</label>
                                                            <select class="form-select" name="accommodation_status" <?php echo $accommodationLocked ? 'disabled' : ''; ?>>
                                                                <option value="Campus" <?php echo ((string)($student['accommodation_status'] ?? 'Campus') === 'Campus') ? 'selected' : ''; ?>>Campus Accommodation</option>
                                                                <option value="Off-Campus" <?php echo ((string)($student['accommodation_status'] ?? 'Campus') === 'Off-Campus') ? 'selected' : ''; ?>>Off-Campus</option>
                                                            </select>
                                                            <?php if ($accommodationLocked): ?>
                                                                <input type="hidden" name="accommodation_status" value="<?php echo e((string)($student['accommodation_status'] ?? 'Campus')); ?>">
                                                            <?php endif; ?>
                                                        </div>
                                                        <div class="col-md-4">
                                                            <button class="btn btn-primary w-100" type="submit" <?php echo $accommodationLocked ? 'disabled aria-disabled="true"' : ''; ?>>Save Status</button>
                                                        </div>
                                                    </form>
                                                    <div class="small text-muted mt-2">If Off-Campus is selected, hostel fee status shows as Off-Campus instead of Not Paid. You can switch back to Campus any time before hostel fee is paid for the current session.</div>
                                                    <?php if ($accommodationLocked): ?>
                                                        <div class="alert alert-secondary mt-3 mb-0"><i class="bi bi-lock"></i> Hostel fee has already been paid for the current session. Accommodation status cannot be changed again until the next session.</div>
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>
                                        <?php endif; ?>
                                        <?php if ($studentDashboardShowClearance): ?>
                                        <div class="col-md-6">
                                            <div class="card dashboard-card">
                                                <div class="card-header">Student Clearance</div>
                                                <div class="card-body">
                                                    <p class="mb-3">Open and print your clearance form from here.</p>
                                                    <a href="?page=clearance_form" class="btn btn-success"><i class="bi bi-patch-check"></i> Open Clearance Form</a>
                                                </div>
                                            </div>
                                        </div>
                                        <?php endif; ?>
                                    </div>
                                    <?php endif; ?>

                                    <?php if (($showLms && $studentDashboardShowLms) || ($showLib && $studentDashboardShowLibrary) || ($showCbt && $studentDashboardShowCbt)): ?>
                                    <div class="row mt-4 dashboard-services-row">
                                        <div class="col-md-12">
                                            <div class="card dashboard-card dashboard-services-card">
                                                <div class="card-header">
                                                    Learning &amp; Student Services
                                                </div>
                                                <div class="card-body">
                                                    <div class="row g-3">
                                                        <?php if ($showLms && $studentDashboardShowLms): ?>
                                                        <div class="col-12 col-lg-4">
                                                            <a class="text-decoration-none" href="?page=student_lms&tab=materials">
                                                                <div class="border rounded numbering-preview-card">
                                                                    <div class="d-flex align-items-center justify-content-between">
                                                                        <div class="fw-semibold"><i class="bi bi-mortarboard"></i> LMS</div>
                                                                        <span class="badge bg-secondary"><?php echo (int)$lmsCount; ?></span>
                                                                    </div>
                                                                    <div class="text-muted small mt-2">View materials and assignments.</div>
                                                                </div>
                                                            </a>
                                                        </div>
                                                        <?php endif; ?>

                                                        <?php if ($showLib && $studentDashboardShowLibrary): ?>
                                                        <div class="col-12 col-lg-4">
                                                            <a class="text-decoration-none" href="?page=student_library&tab=browse">
                                                                <div class="border rounded p-3 bg-light h-100">
                                                                    <div class="d-flex align-items-center justify-content-between">
                                                                        <div class="fw-semibold"><i class="bi bi-book"></i> Library</div>
                                                                        <span class="badge bg-secondary"><?php echo (int)$libCount; ?> loan(s)</span>
                                                                    </div>
                                                                    <div class="text-muted small mt-2">Browse books and track due dates.</div>
                                                                </div>
                                                            </a>
                                                        </div>
                                                        <?php endif; ?>

                                                        <?php if ($showCbt && $studentDashboardShowCbt): ?>
                                                        <div class="col-12 col-lg-4">
                                                            <a class="text-decoration-none" href="?page=student_cbt&tab=available">
                                                                <div class="border rounded p-3 bg-light h-100">
                                                                    <div class="d-flex align-items-center justify-content-between">
                                                                        <div class="fw-semibold"><i class="bi bi-ui-checks-grid"></i> CBT</div>
                                                                        <span class="badge bg-secondary"><?php echo (int)$cbtCount; ?></span>
                                                                    </div>
                                                                    <div class="text-muted small mt-2">See available tests and results.</div>
                                                                </div>
                                                            </a>
                                                        </div>
                                                        <?php endif; ?>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                    <?php endif; ?>

                                    <div class="row mt-4">
                                        <div class="col-md-6">
                                            <div class="card dashboard-card">
                                                <div class="card-header">
                                                    Personal Information
                                                </div>
                                                <div class="card-body">
                                                    <table class="table table-borderless">
                                                        <tr>
                                                            <td><strong>Name:</strong></td>
                                                            <td><?php echo htmlspecialchars(studentFullName($student)); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Email:</strong></td>
                                                            <td><?php echo $student['email']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Phone:</strong></td>
                                                            <td><?php echo $student['mobile_phone']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>State of Origin:</strong></td>
                                                            <td><?php echo $student['state_of_origin']; ?></td>
                                                        </tr>
                                                    </table>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-md-6">
                                            <div class="card dashboard-card">
                                                <div class="card-header">
                                                    Application Details
                                                </div>
                                                <div class="card-body">
                                                    <table class="table table-borderless">
                                                        <tr>
                                                            <td><strong>School:</strong></td>
                                                            <td><?php echo htmlspecialchars((string)($student['school'] ?? $student['school_name'] ?? $student['faculty'] ?? '')); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Course of Study:</strong></td>
                                                            <td><?php echo htmlspecialchars((string)($student['course_of_study'] ?? $student['course_in_view'] ?? $student['department'] ?? '')); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Program:</strong></td>
                                                            <td><?php echo htmlspecialchars((string)($student['program'] ?? $student['programme_name_cache'] ?? $student['programme'] ?? '')); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Mode of Study:</strong></td>
                                                            <td><?php echo htmlspecialchars((string)($student['mode_of_study'] ?? portalInferModeOfStudy((array)$student))); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Application Date:</strong></td>
                                                            <td><?php echo date('d M Y', strtotime($student['created_at'])); ?></td>
                                                        </tr>
                                                    </table>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

                                    <div class="row mt-4">
                                        <div class="col-md-12">
                                            <div class="card dashboard-card dashboard-payments-card">
                                                <div class="card-header">
                                                    Recent Payments
                                                </div>
                                                <div class="card-body">
                                                    <div class="table-responsive">
                                                        <table class="table table-striped">
                                                            <thead>
                                                                <tr>
                                                                    <th>Transaction ID</th>
                                                                    <th>Payment Type</th>
                                                                    <th>Amount</th>
                                                                    <th>Status</th>
                                                                    <th>Date</th>
                                                                    <th>Actions</th>
                                                                </tr>
                                                            </thead>
                                                            <tbody>
                                                                <?php foreach ($recentPayments as $payment): ?>
                                                                <tr>
                                                                    <td><?php echo $payment['transaction_id']; ?></td>
                                                                    <td><?php echo $payment['transaction_type']; ?></td>
                                                                    <td>₦<?php echo number_format($payment['amount']); ?></td>
                                                                    <td>
                                                                        <span class="status-badge status-<?php echo e(portalPaymentDisplayStatusClass($payment)); ?>"><?php echo e(portalPaymentApprovalStatusLabel($payment)); ?>
                                                                        </span>
                                                                    </td>
                                                                    <td>
                                                                        <?php
                                                                        if ($payment['payment_date']) {
                                                                            echo date('d M Y', strtotime($payment['payment_date']));
                                                                        } else {
                                                                            echo 'N/A';
                                                                        }
                                                                        ?>
                                                                    </td>
                                                                    <td>
                                                                        <?php if ($payment['status'] === 'Approved'): ?>
                                                                            <a href="?page=payment_receipt&transaction_id=<?php echo $payment['id']; ?>" class="btn btn-sm btn-primary">View Receipt</a>
                                                                        <?php endif; ?>
                                                                    </td>
                                                                </tr>
                                                                <?php endforeach; ?>
                                                            </tbody>
                                                        </table>
                                                    </div>
                                                    <div class="mt-3">
                                                        <a href="?page=payment_history" class="btn btn-sm btn-outline-primary">View All Payments</a>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

                                    <div class="row mt-4">
                                        <div class="col-md-12">
                                            <div class="card dashboard-card">
                                                <div class="card-header">
                                                    Application Actions
                                                </div>
                                                <div class="card-body">
                                                    <div class="d-flex flex-wrap gap-2">
                                                        <?php if ($student['registration_fee_status'] === 'Paid'): ?>
                                                            <a href="?page=application_form" class="btn btn-primary">
                                                                <i class="bi bi-pencil"></i> Edit Application
                                                            </a>
                                                        <?php else: ?>
                                                            <a href="?page=payment&tab=registration" class="btn btn-success">
                                                                <i class="bi bi-credit-card"></i> Pay Registration Fee
                                                            </a>
                                                        <?php endif; ?>

                                                        <?php if ($student['application_status'] === 'Approved' && !payment_status_is_paid($student['acceptance_fee_status'] ?? '') && portalStudentCanShowFeeMenu($pdo, $student, 'acceptance')): ?>
                                                            <a href="?page=acceptance_payment" class="btn btn-warning">
                                                                <i class="bi bi-credit-card"></i> Pay Acceptance Fee
                                                            </a>
                                                        <?php endif; ?>

                                                        <?php if ($student['application_status'] === 'Approved' && $student['acceptance_fee_status'] === 'Paid'): ?>
                                                            <a href="?page=admission_letter" class="btn btn-info">
                                                                <i class="bi bi-file-text"></i> View Admission Letter
                                                            </a>
                                                        <?php endif; ?>

                                                        <?php if ($student['application_status'] === 'Approved' && $student['acceptance_fee_status'] === 'Paid' && $student['id_card_generated']): ?>
                                                            <a href="?page=student_id_card" class="btn btn-info">
                                                                <i class="bi bi-card-text"></i> View ID Card
                                                            </a>
                                                        <?php endif; ?>

                                                        <?php if ($studentDashboardShowHostel && $student['application_status'] === 'Approved' && payment_status_is_paid($student['acceptance_fee_status'] ?? '') && portalStudentCanShowFeeMenu($pdo, $student, 'hostel')): ?>
                                                            <a href="?page=hostel_payment" class="btn btn-warning">
                                                                <i class="bi bi-house"></i> Pay Hostel Fee
                                                            </a>
                                                        <?php endif; ?>

                                                        <?php if ($student['application_status'] === 'Approved' && payment_status_is_paid($student['acceptance_fee_status'] ?? '') && portalStudentCanShowFeeMenu($pdo, $student, 'school')): ?>
                                                            <a href="?page=school_payment" class="btn btn-warning">
                                                                <i class="bi bi-book"></i> Pay School Fee
                                                            </a>
                                                        <?php endif; ?>

                                                        <?php if ($studentDashboardShowClearance): ?>
                                                        <a href="?page=clearance_form" class="btn btn-outline-success">
                                                            <i class="bi bi-patch-check"></i> Clearance Form
                                                        </a>
                                                        <?php endif; ?>

                                                        <a href="?page=payment_history" class="btn btn-secondary">
                                                            <i class="bi bi-clock-history"></i> Payment History
                                                        </a>

                                                        <a href="?page=print_form" class="btn btn-secondary">
                                                            <i class="bi bi-printer"></i> Print Application
                                                        </a>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    </div>
                    <?php
                } else {
                    redirect('?page=login');
                }
                break;

            case 'profile':
                if (isLoggedIn()) {
                    if (isAdmin()) {
                        $userId = $_SESSION['user_id'];
                        $stmt = $pdo->prepare("SELECT * FROM admins WHERE id = ?");
                        $stmt->execute([$userId]);
                        $user = $stmt->fetch(PDO::FETCH_ASSOC);
                        $userType = 'Admin';
                    } else {
                        $userId = $_SESSION['user_id'];
                        $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                        $stmt->execute([$userId]);
                        $user = $stmt->fetch(PDO::FETCH_ASSOC);
                        $userType = 'Student';
                    }
                    ?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header">
                                    Profile - <?php echo $userType; ?>
                                </div>
                                <div class="card-body">
                                    <?php if ($userType === 'Admin'): ?>
                                        <form method="post" action="">
                                            <input type="hidden" name="action" value="update_profile">

                                            <div class="row">
                                                <div class="col-md-6 mb-3">
                                                    <label for="username" class="form-label">Username</label>
                                                    <input type="text" class="form-control" id="username" name="username" value="<?php echo $user['username']; ?>" required>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="email" class="form-label">Email</label>
                                                    <input type="email" class="form-control" id="email" name="email" value="<?php echo $user['email']; ?>" required>
                                                </div>
                                                <div class="col-md-12 mb-3">
                                                    <label for="full_name" class="form-label">Full Name</label>
                                                    <input type="text" class="form-control" id="full_name" name="full_name" value="<?php echo $user['full_name']; ?>" required>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="password" class="form-label">New Password (leave blank to keep current)</label>
                                                    <input type="password" class="form-control" id="password" name="password">
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="confirm_password" class="form-label">Confirm New Password</label>
                                                    <input type="password" class="form-control" id="confirm_password" name="confirm_password">
                                                </div>
                                            </div>

                                            <button type="submit" class="btn btn-primary">Update Profile</button>
                                        </form>
                                    <?php else: ?>
                                        <div class="mb-3">
                                            <h6 class="mb-2"><i class="bi bi-image"></i> Profile Picture</h6>
                                            <?php $pp = !empty($user['profile_image']) ? $user['profile_image'] : (!empty($user['passport_photo']) ? $user['passport_photo'] : ''); ?>
                                            <div class="d-flex align-items-center gap-3 flex-wrap">
                                                <?php if (!empty($pp)): ?>
                                                    <img src="<?php echo htmlspecialchars($pp); ?>" alt="Profile Picture" style="width:90px;height:90px;border-radius:50%;object-fit:cover;border:2px solid #eee;">
                                                <?php else: ?>
                                                    <div style="width:90px;height:90px;border-radius:50%;background:#f3f3f3;display:flex;align-items:center;justify-content:center;border:2px solid #eee;">
                                                        <i class="bi bi-person" style="font-size:34px;color:#999;"></i>
                                                    </div>
                                                <?php endif; ?>
                                                <form method="post" enctype="multipart/form-data" class="d-flex align-items-end gap-2 flex-wrap">
                                                    <input type="hidden" name="action" value="upload_profile_image">
                                                    <div>
                                                        <label class="form-label mb-1">Upload New</label>
                                                        <input type="file" name="profile_image" class="form-control" accept="image/*" required>
                                                    </div>
                                                    <button class="btn btn-outline-primary" type="submit">Upload</button>
                                                </form>
                                            </div>
                                        </div>

                                        <form method="post" action="" enctype="multipart/form-data">

                                            <input type="hidden" name="action" value="update_profile">

                                            <div class="row">
                                                <div class="col-md-6 mb-3">
                                                    <label for="surname" class="form-label">Surname</label>
                                                    <input type="text" class="form-control" id="surname" name="surname" value="<?php echo $user['surname']; ?>" required>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="first_name" class="form-label">First Name</label>
                                                    <input type="text" class="form-control" id="first_name" name="first_name" value="<?php echo $user['first_name']; ?>" required>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="other_name" class="form-label">Other Name</label>
                                                    <input type="text" class="form-control" id="other_name" name="other_name" value="<?php echo $user['other_name']; ?>">
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="email" class="form-label">Email</label>
                                                    <input type="email" class="form-control" id="email" name="email" value="<?php echo $user['email']; ?>" required>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="mobile_phone" class="form-label">Mobile Phone</label>
                                                    <input type="tel" class="form-control" id="mobile_phone" name="mobile_phone" value="<?php echo $user['mobile_phone']; ?>" required>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="residential_phone" class="form-label">Residential Phone</label>
                                                    <input type="tel" class="form-control" id="residential_phone" name="residential_phone" value="<?php echo $user['residential_phone']; ?>">
                                                </div>
                                                <div class="col-md-12 mb-3">
                                                    <label for="permanent_address" class="form-label">Permanent Address</label>
                                                    <textarea class="form-control" id="permanent_address" name="permanent_address" rows="3" required><?php echo $user['permanent_address']; ?></textarea>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="password" class="form-label">New Password (leave blank to keep current)</label>
                                                    <input type="password" class="form-control" id="password" name="password">
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label for="confirm_password" class="form-label">Confirm New Password</label>
                                                    <input type="password" class="form-control" id="confirm_password" name="confirm_password">
                                                </div>
                                            </div>

                                            <button type="submit" class="btn btn-primary">Update Profile</button>
                                        </form>
                                    <?php endif; ?>
                                </div>
                            </div>
                        </div>
                    </div>
                    <?php
                } else {
                    redirect('?page=login');
                }
                break;
            case 'admin_dashboard':
                if (isAdmin()) {
                    $allDashboardRows = portalFetchStudentMetaRows($pdo, [], false);
                    $availableDashboardYears = portalUniqueAcademicYears($allDashboardRows);
                    $statsYear = trim((string)($_GET['stats_year'] ?? ''));
                    if ($statsYear === '' && $availableDashboardYears) {
                        $statsYear = (string)$availableDashboardYears[0];
                    }

                    $statsRows = portalFetchStudentMetaRows($pdo, ['year' => $statsYear], false);
                    $statsPaymentMap = portalPaymentTotalsMap($pdo, array_column($statsRows, 'id'));

                    $pendingCount = 0;
                    $approvedCount = 0;
                    $rejectedCount = 0;
                    $paidCount = 0;
                    $acceptancePaidCount = 0;
                    $hostelPaidCount = 0;
                    $schoolFeePaidCount = 0;
                    $suspendedCount = 0;
                    $blockedCount = 0;
                    $idCardGeneratedCount = 0;
                    $pendingPaymentsCount = 0;
                    $totalAmountPaid = 0;

                    foreach ($statsRows as $statRow) {
                        $pendingCount += ((string)($statRow['application_status'] ?? '') === 'Pending') ? 1 : 0;
                        $approvedCount += ((string)($statRow['application_status'] ?? '') === 'Approved') ? 1 : 0;
                        $rejectedCount += ((string)($statRow['application_status'] ?? '') === 'Rejected') ? 1 : 0;
                        $paidCount += payment_status_is_paid((string)($statRow['registration_fee_status'] ?? '')) ? 1 : 0;
                        $acceptancePaidCount += payment_status_is_paid((string)($statRow['acceptance_fee_status'] ?? '')) ? 1 : 0;
                        $hostelPaidCount += payment_status_is_paid((string)($statRow['hostel_fee_status'] ?? '')) ? 1 : 0;
                        $schoolFeePaidCount += payment_status_is_paid((string)($statRow['school_fee_status'] ?? '')) ? 1 : 0;
                        $suspendedCount += ((string)($statRow['account_status'] ?? '') === 'Suspended') ? 1 : 0;
                        $blockedCount += ((string)($statRow['account_status'] ?? '') === 'Blocked') ? 1 : 0;
                        $idCardGeneratedCount += !empty($statRow['id_card_generated']) ? 1 : 0;
                        $payMeta = $statsPaymentMap[(int)($statRow['id'] ?? 0)] ?? ['approved_total' => 0, 'pending_total' => 0, 'approved_count' => 0, 'pending_count' => 0];
                        $pendingPaymentsCount += (int)($payMeta['pending_count'] ?? 0);
                        $totalAmountPaid += (float)($payMeta['approved_total'] ?? 0);
                    }

                    $totalCount = count($statsRows);
                    $recentApplications = array_slice(portalSortMetaRows($statsRows, 'created_at', 'desc'), 0, 10);
                    $academyRows = portalFetchStudentMetaRows($pdo, ['year' => $statsYear], true);
                    $academySummaryPreview = array_slice(portalCombinationSummaryRows($academyRows), 0, 8);
                    $dashboardCurrentSession = trim((string)portalCurrentAcademicSessionName($pdo));
                    $dashboardCurrentSemester = trim((string)portalCurrentAcademicSemesterName($pdo));
                    ?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header">
                                    Admin Dashboard
                                </div>
                                <div class="card-body">
                                    <div class="d-flex justify-content-between align-items-start flex-wrap gap-3">
                                        <div>
                                            <h4>Welcome, <?php echo e((string)($_SESSION['full_name'] ?? 'System Administrator')); ?>!</h4>
                                            <p class="mb-0">Here's an overview of the HontTech registration system.</p>
                                        </div>
                                        <div class="d-flex flex-wrap gap-2 align-items-end justify-content-md-end">
                                            <div class="d-flex flex-wrap gap-2 me-md-2">
                                                <div class="border rounded px-3 py-2 bg-light" style="min-width:180px;">
                                                    <div class="small text-muted text-uppercase fw-semibold">Current Session</div>
                                                    <div class="fw-bold"><?php echo e($dashboardCurrentSession !== '' ? $dashboardCurrentSession : 'Not Set'); ?></div>
                                                </div>
                                                <div class="border rounded px-3 py-2 bg-light" style="min-width:180px;">
                                                    <div class="small text-muted text-uppercase fw-semibold">Current Semester</div>
                                                    <div class="fw-bold"><?php echo e($dashboardCurrentSemester !== '' ? $dashboardCurrentSemester : 'Not Set'); ?></div>
                                                </div>
                                            </div>
                                            <form method="get" class="d-flex flex-wrap gap-2 align-items-end mb-0">
                                                <input type="hidden" name="page" value="admin_dashboard">
                                                <div>
                                                    <label class="form-label fw-semibold mb-1">Students Statistics:</label>
                                                    <select name="stats_year" class="form-select" onchange="this.form.submit()">
                                                        <?php foreach ($availableDashboardYears as $yearOption): ?>
                                                            <option value="<?php echo e($yearOption); ?>" <?php echo $statsYear === (string)$yearOption ? 'selected' : ''; ?>><?php echo e($yearOption); ?></option>
                                                        <?php endforeach; ?>
                                                    </select>
                                                </div>
                                            </form>
                                            <a href="?page=admin_previous_admission_record&amp;year=<?php echo urlencode($statsYear); ?>" class="btn btn-outline-primary mt-4 mt-md-0">
                                                <i class="bi bi-clock-history"></i> Previous Admission Record
                                            </a>
                                            <a href="?page=admin_previous_admission_record&amp;view=summary&amp;year=<?php echo urlencode($statsYear); ?>#academy-summary-section" class="btn btn-outline-dark mt-4 mt-md-0">
                                                <i class="bi bi-bar-chart-line"></i> Academy Summary
                                            </a>
                                        </div>
                                    </div>

                                    <div class="alert alert-light border mt-3 mb-4">
                                        Showing dashboard statistics for <strong><?php echo e($statsYear !== '' ? $statsYear : 'All Years'); ?></strong>.
                                        Total approved payment value in this view: <strong>₦<?php echo number_format($totalAmountPaid, 2); ?></strong>.
                                    </div>

                                    <div class="row mt-2">
                                        <div class="col-md-2">
                                            <a href="?page=admin_applications" class="stats-card stats-card-link">
                                                <h3><?php echo $totalCount; ?></h3>
                                                <p>Total Applications</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_applications" class="stats-card stats-card-link">
                                                <h3><?php echo $pendingCount; ?></h3>
                                                <p>Pending</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_applications" class="stats-card stats-card-link">
                                                <h3><?php echo $approvedCount; ?></h3>
                                                <p>Approved</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_applications" class="stats-card stats-card-link">
                                                <h3><?php echo $rejectedCount; ?></h3>
                                                <p>Rejected</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_payments" class="stats-card stats-card-link">
                                                <h3><?php echo $paidCount; ?></h3>
                                                <p>Reg. Fee Paid</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_payments" class="stats-card stats-card-link">
                                                <h3><?php echo $acceptancePaidCount; ?></h3>
                                                <p>Acc. Fee Paid</p>
                                            </a>
                                        </div>
                                    </div>

                                    <div class="row mt-4">
                                        <div class="col-md-2">
                                            <a href="?page=admin_payments" class="stats-card stats-card-link">
                                                <h3><?php echo $hostelPaidCount; ?></h3>
                                                <p>Hostel Fee Paid</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_payments" class="stats-card stats-card-link">
                                                <h3><?php echo $schoolFeePaidCount; ?></h3>
                                                <p>School Fee Paid</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_payments" class="stats-card stats-card-link">
                                                <h3><?php echo $pendingPaymentsCount; ?></h3>
                                                <p>Pending Payments</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_students" class="stats-card stats-card-link">
                                                <h3><?php echo $suspendedCount; ?></h3>
                                                <p>Suspended</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_students" class="stats-card stats-card-link">
                                                <h3><?php echo $blockedCount; ?></h3>
                                                <p>Blocked</p>
                                            </a>
                                        </div>
                                        <div class="col-md-2">
                                            <a href="?page=admin_students" class="stats-card stats-card-link">
                                                <h3><?php echo $idCardGeneratedCount; ?></h3>
                                                <p>ID Cards Generated</p>
                                            </a>
                                        </div>
                                    </div>

                                    <div class="row mt-4">
                                        <div class="col-md-12">
                                            <div class="card dashboard-card">
                                                <div class="card-header d-flex justify-content-between align-items-center">
                                                    <span>Recent Applications</span>
                                                    <a href="?page=admin_applications" class="btn btn-sm btn-outline-light">View All</a>
                                                </div>
                                                <div class="card-body table-responsive">
                                                    <table class="table table-striped table-hover align-middle">
                                                        <thead>
                                                            <tr>
                                                                <th>Application No.</th>
                                                                <th>Name</th>
                                                                <th>Faculty</th>
                                                                <th>Department</th>
                                                                <th>Programme</th>
                                                                <th>Mode of Study</th>
                                                                <th>Fee Type</th><th>Session</th>
                                                                <th>Status</th>
                                                                <th>Total Paid</th>
                                                                <th>Date</th>
                                                                <th>Actions</th>
                                                            </tr>
                                                        </thead>
                                                        <tbody>
                                                            <?php if (!$recentApplications): ?>
                                                                <tr><td colspan="12" class="text-center text-muted">No records found for the selected year.</td></tr>
                                                            <?php else: ?>
                                                                <?php foreach ($recentApplications as $recentRow): ?>
                                                                    <?php
                                                                        $recentPay = $statsPaymentMap[(int)($recentRow['id'] ?? 0)] ?? ['approved_total' => 0];
                                                                        $recentFeeType = trim((string)($recentRow['fee_type'] ?? $recentRow['application_fee_type'] ?? $recentRow['payment_type'] ?? ''));
                                                                        if ($recentFeeType === '') {
                                                                            $recentFeeType = 'Application';
                                                                        }
                                                                    ?>
                                                                    <tr>
                                                                        <td><?php echo e(portalReceiptApplicationNumber($recentRow)); ?></td>
                                                                        <td><?php echo e((string)($recentRow['student_name'] ?? full_name($recentRow))); ?></td>
                                                                        <td><?php echo e((string)($recentRow['faculty_name'] ?? 'Not Set')); ?></td>
                                                                        <td><?php echo e((string)($recentRow['department_name'] ?? 'Not Set')); ?></td>
                                                                        <td><?php echo e((string)($recentRow['programme_name'] ?? 'Not Set')); ?></td>
                                                                        <td><?php echo e(portalInferModeOfStudy((array)$recentRow)); ?></td>
                                                                        <td><?php echo e($recentFeeType); ?></td>
                                                                        <td><?php echo e((string)($recentRow['session'] ?? 'Not Set')); ?></td>
                                                                        <td>
                                                                            <span class="badge bg-<?php echo (($recentRow['application_status'] ?? '') === 'Approved') ? 'success' : ((($recentRow['application_status'] ?? '') === 'Rejected') ? 'danger' : 'secondary'); ?>">
                                                                                <?php echo e((string)($recentRow['application_status'] ?? 'Pending')); ?>
                                                                            </span>
                                                                        </td>
                                                                        <td>₦<?php echo number_format((float)($recentPay['approved_total'] ?? 0), 2); ?></td>
                                                                        <td><?php echo !empty($recentRow['created_at']) ? e(date('d M Y', strtotime((string)$recentRow['created_at']))) : '-'; ?></td>
                                                                        <td>
                                                                            <a href="?page=admin_students&amp;action=view&amp;id=<?php echo (int)$recentRow['id']; ?>" class="btn btn-sm btn-primary">View</a>
                                                                        </td>
                                                                    </tr>
                                                                <?php endforeach; ?>
                                                            <?php endif; ?>
                                                        </tbody>
                                                    </table>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

                                    <div class="row mt-4">
                                        <div class="col-md-6">
                                            <div class="card dashboard-card h-100">
                                                <div class="card-header">
                                                    Quick Actions
                                                </div>
                                                <div class="card-body">
                                                    <div class="d-grid gap-2">
                                                        <a href="?page=admin_applications" class="btn btn-primary">
                                                            <i class="bi bi-file-earmark-text"></i> Manage Applications
                                                        </a>
                                                        <a href="?page=admin_students" class="btn btn-primary">
                                                            <i class="bi bi-people"></i> Manage Students
                                                        </a>
                                                        <a href="?page=admin_previous_admission_record&amp;year=<?php echo urlencode($statsYear); ?>" class="btn btn-outline-primary">
                                                            <i class="bi bi-clock-history"></i> Previous Admission Record
                                                        </a>
                                                        <a href="?page=clearance_form" class="btn btn-success">
                                                            <i class="bi bi-patch-check"></i> Clearance Form
                                                        </a>
                                                        <a href="?page=reports" class="btn btn-info text-white">
                                                            <i class="bi bi-graph-up"></i> Generate Reports
                                                        </a>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-md-6">
                                            <div class="card dashboard-card h-100">
                                                <div class="card-header d-flex justify-content-between align-items-center">
                                                    <span>Academy Summary Preview</span>
                                                    <a href="?page=admin_previous_admission_record&amp;view=summary&amp;year=<?php echo urlencode($statsYear); ?>#academy-summary-section" class="btn btn-sm btn-outline-light">Open Full Summary</a>
                                                </div>
                                                <div class="card-body table-responsive">
                                                    <table class="table table-sm align-middle mb-0">
                                                        <thead>
                                                            <tr>
                                                                <th>Faculty</th>
                                                                <th>Department</th>
                                                                <th>Programme</th>
                                                                <th>Mode of Study</th>
                                                                <th>Semester</th>
                                                                <th>Level</th>
                                                                <th>Year</th>
                                                                <th>Total</th>
                                                            </tr>
                                                        </thead>
                                                        <tbody>
                                                            <?php if (!$academySummaryPreview): ?>
                                                                <tr><td colspan="8" class="text-center text-muted">No previous admission summary is available for this year.</td></tr>
                                                            <?php else: ?>
                                                                <?php foreach ($academySummaryPreview as $summaryRow): ?>
                                                                    <tr>
                                                                        <td><?php echo e((string)$summaryRow['faculty_name']); ?></td>
                                                                        <td><?php echo e((string)$summaryRow['department_name']); ?></td>
                                                                        <td><?php echo e((string)$summaryRow['programme_name']); ?></td>
                                                                        <td><?php echo e(portalInferModeOfStudy((array)$summaryRow)); ?></td>
                                                                        <td><?php echo e((string)$summaryRow['semester']); ?></td>
                                                                        <td><?php echo e((string)$summaryRow['level']); ?></td>
                                                                        <td><?php echo e((string)$summaryRow['academic_year']); ?></td>
                                                                        <td><span class="badge bg-primary"><?php echo (int)$summaryRow['total']; ?></span></td>
                                                                    </tr>
                                                                <?php endforeach; ?>
                                                            <?php endif; ?>
                                                        </tbody>
                                                    </table>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

                                    <div class="row mt-4">
                                        <div class="col-md-6">
                                            <div class="card dashboard-card h-100">
                                                <div class="card-header">
                                                    Clearance Management
                                                </div>
                                                <div class="card-body">
                                                    <p class="mb-3">Open the clearance form directly from the admin dashboard and keep student/admin access aligned.</p>
                                                    <a href="?page=clearance_form" class="btn btn-success">
                                                        <i class="bi bi-patch-check"></i> Open Clearance Form
                                                    </a>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-md-6">
                                            <div class="card dashboard-card h-100">
                                                <div class="card-header">
                                                    System Information
                                                </div>
                                                <div class="card-body">
                                                    <table class="table table-borderless mb-0">
                                                        <tr>
                                                            <td><strong>Academic Session:</strong></td>
                                                            <td><?php echo e((string)portalCurrentAcademicSessionName($pdo)); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Dashboard Year:</strong></td>
                                                            <td><?php echo e($statsYear !== '' ? $statsYear : 'Not Set'); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Total Amount Paid:</strong></td>
                                                            <td>₦<?php echo number_format($totalAmountPaid, 2); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Previous Admission Records:</strong></td>
                                                            <td><?php echo count($academyRows); ?></td>
                                                        </tr>
                                                    </table>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <?php
                }
                break;

            case 'admin_graduated':
                if (isAdmin()) {
                    try { ensurePortalExtensionSupport($pdo); portalSyncGraduationStatuses($pdo); } catch (Throwable $e) {}
                    $filterFacultyId = (int)($_GET['faculty_id'] ?? 0);
                    $filterDepartmentId = (int)($_GET['department_id'] ?? 0);
                    $filterYear = trim((string)($_GET['year'] ?? ''));
                    $graduatedRows = portalFetchGraduatedRows($pdo, [
                        'faculty_id' => $filterFacultyId,
                        'department_id' => $filterDepartmentId,
                        'year' => $filterYear,
                    ]);

                    $facultyOptions = $pdo->query("SELECT id, name FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    $departmentOptions = $pdo->query("SELECT id, name FROM departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    $yearOptions = [];
                    foreach ($graduatedRows as $row) {
                        $yr = trim((string)($row['graduation_year'] ?? ''));
                        if ($yr !== '') $yearOptions[$yr] = $yr;
                    }
                    if ($filterYear !== '') $yearOptions[$filterYear] = $filterYear;
                    if (!$yearOptions) $yearOptions[(string)portalCurrentAcademicYearNumber($pdo)] = (string)portalCurrentAcademicYearNumber($pdo);
                    krsort($yearOptions, SORT_NATURAL);

                    if (isset($_GET['export']) && in_array((string)$_GET['export'], ['csv','pdf'], true)) {
                        $exportRows = [];
                        foreach ($graduatedRows as $row) {
                            $exportRows[] = [
                                (string)($row['registration_no'] ?? ''),
                                (string)($row['application_no'] ?? ''),
                                (string)($row['admission_no'] ?? ''),
                                (string)($row['matric_no'] ?? ''),
                                (string)($row['student_name'] ?? ''),
                                (string)($row['faculty_name'] ?? ''),
                                (string)($row['department_name'] ?? ''),
                                (string)($row['programme_name'] ?? ''),
                                (string)($row['graduation_year'] ?? ''),
                                (string)($row['duration_years'] ?? ''),
                                (string)($row['completed_years'] ?? ''),
                                (string)($row['graduated_at'] ?? ''),
                            ];
                        }
                        $headers = ['Registration No','Application No','Admission No','Matric No','Student Name','Faculty','Department','Programme','Graduation Year','Programme Duration (Years)','Completed Years','Graduated At'];
                        if ((string)$_GET['export'] === 'csv') {
                            moduleCsvDownload('graduated_students_export.csv', $headers, $exportRows);
                        }
                        ?>
                        <html>
                        <head>
                            <title>Graduated Students</title>
                            <style>
                                body{font-family:Arial,sans-serif;font-size:12px;padding:20px}
                                table{width:100%;border-collapse:collapse}
                                th,td{border:1px solid #999;padding:6px;text-align:left}
                                th{background:#f2f2f2}
                                .no-print{margin-bottom:14px}
                            </style>
                        </head>
                        <body>
                            <div class="no-print"><button onclick="window.print()">Print / Save as PDF</button></div>
                            <h3>Graduated Students</h3>
                            <table>
                                <thead><tr><?php foreach ($headers as $h): ?><th><?php echo e($h); ?></th><?php endforeach; ?></tr></thead>
                                <tbody>
                                <?php if (!$exportRows): ?>
                                    <tr><td colspan="<?php echo count($headers); ?>" class="text-center">No graduated students found.</td></tr>
                                <?php else: foreach ($exportRows as $r): ?>
                                    <tr><?php foreach ($r as $c): ?><td><?php echo e((string)$c); ?></td><?php endforeach; ?></tr>
                                <?php endforeach; endif; ?>
                                </tbody>
                            </table>
                        </body>
                        </html>
                        <?php
                        exit;
                    }

                    ?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                    <span>Graduated</span>
                                    <div class="d-flex gap-2">
                                        <a href="?page=admin_graduated&faculty_id=<?php echo (int)$filterFacultyId; ?>&department_id=<?php echo (int)$filterDepartmentId; ?>&year=<?php echo urlencode($filterYear); ?>&export=csv" class="btn btn-sm btn-outline-success"><i class="bi bi-filetype-csv"></i> CSV</a>
                                        <a href="?page=admin_graduated&faculty_id=<?php echo (int)$filterFacultyId; ?>&department_id=<?php echo (int)$filterDepartmentId; ?>&year=<?php echo urlencode($filterYear); ?>&export=pdf" target="_blank" class="btn btn-sm btn-outline-secondary"><i class="bi bi-filetype-pdf"></i> PDF</a>
                                        <button type="button" class="btn btn-sm btn-light" onclick="window.print()"><i class="bi bi-printer"></i> Print</button>
                                    </div>
                                </div>
                                <div class="card-body">
                                    <?php renderFlashes(); ?>
                                    <div class="alert alert-light border mb-4">
                                        Students appear here automatically once the completed academic years for their programme reach the programme duration. Restoring a student preserves all records, payments, results, and history.
                                    </div>
                                    <form method="get" class="row g-3 align-items-end mb-4">
                                        <input type="hidden" name="page" value="admin_graduated">
                                        <div class="col-md-4">
                                            <label class="form-label mb-1">Faculty</label>
                                            <select name="faculty_id" class="form-select">
                                                <option value="0">All Faculties</option>
                                                <?php foreach ($facultyOptions as $opt): ?>
                                                    <option value="<?php echo (int)$opt['id']; ?>" <?php echo $filterFacultyId === (int)$opt['id'] ? 'selected' : ''; ?>><?php echo e((string)$opt['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-md-4">
                                            <label class="form-label mb-1">Department</label>
                                            <select name="department_id" class="form-select">
                                                <option value="0">All Departments</option>
                                                <?php foreach ($departmentOptions as $opt): ?>
                                                    <option value="<?php echo (int)$opt['id']; ?>" <?php echo $filterDepartmentId === (int)$opt['id'] ? 'selected' : ''; ?>><?php echo e((string)$opt['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-md-2">
                                            <label class="form-label">Year</label>
                                            <select name="year" class="form-select">
                                                <option value="">All Years</option>
                                                <?php foreach ($yearOptions as $opt): ?>
                                                    <option value="<?php echo e($opt); ?>" <?php echo $filterYear === (string)$opt ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-md-2 d-grid">
                                            <button class="btn btn-primary" type="submit">Load</button>
                                        </div>
                                    </form>

                                    <div class="row g-3 mb-3">
                                        <div class="col-md-3"><div class="card bg-light"><div class="card-body"><div class="text-muted small">Total Graduated</div><div class="h4 mb-0"><?php echo count($graduatedRows); ?></div></div></div></div>
                                        <div class="col-md-3"><div class="card bg-light"><div class="card-body"><div class="text-muted small">Faculties</div><div class="h4 mb-0"><?php echo count(array_unique(array_map(fn($r)=>(string)($r['faculty_name'] ?? ''), $graduatedRows))); ?></div></div></div></div>
                                        <div class="col-md-3"><div class="card bg-light"><div class="card-body"><div class="text-muted small">Departments</div><div class="h4 mb-0"><?php echo count(array_unique(array_map(fn($r)=>(string)($r['department_name'] ?? ''), $graduatedRows))); ?></div></div></div></div>
                                        <div class="col-md-3"><div class="card bg-light"><div class="card-body"><div class="text-muted small">Year Filter</div><div class="h4 mb-0"><?php echo e($filterYear !== '' ? $filterYear : 'All'); ?></div></div></div></div>
                                    </div>

                                    <div class="table-responsive">
                                        <table class="table table-striped table-bordered align-middle">
                                            <thead>
                                                <tr>
                                                    <th>Student</th>
                                                    <th>Registration / Matric</th>
                                                    <th>Faculty</th>
                                                    <th>Department</th>
                                                    <th>Programme</th>
                                                    <th>Mode of Study</th>
                                                    <th>Duration</th>
                                                    <th>Completed</th>
                                                    <th>Year</th>
                                                    <th>Graduated At</th>
                                                    <th>Restore</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <?php if (!$graduatedRows): ?>
                                                    <tr><td colspan="11" class="text-center text-muted">No graduated students found for the selected filters.</td></tr>
                                                <?php else: foreach ($graduatedRows as $row): ?>
                                                    <tr>
                                                        <td>
                                                            <strong><?php echo e((string)($row['student_name'] ?? '')); ?></strong>
                                                            <div class="small text-muted"><?php echo e((string)($row['application_no'] ?? '')); ?></div>
                                                        </td>
                                                        <td>
                                                            <div><?php echo e((string)($row['registration_no'] ?? '')); ?></div>
                                                            <div class="small text-muted"><?php echo e((string)((trim((string)($row['matric_no'] ?? '')) !== '') ? $row['matric_no'] : 'Pending')); ?></div>
                                                        </td>
                                                        <td><?php echo e((string)($row['faculty_name'] ?? 'Not Set')); ?></td>
                                                        <td><?php echo e((string)($row['department_name'] ?? 'Not Set')); ?></td>
                                                        <td><?php echo e((string)($row['programme_name'] ?? 'Not Set')); ?></td>
                                                        <td><?php echo e(portalInferModeOfStudy((array)$row)); ?></td>
                                                        <td><?php echo (int)($row['duration_years'] ?? 0); ?> year(s)</td>
                                                        <td><?php echo (int)($row['completed_years'] ?? 0); ?> year(s)</td>
                                                        <td><?php echo e((string)($row['graduation_year'] ?? '')); ?></td>
                                                        <td><?php echo !empty($row['graduated_at']) ? e(date('d M Y, h:i A', strtotime((string)$row['graduated_at']))) : '—'; ?></td>
                                                        <td>
                                                            <form method="post" class="d-grid gap-2" onsubmit="return confirm('Restore this student back to active status?');">
                                                                <input type="hidden" name="action" value="restore_graduated_student">
                                                                <input type="hidden" name="student_id" value="<?php echo (int)$row['id']; ?>">
                                                                <textarea name="restore_reason" class="form-control form-control-sm mb-2" rows="2" placeholder="Reason (optional)"></textarea>
                                                                <button type="submit" class="btn btn-sm btn-outline-primary">Restore</button>
                                                            </form>
                                                        </td>
                                                    </tr>
                                                <?php endforeach; endif; ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <?php
                }
                break;

            case 'admin_previous_admission_record':
                if (isAdmin()) {
                    $allPreviousRows = portalFetchStudentMetaRows($pdo, [], true);
                    $availableYears = portalUniqueAcademicYears($allPreviousRows);
                    $selectedYear = trim((string)($_GET['year'] ?? ''));
                    if ($selectedYear === '' && $availableYears) {
                        $selectedYear = (string)$availableYears[0];
                    }

                    $filterFacultyId = (int)($_GET['faculty_id'] ?? 0);
                    $filterDepartmentId = (int)($_GET['department_id'] ?? 0);
                    $filterProgrammeId = (int)($_GET['programme_id'] ?? 0);
                    $filterSession = trim((string)($_GET['session'] ?? ''));
                    $filterSemester = trim((string)($_GET['semester'] ?? ''));
                    $filterLevel = trim((string)($_GET['level'] ?? ''));
                    $sortBy = trim((string)($_GET['sort_by'] ?? 'created_at'));
                    $sortDir = trim((string)($_GET['sort_dir'] ?? 'desc'));

                    $filters = [
                        'faculty_id' => $filterFacultyId,
                        'department_id' => $filterDepartmentId,
                        'programme_id' => $filterProgrammeId,
                        'session' => $filterSession,
                        'semester' => $filterSemester,
                        'level' => $filterLevel,
                        'year' => $selectedYear,
                    ];

                    $recordRows = portalSortMetaRows(portalFetchStudentMetaRows($pdo, $filters, true), $sortBy, $sortDir);
                    $paymentMap = portalPaymentTotalsMap($pdo, array_column($recordRows, 'id'));
                    $facultySummary = portalSummaryCountMap($recordRows, 'faculty_name');
                    $departmentSummary = portalSummaryCountMap($recordRows, 'department_name');
                    $programmeSummary = portalSummaryCountMap($recordRows, 'programme_name');
                    $sessionSummary = portalSummaryCountMap($recordRows, 'session');
                    $levelSummary = portalSummaryCountMap($recordRows, 'level');
                    $yearSummary = portalSummaryCountMap($recordRows, 'academic_year');
                    $academySummaryRows = portalCombinationSummaryRows($recordRows);

                    $sessionOptions = [];
                    $semesterOptions = [];
                    $levelOptions = [];
                    foreach ($allPreviousRows as $optionRow) {
                        $sessionOptions[(string)($optionRow['session'] ?? 'Not Set')] = (string)($optionRow['session'] ?? 'Not Set');
                        $semesterOptions[(string)($optionRow['semester'] ?? 'Not Set')] = (string)($optionRow['semester'] ?? 'Not Set');
                        $levelOptions[(string)($optionRow['level'] ?? 'Not Set')] = (string)($optionRow['level'] ?? 'Not Set');
                    }
                    ksort($sessionOptions, SORT_NATURAL);
                    ksort($semesterOptions, SORT_NATURAL);
                    ksort($levelOptions, SORT_NATURAL);

                    $facultyOptions = $pdo->query("SELECT id, name FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    $departmentOptions = $pdo->query("SELECT id, name FROM departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    $programmeOptions = portalProgrammeSelectorOptions($pdo, $filterFacultyId, $filterDepartmentId) ?: [];
                    ?>
                    <?php
                    $totalApprovedAmount = 0.0;
                    foreach ($paymentMap as $payRow) {
                        $totalApprovedAmount += (float)($payRow['approved_total'] ?? 0);
                    }
                    $selectedFacultyLabel = 'All Faculties';
                    foreach ($facultyOptions as $opt) {
                        if ($filterFacultyId === (int)($opt['id'] ?? 0)) { $selectedFacultyLabel = (string)($opt['name'] ?? 'All Faculties'); break; }
                    }
                    $selectedDepartmentLabel = 'All Departments';
                    foreach ($departmentOptions as $opt) {
                        if ($filterDepartmentId === (int)($opt['id'] ?? 0)) { $selectedDepartmentLabel = (string)($opt['name'] ?? 'All Departments'); break; }
                    }
                    $selectedProgrammeLabel = 'All Programmes';
                    $selectedProgrammeLabel = portalProgrammeSelectorLabelById($pdo, $filterProgrammeId, $filterFacultyId, $filterDepartmentId, 'All Programmes');
                    $activeFilters = [];
                    if ($selectedYear !== '') $activeFilters[] = 'Year: ' . $selectedYear;
                    if ($filterFacultyId > 0) $activeFilters[] = 'Faculty: ' . $selectedFacultyLabel;
                    if ($filterDepartmentId > 0) $activeFilters[] = 'Department: ' . $selectedDepartmentLabel;
                    if ($filterProgrammeId > 0) $activeFilters[] = 'Programme: ' . $selectedProgrammeLabel;
                    if ($filterSession !== '') $activeFilters[] = 'Session: ' . $filterSession;
                    if ($filterSemester !== '') $activeFilters[] = 'Semester: ' . $filterSemester;
                    if ($filterLevel !== '') $activeFilters[] = 'Level: ' . $filterLevel;
                    ?>
                    <style>
                        .previous-admission-page .previous-admission-shell {
                            background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%);
                            border: 1px solid rgba(13, 61, 122, 0.08);
                            border-radius: 20px;
                            box-shadow: 0 14px 34px rgba(10, 37, 64, 0.08);
                            overflow: hidden;
                        }
                        .previous-admission-page .previous-admission-header {
                            background: linear-gradient(135deg, #0b3f7b 0%, #0e5aa6 100%);
                            color: #fff;
                            padding: 1.4rem 1.5rem;
                        }
                        .previous-admission-page .previous-admission-header h4 {
                            margin: 0;
                            font-weight: 700;
                            letter-spacing: .01em;
                        }
                        .previous-admission-page .previous-admission-subtitle {
                            color: rgba(255,255,255,.82);
                            font-size: .95rem;
                            margin-top: .35rem;
                            max-width: 720px;
                        }
                        .previous-admission-page .header-badges .badge {
                            background: rgba(255,255,255,.16);
                            border: 1px solid rgba(255,255,255,.24);
                            color: #fff;
                            font-weight: 600;
                            padding: .45rem .7rem;
                        }
                        .previous-admission-page .header-actions .btn {
                            border-radius: 12px;
                            padding-inline: 1rem;
                        }
                        .previous-admission-page .filter-panel,
                        .previous-admission-page .data-card,
                        .previous-admission-page .summary-card,
                        .previous-admission-page .table-card {
                            border: 1px solid rgba(15, 45, 91, .08);
                            border-radius: 18px;
                            box-shadow: 0 10px 26px rgba(10, 37, 64, 0.06);
                        }
                        .previous-admission-page .section-label {
                            display: flex;
                            align-items: center;
                            gap: .55rem;
                            margin-bottom: 1rem;
                            color: #0b3f7b;
                            font-weight: 700;
                        }
                        .previous-admission-page .section-label i {
                            width: 34px;
                            height: 34px;
                            border-radius: 10px;
                            display: inline-flex;
                            align-items: center;
                            justify-content: center;
                            background: rgba(13, 90, 166, .10);
                        }
                        .previous-admission-page .active-filter-chip {
                            display: inline-flex;
                            align-items: center;
                            gap: .35rem;
                            background: #eef5fd;
                            color: #0b3f7b;
                            border: 1px solid rgba(13, 90, 166, .12);
                            border-radius: 999px;
                            padding: .45rem .8rem;
                            font-size: .86rem;
                            font-weight: 600;
                        }
                        .previous-admission-page .stat-card {
                            position: relative;
                            overflow: hidden;
                            min-height: 140px;
                            border-radius: 18px;
                            border: 1px solid rgba(15, 45, 91, .08);
                            background: #fff;
                            box-shadow: 0 10px 26px rgba(10, 37, 64, 0.06);
                        }
                        .previous-admission-page .stat-card::after {
                            content: '';
                            position: absolute;
                            top: -34px;
                            right: -34px;
                            width: 96px;
                            height: 96px;
                            border-radius: 50%;
                            background: rgba(13, 90, 166, .08);
                        }
                        .previous-admission-page .stat-icon {
                            width: 44px;
                            height: 44px;
                            border-radius: 14px;
                            display: inline-flex;
                            align-items: center;
                            justify-content: center;
                            background: rgba(13, 90, 166, .10);
                            color: #0b3f7b;
                            font-size: 1.15rem;
                        }
                        .previous-admission-page .stat-value {
                            font-size: 2rem;
                            line-height: 1;
                            font-weight: 800;
                            color: #0b2239;
                        }
                        .previous-admission-page .stat-label {
                            color: #5b6b7b;
                            font-size: .93rem;
                            font-weight: 600;
                        }
                        .previous-admission-page .table-card .card-header,
                        .previous-admission-page .summary-card .card-header,
                        .previous-admission-page .filter-panel .card-header {
                            background: #fff;
                            border-bottom: 1px solid rgba(15, 45, 91, .08);
                            padding: 1rem 1.15rem;
                            font-weight: 700;
                            color: #0b3f7b;
                        }
                        .previous-admission-page .table thead th {
                            background: #f3f7fc;
                            color: #0b2d57;
                            font-size: .82rem;
                            font-weight: 800;
                            text-transform: uppercase;
                            letter-spacing: .03em;
                            white-space: nowrap;
                            border-bottom: 1px solid rgba(15, 45, 91, .08);
                        }
                        .previous-admission-page .table tbody td {
                            vertical-align: middle;
                            border-color: rgba(15, 45, 91, .06);
                        }
                        .previous-admission-page .table tbody tr:hover {
                            background: rgba(13, 90, 166, .03);
                        }
                        .previous-admission-page .record-meta {
                            display: flex;
                            flex-direction: column;
                            gap: .15rem;
                        }
                        .previous-admission-page .record-meta small {
                            color: #6c7a89;
                        }
                        .previous-admission-page .summary-mini-table td,
                        .previous-admission-page .summary-mini-table th {
                            padding-top: .55rem;
                            padding-bottom: .55rem;
                        }
                        .previous-admission-page .summary-mini-table tbody tr:last-child td {
                            border-bottom: 0;
                        }
                        .previous-admission-page .summary-anchor {
                            scroll-margin-top: 100px;
                        }
                        @media (max-width: 991.98px) {
                            .previous-admission-page .previous-admission-header {
                                padding: 1.15rem;
                            }
                            .previous-admission-page .header-actions {
                                width: 100%;
                            }
                            .previous-admission-page .header-actions .btn {
                                flex: 1 1 auto;
                            }
                        }
                        @media print {
                            .previous-admission-page .header-actions,
                            .previous-admission-page form,
                            .previous-admission-page .no-print {
                                display: none !important;
                            }
                            .previous-admission-page .previous-admission-shell,
                            .previous-admission-page .filter-panel,
                            .previous-admission-page .data-card,
                            .previous-admission-page .summary-card,
                            .previous-admission-page .table-card,
                            .previous-admission-page .stat-card {
                                box-shadow: none !important;
                                border-color: #dfe6ee !important;
                            }
                        }
                    </style>
                    <div class="previous-admission-page">
                        <div class="previous-admission-shell mb-4">
                            <div class="previous-admission-header d-flex justify-content-between align-items-start flex-wrap gap-3">
                                <div>
                                    <div class="d-flex align-items-center flex-wrap gap-2 header-badges mb-2">
                                        <span class="badge">Admin Analytics</span>
                                        <span class="badge">Admission History</span>
                                        <span class="badge"><?php echo count($recordRows); ?> Record<?php echo count($recordRows) === 1 ? '' : 's'; ?></span>
                                    </div>
                                    <h4>Previous Admission Record</h4>
                                    <div class="previous-admission-subtitle">
                                        Review old admission records in a cleaner control centre, apply detailed academic filters, and inspect academy-wide performance summaries for the selected year and scope.
                                    </div>
                                </div>
                                <div class="header-actions d-flex flex-wrap gap-2">
                                    <a href="?page=admin_dashboard&amp;stats_year=<?php echo urlencode($selectedYear); ?>" class="btn btn-sm btn-outline-light">Back to Dashboard</a>
                                    <a href="#academy-summary-section" class="btn btn-sm btn-light text-primary fw-semibold">Jump to Summary</a>
                                    <button type="button" class="btn btn-sm btn-light" onclick="window.print()"><i class="bi bi-printer"></i> Print</button>
                                </div>
                            </div>

                            <div class="p-3 p-lg-4">
                                <div class="card filter-panel mb-4">
                                    <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                        <span><i class="bi bi-funnel me-2"></i>Filter & Sorting Console</span>
                                        <span class="badge bg-primary-subtle text-primary border border-primary-subtle"><?php echo count($recordRows); ?> filtered result<?php echo count($recordRows) === 1 ? '' : 's'; ?></span>
                                    </div>
                                    <div class="card-body">
                                        <div class="section-label">
                                            <i class="bi bi-sliders"></i>
                                            <span>Active filter overview</span>
                                        </div>
                                        <div class="d-flex flex-wrap gap-2 mb-4">
                                            <?php if ($activeFilters): ?>
                                                <?php foreach ($activeFilters as $filterChip): ?>
                                                    <span class="active-filter-chip"><i class="bi bi-dot"></i><?php echo e($filterChip); ?></span>
                                                <?php endforeach; ?>
                                            <?php else: ?>
                                                <span class="text-muted">No extra filters are active right now.</span>
                                            <?php endif; ?>
                                        </div>

                                        <form method="get" class="row g-3 align-items-end">
                                            <input type="hidden" name="page" value="admin_previous_admission_record">
                                            <div class="col-xl-2 col-lg-3 col-md-4">
                                                <label class="form-label fw-semibold">Year</label>
                                                <select name="year" class="form-select">
                                                    <?php foreach ($availableYears as $yearOption): ?>
                                                        <option value="<?php echo e($yearOption); ?>" <?php echo $selectedYear === (string)$yearOption ? 'selected' : ''; ?>><?php echo e($yearOption); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-xl-2 col-lg-3 col-md-4">
                                                <label class="form-label fw-semibold">Faculty</label>
                                                <select name="faculty_id" class="form-select">
                                                    <option value="0">All Faculties</option>
                                                    <?php foreach ($facultyOptions as $opt): ?>
                                                        <option value="<?php echo (int)$opt['id']; ?>" <?php echo $filterFacultyId === (int)$opt['id'] ? 'selected' : ''; ?>><?php echo e((string)$opt['name']); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-xl-2 col-lg-3 col-md-4">
                                                <label class="form-label fw-semibold">Department</label>
                                                <select name="department_id" class="form-select">
                                                    <option value="0">All Departments</option>
                                                    <?php foreach ($departmentOptions as $opt): ?>
                                                        <option value="<?php echo (int)$opt['id']; ?>" <?php echo $filterDepartmentId === (int)$opt['id'] ? 'selected' : ''; ?>><?php echo e((string)$opt['name']); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-xl-2 col-lg-3 col-md-4">
                                                <label class="form-label fw-semibold">Programme</label>
                                                <select name="programme_id" class="form-select">
                                                    <option value="0">All Programmes</option>
                                                    <?php foreach ($programmeOptions as $opt): ?>
                                                        <option value="<?php echo (int)$opt['id']; ?>" <?php echo portalProgrammeOptionSelected($pdo, $filterProgrammeId, (int)$opt['id']) ? 'selected' : ''; ?>><?php echo e((string)$opt['name']); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-xl-2 col-lg-3 col-md-4">
                                                <label class="form-label fw-semibold">Session</label>
                                                <select name="session" class="form-select">
                                                    <option value="">All Sessions</option>
                                                    <?php foreach ($sessionOptions as $sessionOpt): ?>
                                                        <option value="<?php echo e($sessionOpt); ?>" <?php echo $filterSession === (string)$sessionOpt ? 'selected' : ''; ?>><?php echo e($sessionOpt); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-xl-2 col-lg-3 col-md-4">
                                                <label class="form-label fw-semibold">Semester</label>
                                                <select name="semester" class="form-select">
                                                    <option value="">All Semesters</option>
                                                    <?php foreach ($semesterOptions as $semesterOpt): ?>
                                                        <option value="<?php echo e($semesterOpt); ?>" <?php echo $filterSemester === (string)$semesterOpt ? 'selected' : ''; ?>><?php echo e($semesterOpt); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-xl-2 col-lg-3 col-md-4">
                                                <label class="form-label fw-semibold">Level</label>
                                                <select name="level" class="form-select">
                                                    <option value="">All Levels</option>
                                                    <?php foreach ($levelOptions as $levelOpt): ?>
                                                        <option value="<?php echo e($levelOpt); ?>" <?php echo $filterLevel === (string)$levelOpt ? 'selected' : ''; ?>><?php echo e($levelOpt); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-xl-2 col-lg-3 col-md-4">
                                                <label class="form-label fw-semibold">Sort By</label>
                                                <select name="sort_by" class="form-select">
                                                    <?php foreach (['created_at' => 'Date', 'student_name' => 'Student Name', 'faculty_name' => 'Faculty', 'department_name' => 'Department', 'programme_name' => 'Programme', 'session' => 'Session', 'semester' => 'Semester', 'level' => 'Level', 'academic_year' => 'Year', 'application_no' => 'Application No.', 'admission_no' => 'Admission No.'] as $optValue => $optLabel): ?>
                                                        <option value="<?php echo e($optValue); ?>" <?php echo $sortBy === (string)$optValue ? 'selected' : ''; ?>><?php echo e($optLabel); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-xl-2 col-lg-3 col-md-4">
                                                <label class="form-label fw-semibold">Direction</label>
                                                <select name="sort_dir" class="form-select">
                                                    <option value="desc" <?php echo strtolower($sortDir) === 'desc' ? 'selected' : ''; ?>>Descending</option>
                                                    <option value="asc" <?php echo strtolower($sortDir) === 'asc' ? 'selected' : ''; ?>>Ascending</option>
                                                </select>
                                            </div>
                                            <div class="col-xl-4 col-lg-6 col-md-8 d-flex flex-wrap gap-2 no-print">
                                                <button type="submit" class="btn btn-primary"><i class="bi bi-funnel"></i> Apply Filters</button>
                                                <a href="?page=admin_previous_admission_record" class="btn btn-outline-secondary">Reset</a>
                                                <a href="#academy-summary-section" class="btn btn-outline-dark">Academy Summary</a>
                                            </div>
                                        </form>
                                    </div>
                                </div>

                                <div class="row g-3 mb-4">
                                    <div class="col-12 col-md-6 col-xl-2">
                                        <div class="stat-card p-3 h-100">
                                            <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
                                                <div class="stat-icon"><i class="bi bi-folder2-open"></i></div>
                                                <span class="badge text-bg-light">Live Count</span>
                                            </div>
                                            <div class="stat-value"><?php echo count($recordRows); ?></div>
                                            <div class="stat-label mt-2">Filtered Records</div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-md-6 col-xl-2">
                                        <div class="stat-card p-3 h-100">
                                            <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
                                                <div class="stat-icon"><i class="bi bi-buildings"></i></div>
                                                <span class="badge text-bg-light">Coverage</span>
                                            </div>
                                            <div class="stat-value"><?php echo count($facultySummary); ?></div>
                                            <div class="stat-label mt-2">Schools / Faculties</div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-md-6 col-xl-2">
                                        <div class="stat-card p-3 h-100">
                                            <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
                                                <div class="stat-icon"><i class="bi bi-diagram-3"></i></div>
                                                <span class="badge text-bg-light">Academic</span>
                                            </div>
                                            <div class="stat-value"><?php echo count($departmentSummary); ?></div>
                                            <div class="stat-label mt-2">Departments</div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-md-6 col-xl-2">
                                        <div class="stat-card p-3 h-100">
                                            <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
                                                <div class="stat-icon"><i class="bi bi-collection"></i></div>
                                                <span class="badge text-bg-light">Programme</span>
                                            </div>
                                            <div class="stat-value"><?php echo count($programmeSummary); ?></div>
                                            <div class="stat-label mt-2">Programmes</div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-md-6 col-xl-2">
                                        <div class="stat-card p-3 h-100">
                                            <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
                                                <div class="stat-icon"><i class="bi bi-grid-1x2"></i></div>
                                                <span class="badge text-bg-light">Grouped</span>
                                            </div>
                                            <div class="stat-value"><?php echo count($academySummaryRows); ?></div>
                                            <div class="stat-label mt-2">Summary Rows</div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-md-6 col-xl-2">
                                        <div class="stat-card p-3 h-100">
                                            <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
                                                <div class="stat-icon"><i class="bi bi-cash-stack"></i></div>
                                                <span class="badge text-bg-light">Payments</span>
                                            </div>
                                            <div class="stat-value" style="font-size:1.35rem; line-height:1.2;">₦<?php echo number_format($totalApprovedAmount, 2); ?></div>
                                            <div class="stat-label mt-2">Approved Payments</div>
                                        </div>
                                    </div>
                                </div>

                                <div class="card table-card mb-4">
                                    <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                        <span><i class="bi bi-table me-2"></i>Admission Records</span>
                                        <span class="badge bg-primary-subtle text-primary border border-primary-subtle"><?php echo count($recordRows); ?> row<?php echo count($recordRows) === 1 ? '' : 's'; ?></span>
                                    </div>
                                    <div class="card-body table-responsive">
                                        <table class="table table-hover align-middle mb-0">
                                            <thead>
                                                <tr>
                                                    <th>Student / Record</th>
                                                    <th>School / Faculty</th>
                                                    <th>Department</th>
                                                    <th>Programme</th>
                                                    <th>Mode of Study</th>
                                                    <th>Session</th>
                                                    <th>Semester</th>
                                                    <th>Level</th>
                                                    <th>Year</th>
                                                    <th>Total Paid</th>
                                                    <th>Status</th>
                                                    <th class="text-end">Actions</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <?php if (!$recordRows): ?>
                                                    <tr><td colspan="12" class="text-center text-muted py-4">No previous admission records matched your filters.</td></tr>
                                                <?php else: ?>
                                                    <?php foreach ($recordRows as $recordRow): ?>
                                                        <?php $recordPay = $paymentMap[(int)($recordRow['id'] ?? 0)] ?? ['approved_total' => 0]; ?>
                                                        <tr>
                                                            <td>
                                                                <div class="record-meta">
                                                                    <strong><?php echo e((string)($recordRow['student_name'] ?? full_name($recordRow))); ?></strong>
                                                                    <small>Application: <?php echo e(portalReceiptApplicationNumber($recordRow)); ?></small>
                                                                    <small>Admission: <?php echo e(portalReceiptAdmissionNumber($recordRow)); ?> &middot; Matric: <?php echo e(portalReceiptMatricNumber($recordRow)); ?></small>
                                                                </div>
                                                            </td>
                                                            <td><?php echo e((string)($recordRow['faculty_name'] ?? 'Not Set')); ?></td>
                                                            <td><?php echo e((string)($recordRow['department_name'] ?? 'Not Set')); ?></td>
                                                            <td><?php echo e((string)($recordRow['programme_name'] ?? 'Not Set')); ?></td>
                                                            <td><?php echo e(portalInferModeOfStudy((array)$recordRow)); ?></td>
                                                            <td><?php echo e((string)($recordRow['session'] ?? 'Not Set')); ?></td>
                                                            <td><?php echo e((string)($recordRow['semester'] ?? 'Not Set')); ?></td>
                                                            <td><?php echo e((string)($recordRow['level'] ?? 'Not Set')); ?></td>
                                                            <td><?php echo e((string)($recordRow['academic_year'] ?? 'Not Set')); ?></td>
                                                            <td class="fw-semibold">₦<?php echo number_format((float)($recordPay['approved_total'] ?? 0), 2); ?></td>
                                                            <td>
                                                                <span class="badge bg-<?php echo (($recordRow['application_status'] ?? '') === 'Approved') ? 'success' : 'secondary'; ?>">
                                                                    <?php echo e((string)($recordRow['application_status'] ?? 'Approved')); ?>
                                                                </span>
                                                            </td>
                                                            <td class="text-end text-nowrap">
                                                                <a href="?page=admin_students&amp;action=view&amp;id=<?php echo (int)$recordRow['id']; ?>" class="btn btn-sm btn-primary">View</a>
                                                                <a href="?page=clearance_form&amp;student_id=<?php echo (int)$recordRow['id']; ?>" class="btn btn-sm btn-outline-success">Clearance</a>
                                                            </td>
                                                        </tr>
                                                    <?php endforeach; ?>
                                                <?php endif; ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>

                                <div class="row g-4">
                                    <div class="col-12 col-xl-6">
                                        <div class="card summary-card h-100">
                                            <div class="card-header">Totals by School / Faculty</div>
                                            <div class="card-body table-responsive">
                                                <table class="table table-sm summary-mini-table mb-0">
                                                    <thead><tr><th>School / Faculty</th><th class="text-end">Total</th></tr></thead>
                                                    <tbody>
                                                        <?php if (!$facultySummary): ?>
                                                            <tr><td colspan="2" class="text-center text-muted py-3">No data available.</td></tr>
                                                        <?php else: ?>
                                                            <?php foreach ($facultySummary as $label => $count): ?>
                                                                <tr><td><?php echo e($label); ?></td><td class="text-end fw-semibold"><?php echo (int)$count; ?></td></tr>
                                                            <?php endforeach; ?>
                                                        <?php endif; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-xl-6">
                                        <div class="card summary-card h-100">
                                            <div class="card-header">Totals by Department</div>
                                            <div class="card-body table-responsive">
                                                <table class="table table-sm summary-mini-table mb-0">
                                                    <thead><tr><th>Department</th><th class="text-end">Total</th></tr></thead>
                                                    <tbody>
                                                        <?php if (!$departmentSummary): ?>
                                                            <tr><td colspan="2" class="text-center text-muted py-3">No data available.</td></tr>
                                                        <?php else: ?>
                                                            <?php foreach ($departmentSummary as $label => $count): ?>
                                                                <tr><td><?php echo e($label); ?></td><td class="text-end fw-semibold"><?php echo (int)$count; ?></td></tr>
                                                            <?php endforeach; ?>
                                                        <?php endif; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-xl-6">
                                        <div class="card summary-card h-100">
                                            <div class="card-header">Totals by Programme</div>
                                            <div class="card-body table-responsive">
                                                <table class="table table-sm summary-mini-table mb-0">
                                                    <thead><tr><th>Programme</th><th>Mode of Study</th><th class="text-end">Total</th></tr></thead>
                                                    <tbody>
                                                        <?php if (!$programmeSummary): ?>
                                                            <tr><td colspan="3" class="text-center text-muted py-3">No data available.</td></tr>
                                                        <?php else: ?>
                                                            <?php foreach ($programmeSummary as $label => $count): ?>
                                                                <tr><td><?php echo e($label); ?></td><td><?php echo e(portalInferModeOfStudy(['programme_name' => (string)$label])); ?></td><td class="text-end fw-semibold"><?php echo (int)$count; ?></td></tr>
                                                            <?php endforeach; ?>
                                                        <?php endif; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-xl-6">
                                        <div class="card summary-card h-100">
                                            <div class="card-header">Totals by Session</div>
                                            <div class="card-body table-responsive">
                                                <table class="table table-sm summary-mini-table mb-0">
                                                    <thead><tr><th>Session</th><th class="text-end">Total</th></tr></thead>
                                                    <tbody>
                                                        <?php if (!$sessionSummary): ?>
                                                            <tr><td colspan="2" class="text-center text-muted py-3">No data available.</td></tr>
                                                        <?php else: ?>
                                                            <?php foreach ($sessionSummary as $label => $count): ?>
                                                                <tr><td><?php echo e($label); ?></td><td class="text-end fw-semibold"><?php echo (int)$count; ?></td></tr>
                                                            <?php endforeach; ?>
                                                        <?php endif; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-xl-6">
                                        <div class="card summary-card h-100">
                                            <div class="card-header">Totals by Level</div>
                                            <div class="card-body table-responsive">
                                                <table class="table table-sm summary-mini-table mb-0">
                                                    <thead><tr><th>Level</th><th class="text-end">Total</th></tr></thead>
                                                    <tbody>
                                                        <?php if (!$levelSummary): ?>
                                                            <tr><td colspan="2" class="text-center text-muted py-3">No data available.</td></tr>
                                                        <?php else: ?>
                                                            <?php foreach ($levelSummary as $label => $count): ?>
                                                                <tr><td><?php echo e($label); ?></td><td class="text-end fw-semibold"><?php echo (int)$count; ?></td></tr>
                                                            <?php endforeach; ?>
                                                        <?php endif; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-xl-6">
                                        <div class="card summary-card h-100">
                                            <div class="card-header">Totals by Year</div>
                                            <div class="card-body table-responsive">
                                                <table class="table table-sm summary-mini-table mb-0">
                                                    <thead><tr><th>Year</th><th class="text-end">Total</th></tr></thead>
                                                    <tbody>
                                                        <?php if (!$yearSummary): ?>
                                                            <tr><td colspan="2" class="text-center text-muted py-3">No data available.</td></tr>
                                                        <?php else: ?>
                                                            <?php foreach ($yearSummary as $label => $count): ?>
                                                                <tr><td><?php echo e($label); ?></td><td class="text-end fw-semibold"><?php echo (int)$count; ?></td></tr>
                                                            <?php endforeach; ?>
                                                        <?php endif; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>
                                    </div>
                                </div>

                                <div class="card table-card mt-4 summary-anchor" id="academy-summary-section">
                                    <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                        <span><i class="bi bi-bar-chart-line me-2"></i>Academy Summary</span>
                                        <span class="badge bg-primary-subtle text-primary border border-primary-subtle"><?php echo count($academySummaryRows); ?> grouped row<?php echo count($academySummaryRows) === 1 ? '' : 's'; ?></span>
                                    </div>
                                    <div class="card-body table-responsive">
                                        <table class="table table-hover align-middle mb-0">
                                            <thead>
                                                <tr>
                                                    <th>School / Faculty</th>
                                                    <th>Department</th>
                                                    <th>Programme</th>
                                                    <th>Mode of Study</th>
                                                    <th>Session</th>
                                                    <th>Semester</th>
                                                    <th>Level</th>
                                                    <th>Year</th>
                                                    <th class="text-end">Total</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <?php if (!$academySummaryRows): ?>
                                                    <tr><td colspan="9" class="text-center text-muted py-4">No academy summary is available for the selected filters.</td></tr>
                                                <?php else: ?>
                                                    <?php foreach ($academySummaryRows as $summaryRow): ?>
                                                        <tr>
                                                            <td><?php echo e((string)$summaryRow['faculty_name']); ?></td>
                                                            <td><?php echo e((string)$summaryRow['department_name']); ?></td>
                                                            <td><?php echo e((string)$summaryRow['programme_name']); ?></td>
                                                            <td><?php echo e(portalInferModeOfStudy((array)$summaryRow)); ?></td>
                                                            <td><?php echo e((string)$summaryRow['session']); ?></td>
                                                            <td><?php echo e((string)$summaryRow['semester']); ?></td>
                                                            <td><?php echo e((string)$summaryRow['level']); ?></td>
                                                            <td><?php echo e((string)$summaryRow['academic_year']); ?></td>
                                                            <td class="text-end fw-semibold"><?php echo (int)$summaryRow['total']; ?></td>
                                                        </tr>
                                                    <?php endforeach; ?>
                                                <?php endif; ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <?php
                }
                break;
            case 'admin_clearance':
            case 'clearance_form':
                if (!isLoggedIn()) { redirect('?page=login'); }
                portalEnsureClearanceSupport($pdo);

                if (isAdmin()) {
                    if ($page === 'clearance_form') {
                        $redirectParams = $_GET;
                        $redirectParams['page'] = 'admin_clearance';
                        redirect('?' . http_build_query($redirectParams));
                    }
                    if (!menuEnabled($pdo, 'menu_admin_clearance') || !portalAdminCanManageClearance($pdo)) {
                        flash('danger', 'Access denied: You do not have permission to manage clearance records.');
                        redirect('?page=admin_dashboard');
                    }

                    $clearanceFilterSession = trim((string)($_REQUEST['session'] ?? portalCurrentAcademicSessionName($pdo)));
                    $clearanceSessionOptions = portalAvailableSessionOptions($pdo);
                    if ($clearanceFilterSession === '') {
                        $clearanceFilterSession = (string)($clearanceSessionOptions[0] ?? '');
                    }
                    $clearanceFilterSemester = portalNormalizeSemesterName((string)($_REQUEST['semester'] ?? 'First Semester'));
                    $clearanceFilterFacultyId = (int)($_REQUEST['faculty_id'] ?? 0);
                    $clearanceFilterDepartmentId = (int)($_REQUEST['department_id'] ?? 0);
                    $clearanceFilterProgrammeId = (int)($_REQUEST['programme_id'] ?? 0);
                    $clearanceFilterModeOfStudy = trim((string)($_REQUEST['mode_of_study'] ?? ''));
                    $clearanceFilterLevel = trim((string)($_REQUEST['level'] ?? ''));
                    $clearanceFilterStatus = trim((string)($_REQUEST['clearance_status'] ?? ''));
                    $clearanceSearch = trim((string)($_REQUEST['search'] ?? ''));
                    $selectedStudentId = (int)($_REQUEST['student_id'] ?? 0);

                    if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array((string)($_POST['action'] ?? ''), ['create_clearance_record', 'save_clearance_record', 'approve_clearance_record', 'reject_clearance_record', 'mark_pending_clearance_record', 'bulk_clearance_action'], true)) {
                        $postAction = (string)$_POST['action'];
                        $selectedStudentId = (int)($_POST['student_id'] ?? 0);
                        $clearanceFilterSession = trim((string)($_POST['session'] ?? $clearanceFilterSession));
                        $clearanceFilterSemester = portalNormalizeSemesterName((string)($_POST['semester'] ?? $clearanceFilterSemester));
                        $clearanceFilterFacultyId = (int)($_POST['faculty_id'] ?? $clearanceFilterFacultyId);
                        $clearanceFilterDepartmentId = (int)($_POST['department_id'] ?? $clearanceFilterDepartmentId);
                        $clearanceFilterProgrammeId = (int)($_POST['programme_id'] ?? $clearanceFilterProgrammeId);
                        $clearanceFilterModeOfStudy = trim((string)($_POST['mode_of_study'] ?? $clearanceFilterModeOfStudy));
                        $clearanceFilterLevel = trim((string)($_POST['level'] ?? $clearanceFilterLevel));
                        $clearanceFilterStatus = trim((string)($_POST['clearance_status'] ?? $clearanceFilterStatus));
                        $clearanceSearch = trim((string)($_POST['search'] ?? $clearanceSearch));

                        $studentStmt = $pdo->prepare("SELECT s.*, COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.faculty),''), 'Not Set') AS faculty_name, COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.department),''), 'Not Set') AS department_name, COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme),''), 'Not Set') AS programme_name, p.mode_of_study_type AS programme_mode_of_study_type, p.mode_of_study_custom AS programme_mode_of_study_custom FROM students s LEFT JOIN faculties f ON f.id = s.faculty_id LEFT JOIN departments d ON d.id = s.department_id LEFT JOIN programmes p ON p.id = s.programme_id WHERE s.id = ? LIMIT 1");

                        if ($postAction === 'bulk_clearance_action') {
                            $bulkIds = $_POST['bulk_student_ids'] ?? [];
                            if (!is_array($bulkIds)) { $bulkIds = []; }
                            $bulkIds = array_values(array_unique(array_filter(array_map('intval', $bulkIds), fn($v) => $v > 0)));
                            $bulkMode = trim((string)($_POST['bulk_mode'] ?? ''));
                            if (!$bulkIds) {
                                flash('warning', 'Please select at least one student for bulk clearance action.');
                            } elseif (!in_array($bulkMode, ['create', 'approve', 'reject', 'pending'], true)) {
                                flash('warning', 'Please choose a valid bulk clearance action.');
                            } else {
                                $done = 0;
                                foreach ($bulkIds as $bulkStudentId) {
                                    $studentStmt->execute([$bulkStudentId]);
                                    $actionStudent = $studentStmt->fetch(PDO::FETCH_ASSOC) ?: [];
                                    if (!$actionStudent) { continue; }
                                    ensureStudentNumberProgression($pdo, (int)$bulkStudentId);
                                    $snapshot = portalBuildClearanceSnapshot($pdo, $actionStudent, $clearanceFilterSession, $clearanceFilterSemester);
                                    $override = [
                                        'updated_by' => (int)($_SESSION['user_id'] ?? 0),
                                        'remarks' => trim((string)($_POST['remarks'] ?? '')),
                                        'officer_name' => trim((string)($_POST['officer_name'] ?? '')),
                                        'audit_action' => 'bulk_' . $bulkMode,
                                    ];
                                    if ($bulkMode === 'approve') $override['clearance_status'] = 'Approved';
                                    if ($bulkMode === 'reject') $override['clearance_status'] = 'Rejected';
                                    if ($bulkMode === 'pending') $override['clearance_status'] = 'Pending';
                                    portalUpsertClearanceRecord($pdo, $actionStudent, $snapshot, $override);
                                    $done++;
                                }
                                flash('success', 'Bulk clearance action completed for ' . $done . ' student(s).');
                            }
                        }

                        $studentStmt->execute([$selectedStudentId]);
                        $actionStudent = $studentStmt->fetch(PDO::FETCH_ASSOC) ?: [];

                        if ($postAction !== 'bulk_clearance_action' && !$actionStudent) {
                            flash('danger', 'Student record not found after checking all supported identifiers.');
                        } elseif ($postAction !== 'bulk_clearance_action') {
                            ensureStudentNumberProgression($pdo, (int)$selectedStudentId);
                            $studentStmt->execute([$selectedStudentId]);
                            $actionStudent = $studentStmt->fetch(PDO::FETCH_ASSOC) ?: $actionStudent;
                            $snapshot = portalBuildClearanceSnapshot($pdo, $actionStudent, $clearanceFilterSession, $clearanceFilterSemester);

                            $override = [
                                'updated_by' => (int)($_SESSION['user_id'] ?? 0),
                            ];
                            if (array_key_exists('remarks', $_POST)) {
                                $override['remarks'] = trim((string)$_POST['remarks']);
                            }
                            if (array_key_exists('officer_name', $_POST)) {
                                $override['officer_name'] = trim((string)$_POST['officer_name']);
                            }
                            if (array_key_exists('officer_signature', $_POST)) {
                                $override['officer_signature'] = trim((string)$_POST['officer_signature']);
                            }
                            $noticeTitle = 'Clearance Updated';
                            $noticeBody = 'Your clearance record for ' . $snapshot['session'] . ' - ' . $snapshot['semester'] . ' has been updated.';
                            if ($postAction === 'create_clearance_record') {
                                $override['audit_action'] = 'create';
                                portalUpsertClearanceRecord($pdo, $actionStudent, $snapshot, $override);
                                flash('success', 'Clearance record created successfully.');
                            } elseif ($postAction === 'approve_clearance_record') {
                                $override['clearance_status'] = 'Approved';
                                $override['audit_action'] = 'approve';
                                portalUpsertClearanceRecord($pdo, $actionStudent, $snapshot, $override);
                                $noticeTitle = 'Clearance Approved';
                                $noticeBody = 'Your clearance was approved for ' . $snapshot['session'] . ' - ' . $snapshot['semester'] . '.';
                                flash('success', 'Clearance approved successfully.');
                            } elseif ($postAction === 'reject_clearance_record') {
                                $override['clearance_status'] = 'Rejected';
                                $override['audit_action'] = 'reject';
                                portalUpsertClearanceRecord($pdo, $actionStudent, $snapshot, $override);
                                $noticeTitle = 'Clearance Rejected';
                                $noticeBody = 'Your clearance was rejected for ' . $snapshot['session'] . ' - ' . $snapshot['semester'] . '.';
                                flash('success', 'Clearance rejected successfully.');
                            } elseif ($postAction === 'mark_pending_clearance_record') {
                                $override['clearance_status'] = 'Pending';
                                $override['audit_action'] = 'mark_pending';
                                portalUpsertClearanceRecord($pdo, $actionStudent, $snapshot, $override);
                                $noticeTitle = 'Clearance Pending';
                                $noticeBody = 'Your clearance was marked pending for ' . $snapshot['session'] . ' - ' . $snapshot['semester'] . '.';
                                flash('success', 'Clearance marked as pending successfully.');
                            } else {
                                $override['audit_action'] = 'update';
                                portalUpsertClearanceRecord($pdo, $actionStudent, $snapshot, $override);
                                flash('success', 'Clearance record updated successfully.');
                            }

                            if (function_exists('createNotification')) {
                                createNotification($pdo, 'student', (int)$actionStudent['id'], $noticeTitle, $noticeBody, '?page=clearance_form&session=' . urlencode((string)$snapshot['session']) . '&semester=' . urlencode((string)$snapshot['semester']));
                            }
                        }

                        $redirectParams = [
                            'page' => 'admin_clearance',
                            'student_id' => $selectedStudentId,
                            'search' => $clearanceSearch,
                            'session' => $clearanceFilterSession,
                            'semester' => $clearanceFilterSemester,
                            'faculty_id' => $clearanceFilterFacultyId,
                            'department_id' => $clearanceFilterDepartmentId,
                            'programme_id' => $clearanceFilterProgrammeId,
                            'mode_of_study' => $clearanceFilterModeOfStudy,
                            'level' => $clearanceFilterLevel,
                            'clearance_status' => $clearanceFilterStatus,
                        ];
                        redirect('?' . http_build_query($redirectParams));
                    }

                    $clearanceFilters = [
                        'session' => $clearanceFilterSession,
                        'semester' => $clearanceFilterSemester,
                        'faculty_id' => $clearanceFilterFacultyId,
                        'department_id' => $clearanceFilterDepartmentId,
                        'programme_id' => $clearanceFilterProgrammeId,
                        'mode_of_study' => $clearanceFilterModeOfStudy,
                        'level' => $clearanceFilterLevel,
                        'clearance_status' => $clearanceFilterStatus,
                    ];
                    $clearanceRows = portalFetchAdminClearanceRows($pdo, $clearanceFilters, $clearanceSearch);
                    if ($selectedStudentId <= 0 && $clearanceSearch !== '' && count($clearanceRows) === 1) {
                        $selectedStudentId = (int)($clearanceRows[0]['id'] ?? 0);
                    }

                    $facultyOptions = $pdo->query("SELECT id, name FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    $departmentOptions = $pdo->query("SELECT id, name FROM departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    $programmeOptions = portalProgrammeSelectorOptions($pdo, $clearanceFilterFacultyId, $clearanceFilterDepartmentId) ?: [];
                    $modeOfStudyOptions = portalModeOfStudyOptionValues($pdo);
                    $levelOptions = [];
                    try {
                        $levelStmt = $pdo->query("SELECT DISTINCT level FROM students WHERE COALESCE(level,'') <> '' ORDER BY level ASC");
                        while ($levelStmt && ($levelRow = $levelStmt->fetch(PDO::FETCH_ASSOC))) {
                            $levelValue = trim((string)($levelRow['level'] ?? ''));
                            if ($levelValue !== '') $levelOptions[$levelValue] = $levelValue;
                        }
                    } catch (Throwable $e) {}
                    $semesterOptions = portalAvailableSemesterOptions();

                    $approvedCount = 0;
                    $pendingCount = 0;
                    $rejectedCount = 0;
                    $notCreatedCount = 0;
                    foreach ($clearanceRows as $clearanceRowSummary) {
                        $summaryStatus = (string)($clearanceRowSummary['clearance_status'] ?? 'Not Created');
                        if ($summaryStatus === 'Approved') $approvedCount++;
                        elseif ($summaryStatus === 'Rejected') $rejectedCount++;
                        elseif ($summaryStatus === 'Pending') $pendingCount++;
                        else $notCreatedCount++;
                    }

                    $selectedClearanceStudent = [];
                    $selectedClearanceRecord = [];
                    $selectedSnapshot = [];
                    $selectedNotFound = false;
                    if ($selectedStudentId > 0) {
                        $selectedStmt = $pdo->prepare("SELECT s.*, COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.faculty),''), 'Not Set') AS faculty_name, COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.department),''), 'Not Set') AS department_name, COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme),''), 'Not Set') AS programme_name FROM students s LEFT JOIN faculties f ON f.id = s.faculty_id LEFT JOIN departments d ON d.id = s.department_id LEFT JOIN programmes p ON p.id = s.programme_id WHERE s.id = ? LIMIT 1");
                        $selectedStmt->execute([$selectedStudentId]);
                        $selectedClearanceStudent = $selectedStmt->fetch(PDO::FETCH_ASSOC) ?: [];
                        if ($selectedClearanceStudent) {
                            ensureStudentNumberProgression($pdo, (int)$selectedStudentId);
                            $selectedStmt->execute([$selectedStudentId]);
                            $selectedClearanceStudent = $selectedStmt->fetch(PDO::FETCH_ASSOC) ?: $selectedClearanceStudent;
                            $selectedSnapshot = portalBuildClearanceSnapshot($pdo, $selectedClearanceStudent, $clearanceFilterSession, $clearanceFilterSemester);
                            $selectedClearanceRecord = portalUpsertClearanceRecord($pdo, $selectedClearanceStudent, $selectedSnapshot, ['updated_by' => (int)($_SESSION['user_id'] ?? 0)]);
                        } else {
                            $selectedNotFound = true;
                        }
                    }
                    $selectedPhone = portalClearanceStudentPhone($selectedClearanceStudent);
                    $selectedOfficerSignature = trim((string)($selectedClearanceRecord['officer_signature'] ?? ''));
                    if ($selectedOfficerSignature === '') {
                        $selectedOfficerSignature = (string)(portalAccountOfficerSignaturePath() ?: '');
                    }
                    $selectedOfficerName = trim((string)($selectedClearanceRecord['officer_name'] ?? setting('account_officer_signatory_name', 'Account Officer')));
                    $selectedReceiptNo = trim((string)($selectedClearanceRecord['receipt_no'] ?? ''));
                    if ($selectedReceiptNo === '' && $selectedStudentId > 0) {
                        $selectedReceiptNo = portalClearanceReceiptNumber($pdo, $selectedStudentId, $clearanceFilterSession, $clearanceFilterSemester);
                    }
                    $schoolName = (string)setting('portal_title', 'HontTech System');
                    $schoolLogoPath = portalSchoolLogoPath();
                    ?>
                    <style>
                        .clearance-receipt{background:#fff;border:1px solid #dbe5f0;border-radius:14px;padding:14px 18px;box-shadow:0 10px 28px rgba(15,23,42,.06);width:100%;max-width:min(100%,1320px);margin:0 auto;font-size:12px;line-height:1.28}
                        .clearance-receipt .receipt-head{display:flex;justify-content:space-between;gap:16px;align-items:flex-start;flex-wrap:wrap;border-bottom:2px solid #e9eef5;padding-bottom:12px;margin-bottom:14px}
                        .clearance-receipt .logo-box{width:66px;height:66px;border:1px dashed #cbd5e1;border-radius:10px;display:flex;align-items:center;justify-content:center;background:#f8fafc;overflow:hidden;color:#64748b;font-size:10px;text-align:center;padding:5px;flex:0 0 auto}
                        .clearance-receipt .logo-box img{max-width:100%;max-height:100%;object-fit:contain}
                        .clearance-receipt .receipt-title h3{margin:0 0 2px;font-size:1.18rem}
                        .clearance-receipt .receipt-title p{margin:0;color:#475569;font-size:.92rem}
                        .clearance-head-main{display:grid;grid-template-columns:minmax(260px,1.35fr) minmax(110px,.75fr) minmax(240px,1fr);gap:16px;align-items:start;flex:1 1 auto;width:100%}.clearance-ident{display:flex;gap:12px;align-items:flex-start;min-width:0}.clearance-qr-slot{display:flex;align-items:center;justify-content:center;min-height:96px}.clearance-qr-card{border:1px solid #dbe5f0;border-radius:12px;background:#fff;padding:8px;min-width:96px;min-height:96px;display:flex;align-items:center;justify-content:center;box-shadow:inset 0 0 0 1px rgba(255,255,255,.55)}.clearance-qr-card img{width:92px;height:92px;object-fit:contain;display:block}.clearance-qr-note{font-size:.72rem;color:#64748b;text-align:center;margin-top:4px}.clearance-summary{min-width:250px;display:grid;gap:6px;justify-self:end}
                        .summary-row{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}
                        .summary-label{font-weight:700;white-space:nowrap}
                        .summary-value{text-align:right}
                        .clearance-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px 10px;margin-bottom:12px}
                        .grid-item{padding:8px 10px;border:1px solid #eef2f7;border-radius:9px;background:#fbfdff;font-size:.84rem;page-break-inside:avoid;min-width:0}
                        .field-label{display:block;font-weight:700;margin-bottom:3px}
                        .field-value{display:block;overflow-wrap:anywhere;word-break:break-word}
                        .tight-break{overflow-wrap:anywhere;word-break:break-word}
                        .clearance-break{overflow-wrap:anywhere;word-break:break-word}
                        .clearance-ref{overflow-wrap:anywhere;word-break:break-word;font-size:10.6px}
                        .receipt-status-badge{display:inline-flex;align-items:center;justify-content:center;padding:.2rem .55rem;border-radius:999px;font-size:.78rem;font-weight:700;border:1px solid transparent;line-height:1.2;white-space:nowrap}
                        .receipt-status-success{background:#e8f7ee;color:#166534;border-color:#bbf7d0}
                        .receipt-status-warning{background:#fff7e6;color:#9a6700;border-color:#fde68a}
                        .receipt-status-danger{background:#fef2f2;color:#b91c1c;border-color:#fecaca}
                        .receipt-status-primary{background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe}
                        .receipt-status-secondary{background:#f1f5f9;color:#475569;border-color:#cbd5e1}
                        .receipt-status-info{background:#ecfeff;color:#0f766e;border-color:#a5f3fc}
                        .signature-row{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-top:16px;align-items:flex-end}
                        .signature-box{min-width:180px;flex:1 1 260px}
                        .signature-line{border-top:1px solid #111827;margin-top:26px;padding-top:4px;font-weight:700}
                        .signature-box img{max-height:54px;display:block;margin-bottom:4px}
                        .clearance-footer-meta{display:grid;gap:4px;justify-items:end;text-align:right}
                        .clearance-toolbar .btn{white-space:nowrap}
                        .clearance-status-card .badge{font-size:.9rem}
                        .clearance-receipt .table{margin-bottom:10px}
                        .clearance-receipt .table th,.clearance-receipt .table td{padding:6px 7px;font-size:11.2px;line-height:1.28;vertical-align:top}
                        @media (min-width: 1200px){ .clearance-receipt{padding:16px 20px;} }
                        @media (max-width: 991.98px){ .clearance-grid{grid-template-columns:repeat(2,minmax(0,1fr));} .clearance-receipt{padding:12px 14px;max-width:100%;} .clearance-summary{min-width:100%;} .summary-value{text-align:left;} .clearance-footer-meta{justify-items:start;text-align:left;} }
                        @media (max-width: 575.98px){ .clearance-grid{grid-template-columns:minmax(0,1fr);} .clearance-receipt{padding:10px 10px;} }
                        @media print {
                            @page { size:A4 portrait; margin:6mm; }
                            body * { visibility: hidden !important; }
                            #clearanceReceipt, #clearanceReceipt * { visibility: visible !important; }
                            #clearanceReceipt { position:absolute; left:0; top:0; width:100%; max-width:100%; margin:0; padding:0; box-shadow:none !important; border:none !important; font-size:11px; line-height:1.2; }
                            #clearanceReceipt .table th, #clearanceReceipt .table td{padding:4px 5px;font-size:10.4px;}
                            #clearanceReceipt .receipt-head{padding-bottom:8px;margin-bottom:8px;}
                            #clearanceReceipt .logo-box{width:58px;height:58px;}
                            #clearanceReceipt .receipt-title h3{font-size:1.02rem;}
                            #clearanceReceipt .receipt-title p{font-size:.82rem;}
                            #clearanceReceipt .clearance-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:4px 6px;margin-bottom:8px;}
                            #clearanceReceipt .grid-item{padding:4px 5px;font-size:10px;border-radius:6px;}
                            #clearanceReceipt .signature-row{margin-top:8px;gap:10px;}
                            #clearanceReceipt .signature-line{margin-top:18px;padding-top:3px;}
                            #clearanceReceipt .receipt-status-badge{padding:1px 6px;font-size:9.4px;color:#111827 !important;background:#fff !important;border-color:#94a3b8 !important;}
                            .no-print { display:none !important; }
                        }
                    </style>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card mb-4 no-print">
                                <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                    <span>Admin Clearance Management</span>
                                    <div class="d-flex gap-2 flex-wrap">
                                        <a href="?page=admin_dashboard" class="btn btn-sm btn-outline-light">Back to Dashboard</a>
                                        <a href="?page=admin_previous_admission_record" class="btn btn-sm btn-light"><i class="bi bi-clock-history"></i> Previous Admission Record</a>
                                    </div>
                                </div>
                                <div class="card-body">
                                    <div class="alert alert-light border mb-4">
                                        Search any student by Student ID, full name, application number, admission number, matric number, registration number, email, or phone number. Clearance is managed separately for each session and semester, and the same clearance record is used for both admin and student views.
                                    </div>
                                    <?php renderFlashes(); ?>
                                    <form method="get" class="row g-3 align-items-end">
                                        <input type="hidden" name="page" value="admin_clearance">
                                        <div class="col-lg-3 col-md-6">
                                            <label class="form-label">Search Student</label>
                                            <input type="text" name="search" class="form-control" value="<?php echo e($clearanceSearch); ?>" placeholder="Name, ID, application no., admission no., matric no., email, phone">
                                        </div>
                                        <div class="col-lg-2 col-md-3">
                                            <label class="form-label">Session</label>
                                            <select name="session" class="form-select">
                                                <?php foreach ($clearanceSessionOptions as $opt): ?>
                                                    <option value="<?php echo e($opt); ?>" <?php echo strcasecmp($clearanceFilterSession, $opt) === 0 ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-lg-2 col-md-3">
                                            <label class="form-label">Semester</label>
                                            <select name="semester" class="form-select">
                                                <?php foreach ($semesterOptions as $opt): ?>
                                                    <option value="<?php echo e($opt); ?>" <?php echo strcasecmp($clearanceFilterSemester, $opt) === 0 ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-lg-2 col-md-3">
                                            <label class="form-label">Faculty</label>
                                            <select name="faculty_id" class="form-select">
                                                <option value="0">All Faculties</option>
                                                <?php foreach ($facultyOptions as $opt): ?>
                                                    <option value="<?php echo (int)$opt['id']; ?>" <?php echo $clearanceFilterFacultyId === (int)$opt['id'] ? 'selected' : ''; ?>><?php echo e((string)$opt['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-lg-2 col-md-3">
                                            <label class="form-label">Department</label>
                                            <select name="department_id" class="form-select">
                                                <option value="0">All Departments</option>
                                                <?php foreach ($departmentOptions as $opt): ?>
                                                    <option value="<?php echo (int)$opt['id']; ?>" <?php echo $clearanceFilterDepartmentId === (int)$opt['id'] ? 'selected' : ''; ?>><?php echo e((string)$opt['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-lg-2 col-md-3">
                                            <label class="form-label">Programme</label>
                                            <select name="programme_id" class="form-select">
                                                <option value="0">All Programmes</option>
                                                <?php foreach ($programmeOptions as $opt): ?>
                                                    <option value="<?php echo (int)$opt['id']; ?>" <?php echo portalProgrammeOptionSelected($pdo, $clearanceFilterProgrammeId, (int)$opt['id']) ? 'selected' : ''; ?>><?php echo e((string)$opt['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-lg-2 col-md-3">
                                            <label class="form-label">Mode of Study</label>
                                            <select name="mode_of_study" class="form-select">
                                                <option value="">All Modes</option>
                                                <?php foreach ($modeOfStudyOptions as $modeOpt): ?>
                                                    <option value="<?php echo e((string)$modeOpt); ?>" <?php echo strcasecmp($clearanceFilterModeOfStudy, (string)$modeOpt) === 0 ? 'selected' : ''; ?>><?php echo e((string)$modeOpt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-lg-2 col-md-3">
                                            <label class="form-label">Level</label>
                                            <select name="level" class="form-select">
                                                <option value="">All Levels</option>
                                                <?php foreach ($levelOptions as $opt): ?>
                                                    <option value="<?php echo e($opt); ?>" <?php echo $clearanceFilterLevel === (string)$opt ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-lg-2 col-md-3">
                                            <label class="form-label">Clearance Status</label>
                                            <select name="clearance_status" class="form-select">
                                                <option value="">All Statuses</option>
                                                <?php foreach (['Pending', 'Approved', 'Rejected', 'Not Created'] as $statusOpt): ?>
                                                    <option value="<?php echo e($statusOpt); ?>" <?php echo strcasecmp($clearanceFilterStatus, $statusOpt) === 0 ? 'selected' : ''; ?>><?php echo e($statusOpt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-lg-3 col-md-6 d-flex gap-2">
                                            <button type="submit" class="btn btn-primary"><i class="bi bi-search"></i> Search / Filter</button>
                                            <a href="?page=admin_clearance" class="btn btn-outline-secondary">Reset</a>
                                        </div>
                                    </form>
                                </div>
                            </div>

                            <?php if ($clearanceSearch !== '' && !$clearanceRows): ?>
                                <div class="alert alert-warning no-print">Student record not found. Search was checked across Student ID, full name, application number, admission number, matric number, registration number, email, and phone number.</div>
                            <?php elseif ($selectedNotFound): ?>
                                <div class="alert alert-warning no-print">Student record not found for the selected clearance view.</div>
                            <?php endif; ?>

                            <div class="row g-3 mb-4 no-print">
                                <div class="col-md-3"><div class="card border-primary h-100"><div class="card-body"><div class="text-muted small">Matched Students</div><div class="fs-3 fw-bold"><?php echo count($clearanceRows); ?></div></div></div></div>
                                <div class="col-md-3"><div class="card border-success h-100"><div class="card-body"><div class="text-muted small">Approved</div><div class="fs-3 fw-bold text-success"><?php echo $approvedCount; ?></div></div></div></div>
                                <div class="col-md-3"><div class="card border-warning h-100"><div class="card-body"><div class="text-muted small">Pending</div><div class="fs-3 fw-bold text-warning"><?php echo $pendingCount; ?></div></div></div></div>
                                <div class="col-md-3"><div class="card border-danger h-100"><div class="card-body"><div class="text-muted small">Rejected / Not Created</div><div class="fs-3 fw-bold text-danger"><?php echo $rejectedCount + $notCreatedCount; ?></div></div></div></div>
                            </div>

                            <div class="card mb-4 no-print">
                                <div class="card-header">Admin Clearance Listing</div>
                                <div class="card-body">
                                    <form method="post" id="bulk-clearance-form">
                                        <input type="hidden" name="page" value="admin_clearance">
                                        <input type="hidden" name="search" value="<?php echo e($clearanceSearch); ?>">
                                        <input type="hidden" name="session" value="<?php echo e($clearanceFilterSession); ?>">
                                        <input type="hidden" name="semester" value="<?php echo e($clearanceFilterSemester); ?>">
                                        <input type="hidden" name="faculty_id" value="<?php echo (int)$clearanceFilterFacultyId; ?>">
                                        <input type="hidden" name="department_id" value="<?php echo (int)$clearanceFilterDepartmentId; ?>">
                                        <input type="hidden" name="programme_id" value="<?php echo (int)$clearanceFilterProgrammeId; ?>">
                                        <input type="hidden" name="mode_of_study" value="<?php echo e($clearanceFilterModeOfStudy); ?>">
                                        <input type="hidden" name="level" value="<?php echo e($clearanceFilterLevel); ?>">
                                        <input type="hidden" name="clearance_status" value="<?php echo e($clearanceFilterStatus); ?>">
                                        <div class="d-flex flex-wrap gap-2 align-items-end mb-3">
                                            <div>
                                                <label class="form-label">Bulk Action</label>
                                                <select class="form-select" name="bulk_mode">
                                                    <option value="">Select action</option>
                                                    <option value="create">Create Records</option>
                                                    <option value="approve">Approve Selected</option>
                                                    <option value="pending">Mark Pending</option>
                                                    <option value="reject">Reject Selected</option>
                                                </select>
                                            </div>
                                            <div class="flex-grow-1">
                                                <label class="form-label">Bulk Remarks (optional)</label>
                                                <input type="text" class="form-control" name="remarks" placeholder="Optional remark for selected students">
                                            </div>
                                            <div>
                                                <label class="form-label">Officer Name</label>
                                                <input type="text" class="form-control" name="officer_name" value="<?php echo e($selectedOfficerName); ?>">
                                            </div>
                                            <div>
                                                <button type="submit" name="action" value="bulk_clearance_action" class="btn btn-outline-primary" onclick="return confirm('Apply this bulk clearance action to the selected students?');">Run Bulk Action</button>
                                            </div>
                                        </div>
                                    <div class="table-responsive">
                                    <table class="table table-striped table-hover align-middle">
                                        <thead>
                                            <tr>
                                                <th><input type="checkbox" class="form-check-input" id="bulk-clearance-check-all"></th>
                                                <th>Student Name</th>
                                                <th>Application No.</th>
                                                <th>Admission No.</th>
                                                <th>Matric No.</th>
                                                <th>Registration No.</th>
                                                <th>Faculty</th>
                                                <th>Department</th>
                                                <th>Programme</th>
                                                <th>Mode of Study</th>
                                                <th>Level</th>
                                                <th>Session</th>
                                                <th>Semester</th>
                                                <th>Clearance Status</th>
                                                <th>Payment Status</th>
                                                <th>Actions</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <?php if (!$clearanceRows): ?>
                                                <tr><td colspan="16" class="text-center text-muted">No clearance records matched your current filters.</td></tr>
                                            <?php else: ?>
                                                <?php foreach ($clearanceRows as $listRow): ?>
                                                    <?php
                                                        $listParams = [
                                                            'page' => 'admin_clearance',
                                                            'student_id' => (int)$listRow['id'],
                                                            'search' => $clearanceSearch,
                                                            'session' => $clearanceFilterSession,
                                                            'semester' => $clearanceFilterSemester,
                                                            'faculty_id' => $clearanceFilterFacultyId,
                                                            'department_id' => $clearanceFilterDepartmentId,
                                                            'programme_id' => $clearanceFilterProgrammeId,
                                                            'level' => $clearanceFilterLevel,
                                                            'clearance_status' => $clearanceFilterStatus,
                                                        ];
                                                    ?>
                                                    <tr>
                                                        <td><input type="checkbox" class="form-check-input bulk-clearance-item" name="bulk_student_ids[]" value="<?php echo (int)$listRow['id']; ?>"></td>
                                                        <td><?php echo e((string)($listRow['student_name'] ?? full_name($listRow))); ?></td>
                                                        <td><?php echo e(portalReceiptApplicationNumber($listRow)); ?></td>
                                                        <td><?php echo e(portalReceiptAdmissionNumber($listRow)); ?></td>
                                                        <td><?php echo e(portalReceiptMatricNumber($listRow)); ?></td>
                                                        <td><?php echo e((string)($listRow['registration_no'] ?? '')); ?></td>
                                                        <td><?php echo e((string)($listRow['faculty_name'] ?? 'Not Set')); ?></td>
                                                        <td><?php echo e((string)($listRow['department_name'] ?? 'Not Set')); ?></td>
                                                        <td><?php echo e((string)($listRow['programme_name'] ?? 'Not Set')); ?></td>
                                                        <td><?php echo e(portalInferModeOfStudy((array)$listRow)); ?></td>
                                                        <td><?php echo e((string)($listRow['level'] ?? 'Not Set')); ?></td>
                                                        <td><?php echo e($clearanceFilterSession); ?></td>
                                                        <td><?php echo e($clearanceFilterSemester); ?></td>
                                                        <td><span class="badge bg-<?php echo portalClearanceStatusBadgeClass((string)$listRow['clearance_status']); ?>"><?php echo e((string)$listRow['clearance_status']); ?></span></td>
                                                        <td><?php echo e((string)($listRow['clearance_payment_status'] ?? 'Not Paid')); ?></td>
                                                        <td class="text-nowrap">
                                                            <a href="?<?php echo http_build_query($listParams); ?>" class="btn btn-sm btn-primary">View</a>
                                                            <a href="?<?php echo http_build_query($listParams); ?>#admin-clearance-editor" class="btn btn-sm btn-outline-secondary">Edit</a>
                                                            <form method="post" class="d-inline">
                                                                <input type="hidden" name="page" value="admin_clearance">
                                                                <input type="hidden" name="student_id" value="<?php echo (int)$listRow['id']; ?>">
                                                                <input type="hidden" name="search" value="<?php echo e($clearanceSearch); ?>">
                                                                <input type="hidden" name="session" value="<?php echo e($clearanceFilterSession); ?>">
                                                                <input type="hidden" name="semester" value="<?php echo e($clearanceFilterSemester); ?>">
                                                                <input type="hidden" name="faculty_id" value="<?php echo (int)$clearanceFilterFacultyId; ?>">
                                                                <input type="hidden" name="department_id" value="<?php echo (int)$clearanceFilterDepartmentId; ?>">
                                                                <input type="hidden" name="programme_id" value="<?php echo (int)$clearanceFilterProgrammeId; ?>">
                                                                <input type="hidden" name="level" value="<?php echo e($clearanceFilterLevel); ?>">
                                                                <input type="hidden" name="clearance_status" value="<?php echo e($clearanceFilterStatus); ?>">
                                                                <button type="submit" name="action" value="approve_clearance_record" class="btn btn-sm btn-success">Approve</button>
                                                            </form>
                                                            <form method="post" class="d-inline">
                                                                <input type="hidden" name="page" value="admin_clearance">
                                                                <input type="hidden" name="student_id" value="<?php echo (int)$listRow['id']; ?>">
                                                                <input type="hidden" name="search" value="<?php echo e($clearanceSearch); ?>">
                                                                <input type="hidden" name="session" value="<?php echo e($clearanceFilterSession); ?>">
                                                                <input type="hidden" name="semester" value="<?php echo e($clearanceFilterSemester); ?>">
                                                                <input type="hidden" name="faculty_id" value="<?php echo (int)$clearanceFilterFacultyId; ?>">
                                                                <input type="hidden" name="department_id" value="<?php echo (int)$clearanceFilterDepartmentId; ?>">
                                                                <input type="hidden" name="programme_id" value="<?php echo (int)$clearanceFilterProgrammeId; ?>">
                                                                <input type="hidden" name="level" value="<?php echo e($clearanceFilterLevel); ?>">
                                                                <input type="hidden" name="clearance_status" value="<?php echo e($clearanceFilterStatus); ?>">
                                                                <button type="submit" name="action" value="reject_clearance_record" class="btn btn-sm btn-outline-danger">Reject</button>
                                                            </form>
                                                            <a href="?<?php echo http_build_query(array_merge($listParams, ['print_receipt' => 1])); ?>" class="btn btn-sm btn-outline-dark">Print</a>
                                                            <a href="?<?php echo http_build_query(array_merge($listParams, ['download_receipt' => 1])); ?>" class="btn btn-sm btn-outline-success">Download</a>
                                                        </td>
                                                    </tr>
                                                <?php endforeach; ?>
                                            <?php endif; ?>
                                        </tbody>
                                    </table>
                                </div>
                                    </form>
                                </div>
                            </div>

                            <script>
                            document.addEventListener('DOMContentLoaded', function(){
                                const bulkAll = document.getElementById('bulk-clearance-check-all');
                                if (bulkAll) {
                                    bulkAll.addEventListener('change', function(){
                                        document.querySelectorAll('.bulk-clearance-item').forEach(function(el){ el.checked = bulkAll.checked; });
                                    });
                                }
                            });
                            </script>

                            <?php if ($selectedClearanceStudent): ?>
                                <div class="row g-4">
                                    <div class="col-lg-4 no-print">
                                        <div class="card h-100" id="admin-clearance-editor">
                                            <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                                <span>Manage Selected Clearance</span>
                                                <span class="badge bg-<?php echo portalClearanceStatusBadgeClass((string)($selectedClearanceRecord['clearance_status'] ?? 'Pending')); ?>"><?php echo e((string)($selectedClearanceRecord['clearance_status'] ?? 'Pending')); ?></span>
                                            </div>
                                            <div class="card-body">
                                                <div class="mb-3">
                                                    <strong><?php echo e(full_name($selectedClearanceStudent)); ?></strong><br>
                                                    <span class="text-muted"><?php echo e((string)($selectedClearanceStudent['faculty_name'] ?? 'Not Set')); ?> / <?php echo e((string)($selectedClearanceStudent['department_name'] ?? 'Not Set')); ?> / <?php echo e((string)($selectedClearanceStudent['programme_name'] ?? 'Not Set')); ?></span>
                                                </div>
                                                <div class="row g-2 mb-3 clearance-status-card">
                                                    <div class="col-6"><div class="border rounded p-2 h-100"><div class="text-muted small">Session</div><div class="fw-semibold"><?php echo e($clearanceFilterSession); ?></div></div></div>
                                                    <div class="col-6"><div class="border rounded p-2 h-100"><div class="text-muted small">Semester</div><div class="fw-semibold"><?php echo e($clearanceFilterSemester); ?></div></div></div>
                                                    <div class="col-6"><div class="border rounded p-2 h-100"><div class="text-muted small">Payment Status</div><div class="fw-semibold"><?php echo e((string)($selectedSnapshot['payment_status'] ?? 'Not Paid')); ?></div></div></div>
                                                    <div class="col-6"><div class="border rounded p-2 h-100"><div class="text-muted small">Readiness</div><div class="fw-semibold text-<?php echo !empty($selectedSnapshot['clearance_ready']) ? 'success' : 'danger'; ?>"><?php echo !empty($selectedSnapshot['clearance_ready']) ? 'Ready' : 'Pending'; ?></div></div></div>
                                                </div>
                                                <form method="post">
                                                    <input type="hidden" name="page" value="admin_clearance">
                                                    <input type="hidden" name="student_id" value="<?php echo (int)$selectedClearanceStudent['id']; ?>">
                                                    <input type="hidden" name="search" value="<?php echo e($clearanceSearch); ?>">
                                                    <input type="hidden" name="session" value="<?php echo e($clearanceFilterSession); ?>">
                                                    <input type="hidden" name="semester" value="<?php echo e($clearanceFilterSemester); ?>">
                                                    <input type="hidden" name="faculty_id" value="<?php echo (int)$clearanceFilterFacultyId; ?>">
                                                    <input type="hidden" name="department_id" value="<?php echo (int)$clearanceFilterDepartmentId; ?>">
                                                    <input type="hidden" name="programme_id" value="<?php echo (int)$clearanceFilterProgrammeId; ?>">
                                                    <input type="hidden" name="level" value="<?php echo e($clearanceFilterLevel); ?>">
                                                    <input type="hidden" name="clearance_status" value="<?php echo e($clearanceFilterStatus); ?>">
                                                    <div class="mb-3">
                                                        <label class="form-label">Officer Name</label>
                                                        <input type="text" name="officer_name" class="form-control" value="<?php echo e($selectedOfficerName); ?>" placeholder="Account Officer">
                                                    </div>
                                                    <div class="mb-3">
                                                        <label class="form-label">Officer Signature</label>
                                                        <div class="signature-wrapper border rounded p-2 bg-light">
                                                            <canvas id="clearance_officer_signature" class="signature-canvas"></canvas>
                                                            <div class="d-flex justify-content-between align-items-center mt-2">
                                                                <small class="text-muted">Draw a signature to override the default signature for this clearance record.</small>
                                                                <button type="button" class="btn btn-secondary btn-sm" id="clearance_officer_signature_clear">Clear Pad</button>
                                                            </div>
                                                            <input type="hidden" id="clearance_officer_signature_data" name="officer_signature" value="<?php echo htmlspecialchars($selectedOfficerSignature); ?>">
                                                        </div>
                                                    </div>
                                                    <div class="mb-3">
                                                        <label class="form-label">Remarks / Comments</label>
                                                        <textarea name="remarks" class="form-control" rows="4" placeholder="Add admin remarks or comments for this session and semester."><?php echo e((string)($selectedClearanceRecord['remarks'] ?? '')); ?></textarea>
                                                    </div>
                                                    <div class="d-flex gap-2 flex-wrap">
                                                        <button type="submit" name="action" value="save_clearance_record" class="btn btn-primary">Save Details</button>
                                                        <button type="submit" name="action" value="mark_pending_clearance_record" class="btn btn-warning">Mark Pending</button>
                                                        <button type="submit" name="action" value="approve_clearance_record" class="btn btn-success">Approve</button>
                                                        <button type="submit" name="action" value="reject_clearance_record" class="btn btn-danger">Reject</button>
                                                    </div>
                                                </form>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-lg-8">
                                        <div class="card no-print mb-3">
                                            <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2 clearance-toolbar">
                                                <span>Selected Student Clearance Profile</span>
                                                <div class="d-flex gap-2 flex-wrap">
                                                    <a href="?page=admin_students&amp;action=view&amp;id=<?php echo (int)$selectedClearanceStudent['id']; ?>" class="btn btn-outline-secondary">Open Student Profile</a>
                                                    <button type="button" class="btn btn-secondary" onclick="window.print()"><i class="bi bi-printer"></i> Print Receipt</button>
                                                    <button type="button" class="btn btn-success" onclick="downloadClearanceReceipt('clearanceReceipt', 'clearance-receipt-<?php echo (int)$selectedClearanceStudent['id']; ?>-<?php echo preg_replace('/[^A-Za-z0-9]+/', '-', strtolower($clearanceFilterSemester)); ?>.html')"><i class="bi bi-download"></i> Download Receipt</button>
                                                </div>
                                            </div>
                                            <div class="card-body">
                                                <div class="row g-3 mb-3">
                                                    <div class="col-md-3"><div class="card border-primary h-100"><div class="card-body"><div class="text-muted small">Approved Payments</div><div class="fs-4 fw-bold"><?php echo (int)($selectedSnapshot['approved_payment_count'] ?? 0); ?></div></div></div></div>
                                                    <div class="col-md-3"><div class="card border-warning h-100"><div class="card-body"><div class="text-muted small">Pending Payments</div><div class="fs-4 fw-bold"><?php echo (int)($selectedSnapshot['pending_payment_count'] ?? 0); ?></div></div></div></div>
                                                    <div class="col-md-3"><div class="card border-success h-100"><div class="card-body"><div class="text-muted small">Total Paid</div><div class="fs-4 fw-bold">₦<?php echo number_format((float)($selectedSnapshot['total_amount_paid'] ?? 0), 2); ?></div></div></div></div>
                                                    <div class="col-md-3"><div class="card border-<?php echo !empty($selectedSnapshot['clearance_ready']) ? 'success' : 'danger'; ?> h-100"><div class="card-body"><div class="text-muted small">Clearance Readiness</div><div class="fs-4 fw-bold text-<?php echo !empty($selectedSnapshot['clearance_ready']) ? 'success' : 'danger'; ?>"><?php echo !empty($selectedSnapshot['clearance_ready']) ? 'Ready' : 'Pending'; ?></div></div></div></div>
                                                </div>
                                                <?php if (!empty($selectedClearanceRecord['remarks'])): ?>
                                                    <div class="alert alert-info"><strong>Admin Remarks:</strong> <?php echo e((string)$selectedClearanceRecord['remarks']); ?></div>
                                                <?php endif; ?>
                                                <?php if (!empty($selectedSnapshot['outstanding_items'])): ?>
                                                    <div class="alert alert-warning"><strong>Outstanding for <?php echo e($clearanceFilterSemester); ?>:</strong> <?php echo e(implode(', ', (array)$selectedSnapshot['outstanding_items'])); ?>.</div>
                                                <?php else: ?>
                                                    <div class="alert alert-success">All required checks are complete for <strong><?php echo e($clearanceFilterSession); ?></strong> - <strong><?php echo e($clearanceFilterSemester); ?></strong>.</div>
                                                <?php endif; ?>
                                            </div>
                                        </div>

                                        <div id="clearanceReceipt" class="clearance-receipt">
                                            <div class="receipt-head">
                                                <div class="d-flex gap-3 align-items-start flex-wrap">
                                                    <div class="logo-box">
                                                        <?php if ($schoolLogoPath): ?>
                                                            <img src="<?php echo htmlspecialchars($schoolLogoPath); ?>" alt="School Logo">
                                                        <?php else: ?>
                                                            School Logo
                                                        <?php endif; ?>
                                                    </div>
                                                    <div class="receipt-title">
                                                        <h3><?php echo e($schoolName); ?></h3>
                                                        <p>Admin Semester Clearance Receipt</p>
                                                        <p class="clearance-break">Receipt No: <strong><?php echo e($selectedReceiptNo); ?></strong></p>
                                                    </div>
                                                </div>
                                                <div class="clearance-summary">
                                                    <div class="summary-row"><span class="summary-label">Session:</span><span class="summary-value"><?php echo e($clearanceFilterSession); ?></span></div>
                                                    <div class="summary-row"><span class="summary-label">Semester:</span><span class="summary-value"><?php echo e($clearanceFilterSemester); ?></span></div>
                                                    <div class="summary-row"><span class="summary-label">Clearance Status:</span><span class="summary-value"><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($selectedClearanceRecord['clearance_status'] ?? 'Pending'))); ?>"><?php echo e((string)($selectedClearanceRecord['clearance_status'] ?? 'Pending')); ?></span></span></div>
                                                    <div class="summary-row"><span class="summary-label">Payment Status:</span><span class="summary-value"><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($selectedSnapshot['payment_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($selectedSnapshot['payment_status'] ?? 'Not Paid')); ?></span></span></div>
                                                    <div class="summary-row"><span class="summary-label">Total Payment Amount:</span><span class="summary-value"><strong>₦<?php echo number_format((float)($selectedSnapshot['total_amount_paid'] ?? 0), 2); ?></strong></span></div>
                                                </div>
                                            </div>

                                            <div class="clearance-grid">
                                                <div class="grid-item"><span class="field-label">Student Full Name</span><span class="field-value"><?php echo e(full_name($selectedClearanceStudent)); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Application Number</span><span class="field-value tight-break"><?php echo e(portalReceiptApplicationNumber($selectedClearanceStudent)); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Admission Number</span><span class="field-value tight-break"><?php echo e(portalReceiptAdmissionNumber($selectedClearanceStudent)); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Matric Number</span><span class="field-value tight-break"><?php echo e(portalReceiptMatricNumber($selectedClearanceStudent)); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Registration Number</span><span class="field-value tight-break"><?php echo e(portalStudentNumberDisplay($selectedClearanceStudent, 'registration')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Faculty</span><span class="field-value"><?php echo e((string)($selectedClearanceStudent['faculty_name'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Department</span><span class="field-value"><?php echo e((string)($selectedClearanceStudent['department_name'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Programme</span><span class="field-value"><?php echo e((string)($selectedClearanceStudent['programme_name'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Level</span><span class="field-value"><?php echo e((string)($selectedClearanceStudent['level'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Email</span><span class="field-value tight-break"><?php echo e((string)($selectedClearanceStudent['email'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Phone</span><span class="field-value"><?php echo e($selectedPhone !== '' ? $selectedPhone : 'Not Set'); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Payment Date</span><span class="field-value"><?php echo !empty($selectedSnapshot['last_payment_date']) ? e(date('d M Y', strtotime((string)$selectedSnapshot['last_payment_date']))) : 'Not Available'; ?></span></div>
                                            </div>

                                            <div class="table-responsive mb-3">
                                                <table class="table table-bordered align-middle">
                                                    <thead>
                                                        <tr>
                                                            <th>Item</th>
                                                            <th>Status</th>
                                                            <th>Amount</th>
                                                            <th>Date</th>
                                                            <th>Reference</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <?php if (empty($selectedSnapshot['paid_items'])): ?>
                                                            <tr><td colspan="5" class="text-center text-muted">No approved payment record found for this selected semester.</td></tr>
                                                        <?php else: ?>
                                                            <?php foreach ((array)$selectedSnapshot['paid_items'] as $item): ?>
                                                                <tr>
                                                                    <td><?php echo e((string)$item['label']); ?></td>
                                                                    <td><span class="receipt-status-badge receipt-status-<?php echo e((string)($item['status_class'] ?? portalClearanceReceiptStatusClass((string)($item['status'] ?? 'Pending')))); ?>"><?php echo e((string)($item['status'] ?? 'Pending')); ?></span></td>
                                                                    <td>₦<?php echo number_format((float)$item['amount'], 2); ?></td>
                                                                    <td><?php echo !empty($item['payment_date']) ? e(date('d M Y', strtotime((string)$item['payment_date']))) : '-'; ?></td>
                                                                    <td class="clearance-ref"><?php echo e((string)$item['reference']); ?></td>
                                                                </tr>
                                                            <?php endforeach; ?>
                                                        <?php endif; ?>
                                                        <tr>
                                                            <th colspan="2">Total Payment Amount</th>
                                                            <th colspan="3">₦<?php echo number_format((float)($selectedSnapshot['total_amount_paid'] ?? 0), 2); ?></th>
                                                        </tr>
                                                    </tbody>
                                                </table>
                                            </div>

                                            <div class="table-responsive mb-3">
                                                <table class="table table-bordered align-middle mb-0">
                                                    <thead>
                                                        <tr>
                                                            <th>Section</th>
                                                            <th>Status</th>
                                                            <th>Remark</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <tr><td>Bursary</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($selectedSnapshot['school_fee_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($selectedSnapshot['school_fee_status'] ?? 'Not Paid')); ?></span></td><td>Semester-based school fee confirmation for <?php echo e($clearanceFilterSemester); ?>.</td></tr>
                                                        <tr><td>Registration Unit</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($selectedSnapshot['registration_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($selectedSnapshot['registration_status'] ?? 'Not Paid')); ?></span></td><td>Registration fee validation.</td></tr>
                                                        <tr><td>Acceptance Unit</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($selectedSnapshot['acceptance_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($selectedSnapshot['acceptance_status'] ?? 'Not Paid')); ?></span></td><td>Admission acceptance verification.</td></tr>
                                                        <tr><td>Hostel Unit</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($selectedSnapshot['hostel_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($selectedSnapshot['hostel_status'] ?? 'Not Paid')); ?></span></td><td>Hostel/accommodation check within the selected session and semester.</td></tr>
                                                        <tr><td>Registry</td><td><span class="receipt-status-badge receipt-status-<?php echo trim((string)($selectedClearanceStudent['admission_no'] ?? '')) !== '' ? 'primary' : 'warning'; ?>"><?php echo trim((string)($selectedClearanceStudent['admission_no'] ?? '')) !== '' ? 'Completed' : 'Pending'; ?></span></td><td>Admission number verification.</td></tr>
                                                        <tr><td>Matriculation</td><td><span class="receipt-status-badge receipt-status-<?php echo trim((string)($selectedClearanceStudent['matric_no'] ?? '')) !== '' ? 'primary' : 'info'; ?>"><?php echo trim((string)($selectedClearanceStudent['matric_no'] ?? '')) !== '' ? 'Completed' : 'Processing'; ?></span></td><td>Matric number generation and onboarding.</td></tr>
                                                        <tr><td>Admin Clearance</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($selectedClearanceRecord['clearance_status'] ?? 'Pending'))); ?>"><?php echo e((string)($selectedClearanceRecord['clearance_status'] ?? 'Pending')); ?></span></td><td><?php echo e((string)($selectedClearanceRecord['remarks'] ?? 'No admin remark added yet.')); ?></td></tr>
                                                    </tbody>
                                                </table>
                                            </div>

                                            <div class="signature-row">
                                                <div class="signature-box">
                                                    <?php if ($selectedOfficerSignature): ?>
                                                        <img src="<?php echo htmlspecialchars($selectedOfficerSignature); ?>" alt="Account Officer Signature">
                                                    <?php endif; ?>
                                                    <div class="signature-line">Account Officer</div>
                                                    <?php if ($selectedOfficerName !== '' && strcasecmp($selectedOfficerName, 'Account Officer') !== 0): ?>
                                                        <div class="small text-muted mt-1"><?php echo e($selectedOfficerName); ?></div>
                                                    <?php endif; ?>
                                                </div>
                                                <div class="signature-box">
                                                    <div class="clearance-footer-meta">
                                                        <div><strong>Generated On:</strong> <?php echo e(date('d M Y h:i A')); ?></div>
                                                        <div><strong>Receipt Scope:</strong> <?php echo e($clearanceFilterSession . ' - ' . $clearanceFilterSemester); ?></div>
                                                        <div><strong>Remarks:</strong> <?php echo e((string)($selectedClearanceRecord['remarks'] ?? 'Nil')); ?></div>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            <?php endif; ?>
                        </div>
                    </div>
                    <script>
                    function downloadClearanceReceipt(elementId, filename) {
                        const source = document.getElementById(elementId);
                        if (!source) return;
                        const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${filename}</title><style>body{font-family:Arial,sans-serif;background:#fff;padding:8mm;color:#0f172a;font-size:12px;line-height:1.25}table{width:100%;border-collapse:collapse}th,td{border:1px solid #cbd5e1;padding:5px 6px;text-align:left;font-size:11px;line-height:1.22;vertical-align:top}.clearance-receipt{width:100%;max-width:100%;margin:0 auto}.clearance-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px 8px;margin-bottom:10px}.grid-item{padding:5px 6px;border:1px solid #eef2f7;border-radius:8px;background:#fbfdff;font-size:10.8px;min-width:0}.field-label{display:block;font-weight:700;margin-bottom:2px}.field-value,.tight-break,.clearance-break,.clearance-ref{display:block;overflow-wrap:anywhere;word-break:break-word}.receipt-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap;border-bottom:2px solid #e9eef5;padding-bottom:8px;margin-bottom:10px}.clearance-head-main{display:grid;grid-template-columns:minmax(220px,1.35fr) minmax(90px,.7fr) minmax(230px,1fr);gap:10px;align-items:start;flex:1 1 auto;width:100%}.clearance-ident{display:flex;gap:10px;align-items:flex-start;min-width:0}.logo-box{width:58px;height:58px;border:1px dashed #cbd5e1;border-radius:10px;display:flex;align-items:center;justify-content:center;background:#f8fafc;overflow:hidden;color:#64748b;font-size:10px;text-align:center;padding:5px}.logo-box img{max-width:100%;max-height:100%;object-fit:contain}.receipt-title h3{margin:0 0 2px;font-size:1.02rem}.receipt-title p{margin:0;font-size:.82rem;color:#475569}.clearance-qr-slot{display:flex;align-items:center;justify-content:center;min-height:82px}.clearance-qr-card{border:1px solid #dbe5f0;border-radius:12px;background:#fff;padding:4px;min-width:82px;min-height:82px;display:flex;align-items:center;justify-content:center}.clearance-qr-card img{width:74px;height:74px;object-fit:contain;display:block}.clearance-qr-note{font-size:8.6px;line-height:1.15;color:#64748b;text-align:center;margin-top:3px}.clearance-summary{min-width:230px;display:grid;gap:4px}.summary-row{display:flex;justify-content:space-between;gap:8px;align-items:flex-start}.summary-label{font-weight:700;white-space:nowrap}.summary-value{text-align:right}.receipt-status-badge{display:inline-block;padding:1px 6px;border-radius:999px;font-size:9.4px;font-weight:700;border:1px solid #94a3b8;color:#111827;background:#fff}.signature-row{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-top:10px;align-items:flex-end}.signature-box{min-width:180px;flex:1 1 220px}.signature-line{border-top:1px solid #111827;margin-top:18px;padding-top:3px;font-weight:600}.signature-box img{max-height:54px;display:block;margin-bottom:4px}.clearance-footer-meta{display:grid;gap:4px;justify-items:end;text-align:right}@page{size:A4 portrait;margin:6mm}</style></head><body>${source.outerHTML}</body></html>`;
                        const blob = new Blob([html], {type: 'text/html'});
                        const link = document.createElement('a');
                        link.href = URL.createObjectURL(blob);
                        link.download = filename;
                        document.body.appendChild(link);
                        link.click();
                        setTimeout(() => {
                            URL.revokeObjectURL(link.href);
                            link.remove();
                        }, 300);
                    }
                    document.addEventListener('DOMContentLoaded', function() {
                        <?php if (!empty($_GET['print_receipt']) && $selectedClearanceStudent): ?>
                            window.print();
                        <?php endif; ?>
                        <?php if (!empty($_GET['download_receipt']) && $selectedClearanceStudent): ?>
                            downloadClearanceReceipt('clearanceReceipt', 'clearance-receipt-<?php echo (int)$selectedClearanceStudent['id']; ?>-<?php echo preg_replace('/[^A-Za-z0-9]+/', '-', strtolower($clearanceFilterSemester)); ?>.html');
                        <?php endif; ?>
                    });
                    </script>
                    <?php
                    break;
                }

                $viewerId = function_exists('portalResolveStudentViewerId')
                    ? portalResolveStudentViewerId($pdo, false)
                    : (int)($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0);

                $stmt = $pdo->prepare("SELECT s.*, COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.faculty),''), 'Not Set') AS faculty_name, COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.department),''), 'Not Set') AS department_name, COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme),''), 'Not Set') AS programme_name FROM students s LEFT JOIN faculties f ON f.id = s.faculty_id LEFT JOIN departments d ON d.id = s.department_id LEFT JOIN programmes p ON p.id = s.programme_id WHERE s.id = ? LIMIT 1");
                $stmt->execute([$viewerId]);
                $clearanceStudent = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
                if (!$clearanceStudent) {
                    echo '<div class="alert alert-warning">Student record not found.</div>';
                    break;
                }

                ensureStudentNumberProgression($pdo, (int)$viewerId);
                $stmt->execute([$viewerId]);
                $clearanceStudent = $stmt->fetch(PDO::FETCH_ASSOC) ?: $clearanceStudent;
                [$clearanceSession, $clearanceSemester] = portalSelectedSessionSemester($pdo, $clearanceStudent, 'session', 'semester');
                $clearanceSessions = portalAvailableSessionOptions($pdo, $clearanceStudent);
                $studentSnapshot = portalBuildClearanceSnapshot($pdo, $clearanceStudent, $clearanceSession, $clearanceSemester);
                $clearanceRecord = portalUpsertClearanceRecord($pdo, $clearanceStudent, $studentSnapshot, ['updated_by' => (int)($_SESSION['user_id'] ?? 0)]);
                $receiptNo = trim((string)($clearanceRecord['receipt_no'] ?? ''));
                if ($receiptNo === '') {
                    $receiptNo = portalClearanceReceiptNumber($pdo, (int)$viewerId, $clearanceSession, $clearanceSemester);
                }
                $schoolLogoPath = portalSchoolLogoPath();
                $accountOfficerSignature = trim((string)($clearanceRecord['officer_signature'] ?? ''));
                if ($accountOfficerSignature === '') {
                    $accountOfficerSignature = (string)(portalAccountOfficerSignaturePath() ?: '');
                }
                $accountOfficerName = trim((string)($clearanceRecord['officer_name'] ?? setting('account_officer_signatory_name', 'Account Officer')));
                $schoolName = (string)setting('portal_title', 'HontTech System');
                $studentPhone = portalClearanceStudentPhone($clearanceStudent);
                $clearanceRecord = portalClearanceEnsurePublicToken($pdo, $clearanceRecord);
                $clearanceQrUrl = portalClearancePublicUrl($pdo, $clearanceRecord);
                $clearanceQrSrc = $clearanceQrUrl !== '' ? generateQRCode($clearanceQrUrl) : '';
                ?>
                <style>
                        .clearance-receipt{background:#fff;border:1px solid #dbe5f0;border-radius:14px;padding:14px 18px;box-shadow:0 10px 28px rgba(15,23,42,.06);width:100%;max-width:min(100%,1320px);margin:0 auto;font-size:12px;line-height:1.28}
                        .clearance-receipt .receipt-head{display:flex;justify-content:space-between;gap:16px;align-items:flex-start;flex-wrap:wrap;border-bottom:2px solid #e9eef5;padding-bottom:12px;margin-bottom:14px}
                        .clearance-receipt .logo-box{width:66px;height:66px;border:1px dashed #cbd5e1;border-radius:10px;display:flex;align-items:center;justify-content:center;background:#f8fafc;overflow:hidden;color:#64748b;font-size:10px;text-align:center;padding:5px;flex:0 0 auto}
                        .clearance-receipt .logo-box img{max-width:100%;max-height:100%;object-fit:contain}
                        .clearance-receipt .receipt-title h3{margin:0 0 2px;font-size:1.18rem}
                        .clearance-receipt .receipt-title p{margin:0;color:#475569;font-size:.92rem}
                        .clearance-summary{min-width:250px;display:grid;gap:4px}
                        .summary-row{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}
                        .summary-label{font-weight:700;white-space:nowrap}
                        .summary-value{text-align:right}
                        .clearance-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px 10px;margin-bottom:12px}
                        .grid-item{padding:8px 10px;border:1px solid #eef2f7;border-radius:9px;background:#fbfdff;font-size:.84rem;page-break-inside:avoid;min-width:0}
                        .field-label{display:block;font-weight:700;margin-bottom:3px}
                        .field-value{display:block;overflow-wrap:anywhere;word-break:break-word}
                        .tight-break{overflow-wrap:anywhere;word-break:break-word}
                        .clearance-break{overflow-wrap:anywhere;word-break:break-word}
                        .clearance-ref{overflow-wrap:anywhere;word-break:break-word;font-size:10.6px}
                        .receipt-status-badge{display:inline-flex;align-items:center;justify-content:center;padding:.2rem .55rem;border-radius:999px;font-size:.78rem;font-weight:700;border:1px solid transparent;line-height:1.2;white-space:nowrap}
                        .receipt-status-success{background:#e8f7ee;color:#166534;border-color:#bbf7d0}
                        .receipt-status-warning{background:#fff7e6;color:#9a6700;border-color:#fde68a}
                        .receipt-status-danger{background:#fef2f2;color:#b91c1c;border-color:#fecaca}
                        .receipt-status-primary{background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe}
                        .receipt-status-secondary{background:#f1f5f9;color:#475569;border-color:#cbd5e1}
                        .receipt-status-info{background:#ecfeff;color:#0f766e;border-color:#a5f3fc}
                        .signature-row{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-top:16px;align-items:flex-end}
                        .signature-box{min-width:180px;flex:1 1 260px}
                        .signature-line{border-top:1px solid #111827;margin-top:26px;padding-top:4px;font-weight:700}
                        .signature-box img{max-height:54px;display:block;margin-bottom:4px}
                        .clearance-footer-meta{display:grid;gap:4px;justify-items:end;text-align:right}
                        .clearance-toolbar .btn{white-space:nowrap}
                        .clearance-status-card .badge{font-size:.9rem}
                        .clearance-receipt .table{margin-bottom:10px}
                        .clearance-receipt .table th,.clearance-receipt .table td{padding:6px 7px;font-size:11.2px;line-height:1.28;vertical-align:top}
                        @media (min-width: 1200px){ .clearance-receipt{padding:16px 20px;} }
                        @media (max-width: 991.98px){ .clearance-grid{grid-template-columns:repeat(2,minmax(0,1fr));} .clearance-receipt{padding:12px 14px;max-width:100%;} .clearance-summary{min-width:100%;} .summary-value{text-align:left;} .clearance-footer-meta{justify-items:start;text-align:left;} }
                        @media (max-width: 575.98px){ .clearance-grid{grid-template-columns:minmax(0,1fr);} .clearance-receipt{padding:10px 10px;} }
                        @media print {
                            @page { size:A4 portrait; margin:6mm; }
                            body * { visibility: hidden !important; }
                            #clearanceReceipt, #clearanceReceipt * { visibility: visible !important; }
                            #clearanceReceipt { position:absolute; left:0; top:0; width:100%; max-width:100%; margin:0; padding:0; box-shadow:none !important; border:none !important; font-size:11px; line-height:1.2; }
                            #clearanceReceipt .table th, #clearanceReceipt .table td{padding:4px 5px;font-size:10.4px;}
                            #clearanceReceipt .receipt-head{padding-bottom:8px;margin-bottom:8px;}
                            #clearanceReceipt .logo-box{width:58px;height:58px;}
                            #clearanceReceipt .receipt-title h3{font-size:1.02rem;}
                            #clearanceReceipt .receipt-title p{font-size:.82rem;}
                            #clearanceReceipt .clearance-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:4px 6px;margin-bottom:8px;}
                            #clearanceReceipt .grid-item{padding:4px 5px;font-size:10px;border-radius:6px;}
                            #clearanceReceipt .signature-row{margin-top:8px;gap:10px;}
                            #clearanceReceipt .signature-line{margin-top:18px;padding-top:3px;}
                            #clearanceReceipt .receipt-status-badge{padding:1px 6px;font-size:9.4px;color:#111827 !important;background:#fff !important;border-color:#94a3b8 !important;}
                            .no-print { display:none !important; }
                        }
                </style>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card no-print mb-3">
                            <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                <h4 class="mb-0">Semester-Based Clearance</h4>
                                <div class="d-flex gap-2 flex-wrap">
                                    <button type="button" class="btn btn-secondary" onclick="window.print()"><i class="bi bi-printer"></i> Print Receipt</button>
                                    <button type="button" class="btn btn-success" onclick="downloadClearanceReceipt('clearanceReceipt', 'student-clearance-receipt-<?php echo (int)$viewerId; ?>-<?php echo preg_replace('/[^A-Za-z0-9]+/', '-', strtolower($clearanceSemester)); ?>.html')"><i class="bi bi-download"></i> Download Receipt</button>
                                </div>
                            </div>
                            <div class="card-body">
                                <form method="get" class="row g-3 align-items-end mb-3 no-print">
                                    <input type="hidden" name="page" value="clearance_form">
                                    <div class="col-md-4">
                                        <label class="form-label">Session</label>
                                        <select name="session" class="form-select">
                                            <?php foreach ($clearanceSessions as $opt): ?>
                                                <option value="<?php echo e($opt); ?>" <?php echo strcasecmp($clearanceSession, $opt) === 0 ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-4">
                                        <label class="form-label">Semester</label>
                                        <select name="semester" class="form-select">
                                            <?php foreach (portalAvailableSemesterOptions() as $opt): ?>
                                                <option value="<?php echo e($opt); ?>" <?php echo strcasecmp($clearanceSemester, $opt) === 0 ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-4 d-grid">
                                        <button type="submit" class="btn btn-primary">Load Clearance</button>
                                    </div>
                                </form>
                                <div class="row g-3 mb-3">
                                    <div class="col-md-3"><div class="card border-primary h-100"><div class="card-body"><div class="text-muted small">Payment Status</div><div class="fs-4 fw-bold"><?php echo e((string)($studentSnapshot['payment_status'] ?? 'Not Paid')); ?></div></div></div></div>
                                    <div class="col-md-3"><div class="card border-<?php echo portalClearanceStatusBadgeClass((string)($clearanceRecord['clearance_status'] ?? 'Pending')); ?> h-100"><div class="card-body"><div class="text-muted small">Admin Clearance Status</div><div class="fs-4 fw-bold"><?php echo e((string)($clearanceRecord['clearance_status'] ?? 'Pending')); ?></div></div></div></div>
                                    <div class="col-md-3"><div class="card border-success h-100"><div class="card-body"><div class="text-muted small">Approved Payments</div><div class="fs-4 fw-bold"><?php echo (int)($studentSnapshot['approved_payment_count'] ?? 0); ?></div></div></div></div>
                                    <div class="col-md-3"><div class="card border-<?php echo !empty($studentSnapshot['clearance_ready']) ? 'success' : 'danger'; ?> h-100"><div class="card-body"><div class="text-muted small">Readiness</div><div class="fs-4 fw-bold text-<?php echo !empty($studentSnapshot['clearance_ready']) ? 'success' : 'danger'; ?>"><?php echo !empty($studentSnapshot['clearance_ready']) ? 'Ready' : 'Pending'; ?></div></div></div></div>
                                </div>
                                <?php if (!empty($clearanceRecord['remarks'])): ?>
                                    <div class="alert alert-info no-print"><strong>Admin Remarks:</strong> <?php echo e((string)$clearanceRecord['remarks']); ?></div>
                                <?php endif; ?>
                                <?php if (!empty($studentSnapshot['outstanding_items'])): ?>
                                    <div class="alert alert-warning no-print"><strong>Outstanding for <?php echo e($clearanceSemester); ?>:</strong> <?php echo e(implode(', ', (array)$studentSnapshot['outstanding_items'])); ?>.</div>
                                <?php else: ?>
                                    <div class="alert alert-success no-print">Clearance is fully ready for <strong><?php echo e($clearanceSession); ?></strong> - <strong><?php echo e($clearanceSemester); ?></strong>.</div>
                                <?php endif; ?>
                            </div>
                        </div>

                                        <div id="clearanceReceipt" class="clearance-receipt">
                                            <div class="receipt-head">
                                                <div class="clearance-head-main">
                                                    <div class="clearance-ident">
                                                        <div class="logo-box">
                                                            <?php if ($schoolLogoPath): ?>
                                                                <img src="<?php echo htmlspecialchars($schoolLogoPath); ?>" alt="School Logo">
                                                            <?php else: ?>
                                                                School Logo
                                                            <?php endif; ?>
                                                        </div>
                                                        <div class="receipt-title">
                                                            <h3><?php echo e($schoolName); ?></h3>
                                                            <p>Semester Clearance Receipt</p>
                                                            <p class="clearance-break">Receipt No: <strong><?php echo e($receiptNo); ?></strong></p>
                                                        </div>
                                                    </div>
                                                    <div class="clearance-qr-slot">
                                                        <div>
                                                            <div class="clearance-qr-card"><?php if ($clearanceQrSrc !== ''): ?><img src="<?php echo e($clearanceQrSrc); ?>" alt="Clearance QR Code"><?php endif; ?></div>
                                                            <?php if ($clearanceQrSrc !== ''): ?><div class="clearance-qr-note">Scan to verify clearance</div><?php endif; ?>
                                                        </div>
                                                    </div>
                                                    <div class="clearance-summary">
                                                        <div class="summary-row"><span class="summary-label">Session:</span><span class="summary-value"><?php echo e($clearanceSession); ?></span></div>
                                                        <div class="summary-row"><span class="summary-label">Semester:</span><span class="summary-value"><?php echo e($clearanceSemester); ?></span></div>
                                                        <div class="summary-row"><span class="summary-label">Clearance Status:</span><span class="summary-value"><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($clearanceRecord['clearance_status'] ?? 'Pending'))); ?>"><?php echo e((string)($clearanceRecord['clearance_status'] ?? 'Pending')); ?></span></span></div>
                                                        <div class="summary-row"><span class="summary-label">Payment Status:</span><span class="summary-value"><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($studentSnapshot['payment_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($studentSnapshot['payment_status'] ?? 'Not Paid')); ?></span></span></div>
                                                        <div class="summary-row"><span class="summary-label">Total Payment Amount:</span><span class="summary-value"><strong>₦<?php echo number_format((float)($studentSnapshot['total_amount_paid'] ?? 0), 2); ?></strong></span></div>
                                                    </div>
                                                </div>
                                            </div>

                                            <div class="clearance-grid">
                                                <div class="grid-item"><span class="field-label">Student Full Name</span><span class="field-value"><?php echo e(full_name($clearanceStudent)); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Application Number</span><span class="field-value tight-break"><?php echo e(portalReceiptApplicationNumber($clearanceStudent)); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Admission Number</span><span class="field-value tight-break"><?php echo e(portalReceiptAdmissionNumber($clearanceStudent)); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Matric Number</span><span class="field-value tight-break"><?php echo e(portalReceiptMatricNumber($clearanceStudent)); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Registration Number</span><span class="field-value tight-break"><?php echo e(portalStudentNumberDisplay($clearanceStudent, 'registration')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Faculty</span><span class="field-value"><?php echo e((string)($clearanceStudent['faculty_name'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Department</span><span class="field-value"><?php echo e((string)($clearanceStudent['department_name'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Programme</span><span class="field-value"><?php echo e((string)($clearanceStudent['programme_name'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Level</span><span class="field-value"><?php echo e((string)($clearanceStudent['level'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Email</span><span class="field-value tight-break"><?php echo e((string)($clearanceStudent['email'] ?? 'Not Set')); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Phone</span><span class="field-value"><?php echo e($studentPhone !== '' ? $studentPhone : 'Not Set'); ?></span></div>
                                                <div class="grid-item"><span class="field-label">Payment Date</span><span class="field-value"><?php echo !empty($studentSnapshot['last_payment_date']) ? e(date('d M Y', strtotime((string)$studentSnapshot['last_payment_date']))) : 'Not Available'; ?></span></div>
                                            </div>

                                            <div class="table-responsive mb-3">
                                                <table class="table table-bordered align-middle">
                                                    <thead>
                                                        <tr>
                                                            <th>Item</th>
                                                            <th>Status</th>
                                                            <th>Amount</th>
                                                            <th>Date</th>
                                                            <th>Reference</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <?php if (empty($studentSnapshot['paid_items'])): ?>
                                                            <tr><td colspan="5" class="text-center text-muted">No approved payment record found for this selected semester.</td></tr>
                                                        <?php else: ?>
                                                            <?php foreach ((array)$studentSnapshot['paid_items'] as $item): ?>
                                                                <tr>
                                                                    <td><?php echo e((string)$item['label']); ?></td>
                                                                    <td><span class="receipt-status-badge receipt-status-<?php echo e((string)($item['status_class'] ?? portalClearanceReceiptStatusClass((string)($item['status'] ?? 'Pending')))); ?>"><?php echo e((string)($item['status'] ?? 'Pending')); ?></span></td>
                                                                    <td>₦<?php echo number_format((float)$item['amount'], 2); ?></td>
                                                                    <td><?php echo !empty($item['payment_date']) ? e(date('d M Y', strtotime((string)$item['payment_date']))) : '-'; ?></td>
                                                                    <td class="clearance-ref"><?php echo e((string)$item['reference']); ?></td>
                                                                </tr>
                                                            <?php endforeach; ?>
                                                        <?php endif; ?>
                                                        <tr>
                                                            <th colspan="2">Total Payment Amount</th>
                                                            <th colspan="3">₦<?php echo number_format((float)($studentSnapshot['total_amount_paid'] ?? 0), 2); ?></th>
                                                        </tr>
                                                    </tbody>
                                                </table>
                                            </div>

                                            <div class="table-responsive mb-3">
                                                <table class="table table-bordered align-middle mb-0">
                                                    <thead>
                                                        <tr>
                                                            <th>Section</th>
                                                            <th>Status</th>
                                                            <th>Remark</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <tr><td>Bursary</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($studentSnapshot['school_fee_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($studentSnapshot['school_fee_status'] ?? 'Not Paid')); ?></span></td><td>Semester-based school fee confirmation for <?php echo e($clearanceSemester); ?>.</td></tr>
                                                        <tr><td>Registration Unit</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($studentSnapshot['registration_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($studentSnapshot['registration_status'] ?? 'Not Paid')); ?></span></td><td>Registration fee validation.</td></tr>
                                                        <tr><td>Acceptance Unit</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($studentSnapshot['acceptance_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($studentSnapshot['acceptance_status'] ?? 'Not Paid')); ?></span></td><td>Admission acceptance verification.</td></tr>
                                                        <tr><td>Hostel Unit</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($studentSnapshot['hostel_status'] ?? 'Not Paid'))); ?>"><?php echo e((string)($studentSnapshot['hostel_status'] ?? 'Not Paid')); ?></span></td><td>Hostel/accommodation check within the selected session and semester.</td></tr>
                                                        <tr><td>Registry</td><td><span class="receipt-status-badge receipt-status-<?php echo trim((string)($clearanceStudent['admission_no'] ?? '')) !== '' ? 'primary' : 'warning'; ?>"><?php echo trim((string)($clearanceStudent['admission_no'] ?? '')) !== '' ? 'Completed' : 'Pending'; ?></span></td><td>Admission number verification.</td></tr>
                                                        <tr><td>Matriculation</td><td><span class="receipt-status-badge receipt-status-<?php echo trim((string)($clearanceStudent['matric_no'] ?? '')) !== '' ? 'primary' : 'info'; ?>"><?php echo trim((string)($clearanceStudent['matric_no'] ?? '')) !== '' ? 'Completed' : 'Processing'; ?></span></td><td>Matric number generation and onboarding.</td></tr>
                                                        <tr><td>Admin Clearance</td><td><span class="receipt-status-badge receipt-status-<?php echo e(portalClearanceReceiptStatusClass((string)($clearanceRecord['clearance_status'] ?? 'Pending'))); ?>"><?php echo e((string)($clearanceRecord['clearance_status'] ?? 'Pending')); ?></span></td><td><?php echo e((string)($clearanceRecord['remarks'] ?? 'No admin remark added yet.')); ?></td></tr>
                                                    </tbody>
                                                </table>
                                            </div>

                                            <div class="signature-row">
                                                <div class="signature-box">
                                                    <?php if ($accountOfficerSignature): ?>
                                                        <img src="<?php echo htmlspecialchars($accountOfficerSignature); ?>" alt="Account Officer Signature">
                                                    <?php endif; ?>
                                                    <div class="signature-line">Account Officer</div>
                                                    <?php if ($accountOfficerName !== '' && strcasecmp($accountOfficerName, 'Account Officer') !== 0): ?>
                                                        <div class="small text-muted mt-1"><?php echo e($accountOfficerName); ?></div>
                                                    <?php endif; ?>
                                                </div>
                                                <div class="signature-box">
                                                    <div class="clearance-footer-meta">
                                                        <div><strong>Generated On:</strong> <?php echo e(date('d M Y h:i A')); ?></div>
                                                        <div><strong>Receipt Scope:</strong> <?php echo e($clearanceSession . ' - ' . $clearanceSemester); ?></div>
                                                        <div><strong>Remarks:</strong> <?php echo e((string)($clearanceRecord['remarks'] ?? 'Nil')); ?></div>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                    </div>
                </div>
                <script>
                function downloadClearanceReceipt(elementId, filename) {
                    const source = document.getElementById(elementId);
                    if (!source) return;
                    const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${filename}</title><style>body{font-family:Arial,sans-serif;background:#fff;padding:8mm;color:#0f172a;font-size:12px;line-height:1.25}table{width:100%;border-collapse:collapse}th,td{border:1px solid #cbd5e1;padding:5px 6px;text-align:left;font-size:11px;line-height:1.22;vertical-align:top}.clearance-receipt{width:100%;max-width:100%;margin:0 auto}.clearance-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px 8px;margin-bottom:10px}.grid-item{padding:5px 6px;border:1px solid #eef2f7;border-radius:8px;background:#fbfdff;font-size:10.8px;min-width:0}.field-label{display:block;font-weight:700;margin-bottom:2px}.field-value,.tight-break,.clearance-break,.clearance-ref{display:block;overflow-wrap:anywhere;word-break:break-word}.receipt-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap;border-bottom:2px solid #e9eef5;padding-bottom:8px;margin-bottom:10px}.logo-box{width:58px;height:58px;border:1px dashed #cbd5e1;border-radius:10px;display:flex;align-items:center;justify-content:center;background:#f8fafc;overflow:hidden;color:#64748b;font-size:10px;text-align:center;padding:5px}.logo-box img{max-width:100%;max-height:100%;object-fit:contain}.receipt-title h3{margin:0 0 2px;font-size:1.02rem}.receipt-title p{margin:0;font-size:.82rem;color:#475569}.clearance-summary{min-width:230px;display:grid;gap:4px}.summary-row{display:flex;justify-content:space-between;gap:8px;align-items:flex-start}.summary-label{font-weight:700;white-space:nowrap}.summary-value{text-align:right}.receipt-status-badge{display:inline-block;padding:1px 6px;border-radius:999px;font-size:9.4px;font-weight:700;border:1px solid #94a3b8;color:#111827;background:#fff}.signature-row{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-top:10px;align-items:flex-end}.signature-box{min-width:180px;flex:1 1 220px}.signature-line{border-top:1px solid #111827;margin-top:18px;padding-top:3px;font-weight:600}.signature-box img{max-height:54px;display:block;margin-bottom:4px}.clearance-footer-meta{display:grid;gap:4px;justify-items:end;text-align:right}@page{size:A4 portrait;margin:6mm}</style></head><body>${source.outerHTML}</body></html>`;
                    const blob = new Blob([html], {type: 'text/html'});
                    const link = document.createElement('a');
                    link.href = URL.createObjectURL(blob);
                    link.download = filename;
                    document.body.appendChild(link);
                    link.click();
                    setTimeout(() => {
                        URL.revokeObjectURL(link.href);
                        link.remove();
                    }, 300);
                }
                document.addEventListener('DOMContentLoaded', function() {
                    <?php if (!empty($_GET['download_receipt'])): ?>
                        downloadClearanceReceipt('clearanceReceipt', 'student-clearance-receipt-<?php echo (int)$viewerId; ?>-<?php echo preg_replace('/[^A-Za-z0-9]+/', '-', strtolower($clearanceSemester)); ?>.html');
                    <?php endif; ?>
                });
                </script>
                <?php
                break;

            case 'admin_students':
                if (isAdmin()) {
                    // Superadmin: access student account securely (impersonate)
                    if (isset($_GET['action']) && $_GET['action'] === 'login' && isset($_GET['id'])) {
                        if (!isSuperadmin($pdo)) {
                            flash('danger', 'Only Superadmin can access another user account.');
                            redirect('?page=admin_students');
                        }
                        portalStartSuperadminImpersonation($pdo, 'student', (int)$_GET['id']);
                    }

                    // Handle view student
                    if (isset($_GET['action']) && $_GET['action'] === 'view' && isset($_GET['id'])) {
                        $studentId = $_GET['id'];

                        // Get student information
                        $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                        $stmt->execute([$studentId]);
                        $student = $stmt->fetch(PDO::FETCH_ASSOC);

                        if ($student) {
                            ?>
                            <div class="row">
                                <div class="col-md-12">
                                    <div class="card">
                                        <div class="card-header d-flex justify-content-between align-items-center">
                                            <h4 class="mb-0">Student Details - <?php echo htmlspecialchars(portalStudentNumberDisplay($student, 'matric'), ENT_QUOTES, 'UTF-8'); ?></h4>
                                            <a href="?page=admin_students" class="btn btn-secondary">Back to Students</a>
                                        </div>
                                        <div class="card-body">
                                            <?php renderFlashes(); ?>
                                            <div class="row">
                                                <div class="col-md-6">
                                                    <h5>Personal Information</h5>
                                                    <table class="table table-borderless">
                                                        <tr>
                                                            <td><strong>Admission Number:</strong></td>
                                                            <td><?php echo e(portalStudentNumberDisplay($student, 'admission')); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Matric Number:</strong></td>
                                                            <td><?php echo htmlspecialchars(portalStudentNumberDisplay($student, 'matric'), ENT_QUOTES, 'UTF-8'); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Name:</strong></td>
                                                            <td><?php echo htmlspecialchars(studentFullName($student)); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Email:</strong></td>
                                                            <td><?php echo $student['email']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Phone:</strong></td>
                                                            <td><?php echo $student['mobile_phone']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>State of Origin:</strong></td>
                                                            <td><?php echo $student['state_of_origin']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Account Status:</strong></td>
                                                            <td>
                                                                <span class="status-badge account-status-badge status-<?php echo strtolower(str_replace(' ', '-', $student['account_status'])); ?>">
                                                                    <?php echo $student['account_status']; ?>
                                                                </span>
                                                            </td>
                                                        </tr>
                                                    </table>
                                                </div>
                                                <div class="col-md-6">
                                                    <h5>Application Status</h5>
                                                    <table class="table table-borderless">
                                                        <tr>
                                                            <td><strong>Application Status:</strong></td>
                                                            <td>
                                                                <span class="status-badge status-<?php echo strtolower(str_replace(' ', '-', $student['application_status'])); ?>">
                                                                    <?php echo $student['application_status']; ?>
                                                                </span>
                                                            </td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Registration Fee:</strong></td>
                                                            <td><?php echo $student['registration_fee_status']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Acceptance Fee:</strong></td>
                                                            <td><?php echo $student['acceptance_fee_status']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Hostel Fee:</strong></td>
                                                            <td><?php echo htmlspecialchars(portalStudentHostelStatusLabel($student)); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>School Fee:</strong></td>
                                                            <td><?php echo $student['school_fee_status']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Payment Plan:</strong></td>
                                                            <td><?php echo $student['payment_plan']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Payment Plan Permission:</strong></td>
                                                            <td><?php echo htmlspecialchars(portalStudentPaymentPlanPermission($student)); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>ID Card:</strong></td>
                                                            <td><?php echo $student['id_card_generated'] ? 'Generated' : 'Not Generated'; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Application Date:</strong></td>
                                                            <td><?php echo date('d M Y', strtotime($student['created_at'])); ?></td>
                                                        </tr>
                                                    </table>
                                                </div>
                                            </div>

                                            <div class="row mt-4">
                                                <div class="col-md-12">
                                                    <h5>Account Actions</h5>
                                                    <div class="d-flex flex-wrap gap-2">
                                                        <form method="post" class="row g-2 align-items-end border rounded p-3 bg-light" style="max-width:560px;">
                                                            <input type="hidden" name="action" value="update_student_payment_permission">
                                                            <input type="hidden" name="student_id" value="<?php echo (int)$studentId; ?>">
                                                            <div class="col-md-7">
                                                                <label class="form-label">School Fee Plan Permission</label>
                                                                <select class="form-select" name="payment_plan_permission">
                                                                    <?php $currentPlanPermission = portalStudentPaymentPlanPermission($student); ?>
                                                                    <option value="Both" <?php echo $currentPlanPermission === 'Both' ? 'selected' : ''; ?>>Allow Both Plans</option>
                                                                    <option value="One-time Only" <?php echo $currentPlanPermission === 'One-time Only' ? 'selected' : ''; ?>>One-time Payment Only</option>
                                                                    <option value="Installment Only" <?php echo $currentPlanPermission === 'Installment Only' ? 'selected' : ''; ?>>Installment Only</option>
                                                                </select>
                                                            </div>
                                                            <div class="col-md-5">
                                                                <button type="submit" class="btn btn-outline-primary w-100">Save Payment Permission</button>
                                                            </div>
                                                        </form>
                                                        <?php if (portalAdminStudentStatusUpdateAllowed($pdo)): ?>
                                                        <form method="post" action="" style="display: inline;">
                                                            <input type="hidden" name="action" value="update_student_status">
                                                            <input type="hidden" name="student_id" value="<?php echo $studentId; ?>">
                                                            <input type="hidden" name="student_status_token" value="<?php echo e(portalStudentStatusUpdateToken()); ?>">
                                                            <input type="hidden" name="return_url" value="<?php echo e('?page=admin_students&action=view&id=' . (int)$studentId); ?>">

                                                            <div class="d-inline-block me-2">
                                                                <select name="account_status" class="form-select form-select-sm">
                                                                    <option value="Active" <?php echo portalNormalizeStudentAccountStatus($student['account_status'] ?? 'Active') === 'Active' ? 'selected' : ''; ?>>Active</option>
                                                                    <option value="Suspended" <?php echo portalNormalizeStudentAccountStatus($student['account_status'] ?? 'Active') === 'Suspended' ? 'selected' : ''; ?>>Suspended</option>
                                                                    <option value="Blocked" <?php echo portalNormalizeStudentAccountStatus($student['account_status'] ?? 'Active') === 'Blocked' ? 'selected' : ''; ?>>Blocked</option>
                                                                </select>
                                                            </div>

                                                            <button type="submit" class="btn btn-sm btn-warning">Update Status</button>
                                                        </form>
                                                        <?php endif; ?>

                                                        <a href="?page=admin_applications&action=view&id=<?php echo (int)$studentId; ?>&edit=1#adminApplicationEditor" class="btn btn-sm btn-primary">
                                                            <i class="bi bi-pencil-square"></i> Edit Submitted Form
                                                        </a>

                                                        <button type="button" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#deleteStudentModal" data-student-id="<?php echo $studentId; ?>" data-student-name="<?php echo htmlspecialchars(studentFullName($student)); ?>">
                                                            <i class="bi bi-trash"></i> Delete Student
                                                        </button>

                                                        <?php if ($student['application_status'] === 'Approved' && !$student['id_card_generated']): ?>
                                                        <button type="button" class="btn btn-sm btn-info" onclick="generateIdCard(<?php echo $studentId; ?>)">
                                                            <i class="bi bi-card-text"></i> Generate ID Card
                                                        </button>
                                                        <?php endif; ?>
                                                    </div>
                                                </div>
                                            </div>

                                            <?php portalRenderAdminApplicationAcademicControls($pdo, $student, 'admin_students'); ?>

                                            <div class="row mt-4">
                                                <div class="col-md-12">
                                                    <h5>Payment History</h5>
                                                    <div class="table-responsive">
                                                        <table class="table table-striped">
                                                            <thead>
                                                                <tr>
                                                                    <th>Transaction ID</th>
                                                                    <th>Payment Type</th>
                                                                    <th>Amount</th>
                                                                    <th>Status</th>
                                                                    <th>Date</th>
                                                                    <th>Actions</th>
                                                                </tr>
                                                            </thead>
                                                            <tbody>
                                                                <?php
                                                                $stmt = $pdo->prepare("SELECT * FROM payment_transactions WHERE student_id = ? ORDER BY created_at DESC");
                                                                $stmt->execute([$studentId]);
                                                                $payments = $stmt->fetchAll(PDO::FETCH_ASSOC);

                                                                foreach ($payments as $payment) {
                                                                    echo "<tr data-student-id=\"" . (int)($payment['student_id'] ?? 0) . "\">";
                                                                    echo "<td>" . $payment['transaction_id'] . "</td>";
                                                                    echo "<td>" . htmlspecialchars(bestPaymentType($payment)) . "</td>";
                                                                    echo "<td>₦" . number_format($payment['amount']) . "</td>";
                                                                    echo "<td><span class=\"status-badge status-" . strtolower(str_replace(' ', '-', $payment['status'])) . "\">" . htmlspecialchars(portalPaymentApprovalStatusLabel($payment)) . "</span></td>";
                                                                    echo "<td>" . date('d M Y', strtotime($payment['created_at'])) . "</td>";
                                                                    echo "<td>
                                                                            <a href=\"?page=admin_payments&action=view&id=" . $payment['id'] . "\" class=\"btn btn-sm btn-primary\">View</a>
                                                                          </td>";
                                                                    echo "</tr>";
                                                                }
                                                                ?>
                                                            </tbody>
                                                        </table>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <!-- Delete Student Modal -->
                        <div class="modal fade" id="deleteStudentModal" tabindex="-1">
                            <div class="modal-dialog">
                                <div class="modal-content">
                                    <div class="modal-header">
                                        <h5 class="modal-title">Delete Student</h5>
                                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                                    </div>
                                    <div class="modal-body">
                                        <p>Are you sure you want to delete <strong id="deleteStudentName"></strong>?</p>
                                        <p class="text-danger">This action cannot be undone and will delete all associated data including application details, payment history, and other records.</p>
                                    </div>
                                    <div class="modal-footer">
                                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
                                        <form method="post" action="" id="deleteStudentForm">
                                            <input type="hidden" name="action" value="delete_student">
                                            <input type="hidden" name="student_id" id="deleteStudentId">
                                            <button type="submit" class="btn btn-danger">Delete</button>
                                        </form>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <script>
                        document.addEventListener('DOMContentLoaded', function() {
                            // Save signature data before form submission
                            const form = document.querySelector("form[action='']");
                            if(form) {
                                form.addEventListener("submit", function() {
                                    const canvas = document.getElementById("officer_signature");
                                    if(canvas) {
                                        const data = canvas.toDataURL();
                                        document.getElementById("officer_signature_data").value = data;
                                    }
                                });
                            }

                            // Update student status
                            window.updateStudentStatus = function(studentId) {
                                const status = document.getElementById("application_status").value;

                                if (confirm("Are you sure you want to update application status to " + status + "?")) {
                                    const form = document.createElement("form");
                                    form.method = "POST";
                                    form.innerHTML = `
                                        <input type="hidden" name="action" value="update_status">
                                        <input type="hidden" name="student_id" value="${studentId}">
                                        <input type="hidden" name="status" value="${status}">
                                    `;
                                    document.body.appendChild(form);
                                    form.submit();
                                }
                            };

                            // Update account status (Active/Suspended/Blocked)
                            (function(){
                                const pendingStatusRequests = new Set();
                                let floatingStatusMenu = null;

                                function statusClassName(status) {
                                    return 'status-' + String(status || '').trim().toLowerCase().replace(/\s+/g, '-');
                                }

                                function setStudentStatusActionBusy(dropdownEl, busy) {
                                    if (!dropdownEl) return;
                                    dropdownEl.querySelectorAll('.student-status-action').forEach(function(actionButton) {
                                        actionButton.disabled = !!busy;
                                        actionButton.classList.toggle('disabled', !!busy);
                                    });
                                }

                                function showStudentStatusFeedback(message, type) {
                                    const existing = document.getElementById('studentStatusFeedback');
                                    if (existing) existing.remove();
                                    const box = document.createElement('div');
                                    box.id = 'studentStatusFeedback';
                                    box.className = 'alert alert-' + (type || 'info') + ' alert-dismissible fade show student-status-feedback';
                                    box.setAttribute('role', 'alert');
                                    box.innerHTML = String(message || '') + '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>';
                                    document.body.appendChild(box);
                                    window.setTimeout(function(){
                                        try {
                                            if (window.bootstrap && bootstrap.Alert) {
                                                bootstrap.Alert.getOrCreateInstance(box).close();
                                            } else {
                                                box.remove();
                                            }
                                        } catch (e) {
                                            box.remove();
                                        }
                                    }, 2600);
                                }

                                function updateStudentStatusRow(studentId, status) {
                                    const row = document.querySelector('tr[data-student-id="' + studentId + '"]');
                                    if (!row) return;
                                    row.dataset.accountStatus = status;
                                    const badge = row.querySelector('.account-status-badge');
                                    if (badge) {
                                        badge.textContent = status;
                                        badge.dataset.statusValue = status;
                                        badge.className = 'status-badge account-status-badge ' + statusClassName(status);
                                    }
                                    row.querySelectorAll('.student-status-action').forEach(function(link) {
                                        const isCurrent = String(link.dataset.status || '') === String(status);
                                        link.classList.toggle('active', isCurrent);
                                        link.dataset.currentStatus = status;
                                        link.setAttribute('aria-pressed', isCurrent ? 'true' : 'false');
                                    });
                                    const scope = row.closest('.card-body') || document;
                                    const filterBtn = scope.querySelector('#filter_btn');
                                    if (filterBtn) {
                                        filterBtn.dispatchEvent(new Event('click', { bubbles: true, cancelable: true }));
                                    }
                                }

                                function positionFloatingStatusMenu(dropdownEl) {
                                    if (!dropdownEl) return;
                                    const toggle = dropdownEl.querySelector('.student-status-toggle');
                                    const menu = dropdownEl.querySelector('.student-status-menu');
                                    if (!toggle || !menu || !menu.classList.contains('show')) return;
                                    const rect = toggle.getBoundingClientRect();
                                    const menuHeight = menu.offsetHeight || 0;
                                    const menuWidth = menu.offsetWidth || 0;
                                    const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;
                                    const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
                                    let top = rect.bottom + 4;
                                    if (top + menuHeight + 8 > viewportHeight && rect.top - menuHeight - 4 >= 8) {
                                        top = rect.top - menuHeight - 4;
                                    }
                                    let left = dropdownEl.classList.contains('dropstart') ? (rect.left - menuWidth) : rect.left;
                                    left = Math.max(8, Math.min(left, viewportWidth - menuWidth - 8));
                                    top = Math.max(8, Math.min(top, viewportHeight - menuHeight - 8));
                                    menu.classList.add('student-status-menu-floating');
                                    menu.style.top = Math.round(top) + 'px';
                                    menu.style.left = Math.round(left) + 'px';
                                    menu.style.zIndex = '2000';
                                }

                                function resetFloatingStatusMenu(dropdownEl) {
                                    if (!dropdownEl) return;
                                    const menu = dropdownEl.querySelector('.student-status-menu');
                                    if (!menu) return;
                                    menu.classList.remove('student-status-menu-floating');
                                    menu.style.top = '';
                                    menu.style.left = '';
                                    menu.style.zIndex = '';
                                }

                                document.addEventListener('shown.bs.dropdown', function (event) {
                                    const dropdownEl = event.target && event.target.closest ? event.target.closest('[data-student-status-dropdown="1"]') : null;
                                    if (!dropdownEl) return;
                                    floatingStatusMenu = dropdownEl;
                                    window.requestAnimationFrame(function(){ positionFloatingStatusMenu(dropdownEl); });
                                });

                                document.addEventListener('hide.bs.dropdown', function (event) {
                                    const dropdownEl = event.target && event.target.closest ? event.target.closest('[data-student-status-dropdown="1"]') : null;
                                    if (!dropdownEl) return;
                                    resetFloatingStatusMenu(dropdownEl);
                                    if (floatingStatusMenu === dropdownEl) floatingStatusMenu = null;
                                });

                                window.addEventListener('resize', function(){ if (floatingStatusMenu) positionFloatingStatusMenu(floatingStatusMenu); });
                                window.addEventListener('scroll', function(){ if (floatingStatusMenu) positionFloatingStatusMenu(floatingStatusMenu); }, true);

                                window.updateAccountStatus = function(studentId, status, token) {
                                    studentId = parseInt(studentId || 0, 10) || 0;
                                    status = String(status || '').trim();
                                    token = String(token || '').trim();
                                    if (!studentId || !status || !token) {
                                        showStudentStatusFeedback('Unable to update status. Please refresh the page and try again.', 'danger');
                                        return;
                                    }
                                    if (pendingStatusRequests.has(studentId)) return;
                                    pendingStatusRequests.add(studentId);

                                    const row = document.querySelector('tr[data-student-id="' + studentId + '"]');
                                    const dropdownEl = row ? row.querySelector('[data-student-status-dropdown="1"]') : null;
                                    const toggle = dropdownEl ? dropdownEl.querySelector('.student-status-toggle') : null;
                                    const currentStatus = row ? String(row.dataset.accountStatus || '').trim() : '';

                                    if (currentStatus && currentStatus === status) {
                                        showStudentStatusFeedback('Student status is already <strong>' + status + '</strong>.', 'info');
                                        if (toggle && window.bootstrap && bootstrap.Dropdown) {
                                            try { bootstrap.Dropdown.getOrCreateInstance(toggle).hide(); } catch (e) {}
                                        }
                                        pendingStatusRequests.delete(studentId);
                                        return;
                                    }

                                    if (toggle) toggle.classList.add('is-loading');
                                    setStudentStatusActionBusy(dropdownEl, true);

                                    const params = new URLSearchParams();
                                    params.append('action', 'update_student_status');
                                    params.append('student_id', String(studentId));
                                    params.append('account_status', status);
                                    params.append('student_status_token', token);
                                    params.append('ajax', '1');
                                    params.append('return_url', window.location.search || '?page=admin_students');

                                    fetch(window.location.href, {
                                        method: 'POST',
                                        headers: {
                                            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                                            'X-Requested-With': 'XMLHttpRequest',
                                            'X-Portal-Student-Status-Token': token
                                        },
                                        body: params.toString(),
                                        credentials: 'same-origin'
                                    })
                                    .then(function(response){
                                        return response.text().then(function(text){
                                            let data = null;
                                            try { data = text ? JSON.parse(text) : null; } catch (e) {}
                                            if (!response.ok) {
                                                throw new Error((data && data.message) ? data.message : 'Unable to update status.');
                                            }
                                            if (!data || !data.ok) {
                                                throw new Error((data && (data.message || data.error)) ? (data.message || data.error) : 'Unable to update status.');
                                            }
                                            return data;
                                        });
                                    })
                                    .then(function(data){
                                        updateStudentStatusRow(studentId, data.account_status || status);
                                        if (toggle && window.bootstrap && bootstrap.Dropdown) {
                                            try { bootstrap.Dropdown.getOrCreateInstance(toggle).hide(); } catch (e) {}
                                        }
                                        showStudentStatusFeedback((data && data.message) ? data.message : ('Student status updated to <strong>' + (data.account_status || status) + '</strong>.'), (data && data.message_type) ? data.message_type : 'success');
                                    })
                                    .catch(function(err){
                                        showStudentStatusFeedback('Status update failed: ' + (err && err.message ? err.message : err), 'danger');
                                    })
                                    .finally(function(){
                                        pendingStatusRequests.delete(studentId);
                                        setStudentStatusActionBusy(dropdownEl, false);
                                        if (toggle) toggle.classList.remove('is-loading');
                                    });
                                };

                                document.addEventListener('click', function(event){
                                    const link = event.target.closest ? event.target.closest('.student-status-action') : null;
                                    if (!link) return;
                                    event.preventDefault();
                                    event.stopPropagation();
                                    const dropdownEl = link.closest('[data-student-status-dropdown="1"]');
                                    const studentId = link.dataset.studentId || (dropdownEl ? dropdownEl.dataset.studentId : '');
                                    const status = link.dataset.status || '';
                                    const token = dropdownEl ? dropdownEl.dataset.statusToken || '' : '';
                                    window.updateAccountStatus(studentId, status, token);
                                });
                            })();

                            // Generate ID Card
                            window.generateIdCard = function(studentId) {
                                if (confirm("Are you sure you want to generate an ID card for this student?")) {
                                    const xhr = new XMLHttpRequest();
                                    xhr.open("POST", "", true);
                                    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                                    xhr.onreadystatechange = function() {
                                        if (xhr.readyState === 4 && xhr.status === 200) {
                                            if (xhr.responseText === "success") {
                                                alert("ID card generated successfully");
                                                location.reload();
                                            } else {
                                                alert("Error: " + xhr.responseText);
                                            }
                                        }
                                    };
                                    xhr.send("action=generate_id_card&student_id=" + studentId);
                                }
                            };

                            // Delete student modal
                            const deleteStudentModal = document.getElementById('deleteStudentModal');
                            if (deleteStudentModal) deleteStudentModal.addEventListener('show.bs.modal', function(event) {
                                const button = event.relatedTarget;
                                const studentId = button.getAttribute('data-student-id');
                                const studentName = button.getAttribute('data-student-name');

                                document.getElementById('deleteStudentId').value = studentId;
                                document.getElementById('deleteStudentName').textContent = studentName;
                            });
                        });
                        </script>
                        <?php
                    } else {
                        echo '<div class="alert alert-danger">Student not found.</div>';
                    }
                } else {
                    // List all students
                    ?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header">
                                    Manage Students
                                </div>
                                <div class="card-body">

                                    <?php if (isset($_SESSION['flash_success'])): ?>
                                        <div class="alert alert-success"><?php echo htmlspecialchars($_SESSION['flash_success']); unset($_SESSION['flash_success']); ?></div>
                                    <?php endif; ?>
                                    <?php if (isset($_SESSION['flash_error'])): ?>
                                        <div class="alert alert-danger"><?php echo htmlspecialchars($_SESSION['flash_error']); unset($_SESSION['flash_error']); ?></div>
                                    <?php endif; ?>

                                    <div class="border rounded p-3 mb-3">
                                        <h6 class="mb-2"><i class="bi bi-plus-circle"></i> Assign Payment (Admin)</h6>
                                        <form method="post" class="row g-2 align-items-end">
                                            <input type="hidden" name="action" value="admin_assign_payment">
                                            <div class="col-md-3">
                                                <label class="form-label">Scope</label>
                                                <select name="assign_scope" class="form-select" id="assign_scope">
                                                    <option value="single" selected>Single Student</option>
                                                    <option value="all">All Students</option>
                                                </select>
                                            </div>
                                            <div class="col-md-3">
                                                <label class="form-label">Student (for single)</label>
                                                <?php $assignSelectedStudentId = (string)($_POST['student_id'] ?? ''); ?>
                                                <select name="student_id" class="form-select" id="assign_student">
                                                    <option value="">Select student...</option>
                                                    <?php
                                                        $st = $pdo->query("SELECT id, registration_no, application_no, admission_no, matric_no, first_name, surname, other_name FROM students ORDER BY surname, first_name, other_name");
                                                        while ($s = $st->fetch(PDO::FETCH_ASSOC)) {
                                                            $optionValue = (string)($s['id'] ?? '');
                                                            $optionLabel = studentAdminSingleSelectLabel($s);
                                                            $selected = ($assignSelectedStudentId !== '' && $assignSelectedStudentId === $optionValue) ? ' selected' : '';
                                                            echo '<option value="' . htmlspecialchars($optionValue, ENT_QUOTES, 'UTF-8') . '"' . $selected . '>' . htmlspecialchars($optionLabel, ENT_QUOTES, 'UTF-8') . '</option>';
                                                        }
                                                    ?>
                                                </select>
                                            </div>
                                            <div class="col-md-2">
                                                <label class="form-label">Payment Type</label>
                                                <select name="transaction_type" class="form-select" id="assign_payment_type" required>
                                                    <option value="Registration Fee">Registration Fee</option>
                                                    <option value="Acceptance Fee">Acceptance Fee</option>
                                                    <option value="Hostel Fee">Hostel Fee</option>
                                                    <option value="School Fee">School Fee</option>
                                                    <option value="__custom__">Custom Fee</option>
                                                </select>
                                            </div>
                                            <div class="col-md-3" id="custom_fee_name_wrap" style="display:none;">
                                                <label class="form-label">Custom Fee Name</label>
                                                <input type="text" name="custom_fee_name" class="form-control" placeholder="e.g., Lab Fee, Extra Lessons">
                                            </div>
                                            <div class="col-md-2">
                                                <label class="form-label">Amount (₦)</label>
                                                <input type="number" step="0.01" name="amount" class="form-control" required>
                                            </div>
                                            <div class="col-md-2">
                                                <label class="form-label">Status</label>
                                                <select name="status" class="form-select">
                                                    <option value="Approved" selected>Approved</option>
                                                    <option value="Pending">Pending</option>
                                                    <option value="Rejected">Rejected</option>
                                                    <option value="Successful">Successful</option>
                                                    <option value="Failed">Failed</option>
                                                </select>
                                            </div>
                                            <div class="col-md-12">
                                                <label class="form-label">Notes (optional)</label>
                                                <input type="text" name="notes" class="form-control" placeholder="e.g., Bank transfer confirmed">
                                            </div>
                                            <div class="col-md-12 d-grid">
                                                <button class="btn btn-primary" type="submit">Assign Payment</button>
                                            </div>
                                        </form>
                                        <div class="small text-muted mt-2">Tip: choose “All Students” to assign the same payment to everyone.</div>
                                        <script>
                                        (function(){
                                            const sel = document.getElementById('assign_payment_type');
                                            const wrap = document.getElementById('custom_fee_name_wrap');
                                            if(!sel || !wrap) return;
                                            const input = wrap.querySelector('input');
                                            const toggle = ()=>{
                                                const isCustom = (sel.value === '__custom__');
                                                wrap.style.display = isCustom ? '' : 'none';
                                                if (input) input.required = isCustom;
                                            };
                                            sel.addEventListener('change', toggle);
                                            toggle();
                                        })();
                                        </script>
                                    </div>

                                    <div class="mb-3">
                                        <div class="row">
                                            <div class="col-md-4">
                                                <input type="text" class="form-control" id="search" placeholder="Search by name, email, or admission number...">
                                            </div>
                                            <div class="col-md-2">
                                                <select class="form-select" id="status_filter">
                                                    <option value="">All Status</option>
                                                    <option value="Active">Active</option>
                                                    <option value="Suspended">Suspended</option>
                                                    <option value="Blocked">Blocked</option>
                                                </select>
                                            </div>
                                            <div class="col-md-2">
                                                <select class="form-select" id="application_filter">
                                                    <option value="">All Applications</option>
                                                    <option value="Pending">Pending</option>
                                                    <option value="Under Review">Under Review</option>
                                                    <option value="Approved">Approved</option>
                                                    <option value="Rejected">Rejected</option>
                                                </select>
                                            </div>
                                        </div>
                                        <div class="row">
                                            <div class="col-md-2">
                                                <button class="btn btn-primary w-100" id="filter_btn">Filter</button>
                                            </div>
                                            <div class="col-md-2">
                                                <button class="btn btn-secondary w-100" id="reset_btn">Reset</button>
                                            </div>
                                        </div>
                                    </div>

                                    <div id="studentStatusFeedbackAnchor"></div>
                                    <style>
                                        .students-table-responsive { position: relative; }
                                        .students-table-responsive .student-actions-cell { white-space: nowrap; }
                                        .students-table-responsive .student-status-dropdown .dropdown-menu {
                                            z-index: 1085;
                                            min-width: 12rem;
                                        }
                                        .students-table-responsive .student-status-dropdown .dropdown-menu.student-status-menu-floating {
                                            position: fixed !important;
                                            inset: auto auto auto auto !important;
                                            transform: none !important;
                                            margin: 0 !important;
                                        }
                                        .students-table-responsive .student-status-toggle.is-loading {
                                            pointer-events: none;
                                            opacity: .7;
                                        }
                                        .student-status-feedback {
                                            position: fixed;
                                            right: 16px;
                                            bottom: 16px;
                                            z-index: 2000;
                                            min-width: 280px;
                                            max-width: min(92vw, 420px);
                                            box-shadow: 0 0.75rem 1.5rem rgba(0,0,0,.16);
                                        }
                                    </style>
                                    <div class="table-responsive students-table-responsive" data-students-table-wrap="1">
                                        <table class="table table-striped align-middle">
                                            <thead>
                                                <tr>
                                                    <th>Matric No.</th>
                                                    <th>Name</th>
                                                    <th>School</th>
                                                    <th>Course of Study</th>
                                                    <th>Programme</th>
                                                    <th>Mode of Study</th>
                                                    <th>Email</th>
                                                    <th>Phone</th>
                                                    <th>Application</th>
                                                    <th>Account</th>
                                                    <th>ID Card</th>
                                                    <th>Date</th>
                                                    <th>Actions</th>
                                                </tr>
                                            </thead>
                                            <tbody id="students_table">
                                                <?php
                                                portalEnsureStudentAcademicMappingSupport($pdo);
                                                $stmt = $pdo->query("SELECT s.*, COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.school_name),''), NULLIF(TRIM(s.school),''), NULLIF(TRIM(s.faculty),'')) AS faculty_name, COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.course_of_study),''), NULLIF(TRIM(s.course_in_view),''), NULLIF(TRIM(s.department),'')) AS department_name, COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme_name_cache),''), NULLIF(TRIM(s.programme),'')) AS programme_name FROM students s LEFT JOIN faculties f ON f.id = s.faculty_id LEFT JOIN departments d ON d.id = s.department_id LEFT JOIN programmes p ON p.id = s.programme_id ORDER BY COALESCE(s.created_at, NOW()) DESC, s.id DESC");
                                                while ($student = $stmt->fetch(PDO::FETCH_ASSOC)) {
                                                    echo portalRenderAdminStudentListRow($pdo, $student);
                                                }
                                                ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="row mt-4">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header d-flex justify-content-between align-items-center">
                                    <span>Assigned / Custom Payments (Editable)</span>
                                    <span class="text-muted small">Edit or delete unpaid custom payments assigned to students.</span>
                                </div>
                                <div class="card-body">
                                    <?php $adminPaymentSessions = portalAvailableSessionOptions($pdo); $adminPaymentSession = trim((string)($_GET['payment_session'] ?? '')); $adminPaymentSemester = portalNormalizeSemesterName($_GET['payment_semester'] ?? 'First Semester'); ?>
                                    <form method="get" class="row g-3 align-items-end mb-3">
                                        <input type="hidden" name="page" value="admin_payments">
                                        <div class="col-md-4">
                                            <label class="form-label">Session</label>
                                            <select name="payment_session" class="form-select">
                                                <option value="">All Sessions</option>
                                                <?php foreach ($adminPaymentSessions as $opt): ?>
                                                    <option value="<?php echo e($opt); ?>" <?php echo strcasecmp($adminPaymentSession, $opt) === 0 ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-md-4">
                                            <label class="form-label">Semester</label>
                                            <select name="payment_semester" class="form-select">
                                                <option value="">All Semesters</option>
                                                <?php foreach (portalAvailableSemesterOptions() as $opt): ?>
                                                    <option value="<?php echo e($opt); ?>" <?php echo strcasecmp($adminPaymentSemester, $opt) === 0 ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-md-4 d-grid">
                                            <button type="submit" class="btn btn-outline-primary">Apply Period Filter</button>
                                        </div>
                                    </form>
                                    <div class="table-responsive">
                                        <table class="table table-striped align-middle">
                                            <thead>
                                                <tr>
                                                    <th>Student</th>
                                                    <th>Title</th>
                                                    <th>Amount</th>
                                                    <th>Status</th>
                                                    <th>Date</th>
                                                    <th>Actions</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <?php
                                                    $st = $pdo->query("SELECT ap.*, s.first_name, s.surname, s.registration_no
                                                                       FROM assigned_payments ap
                                                                       JOIN students s ON ap.student_id = s.id
                                                                       ORDER BY ap.id DESC LIMIT 300");
                                                    while ($ap = $st->fetch(PDO::FETCH_ASSOC)) {
                                                        $studentName = trim(($ap['first_name'] ?? '').' '.($ap['surname'] ?? ''));
                                                        $title = (string)($ap['title'] ?? '');
                                                        $amount = (float)($ap['amount'] ?? 0);
                                                        $status = (string)($ap['status'] ?? '');
                                                        $created = !empty($ap['created_at']) ? date('d M Y', strtotime((string)$ap['created_at'])) : '';
                                                        echo "<tr>";
                                                        echo "<td>".htmlspecialchars($studentName)." <div class='text-muted small'>".htmlspecialchars((string)($ap['registration_no'] ?? ''))."</div></td>";
                                                        echo "<td>".htmlspecialchars($title)."</td>";
                                                        echo "<td>₦".number_format($amount,2)."</td>";
                                                        echo "<td><span class='badge bg-".(($status==='Paid')?'success':(($status==='Not Paid')?'secondary':'warning'))."'>".htmlspecialchars($status)."</span></td>";
                                                        echo "<td>".htmlspecialchars($created)."</td>";
                                                        echo "<td>";
                                                        if ($status !== 'Paid') {
                                                            echo "<button class='btn btn-sm btn-outline-primary me-1 ap-edit-btn' data-id='".(int)$ap['id']."' data-title='".htmlspecialchars($title,ENT_QUOTES)."' data-amount='".htmlspecialchars((string)$amount,ENT_QUOTES)."'>Edit</button>";
                                                            echo "<a class='btn btn-sm btn-outline-danger' onclick='return confirm(\"Delete this assigned payment?\");' href='?page=admin_payments&action=delete_assigned&id=".(int)$ap['id']."'>Delete</a>";
                                                        } else {
                                                            echo "<span class='text-muted'>Locked</span>";
                                                        }
                                                        echo "</td>";
                                                        echo "</tr>";
                                                    }
                                                ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- Edit Assigned Payment Modal -->
                    <div class="modal fade" id="editAssignedPaymentModal" tabindex="-1" aria-hidden="true">
                        <div class="modal-dialog">
                            <div class="modal-content">
                                <form method="post">
                                    <div class="modal-header">
                                        <h5 class="modal-title">Edit Custom Payment</h5>
                                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                                    </div>
                                    <div class="modal-body">
                                        <input type="hidden" name="action" value="update_assigned_payment">
                                        <input type="hidden" name="assigned_id" id="ap_edit_id" value="">
                                        <div class="mb-3">
                                            <label class="form-label">Title</label>
                                            <input type="text" class="form-control" name="title" id="ap_edit_title" required>
                                        </div>
                                        <div class="mb-3">
                                            <label class="form-label">Amount (₦)</label>
                                            <input type="number" step="0.01" class="form-control" name="amount" id="ap_edit_amount" required>
                                        </div>
                                        <div class="alert alert-info small mb-0">Editing resets the assigned payment status to <strong>Not Paid</strong>.</div>
                                    </div>
                                    <div class="modal-footer">
                                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
                                        <button type="submit" class="btn btn-primary">Save Changes</button>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>

                    <script>
                        document.addEventListener('DOMContentLoaded', function(){
                            var modalEl = document.getElementById('editAssignedPaymentModal');
                            if (!modalEl) return;
                            var modal = new bootstrap.Modal(modalEl);
                            document.querySelectorAll('.ap-edit-btn').forEach(function(btn){
                                btn.addEventListener('click', function(){
                                    document.getElementById('ap_edit_id').value = this.getAttribute('data-id') || '';
                                    document.getElementById('ap_edit_title').value = this.getAttribute('data-title') || '';
                                    document.getElementById('ap_edit_amount').value = this.getAttribute('data-amount') || '';
                                    modal.show();
                                });
                            });
                        });
                    </script>

                    <?php
                }

                // Handle AJAX requests for filtering
                if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
                    if ($_POST['action'] === 'filter_students') {
                        $search = $_POST['search'] ?? '';
                        $status = $_POST['status'] ?? '';
                        $application = $_POST['application'] ?? '';

                        $query = "SELECT s.*, COALESCE(NULLIF(TRIM(f.name),''), NULLIF(TRIM(s.school_name),''), NULLIF(TRIM(s.school),''), NULLIF(TRIM(s.faculty),'')) AS faculty_name, COALESCE(NULLIF(TRIM(d.name),''), NULLIF(TRIM(s.course_of_study),''), NULLIF(TRIM(s.course_in_view),''), NULLIF(TRIM(s.department),'')) AS department_name, COALESCE(NULLIF(TRIM(p.name),''), NULLIF(TRIM(s.programme_name_cache),''), NULLIF(TRIM(s.programme),'')) AS programme_name FROM students s LEFT JOIN faculties f ON f.id = s.faculty_id LEFT JOIN departments d ON d.id = s.department_id LEFT JOIN programmes p ON p.id = s.programme_id WHERE 1=1";
                        $params = [];

                        if (!empty($search)) {
                            $query .= " AND (s.first_name LIKE ? OR s.surname LIKE ? OR s.other_name LIKE ? OR s.email LIKE ? OR COALESCE(s.school_name,'') LIKE ? OR COALESCE(s.school,'') LIKE ? OR COALESCE(s.course_of_study,'') LIKE ? OR COALESCE(s.course_in_view,'') LIKE ? OR COALESCE(s.programme_name_cache,'') LIKE ? OR COALESCE(s.programme,'') LIKE ? OR COALESCE(f.name,'') LIKE ? OR COALESCE(d.name,'') LIKE ? OR COALESCE(p.name,'') LIKE ? OR s.application_no LIKE ? OR s.admission_no LIKE ? OR s.registration_no LIKE ? OR s.matric_no LIKE ?)";
                            $searchParam = "%$search%";
                            $params = array_merge($params, [$searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam]);
                        }

                        if (!empty($status)) {
                            $query .= " AND s.account_status = ?";
                            $params[] = $status;
                        }

                        if (!empty($application)) {
                            $query .= " AND s.application_status = ?";
                            $params[] = $application;
                        }

                        $query .= " ORDER BY COALESCE(s.created_at, NOW()) DESC, s.id DESC";

                        portalEnsureStudentAcademicMappingSupport($pdo);
                        $stmt = $pdo->prepare($query);
                        $stmt->execute($params);

                        while ($student = $stmt->fetch(PDO::FETCH_ASSOC)) {
                            echo portalRenderAdminStudentListRow($pdo, $student);
                        }

                        exit;
                    }
                }
            } else {
                redirect('?page=login');
            }
            break;

            case 'admin_payments':
                if (isAdmin()) {

                    // Admin: delete assigned payment
                    if (isset($_GET['action']) && $_GET['action'] === 'delete_assigned' && isset($_GET['id'])) {
                        $aid = (int)$_GET['id'];
                        try {
                            // allow delete only if not paid
                            $st = $pdo->prepare("SELECT status FROM assigned_payments WHERE id=? LIMIT 1");
                            $st->execute([$aid]);
                            $stt = $st->fetchColumn();
                            if ($stt && $stt !== 'Paid') {
                                $del = $pdo->prepare("DELETE FROM assigned_payments WHERE id=?");
                                $del->execute([$aid]);
                                flash('success','Assigned payment deleted successfully.');
                            } else {
                                flash('warning','Paid assigned payments cannot be deleted.');
                            }
                        } catch (Throwable $e) {
                            error_log('Delete assigned payment failed: '.$e->getMessage());
                            flash('danger','Unable to delete assigned payment.');
                        }
                        redirect('?page=admin_payments');
                    }

                    // Admin: edit assigned/custom payment (title + amount) before it is paid
                    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_assigned_payment') {
                        $aid = (int)($_POST['assigned_id'] ?? 0);
                        $title = trim((string)($_POST['title'] ?? ''));
                        $amount = (float)($_POST['amount'] ?? 0);
                        if ($aid > 0 && $title !== '' && $amount > 0) {
                            try {
                                $st = $pdo->prepare("SELECT status FROM assigned_payments WHERE id=? LIMIT 1");
                                $st->execute([$aid]);
                                $stt = (string)$st->fetchColumn();
                                if ($stt !== 'Paid') {
                                    $up = $pdo->prepare("UPDATE assigned_payments SET title=?, amount=?, status='Not Paid' WHERE id=?");
                                    $up->execute([$title, $amount, $aid]);
                                    flash('success','Custom payment updated successfully.');
                                } else {
                                    flash('warning','Paid assigned payments cannot be edited.');
                                }
                            } catch (Throwable $e) {
                                error_log('Update assigned payment failed: '.$e->getMessage());
                                flash('danger','Unable to update custom payment.');
                            }
                        } else {
                            flash('warning','Please enter a valid title and amount.');
                        }
                        redirect('?page=admin_payments');
                    }


                    // Handle view payment
                    if (isset($_GET['action']) && $_GET['action'] === 'view' && isset($_GET['id'])) {
                        $paymentId = $_GET['id'];

                        // Get payment details
                        $stmt = $pdo->prepare("SELECT pt.*, s.first_name, s.other_name, s.surname, s.registration_no, s.matric_no, a.username as approved_by_username FROM payment_transactions pt
                            JOIN students s ON pt.student_id = s.id
                            LEFT JOIN admins a ON pt.approved_by = a.id
                            WHERE pt.id = ?");
                        $stmt->execute([$paymentId]);
                        $payment = $stmt->fetch(PDO::FETCH_ASSOC);

                        if ($payment) {
                            ?>
                            <div class="row">
                                <div class="col-md-12">
                                    <div class="card">
                                        <div class="card-header d-flex justify-content-between align-items-center">
                                            <h4 class="mb-0">Payment Details - Transaction #<?php echo $payment['transaction_id']; ?></h4>
                                            <a href="?page=admin_payments" class="btn btn-secondary">Back to Payments</a>
                                        </div>
                                        <div class="card-body">
                                            <div class="row">
                                                <div class="col-md-6">
                                                    <h5>Payment Information</h5>
                                                    <table class="table table-borderless">
                                                        <tr>
                                                            <td><strong>Transaction ID:</strong></td>
                                                            <td><?php echo $payment['transaction_id']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Reference:</strong></td>
                                                            <td><?php echo $payment['reference']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Paystack Reference:</strong></td>
                                                            <td><?php echo $payment['paystack_reference'] ?? 'N/A'; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Payment Type:</strong></td>
                                                            <td><?php echo $payment['transaction_type']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Entered Amount Paid:</strong></td>
                                                            <td>₦<?php echo number_format((float)portalPaymentEnteredAmount($payment), 2); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Approved / Credited Amount:</strong></td>
                                                            <td>₦<?php echo number_format((float)portalPaymentApprovedAmount($payment), 2); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Amount:</strong></td>
                                                            <td>₦<?php echo number_format((float)$payment['amount'], 2); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Payment Method:</strong></td>
                                                            <td><?php echo $payment['payment_method']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Date Submitted:</strong></td>
                                                            <td><?php echo !empty($payment['manual_proof_uploaded_at']) ? date('d M Y, g:i A', strtotime((string)$payment['manual_proof_uploaded_at'])) : date('d M Y, g:i A', strtotime((string)($payment['created_at'] ?? 'now'))); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Proof Status:</strong></td>
                                                            <td>
                                                                <?php $proofLabel = portalPaymentProofStatusLabel($payment); ?>
                                                                <span class="badge bg-<?php echo e(portalManualPaymentStatusClass($proofLabel)); ?>"><?php echo e($proofLabel); ?></span>
                                                            </td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Session:</strong></td>
                                                            <td><?php echo e(portalPaymentAcademicSession($payment, $payment)); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Semester:</strong></td>
                                                            <td><?php echo e(portalPaymentSemester($payment, $payment)); ?></td>
                                                        </tr>
                                                        <?php
                                                            // Professional single button: open Proof + Receipt in one modal (if available)
                                                            $proofUrl = !empty($payment['manual_proof_path']) ? (string)$payment['manual_proof_path'] : '';
                                                            $receiptUrl = '';
                                                            $st = (string)($payment['status'] ?? '');
                                                            if (in_array($st, ['Approved','Successful'], true)) {
                                                                $rp = 'payment_receipt';
                                                                if (($payment['transaction_type'] ?? '') === 'Acceptance Fee') $rp = 'acceptance_payment_receipt';
                                                                elseif (($payment['transaction_type'] ?? '') === 'Hostel Fee') $rp = 'hostel_payment_receipt';
                                                                elseif (($payment['transaction_type'] ?? '') === 'School Fee') $rp = 'school_payment_receipt';
                                                                $receiptUrl = "?page={$rp}&transaction_id=" . (int)$payment['id'];
                                                            }
                                                        ?>
                                                        <?php if ($proofUrl || $receiptUrl): ?>
                                                        <tr>
                                                            <td><strong>Documents:</strong></td>
                                                            <td>
                                                                <a class="btn btn-sm btn-outline-dark" href="#"
                                                                   data-doc-title="Payment Documents"
                                                                   data-proof-url="<?php echo htmlspecialchars($proofUrl); ?>"
                                                                   data-receipt-url="<?php echo htmlspecialchars($receiptUrl); ?>"
                                                                   data-prefer="<?php echo $proofUrl ? 'proof' : 'receipt'; ?>">
                                                                    <i class="bi bi-folder2-open"></i> View Proof & Receipt
                                                                </a>
                                                            </td>
                                                        </tr>
                                                        <?php endif; ?>
<tr>
                                                            <td><strong>Status:</strong></td>
                                                            <td>
                                                                <span class="status-badge status-<?php echo strtolower(str_replace(' ', '-', $payment['status'])); ?>">
                                                                    <?php echo $payment['status']; ?>
                                                                </span>
                                                            </td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Payment Date:</strong></td>
                                                            <td>
                                                                <?php
                                                                if ($payment['payment_date']) {
                                                                    echo date('d M Y, g:i A', strtotime($payment['payment_date']));
                                                                } else {
                                                                    echo 'N/A';
                                                                }
                                                                ?>
                                                            </td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Approval Date:</strong></td>
                                                            <td>
                                                                <?php
                                                                if ($payment['approval_date']) {
                                                                    echo date('d M Y, g:i A', strtotime($payment['approval_date']));
                                                                } else {
                                                                    echo 'N/A';
                                                                }
                                                                ?>
                                                            </td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Approved By:</strong></td>
                                                            <td><?php echo $payment['approved_by_username'] ?? 'N/A'; ?></td>
                                                        </tr>
                                                        <?php if ($payment['total_installments'] > 1): ?>
                                                        <tr>
                                                            <td><strong>Installment:</strong></td>
                                                            <td><?php echo $payment['installment_number']; ?> of <?php echo $payment['total_installments']; ?></td>
                                                        </tr>
                                                        <?php endif; ?>
                                                    </table>
                                                </div>
                                                <div class="col-md-6">
                                                    <h5>Student Information</h5>
                                                    <table class="table table-borderless">
                                                        <tr>
                                                            <td><strong>Admission Number:</strong></td>
                                                            <td><?php echo $payment['registration_no']; ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Matric Number:</strong></td>
                                                            <td><?php echo htmlspecialchars((string)($payment['matric_no'] ?? 'Pending')); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <td><strong>Name:</strong></td>
                                                            <td><?php echo trim(($payment['first_name'] ?? '') . ' ' . ($payment['other_name'] ?? '') . ' ' . ($payment['surname'] ?? '')); ?></td>
                                                        </tr>
                                                    </table>

                                                    <?php if (!empty($payment['manual_notes'])): ?>
                                                    <h5>Student Notes</h5>
                                                    <p><?php echo nl2br(htmlspecialchars((string)$payment['manual_notes'])); ?></p>
                                                    <?php endif; ?>

                                                    <?php if ($payment['approval_notes']): ?>
                                                    <h5>Approval Notes</h5>
                                                    <p><?php echo $payment['approval_notes']; ?></p>
                                                    <?php endif; ?>

                                                    <?php if ($payment['qr_code_data']): ?>
                                                    <h5>QR Code</h5>
                                                    <div class="text-center">
                                                        <img src="<?php echo $payment['qr_code_data']; ?>" alt="Payment QR Code">
                                                    </div>
                                                    <?php endif; ?>
                                                </div>
                                            </div>

                                            <?php if ($payment['status'] === 'Successful' || $payment['status'] === 'Pending'): ?>
                                            <div class="row mt-4">
                                                <div class="col-md-12">
                                                    <h5>Payment Approval</h5>
                                                    <form method="post" action="">
                                                        <input type="hidden" name="action" value="update_payment_status">
                                                        <input type="hidden" name="transaction_id" value="<?php echo $paymentId; ?>">

                                                        <div class="row">
                                                            <div class="col-md-6 mb-3">
                                                                <label for="status" class="form-label">Status</label>
                                                                <select class="form-select" id="status" name="status" required>
                                                                    <option value="">Select Status</option>
                                                                    <option value="Approved">Approve</option>
                                                                    <option value="Rejected">Reject</option>
                                                                </select>
                                                            </div>
                                                            <div class="col-md-6 mb-3">
                                                                <label for="approved_amount" class="form-label">Approved Amount (₦)</label>
                                                                <input type="number" class="form-control" id="approved_amount" name="approved_amount" min="0" step="0.01" value="<?php echo htmlspecialchars(number_format((float)portalPaymentEnteredAmount($payment), 2, '.', '')); ?>">
                                                            </div>
                                                            <div class="col-md-12 mb-3">
                                                                <label for="approval_notes" class="form-label">Approval Notes</label>
                                                                <textarea class="form-control" id="approval_notes" name="approval_notes" rows="3"></textarea>
                                                            </div>
                                                        </div>

                                                        <button type="submit" class="btn btn-primary">Update Status</button>
                                                    </form>
                                                </div>
                                            </div>
                                            <?php endif; ?>

                                            <?php if ($payment['status'] === 'Approved'): ?>
                                            <div class="row mt-4">
                                                <div class="col-md-12">
                                                    <div class="d-flex gap-2">
                                                        <a href="<?php echo (($payment['transaction_type'] ?? '') === 'Wallet Funding') ? '?page=wallet' : ('?page=payment_receipt&transaction_id=' . (int)$paymentId); ?>" class="btn btn-primary" target="_blank">
                                                            <i class="bi bi-receipt"></i> <?php echo (($payment['transaction_type'] ?? '') === 'Wallet Funding') ? 'Open Wallet' : 'View Receipt'; ?>
                                                        </a>
                                                        <button class="btn btn-secondary" onclick="window.print()">
                                                            <i class="bi bi-printer"></i> Print
                                                        </button>
                                                    </div>
                                                </div>
                                            </div>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <?php
                    } else {
                        echo '<div class="alert alert-danger">Payment not found.</div>';
                    }
                } else {
                    // List all payments
                    ?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header">
                                    Manage Payments
                                </div>
                                <div class="card-body">
                                    <?php $adminPaymentSession = trim((string)($_GET['payment_session'] ?? '')); $adminPaymentSemesterRaw = trim((string)($_GET['payment_semester'] ?? '')); $adminPaymentSemester = $adminPaymentSemesterRaw !== '' ? portalNormalizeSemesterName($adminPaymentSemesterRaw) : ''; $adminPaymentSessions = portalAvailableSessionOptions($pdo); ?>
                                    <div class="mb-3">
                                        <div class="row g-2">
                                            <div class="col-md-3">
                                                <input type="text" class="form-control" id="search" placeholder="Search by name, email, or reference...">
                                            </div>
                                            <div class="col-md-2">
                                                <select class="form-select" id="status_filter">
                                                    <option value="">All Status</option>
                                                    <option value="Pending">Pending</option>
                                                    <option value="Successful">Successful</option>
                                                    <option value="Approved">Approved</option>
                                                    <option value="Rejected">Rejected</option>
                                                    <option value="Failed">Failed</option>
                                                </select>
                                            </div>
                                            <div class="col-md-2">
                                                <select class="form-select" id="type_filter">
                                                    <option value="">All Types</option>
                                                    <option value="Registration Fee">Registration Fee</option>
                                                    <option value="Acceptance Fee">Acceptance Fee</option>
                                                    <option value="Hostel Fee">Hostel Fee</option>
                                                    <option value="School Fee">School Fee</option>
                                                    <option value="Assigned Payment">Assigned Payment</option>
                                                    <option value="Custom">Custom</option>
                                                </select>
                                            </div>
                                            <div class="col-md-2">
                                                <select class="form-select" id="payment_session_filter">
                                                    <option value="">All Sessions</option>
                                                    <?php foreach ($adminPaymentSessions as $opt): ?>
                                                        <option value="<?php echo e($opt); ?>" <?php echo strcasecmp($adminPaymentSession, $opt) === 0 ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-md-3">
                                                <select class="form-select" id="payment_semester_filter">
                                                    <option value="">All Semesters</option>
                                                    <?php foreach (portalAvailableSemesterOptions() as $opt): ?>
                                                        <option value="<?php echo e($opt); ?>" <?php echo ($adminPaymentSemester !== '' && strcasecmp($adminPaymentSemester, $opt) === 0) ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                        </div>
                                        <div class="row mt-2">
                                            <div class="col-md-2">
                                                <button class="btn btn-primary w-100" id="filter_btn">Filter</button>
                                            </div>
                                            <div class="col-md-2">
                                                <button class="btn btn-secondary w-100" id="reset_btn">Reset</button>
                                            </div>
                                        </div>
                                    </div>

                                    <div class="table-responsive">
                                        <table class="table table-striped">
                                            <thead>
                                                <tr>
                                                    <th>Transaction ID</th>
                                                    <th>Student</th>
                                                    <th>Payment Type</th>
                                                    <th>Amount Paid</th>
                                                    <th>Approved / Credited</th>
                                                    <th>Reference</th>
                                                    <th>View Upload</th>
                                                    <th>Proof Status</th>
                                                    <th>Session</th>
                                                    <th>Semester</th>
                                                    <th>Status</th>
                                                    <th>Date</th>
                                                    <th>Actions</th>
                                                </tr>
                                            </thead>
                                            <tbody id="payments_table">
                                                <?php
                                                $stmt = $pdo->query("SELECT pt.*, s.first_name, s.surname, s.registration_no, s.application_no, s.admission_no, s.matric_no, s.session AS student_session, s.semester AS student_semester FROM payment_transactions pt
                                                JOIN students s ON pt.student_id = s.id
                                                ORDER BY pt.created_at DESC");
										while ($payment = $stmt->fetch(PDO::FETCH_ASSOC)) {
										    // Use the student_id from the payment row (avoid undefined $student)
										    $studentId = isset($payment['student_id']) ? (int)$payment['student_id'] : 0;
										    echo "<tr data-student-id=\"" . $studentId . "\">";
                                                    echo "<td>" . $payment['transaction_id'] . "</td>";
                                                    echo "<td>" . $payment['first_name'] . " " . $payment['surname'] . "</td>";
                                                    echo "<td>" . htmlspecialchars(bestPaymentType($payment)) . "</td>";
                                                    echo "<td>₦" . number_format((float)portalPaymentEnteredAmount($payment), 2) . "</td>";
                                                    echo "<td>₦" . number_format((float)portalPaymentApprovedAmount($payment), 2) . "</td>";
                                                    echo "<td>" . $payment['reference'] . "</td>";
                                                    echo "<td>";
                                                    // Professional modal: show Proof + Receipt together when both are available
                                                    $proofUrl = !empty($payment['manual_proof_path']) ? (string)$payment['manual_proof_path'] : '';
                                                    $receiptUrl = '';
                                                    $st = (string)($payment['status'] ?? '');
                                                    if (in_array($st, ['Approved','Successful'], true)) {
                                                        $rp = 'payment_receipt';
                                                        $t = (string)($payment['transaction_type'] ?? '');
                                                        if ($t === 'Acceptance Fee') $rp = 'acceptance_payment_receipt';
                                                        elseif ($t === 'Hostel Fee') $rp = 'hostel_payment_receipt';
                                                        elseif ($t === 'School Fee') $rp = 'school_payment_receipt';
                                                        $receiptUrl = "?page={$rp}&transaction_id=" . (int)$payment['id'];
                                                    }

                                                    if ($proofUrl && $receiptUrl) {
                                                        echo "<a href=\"#\" class=\"btn btn-sm btn-outline-dark\" data-doc-title=\"Payment Documents\" data-proof-url=\"" . htmlspecialchars($proofUrl) . "\" data-receipt-url=\"" . htmlspecialchars($receiptUrl) . "\" data-prefer=\"proof\"><i class=\"bi bi-folder2-open\"></i> View</a>";
                                                    } elseif ($receiptUrl) {
                                                        echo "<a href=\"" . htmlspecialchars($receiptUrl) . "\" class=\"btn btn-sm btn-outline-success js-payment-doc\" data-doc-title=\"Payment Receipt\" data-doc-url=\"" . htmlspecialchars($receiptUrl) . "\">View</a>";
                                                    } elseif ($proofUrl) {
                                                        echo "<a href=\"" . htmlspecialchars($proofUrl) . "\" class=\"btn btn-sm btn-outline-primary js-payment-doc\" data-doc-title=\"Proof of Payment\" data-doc-url=\"" . htmlspecialchars($proofUrl) . "\">View</a>";
                                                    } else {
                                                        echo "<span class=\"text-muted\">View Upload</span>";
                                                    }
                                                    echo "</td>";
                                                    $proofLabel = portalPaymentProofStatusLabel($payment);
                                                    echo "<td><span class=\"badge bg-" . htmlspecialchars(portalManualPaymentStatusClass($proofLabel)) . "\">" . htmlspecialchars($proofLabel) . "</span></td>";

                                                    echo "<td>" . htmlspecialchars(portalPaymentAcademicSession($payment, $payment)) . "</td>";
                                                    echo "<td>" . htmlspecialchars(portalPaymentSemester($payment, $payment)) . "</td>";
                                                    echo "<td><span class=\"status-badge status-" . htmlspecialchars(portalPaymentDisplayStatusClass($payment)) . "\">" . htmlspecialchars(portalPaymentApprovalStatusLabel($payment)) . "</span></td>";
                                                    echo "<td>" . date('d M Y', strtotime($payment['created_at'])) . "</td>";
                                                    // Keep Actions clean: only one View button (receipt/proof are handled via the View column modal)
                                                    echo "<td>\n                                                            <a href=\"?page=admin_payments&action=view&id=" . $payment['id'] . "\" class=\"btn btn-sm btn-primary\">View</a>";
                                                    echo "</td>";
                                                    echo "</tr>";
                                                }
                                                ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <?php
                }

                // Handle AJAX requests for filtering
                if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
                    if ($_POST['action'] === 'filter_payments') {
                        $search = $_POST['search'] ?? '';
                        $status = $_POST['status'] ?? '';
                        $type = $_POST['type'] ?? '';
                        $paymentSession = trim((string)($_POST['payment_session'] ?? ''));
                        $paymentSemesterRaw = trim((string)($_POST['payment_semester'] ?? ''));
                        $paymentSemester = $paymentSemesterRaw !== '' ? portalNormalizeSemesterName($paymentSemesterRaw) : '';

                        $query = "SELECT pt.*, s.first_name, s.surname, s.registration_no, s.application_no, s.admission_no, s.matric_no, s.session AS student_session, s.semester AS student_semester, COALESCE(NULLIF(TRIM(s.faculty),''),'Not Set') AS faculty_name, COALESCE(NULLIF(TRIM(s.department),''),'Not Set') AS department_name, COALESCE(NULLIF(TRIM(s.programme),''),'Not Set') AS programme_name, COALESCE(NULLIF(TRIM(s.level),''),'Not Set') AS level_name FROM payment_transactions pt
                        JOIN students s ON pt.student_id = s.id
                        WHERE 1=1";
                        $params = [];

                        if (!empty($search)) {
                            $query .= " AND (s.first_name LIKE ? OR s.surname LIKE ? OR s.email LIKE ? OR pt.reference LIKE ?)";
                            $searchParam = "%$search%";
                            $params = array_merge($params, [$searchParam, $searchParam, $searchParam, $searchParam]);
                        }

                        if (!empty($status)) {
                            $query .= " AND pt.status = ?";
                            $params[] = $status;
                        }

                        if (!empty($type)) {
                            $query .= " AND pt.transaction_type = ?";
                            $params[] = $type;
                        }

                        $query .= " ORDER BY pt.created_at DESC";

                        $stmt = $pdo->prepare($query);
                        $stmt->execute($params);

                        while ($payment = $stmt->fetch(PDO::FETCH_ASSOC)) {
                            if (($paymentSession !== '' || $paymentSemester !== '') && !portalTransactionMatchesPeriod($payment, $paymentSession, $paymentSemester, $payment)) continue;
                            echo "<tr data-student-id=\"" . (int)($payment['student_id'] ?? 0) . "\">";
                            echo "<td>" . $payment['transaction_id'] . "</td>";
                            echo "<td>" . $payment['first_name'] . " " . $payment['surname'] . "</td>";
                            echo "<td>" . htmlspecialchars(bestPaymentType($payment)) . "</td>";
                            echo "<td>₦" . number_format((float)portalPaymentEnteredAmount($payment), 2) . "</td>";
                            echo "<td>₦" . number_format((float)portalPaymentApprovedAmount($payment), 2) . "</td>";
                            echo "<td>" . $payment['reference'] . "</td>";
                            echo "<td>" . htmlspecialchars(portalPaymentAcademicSession($payment, $payment)) . "</td>";
                            echo "<td>" . htmlspecialchars(portalPaymentSemester($payment, $payment)) . "</td>";
                            echo "<td><span class=\"status-badge status-" . htmlspecialchars(portalPaymentDisplayStatusClass($payment)) . "\">" . htmlspecialchars(portalPaymentApprovalStatusLabel($payment)) . "</span></td>";
                            echo "<td>" . date('d M Y', strtotime($payment['created_at'])) . "</td>";
                            // Keep Actions clean: only one View button (receipt/proof are in the modal via the View column)
                            echo "<td>\n                                    <a href=\"?page=admin_payments&action=view&id=" . $payment['id'] . "\" class=\"btn btn-sm btn-primary\">View</a>";
                            echo "</td>";
                            echo "</tr>";
                        }

                        exit;
                    }
                }
            } else {
                redirect('?page=login');
            }
            break;

            case 'admin_applications':
                if (isAdmin()) {
                    // Handle view application
                    if (isset($_GET['action']) && $_GET['action'] === 'view' && isset($_GET['id'])) {
                        $studentId = $_GET['id'];

                        // Get student information
                        $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
                        $stmt->execute([$studentId]);
                        $student = $stmt->fetch(PDO::FETCH_ASSOC);

                        // Get education history
                        $stmt = $pdo->prepare("SELECT * FROM education_history WHERE student_id = ?");
                        $stmt->execute([$studentId]);
                        $educationHistory = $stmt->fetchAll(PDO::FETCH_ASSOC);

                        // Get examination results
                        $stmt = $pdo->prepare("SELECT * FROM examination_results WHERE student_id = ?");
                        $stmt->execute([$studentId]);
                        $examResults = $stmt->fetchAll(PDO::FETCH_ASSOC);

                        // Get referee information
                        $stmt = $pdo->prepare("SELECT * FROM referees WHERE student_id = ?");
                        $stmt->execute([$studentId]);
                        $referee = $stmt->fetch(PDO::FETCH_ASSOC);

                        // Get student documents
                        $stmt = $pdo->prepare("SELECT * FROM student_documents WHERE student_id = ? ORDER BY id DESC");
                        $stmt->execute([$studentId]);
                        $documents = $stmt->fetchAll(PDO::FETCH_ASSOC);

                        // Get admin decision
                        $stmt = $pdo->prepare("SELECT * FROM admin_decisions WHERE student_id = ?");
                        $stmt->execute([$studentId]);
                        $adminDecision = $stmt->fetch(PDO::FETCH_ASSOC);
                        ?>
                        <div class="row">
                            <div class="col-md-12">
                                <div class="card">
                                    <div class="card-header d-flex justify-content-between align-items-center">
                                        <h4 class="mb-0">Application Details - <?php echo e(portalStudentNumberDisplay($student, 'application')); ?></h4>
                                        <div>
                                            <a href="?page=admin_applications" class="btn btn-secondary">Back to Applications</a>
                                            <button type="button" class="btn btn-danger ms-1" data-bs-toggle="modal" data-bs-target="#deleteApplicationModal" data-student-id="<?php echo $studentId; ?>" data-student-name="<?php echo htmlspecialchars(studentFullName($student)); ?>">
                                                <i class="bi bi-trash"></i> Delete Application
                                            </button>
                                        </div>
                                    </div>
                                    <div class="card-body">
                                        <?php renderFlashes(); ?>
                                        <?php portalRenderAdminSubmittedApplicationEditor($pdo, $student, 'admin_applications'); ?>
                                        <!-- Personal Information -->
                                        <div class="form-section">
                                            <h3>Personal Information</h3>
                                            <div class="row">
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Admission Number</label>
                                                    <input type="text" class="form-control" value="<?php echo e(portalStudentNumberDisplay($student, 'admission')); ?>" readonly>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Matric Number</label>
                                                    <input type="text" class="form-control" value="<?php echo htmlspecialchars(portalStudentNumberDisplay($student, 'matric'), ENT_QUOTES, 'UTF-8'); ?>" readonly>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Application Status</label>
                                                    <select class="form-select" id="application_status" name="application_status">
                                                        <option value="Pending" <?php echo $student['application_status'] === 'Pending' ? 'selected' : ''; ?>>Pending</option>
                                                        <option value="Under Review" <?php echo $student['application_status'] === 'Under Review' ? 'selected' : ''; ?>>Under Review</option>
                                                        <option value="Approved" <?php echo $student['application_status'] === 'Approved' ? 'selected' : ''; ?>>Approved</option>
                                                        <option value="Rejected" <?php echo $student['application_status'] === 'Rejected' ? 'selected' : ''; ?>>Rejected</option>
                                                    </select>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">Surname</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['surname']; ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">First Name</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['first_name']; ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">Other Name</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['other_name']; ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">Sex</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['sex']; ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">Date of Birth</label>
                                                    <input type="text" class="form-control" value="<?php echo date('d M Y', strtotime($student['date_of_birth'])); ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">Nationality</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['nationality']; ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">State of Origin</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['state_of_origin']; ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">Local Government Area</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['lga']; ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">Mobile Phone</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['mobile_phone']; ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">Residential Phone</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['residential_phone']; ?>" readonly>
                                                </div>
                                                <div class="col-md-4 mb-3">
                                                    <label class="form-label">Email</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['email']; ?>" readonly>
                                                </div>
                                                <div class="col-md-12 mb-3">
                                                    <label class="form-label">Permanent Address</label>
                                                    <textarea class="form-control" rows="3" readonly><?php echo $student['permanent_address']; ?></textarea>
                                                </div>
                                                <div class="col-md-12 mb-3">
                                                    <label class="form-label">Extra Curricular Activities</label>
                                                    <textarea class="form-control" rows="2" readonly><?php echo $student['extra_curricular']; ?></textarea>
                                                </div>
                                            </div>
                                        </div>

<?php portalRenderAdminApplicationAcademicControls($pdo, $student, 'admin_applications'); ?>

                                        <!-- Profile Image -->
                                        <div class="form-section">
                                            <h3>Profile Image</h3>
                                            <div class="row">
                                                <div class="col-md-12 mb-3">
                                                    <?php if ($student['profile_image']): ?>
                                                        <img src="<?php echo $student['profile_image']; ?>" alt="Profile Image" style="max-width: 200px; max-height: 200px; border-radius: 5px;">
                                                    <?php else: ?>
                                                        <p>No profile image uploaded.</p>
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>

                                        <!-- Passport Photo -->
                                        <div class="form-section">
                                            <h3>Passport Photograph</h3>
                                            <div class="row">
                                                <div class="col-md-12 mb-3">
                                                    <?php if ($student['passport_photo']): ?>
                                                        <img src="<?php echo $student['passport_photo']; ?>" alt="Passport Photo" style="max-width: 200px; max-height: 200px; border-radius: 5px;">
                                                    <?php else: ?>
                                                        <p>No passport photo uploaded.</p>
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>

                                        <!-- Supporting Documents -->
                                        <div class="form-section">
                                            <h3>Supporting Documents</h3>
                                            <div class="row">
                                                <div class="col-md-12 mb-3">
                                                    <?php if (!empty($documents)): ?>
                                                        <div class="document-list">
                                                            <?php foreach ($documents as $doc): ?>
                                                                <div class="document-item">
                                                                    <div>
                                                                        <i class="bi bi-file-earmark"></i>
                                                                        <span><?php echo $doc['document_name']; ?></span>
                                                                        <span class="badge bg-secondary ms-2"><?php echo $doc['document_type']; ?></span>
                                                                    </div>
                                                                    <div>
                                                                        <a href="<?php echo $doc['file_path']; ?>" target="_blank" class="btn btn-sm btn-outline-primary">View Upload</a>
                                                                    </div>
                                                                </div>
                                                            <?php endforeach; ?>
                                                        </div>
                                                    <?php else: ?>
                                                        <p>No supporting documents uploaded.</p>
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>

                                        <!-- Next of Kin & Sponsor -->
                                        <div class="form-section">
                                            <h3>Next of Kin & Sponsor</h3>
                                            <div class="row">
                                                <div class="col-md-12 mb-3">
                                                    <label class="form-label">Next of Kin/Parent Name & Address</label>
                                                    <textarea class="form-control" rows="3" readonly><?php echo $student['nok_name']; ?></textarea>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Next of Kin Telephone</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['nok_phone']; ?>" readonly>
                                                </div>
                                                <div class="col-md-12 mb-3">
                                                    <label class="form-label">Sponsor's Name & Address</label>
                                                    <textarea class="form-control" rows="3" readonly><?php echo $student['sponsor_name']; ?></textarea>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Sponsor's Telephone</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['sponsor_phone']; ?>" readonly>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Sponsor's Occupation</label>
                                                    <input type="text" class="form-control" value="<?php echo $student['sponsor_occupation']; ?>" readonly>
                                                </div>
                                            </div>
                                        </div>

                                        <!-- Academic Structure -->
                                        <div class="form-section">
                                            <h3>Academic Structure</h3>
                                            <div class="row">
                                                <div class="col-md-4 mb-3"><label class="form-label">School</label><input type="text" class="form-control" value="<?php echo htmlspecialchars((string)($student['school'] ?? $student['school_name'] ?? $student['faculty'] ?? '')); ?>" readonly></div>
                                                <div class="col-md-4 mb-3"><label class="form-label">Course of Study</label><input type="text" class="form-control" value="<?php echo htmlspecialchars((string)($student['course_of_study'] ?? $student['course_in_view'] ?? $student['department'] ?? '')); ?>" readonly></div>
                                                <div class="col-md-4 mb-3"><label class="form-label">Program</label><input type="text" class="form-control" value="<?php echo htmlspecialchars((string)($student['program'] ?? $student['programme_name_cache'] ?? $student['programme'] ?? '')); ?>" readonly></div>
                                            </div>
                                        </div>

                                        <!-- Education History -->
                                        <div class="form-section">
                                            <h3>Post-Primary Institution Attended with Dates</h3>
                                            <div class="table-responsive">
                                                <table class="table table-bordered">
                                                    <thead>
                                                        <tr>
                                                            <th>Name of Institution</th>
                                                            <th>From (Date)</th>
                                                            <th>To (Date)</th>
                                                            <th>Qualifications Obtained</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <?php foreach ($educationHistory as $edu): ?>
                                                        <tr>
                                                            <td><?php echo $edu['institution_name']; ?></td>
                                                            <td><?php echo date('M Y', strtotime($edu['from_date'])); ?></td>
                                                            <td><?php echo date('M Y', strtotime($edu['to_date'])); ?></td>
                                                            <td><?php echo $edu['qualification']; ?></td>
                                                        </tr>
                                                        <?php endforeach; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>

                                        <!-- Examination Results -->
                                        <div class="form-section">
                                            <h3>School Certificate Results</h3>
                                            <div class="table-responsive">
                                                <table class="table table-bordered">
                                                    <thead>
                                                        <tr>
                                                            <th>Names of Examination</th>
                                                            <th>Date</th>
                                                            <th>Exam Number</th>
                                                            <th>Subjects & Grades</th>
                                                            <th>Credentials Enclosed?</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <?php foreach ($examResults as $exam): ?>
                                                        <tr>
                                                            <td><?php echo $exam['exam_type']; ?></td>
                                                            <td><?php echo date('M Y', strtotime($exam['exam_date'])); ?></td>
                                                            <td><?php echo $exam['exam_number']; ?></td>
                                                            <td><?php echo $exam['subjects_grades']; ?></td>
                                                            <td><?php echo $exam['credentials_attached'] ? 'Yes' : 'No'; ?></td>
                                                        </tr>
                                                        <?php endforeach; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>

                                        <!-- Referee Information -->
                                        <div class="form-section">
                                            <h3>Referee Information</h3>
                                            <div class="row">
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Referee Full Name</label>
                                                    <input type="text" class="form-control" value="<?php echo $referee['full_name']; ?>" readonly>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Occupation</label>
                                                    <input type="text" class="form-control" value="<?php echo $referee['occupation']; ?>" readonly>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Post Held</label>
                                                    <input type="text" class="form-control" value="<?php echo $referee['post_held']; ?>" readonly>
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">Date</label>
                                                    <input type="text" class="form-control" value="<?php echo date('d M Y', strtotime($referee['date'])); ?>" readonly>
                                                </div>
                                                <div class="col-md-12 mb-3">
                                                    <label class="form-label">Address</label>
                                                    <textarea class="form-control" rows="3" readonly><?php echo $referee['address']; ?></textarea>
                                                </div>
                                                <div class="col-md-12 mb-3">
                                                    <label class="form-label">Referee's Signature</label>
                                                    <?php if ($referee['signature_data']): ?>
                                                        <img src="<?php echo $referee['signature_data']; ?>" alt="Referee Signature" style="max-height: 150px;">
                                                    <?php else: ?>
                                                        <p>No signature provided</p>
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>

                                        <!-- Applicant Signature -->
                                        <div class="form-section">
                                            <h3>Applicant Signature</h3>
                                            <?php if ($student['signature_data']): ?>
                                                <img src="<?php echo $student['signature_data']; ?>" alt="Applicant Signature" style="max-height: 150px;">
                                            <?php else: ?>
                                                <p>No signature provided</p>
                                            <?php endif; ?>
                                        </div>

                                        <!-- Admin Decision -->
                                        <div class="form-section">
                                            <h3>Administrative Decision</h3>
                                            <form method="post" action="">
                                                <input type="hidden" name="action" value="admin_decision">
                                                <input type="hidden" name="student_id" value="<?php echo $studentId; ?>">

                                                <div class="row">
                                                    <div class="col-md-6 mb-3">
                                                        <div class="form-check">
                                                            <input class="form-check-input" type="checkbox" name="qualified" id="qualified" <?php echo $adminDecision && $adminDecision['qualified'] ? 'checked' : ''; ?>>
                                                            <label class="form-check-label" for="qualified">
                                                                Qualified
                                                            </label>
                                                        </div>
                                                    </div>
                                                    <div class="col-md-6 mb-3">
                                                        <label for="officer_name" class="form-label">Name & Signature of Officer</label>
                                                        <input type="text" class="form-control" id="officer_name" name="officer_name" value="<?php echo $adminDecision['officer_name'] ?? ''; ?>" required>
                                                    </div>
                                                    <div class="col-md-12 mb-3">
                                                        <label class="form-label">Officer Signature</label>
                                                        <div class="signature-wrapper border p-2 bg-light">
                                                            <canvas id="officer_signature" class="signature-canvas"></canvas>
                                                            <div class="d-flex justify-content-between mt-2">
                                                                <span>Clear</span>
                                                                <button type="button" class="btn btn-secondary btn-sm" id="officer_signature_clear">Clear Pad</button>
                                                            </div>
                                                            <input type="hidden" id="officer_signature_data" name="officer_signature" value="<?php echo $adminDecision['officer_signature'] ?? ''; ?>">
                                                        </div>
                                                    </div>
                                                </div>

                                                <div class="d-flex justify-content-between">
                                                    <button type="submit" class="btn btn-primary">Save Decision</button>
                                                    <button type="button" class="btn btn-success" onclick="updateApplicationStatus(<?php echo $studentId; ?>)">Update Status</button>
                                                </div>
                                            </form>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- Delete Application Modal -->
                    <div class="modal fade" id="deleteApplicationModal" tabindex="-1">
                        <div class="modal-dialog">
                            <div class="modal-content">
                                <div class="modal-header">
                                    <h5 class="modal-title">Delete Application</h5>
                                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                                </div>
                                <div class="modal-body">
                                    <p>Are you sure you want to delete the application for <strong id="deleteApplicationName"></strong>?</p>
                                    <p class="text-danger">This action cannot be undone and will delete all associated data including education history, examination results, referee information, and payment records.</p>
                                </div>
                                <div class="modal-footer">
                                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
                                    <form method="post" action="" id="deleteApplicationForm">
                                        <input type="hidden" name="action" value="delete_application">
                                        <input type="hidden" name="student_id" id="deleteApplicationId">
                                        <button type="submit" class="btn btn-danger">Delete</button>
                                    </form>
                                </div>
                            </div>
                        </div>
                    </div>

                    <script>
                    document.addEventListener('DOMContentLoaded', function() {
                        // Save signature data before form submission
                        const form = document.querySelector("form[action='']");
                        if(form) {
                            form.addEventListener("submit", function() {
                                const canvas = document.getElementById("officer_signature");
                                if(canvas) {
                                    const data = canvas.toDataURL();
                                    document.getElementById("officer_signature_data").value = data;
                                }
                            });
                        }

                        // Update application status
                        window.updateApplicationStatus = function(studentId) {
                            const status = document.getElementById("application_status").value;

                            if (confirm("Are you sure you want to update application status to " + status + "?")) {
                                const form = document.createElement("form");
                                form.method = "POST";
                                form.innerHTML = `
                                    <input type="hidden" name="action" value="update_status">
                                    <input type="hidden" name="student_id" value="${studentId}">
                                    <input type="hidden" name="status" value="${status}">
                                `;
                                document.body.appendChild(form);
                                form.submit();
                            }
                        };

                        // Delete application modal
                        const deleteApplicationModal = document.getElementById('deleteApplicationModal');
                        if (deleteApplicationModal) deleteApplicationModal.addEventListener('show.bs.modal', function(event) {
                            const button = event.relatedTarget;
                            const studentId = button.getAttribute('data-student-id');
                            const studentName = button.getAttribute('data-student-name');

                            document.getElementById('deleteApplicationId').value = studentId;
                            document.getElementById('deleteApplicationName').textContent = studentName;
                        });
                    });
                    </script>
                    <?php
                } else {
                    // List all applications
                    ?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header">
                                    Manage Applications
                                </div>
                                <div class="card-body">
                                    <div class="mb-3">
                                        <div class="row">
                                            <div class="col-md-4">
                                                <input type="text" class="form-control" id="search" placeholder="Search by name, email, or admission number...">
                                            </div>
                                            <div class="col-md-2">
                                                <select class="form-select" id="status_filter">
                                                    <option value="">All Status</option>
                                                    <option value="Pending">Pending</option>
                                                    <option value="Under Review">Under Review</option>
                                                    <option value="Approved">Approved</option>
                                                    <option value="Rejected">Rejected</option>
                                                </select>
                                            </div>
                                            <div class="col-md-2">
                                                <select class="form-select" id="payment_filter">
                                                    <option value="">All Payment</option>
                                                    <option value="Pending">Pending</option>
                                                    <option value="Paid">Paid</option>
                                                    <option value="Failed">Failed</option>
                                                </select>
                                            </div>
                                            <div class="col-md-2">
                                                <select class="form-select" id="acceptance_payment_filter">
                                                    <option value="">All</option>
                                                    <option value="Pending">Pending</option>
                                                    <option value="Paid">Paid</option>
                                                    <option value="Failed">Failed</option>
                                                </select>
                                            </div>
                                        </div>
                                        <div class="row">
                                            <div class="col-md-2">
                                                <button class="btn btn-primary w-100" id="filter_btn">Filter</button>
                                            </div>
                                            <div class="col-md-2">
                                                <button class="btn btn-secondary w-100" id="reset_btn">Reset</button>
                                            </div>
                                        </div>
                                    </div>

                                    <div class="table-responsive">
                                        <table class="table table-striped">
                                            <thead>
                                                <tr>
                                                    <th>Application No.</th>
                                                    <th>Name</th>
                                                    <th>Email</th>
                                                    <th>Phone</th>
                                                    <th>Reg. Fee</th>
                                                    <th>Acc. Fee</th>
                                                    <th>Hostel</th>
                                                    <th>School</th>
                                                    <th>Status</th>
                                                    <th>Date</th>
                                                    <th>Actions</th>
                                                </tr>
                                            </thead>
                                            <tbody id="applications_table">
                                                <?php
                                                $stmt = $pdo->query("SELECT * FROM students ORDER BY created_at DESC");
                                                while ($student = $stmt->fetch(PDO::FETCH_ASSOC)) {
                                                    echo "<tr data-student-id=\"" . (int)($student['id'] ?? 0) . "\">";
                                                    echo "<td>" . e(portalReceiptApplicationNumber($student)) . "</td>";
                                                    echo "<td>" . htmlspecialchars(studentFullName($student)) . "</td>";
                                                    echo "<td>" . $student['email'] . "</td>";
                                                    echo "<td>" . $student['mobile_phone'] . "</td>";
                                                    echo "<td>" . $student['registration_fee_status'] . "</td>";
                                                    echo "<td>" . $student['acceptance_fee_status'] . "</td>";
                                                    echo "<td>" . htmlspecialchars(portalStudentHostelStatusLabel($student)) . "</td>";
                                                    echo "<td>" . $student['school_fee_status'] . "</td>";
                                                    echo "<td><span class=\"status-badge status-" . strtolower(str_replace(' ', '-', $student['application_status'])) . "\">" . $student['application_status'] . "</span></td>";
                                                    echo "<td>" . date('d M Y', strtotime($student['created_at'])) . "</td>";
                                                    echo "<td>
                                                            <a href=\"?page=admin_applications&action=view&id=" . $student['id'] . "\" class=\"btn btn-sm btn-primary\">View</a> <a href=\"?page=admin_edit_application&id=" . $student['id'] . "\" class=\"btn btn-sm btn-warning\">Edit</a>
                                                            <button type=\"button\" class=\"btn btn-sm btn-danger ms-1\" onclick=\"deleteApplication(" . $student['id'] . ")\">Delete</button>
                                                        </td>";
                                                    echo "</tr>";
                                                }
                                                ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <?php
                }

                // Handle AJAX requests for filtering and deleting
                if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
                    if ($_POST['action'] === 'filter_applications') {
                        $search = $_POST['search'] ?? '';
                        $status = $_POST['status'] ?? '';
                        $payment = $_POST['payment'] ?? '';
                        $acceptancePayment = $_POST['acceptance_payment'] ?? '';

                        $query = "SELECT * FROM students WHERE 1=1";
                        $params = [];

                        if (!empty($search)) {
                            $query .= " AND (first_name LIKE ? OR surname LIKE ? OR email LIKE ? OR application_no LIKE ? OR admission_no LIKE ? OR registration_no LIKE ?)";
                            $searchParam = "%$search%";
                            $params = array_merge($params, [$searchParam, $searchParam, $searchParam, $searchParam, $searchParam, $searchParam]);
                        }

                        if (!empty($status)) {
                            $query .= " AND application_status = ?";
                            $params[] = $status;
                        }

                        if (!empty($payment)) {
                            $query .= " AND registration_fee_status = ?";
                            $params[] = $payment;
                        }

                        if (!empty($acceptancePayment)) {
                            $query .= " AND acceptance_fee_status = ?";
                            $params[] = $acceptancePayment;
                        }

                        $query .= " ORDER BY created_at DESC";

                        $stmt = $pdo->prepare($query);
                        $stmt->execute($params);

                        while ($student = $stmt->fetch(PDO::FETCH_ASSOC)) {
                            echo "<tr data-student-id=\"" . (int)($student['id'] ?? 0) . "\">";
                            echo "<td>" . e(portalReceiptApplicationNumber($student)) . "</td>";
                            echo "<td>" . htmlspecialchars(studentFullName($student)) . "</td>";
                            echo "<td>" . $student['email'] . "</td>";
                            echo "<td>" . $student['mobile_phone'] . "</td>";
                            echo "<td>" . $student['registration_fee_status'] . "</td>";
                            echo "<td>" . $student['acceptance_fee_status'] . "</td>";
                            echo "<td>" . htmlspecialchars(portalStudentHostelStatusLabel($student)) . "</td>";
                            echo "<td>" . $student['school_fee_status'] . "</td>";
                            echo "<td><span class=\"status-badge status-" . strtolower(str_replace(' ', '-', $student['application_status'])) . "\">" . $student['application_status'] . "</span></td>";
                            echo "<td>" . date('d M Y', strtotime($student['created_at'])) . "</td>";
                            echo "<td>
                                    <a href=\"?page=admin_applications&action=view&id=" . $student['id'] . "\" class=\"btn btn-sm btn-primary\">View</a>
                                    <button class=\"btn btn-sm btn-danger ms-1\" onclick=\"deleteApplication(" . $student['id'] . ")\">Delete</button>
                                </td>";
                            echo "</tr>";
                        }

                        exit;
                    } elseif ($_POST['action'] === 'delete_application') {
                        $id = $_POST['id'] ?? 0;

                        if ($id > 0) {
                            // Delete related records
                            $stmt = $pdo->prepare("DELETE FROM education_history WHERE student_id = ?");
                            $stmt->execute([$id]);

                            $stmt = $pdo->prepare("DELETE FROM examination_results WHERE student_id = ?");
                            $stmt->execute([$id]);

                            $stmt = $pdo->prepare("DELETE FROM referees WHERE student_id = ?");
                            $stmt->execute([$id]);

                            $stmt = $pdo->prepare("DELETE FROM admin_decisions WHERE student_id = ?");
                            $stmt->execute([$id]);

                            $stmt = $pdo->prepare("DELETE FROM payment_transactions WHERE student_id = ?");
                            $stmt->execute([$id]);

                            $stmt = $pdo->prepare("DELETE FROM student_documents WHERE student_id = ?");
                            $stmt->execute([$id]);

                            // Delete student
                            $stmt = $pdo->prepare("DELETE FROM students WHERE id = ?");
                            $stmt->execute([$id]);

                            echo "success";
                        } else {
                            echo "Invalid application ID";
                        }

                        exit;
                    }
                }
            } else {
                redirect('?page=login');
            }
            break;

            case 'admin_add_application':
                if (isAdmin()) {
                    portalEnsureApplicationAcademicFieldSupport($pdo);
                    $error = '';
                    $adminRegisterFaculties = [];
                    try { $adminRegisterFaculties = $pdo->query("SELECT id, name FROM faculties WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $adminRegisterFaculties = []; }
                    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_create_application') {
                        $surname = trim($_POST['surname'] ?? '');
                        $firstName = trim($_POST['first_name'] ?? '');
                        $otherName = trim($_POST['other_name'] ?? '');
                        $maidenName = trim($_POST['maiden_name'] ?? '');
                        $sex = trim((string)($_POST['sex'] ?? ''));
                        $dob = trim((string)($_POST['date_of_birth'] ?? ''));
                        $nationality = trim($_POST['nationality'] ?? 'Nigerian');
                        $state = trim($_POST['state_of_origin'] ?? '');
                        $lga = trim($_POST['lga'] ?? '');
                        $mobilePhone = trim($_POST['mobile_phone'] ?? '');
                        $residentialPhone = trim($_POST['residential_phone'] ?? '');
                        $email = trim($_POST['email'] ?? '');
                        $permanentAddress = trim($_POST['permanent_address'] ?? '');
                        $extraCurricular = trim($_POST['extra_curricular'] ?? '');
                        $nokName = trim($_POST['nok_name'] ?? '');
                        $nokPhone = trim($_POST['nok_phone'] ?? '');
                        $sponsorName = trim($_POST['sponsor_name'] ?? '');
                        $sponsorPhone = trim($_POST['sponsor_phone'] ?? '');
                        $sponsorOccupation = trim($_POST['sponsor_occupation'] ?? '');
                        $password = $_POST['password'] ?? '';
                        $registrationAcademic = null;
                        try {
                            $registrationAcademic = portalValidateApplicationAcademicSelection($pdo, (int)($_POST['faculty_id'] ?? 0), (int)($_POST['department_id'] ?? 0), (int)($_POST['programme_id'] ?? 0));
                        } catch (Throwable $e) {
                            $error = $e->getMessage();
                        }
                        if ($error === '' && ($surname === '' || $firstName === '' || $sex === '' || $dob === '' || $nationality === '' || $state === '' || $lga === '' || $mobilePhone === '' || $email === '' || $permanentAddress === '' || $nokName === '' || $nokPhone === '' || $sponsorName === '' || $sponsorPhone === '' || $sponsorOccupation === '' || $password === '')) {
                            $error = 'Please fill all required fields.';
                        } elseif ($error === '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
                            $error = 'Please enter a valid email address.';
                        } else {
                            try {
                                $check = $pdo->prepare("SELECT COUNT(*) FROM students WHERE email = ?");
                                $check->execute([$email]);
                                if ((int)$check->fetchColumn() > 0) {
                                    $error = 'Email already registered.';
                                } else {
                                    $registrationNo = generateRegistrationNumber();
                                    $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
                                    $stmt = $pdo->prepare("INSERT INTO students (registration_no, surname, first_name, other_name, maiden_name, sex, date_of_birth, nationality, state_of_origin, lga, mobile_phone, residential_phone, email, permanent_address, extra_curricular, faculty_id, department_id, programme_id, faculty, department, programme, program, school_name, school, course_in_view, course_of_study, programme_name_cache, nok_name, nok_phone, sponsor_name, sponsor_phone, sponsor_occupation, subject1, subject2, subject3, password) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
                                    $stmt->execute([$registrationNo, $surname, $firstName, $otherName, $maidenName, $sex, $dob, $nationality, $state, $lga, $mobilePhone, $residentialPhone, $email, $permanentAddress, $extraCurricular, (int)($registrationAcademic['faculty_id'] ?? 0), (int)($registrationAcademic['department_id'] ?? 0), (int)($registrationAcademic['programme_id'] ?? 0), $registrationAcademic['school'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['programme'] ?? '', $registrationAcademic['programme'] ?? '', $registrationAcademic['school'] ?? '', $registrationAcademic['school'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['programme'] ?? '', $nokName, $nokPhone, $sponsorName, $sponsorPhone, $sponsorOccupation, '', '', '', $hashedPassword]);
                                    $_SESSION['application_added'] = true;
                                    redirect('?page=admin_applications');
                                }
                            } catch (Throwable $e) {
                                error_log('Admin add application failed: ' . $e->getMessage());
                                $error = 'Unable to add application. Please check database schema and try again.';
                            }
                        }
                    }
                    ?>
                    <div class="card">
                        <div class="card-header"><h5 class="mb-0"><i class="bi bi-plus-circle"></i> Add Application</h5></div>
                        <div class="card-body">
                            <?php if (!empty($error)): ?><div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div><?php endif; ?>
                            <form method="post" id="adminAddApplicationForm">
                                <input type="hidden" name="action" value="admin_create_application">
                                <div class="row">
                                    <div class="col-md-4 mb-3"><label class="form-label">School *</label><select name="faculty_id" class="form-select" required><option value="">Select School</option><?php foreach ($adminRegisterFaculties as $facultyRow): ?><option value="<?php echo (int)($facultyRow['id'] ?? 0); ?>" <?php echo ((int)($_POST['faculty_id'] ?? 0) === (int)($facultyRow['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e((string)($facultyRow['name'] ?? '')); ?></option><?php endforeach; ?></select><div class="form-text">Choose your school first. Only linked course options will be shown.</div></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Course of Study *</label><select name="department_id" class="form-select" required><option value="">Select Course of Study</option><?php if ((int)($_POST['department_id'] ?? 0) > 0): ?><option value="<?php echo (int)($_POST['department_id'] ?? 0); ?>" selected><?php echo e((string)portalDepartmentNameById($pdo, (int)($_POST['department_id'] ?? 0), 'Current Selection')); ?></option><?php endif; ?></select></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Program *</label><select name="programme_id" class="form-select" required><option value="">Select Program</option><?php if ((int)($_POST['programme_id'] ?? 0) > 0): ?><option value="<?php echo (int)($_POST['programme_id'] ?? 0); ?>" selected><?php echo e((string)portalProgrammeSelectorLabelById($pdo, (int)($_POST['programme_id'] ?? 0), (int)($_POST['faculty_id'] ?? 0), (int)($_POST['department_id'] ?? 0), 'Current Selection')); ?></option><?php endif; ?></select></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Surname *</label><input name="surname" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['surname'] ?? '')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">First Name *</label><input name="first_name" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['first_name'] ?? '')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Other Name</label><input name="other_name" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['other_name'] ?? '')); ?>"></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Maiden Name</label><input name="maiden_name" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['maiden_name'] ?? '')); ?>"></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Sex *</label><select name="sex" class="form-select" required><option value="">Select...</option><option value="Male" <?php echo (($_POST['sex'] ?? '') === 'Male') ? 'selected' : ''; ?>>Male</option><option value="Female" <?php echo (($_POST['sex'] ?? '') === 'Female') ? 'selected' : ''; ?>>Female</option></select></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Date of Birth *</label><input type="date" name="date_of_birth" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['date_of_birth'] ?? '')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Nationality *</label><input name="nationality" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['nationality'] ?? 'Nigerian')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">State of Origin *</label><input name="state_of_origin" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['state_of_origin'] ?? '')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">LGA *</label><input name="lga" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['lga'] ?? '')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Mobile Phone *</label><input name="mobile_phone" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['mobile_phone'] ?? '')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Residential Phone</label><input name="residential_phone" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['residential_phone'] ?? '')); ?>"></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Email *</label><input type="email" name="email" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['email'] ?? '')); ?>" required></div>
                                    <div class="col-12 mb-3"><label class="form-label">Permanent Address *</label><textarea name="permanent_address" class="form-control" rows="2" required><?php echo htmlspecialchars((string)($_POST['permanent_address'] ?? '')); ?></textarea></div>
                                    <div class="col-12 mb-3"><label class="form-label">Extra Curricular</label><textarea name="extra_curricular" class="form-control" rows="2"><?php echo htmlspecialchars((string)($_POST['extra_curricular'] ?? '')); ?></textarea></div>
                                    <div class="col-md-6 mb-3"><label class="form-label">Next of Kin Name *</label><input name="nok_name" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['nok_name'] ?? '')); ?>" required></div>
                                    <div class="col-md-6 mb-3"><label class="form-label">Next of Kin Phone *</label><input name="nok_phone" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['nok_phone'] ?? '')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Sponsor Name *</label><input name="sponsor_name" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['sponsor_name'] ?? '')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Sponsor Phone *</label><input name="sponsor_phone" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['sponsor_phone'] ?? '')); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Sponsor Occupation *</label><input name="sponsor_occupation" class="form-control" value="<?php echo htmlspecialchars((string)($_POST['sponsor_occupation'] ?? '')); ?>" required></div>
                                    <div class="col-md-6 mb-3"><label class="form-label">Login Password *</label><input type="password" name="password" class="form-control" required></div>
                                </div>
                                <div class="d-flex gap-2"><button class="btn btn-primary" type="submit"><i class="bi bi-save"></i> Save</button><a class="btn btn-outline-secondary" href="?page=admin_applications">Cancel</a></div>
                            </form>
                        </div>
                    </div>
                    <?php
                } else {
                    redirect('?page=login');
                }
                break;

            case 'admin_edit_application':
                if (isAdmin()) {
                    portalEnsureApplicationAcademicFieldSupport($pdo);
                    $studentId = (int)($_GET['id'] ?? 0);
                    if ($studentId <= 0) { redirect('?page=admin_applications'); }
                    $error = '';
                    $success = false;
                    $adminRegisterFaculties = [];
                    try { $adminRegisterFaculties = $pdo->query("SELECT id, name FROM faculties WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $adminRegisterFaculties = []; }
                    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'admin_save_application') {
                        $surname = trim($_POST['surname'] ?? ''); $firstName = trim($_POST['first_name'] ?? ''); $otherName = trim($_POST['other_name'] ?? ''); $maidenName = trim($_POST['maiden_name'] ?? '');
                        $sex = trim((string)($_POST['sex'] ?? '')); $dob = trim((string)($_POST['date_of_birth'] ?? '')); $nationality = trim($_POST['nationality'] ?? '');
                        $state = trim($_POST['state_of_origin'] ?? ''); $lga = trim($_POST['lga'] ?? ''); $mobilePhone = trim($_POST['mobile_phone'] ?? ''); $residentialPhone = trim($_POST['residential_phone'] ?? '');
                        $email = trim($_POST['email'] ?? ''); $permanentAddress = trim($_POST['permanent_address'] ?? ''); $extraCurricular = trim($_POST['extra_curricular'] ?? '');
                        $nokName = trim($_POST['nok_name'] ?? ''); $nokPhone = trim($_POST['nok_phone'] ?? ''); $sponsorName = trim($_POST['sponsor_name'] ?? ''); $sponsorPhone = trim($_POST['sponsor_phone'] ?? ''); $sponsorOccupation = trim($_POST['sponsor_occupation'] ?? '');
                        $newPassword = $_POST['new_password'] ?? ''; $registrationAcademic = null;
                        try { $registrationAcademic = portalValidateApplicationAcademicSelection($pdo, (int)($_POST['faculty_id'] ?? 0), (int)($_POST['department_id'] ?? 0), (int)($_POST['programme_id'] ?? 0)); } catch (Throwable $e) { $error = $e->getMessage(); }
                        if ($error === '' && ($surname === '' || $firstName === '' || $sex === '' || $dob === '' || $nationality === '' || $state === '' || $lga === '' || $mobilePhone === '' || $email === '' || $permanentAddress === '' || $nokName === '' || $nokPhone === '' || $sponsorName === '' || $sponsorPhone === '' || $sponsorOccupation === '')) {
                            $error = 'Please fill all required fields.';
                        } elseif ($error === '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
                            $error = 'Please enter a valid email address.';
                        } else {
                            try {
                                $pdo->beginTransaction();
                                $stmt = $pdo->prepare("UPDATE students SET surname=?, first_name=?, other_name=?, maiden_name=?, sex=?, date_of_birth=?, nationality=?, state_of_origin=?, lga=?, mobile_phone=?, residential_phone=?, email=?, permanent_address=?, extra_curricular=?, faculty_id=?, department_id=?, programme_id=?, faculty=?, department=?, programme=?, program=?, school_name=?, school=?, course_in_view=?, course_of_study=?, programme_name_cache=?, nok_name=?, nok_phone=?, sponsor_name=?, sponsor_phone=?, sponsor_occupation=?, subject1='', subject2='', subject3='' WHERE id=?");
                                $stmt->execute([$surname, $firstName, $otherName, $maidenName, $sex, $dob, $nationality, $state, $lga, $mobilePhone, $residentialPhone, $email, $permanentAddress, $extraCurricular, (int)($registrationAcademic['faculty_id'] ?? 0), (int)($registrationAcademic['department_id'] ?? 0), (int)($registrationAcademic['programme_id'] ?? 0), $registrationAcademic['school'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['programme'] ?? '', $registrationAcademic['programme'] ?? '', $registrationAcademic['school'] ?? '', $registrationAcademic['school'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['course_of_study'] ?? '', $registrationAcademic['programme'] ?? '', $nokName, $nokPhone, $sponsorName, $sponsorPhone, $sponsorOccupation, $studentId]);
                                if (!empty($newPassword)) { $hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT); $pdo->prepare("UPDATE students SET password=? WHERE id=?")->execute([$hashedPassword, $studentId]); }
                                $pdo->commit(); $success = true;
                            } catch (Throwable $e) {
                                if ($pdo->inTransaction()) $pdo->rollBack(); error_log('Admin edit application failed: ' . $e->getMessage()); $error = 'Unable to save changes. Please try again.';
                            }
                        }
                    }
                    $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?"); $stmt->execute([$studentId]); $student = $stmt->fetch(PDO::FETCH_ASSOC); if (!$student) { redirect('?page=admin_applications'); }
                    $studentAcademicState = portalStudentApplicationAcademicState($pdo, (array)$student); $editFacultyId = (int)($_POST['faculty_id'] ?? ($studentAcademicState['faculty_id'] ?? 0)); $editDepartmentId = (int)($_POST['department_id'] ?? ($studentAcademicState['department_id'] ?? 0)); $editProgrammeId = (int)($_POST['programme_id'] ?? ($studentAcademicState['programme_id'] ?? 0));
                    ?>
                    <div class="card">
                        <div class="card-header d-flex align-items-center justify-content-between"><h5 class="mb-0"><i class="bi bi-pencil-square"></i> Edit Application</h5><a class="btn btn-outline-secondary btn-sm" href="?page=admin_applications&action=view&id=<?php echo (int)$studentId; ?>"><i class="bi bi-eye"></i> View</a></div>
                        <div class="card-body">
                            <?php if ($success): ?><div class="alert alert-success">Application updated successfully.</div><?php endif; ?>
                            <?php if (!empty($error)): ?><div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div><?php endif; ?>
                            <form method="post" id="adminEditApplicationForm">
                                <input type="hidden" name="action" value="admin_save_application">
                                <div class="row">
                                    <div class="col-md-4 mb-3"><label class="form-label">Registration No</label><input class="form-control" value="<?php echo htmlspecialchars($student['registration_no']); ?>" readonly></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">School *</label><select name="faculty_id" class="form-select" required><option value="">Select School</option><?php foreach ($adminRegisterFaculties as $facultyRow): ?><option value="<?php echo (int)($facultyRow['id'] ?? 0); ?>" <?php echo ($editFacultyId === (int)($facultyRow['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e((string)($facultyRow['name'] ?? '')); ?></option><?php endforeach; ?></select></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Course of Study *</label><select name="department_id" class="form-select" required><option value="">Select Course of Study</option><?php if ($editDepartmentId > 0): ?><option value="<?php echo $editDepartmentId; ?>" selected><?php echo e((string)($studentAcademicState['course_of_study'] ?? '')); ?></option><?php endif; ?></select></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Program *</label><select name="programme_id" class="form-select" required><option value="">Select Program</option><?php if ($editProgrammeId > 0): ?><option value="<?php echo $editProgrammeId; ?>" selected><?php echo e((string)($studentAcademicState['programme'] ?? '')); ?></option><?php endif; ?></select></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Surname *</label><input name="surname" class="form-control" value="<?php echo htmlspecialchars($_POST['surname'] ?? $student['surname']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">First Name *</label><input name="first_name" class="form-control" value="<?php echo htmlspecialchars($_POST['first_name'] ?? $student['first_name']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Other Name</label><input name="other_name" class="form-control" value="<?php echo htmlspecialchars($_POST['other_name'] ?? ($student['other_name'] ?? '')); ?>"></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Maiden Name</label><input name="maiden_name" class="form-control" value="<?php echo htmlspecialchars($_POST['maiden_name'] ?? ($student['maiden_name'] ?? '')); ?>"></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Sex *</label><select name="sex" class="form-select" required><option value="">Select...</option><option value="Male" <?php echo (($_POST['sex'] ?? $student['sex'])==='Male')?'selected':''; ?>>Male</option><option value="Female" <?php echo (($_POST['sex'] ?? $student['sex'])==='Female')?'selected':''; ?>>Female</option></select></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Date of Birth *</label><input type="date" name="date_of_birth" class="form-control" value="<?php echo htmlspecialchars($_POST['date_of_birth'] ?? $student['date_of_birth']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Nationality *</label><input name="nationality" class="form-control" value="<?php echo htmlspecialchars($_POST['nationality'] ?? $student['nationality']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">State of Origin *</label><input name="state_of_origin" class="form-control" value="<?php echo htmlspecialchars($_POST['state_of_origin'] ?? $student['state_of_origin']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">LGA *</label><input name="lga" class="form-control" value="<?php echo htmlspecialchars($_POST['lga'] ?? $student['lga']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Mobile Phone *</label><input name="mobile_phone" class="form-control" value="<?php echo htmlspecialchars($_POST['mobile_phone'] ?? $student['mobile_phone']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Residential Phone</label><input name="residential_phone" class="form-control" value="<?php echo htmlspecialchars($_POST['residential_phone'] ?? ($student['residential_phone'] ?? '')); ?>"></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Email *</label><input type="email" name="email" class="form-control" value="<?php echo htmlspecialchars($_POST['email'] ?? $student['email']); ?>" required></div>
                                    <div class="col-12 mb-3"><label class="form-label">Permanent Address *</label><textarea name="permanent_address" class="form-control" rows="2" required><?php echo htmlspecialchars($_POST['permanent_address'] ?? $student['permanent_address']); ?></textarea></div>
                                    <div class="col-12 mb-3"><label class="form-label">Extra Curricular</label><textarea name="extra_curricular" class="form-control" rows="2"><?php echo htmlspecialchars($_POST['extra_curricular'] ?? ($student['extra_curricular'] ?? '')); ?></textarea></div>
                                    <div class="col-md-6 mb-3"><label class="form-label">Next of Kin Name *</label><input name="nok_name" class="form-control" value="<?php echo htmlspecialchars($_POST['nok_name'] ?? $student['nok_name']); ?>" required></div>
                                    <div class="col-md-6 mb-3"><label class="form-label">Next of Kin Phone *</label><input name="nok_phone" class="form-control" value="<?php echo htmlspecialchars($_POST['nok_phone'] ?? $student['nok_phone']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Sponsor Name *</label><input name="sponsor_name" class="form-control" value="<?php echo htmlspecialchars($_POST['sponsor_name'] ?? $student['sponsor_name']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Sponsor Phone *</label><input name="sponsor_phone" class="form-control" value="<?php echo htmlspecialchars($_POST['sponsor_phone'] ?? $student['sponsor_phone']); ?>" required></div>
                                    <div class="col-md-4 mb-3"><label class="form-label">Sponsor Occupation *</label><input name="sponsor_occupation" class="form-control" value="<?php echo htmlspecialchars($_POST['sponsor_occupation'] ?? $student['sponsor_occupation']); ?>" required></div>
                                    <div class="col-md-6 mb-3"><label class="form-label">Reset Password</label><input type="password" name="new_password" class="form-control" placeholder="Leave empty to keep current"></div>
                                </div>
                                <div class="d-flex gap-2"><button class="btn btn-primary" type="submit"><i class="bi bi-save"></i> Save Changes</button><a class="btn btn-outline-secondary" href="?page=admin_applications">Back</a></div>
                            </form>
                        </div>
                    </div>
                    <?php
                } else {
                    redirect('?page=login');
                }
                break;
            case 'admin_wallets':
                if (!isAdmin()) { redirect('?page=login'); }
                // List students + wallet balances
                $rows = [];
                try {
                    // Ensure wallets exist for listed students
                    $pdo->exec("INSERT IGNORE INTO wallets (student_id, balance) SELECT id, 0.00 FROM students");
                } catch (Throwable $e) {}
                try {
                    $q = $pdo->query("SELECT s.id, s.registration_no, s.surname, s.first_name, s.other_name, s.email,
                                         w.balance, COALESCE(w.status,'Active') AS wallet_status, COALESCE(w.is_enabled,1) AS wallet_enabled
                                  FROM students s
                                  LEFT JOIN wallets w ON w.student_id = s.id
                                  ORDER BY s.id DESC
                                  LIMIT 300");
                    $rows = $q->fetchAll(PDO::FETCH_ASSOC);
                } catch (Throwable $e) {}

                $walletActivityRows = [];
                try {
                    portalEnsureWalletTransactionMetaSupport($pdo);
                    $wa = $pdo->query("SELECT wt.*, s.registration_no, s.surname, s.first_name, s.other_name
                        FROM wallet_transactions wt
                        JOIN students s ON s.id = wt.student_id
                        ORDER BY wt.id DESC
                        LIMIT 50");
                    $walletActivityRows = $wa->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) {}

                ?>
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header d-flex justify-content-between align-items-center">
                                <h5 class="mb-0">Student Wallets</h5>
                                <span class="text-muted small">Manage balances, enable/disable, suspend/block.</span>
                            </div>
                            <div class="card-body">
                                <?php renderFlashes(); ?>
                                <div class="table-responsive">
                                    <table class="table table-striped align-middle">
                                        <thead>
                                            <tr>
                                                <th>Matric No.</th>
                                                <th>Name</th>
                                                <th>Email</th>
                                                <th class="text-end">Balance (₦)</th>
                                                <th>Status</th>
                                                <th>Enabled</th>
                                                <th>Action</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                        <?php foreach ($rows as $r): ?>
                                            <tr>
                                                <td><?php echo htmlspecialchars((string)$r['registration_no']); ?></td>
                                                <td><?php echo htmlspecialchars(full_name($r)); ?></td>
                                                <td><?php echo htmlspecialchars((string)$r['email']); ?></td>
                                                <td class="text-end"><?php echo number_format((float)($r['balance'] ?? 0), 2); ?></td>
                                                <td>
                                                    <span class="badge bg-<?php echo ($r['wallet_status']==='Active'?'success':($r['wallet_status']==='Suspended'?'warning':'danger')); ?>">
                                                        <?php echo htmlspecialchars((string)$r['wallet_status']); ?>
                                                    </span>
                                                </td>
                                                <td><?php echo ((int)$r['wallet_enabled']===1) ? '<span class="badge bg-success">Yes</span>' : '<span class="badge bg-secondary">No</span>'; ?></td>
                                                <td>
                                                    <button class="btn btn-sm btn-primary"
                                                        data-bs-toggle="modal" data-bs-target="#walletModal"
                                                        data-student-id="<?php echo (int)$r['id']; ?>"
                                                        data-student-name="<?php echo htmlspecialchars(full_name($r)); ?>"
                                                        data-wallet-status="<?php echo htmlspecialchars((string)$r['wallet_status']); ?>"
                                                        data-wallet-enabled="<?php echo (int)$r['wallet_enabled']; ?>">
                                                        Manage
                                                    </button>
                                                </td>
                                            </tr>
                                        <?php endforeach; ?>
                                        <?php if (empty($rows)): ?>
                                            <tr><td colspan="7" class="text-center text-muted">No students found.</td></tr>
                                        <?php endif; ?>
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="row mt-3">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-header d-flex justify-content-between align-items-center">
                                <h6 class="mb-0">Recent Wallet Activity</h6>
                                <span class="text-muted small">Shows previous balance, new balance, and related reference.</span>
                            </div>
                            <div class="card-body">
                                <div class="table-responsive">
                                    <table class="table table-striped align-middle">
                                        <thead>
                                            <tr>
                                                <th>Date</th>
                                                <th>Student</th>
                                                <th>Type</th>
                                                <th>Amount</th>
                                                <th>Previous Balance</th>
                                                <th>New Balance</th>
                                                <th>Reference</th>
                                                <th>Narration</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <?php if (empty($walletActivityRows)): ?>
                                                <tr><td colspan="8" class="text-center text-muted">No wallet activity found.</td></tr>
                                            <?php else: foreach ($walletActivityRows as $activity): ?>
                                                <tr>
                                                    <td><?php echo e((string)($activity['created_at'] ?? '')); ?></td>
                                                    <td><?php echo e(full_name($activity)); ?> <div class="small text-muted"><?php echo e((string)($activity['registration_no'] ?? '')); ?></div></td>
                                                    <td><?php echo e(strtoupper((string)($activity['type'] ?? ''))); ?></td>
                                                    <td>₦<?php echo number_format((float)($activity['amount'] ?? 0), 2); ?></td>
                                                    <td>₦<?php echo number_format((float)($activity['balance_before'] ?? 0), 2); ?></td>
                                                    <td>₦<?php echo number_format((float)($activity['balance_after'] ?? 0), 2); ?></td>
                                                    <td><?php echo e((string)($activity['related_payment_reference'] ?? $activity['related_transaction_id'] ?? '')); ?></td>
                                                    <td><?php echo e((string)($activity['narration'] ?? '')); ?></td>
                                                </tr>
                                            <?php endforeach; endif; ?>
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Wallet Manage Modal -->
                <div class="modal fade" id="walletModal" tabindex="-1" aria-hidden="true">
                    <div class="modal-dialog">
                        <div class="modal-content">
                            <form method="post">
                                <div class="modal-header">
                                    <h5 class="modal-title">Manage Wallet</h5>
                                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                                </div>
                                <div class="modal-body">
                                    <input type="hidden" name="action" value="admin_wallet_update">
                                    <input type="hidden" name="student_id" id="wm_student_id" value="">
                                    <div class="mb-2">
                                        <label class="form-label">Student</label>
                                        <input type="text" class="form-control" id="wm_student_name" value="" disabled>
                                    </div>

                                    <div class="mb-2">
                                        <label class="form-label">Operation</label>
                                        <select class="form-select" name="op" id="wm_op">
                                            <option value="credit">Credit</option>
                                            <option value="debit">Debit</option>
                                            <option value="update_flags">Update Status/Enable</option>
                                        </select>
                                    </div>

                                    <div id="wm_amount_block">
                                        <div class="mb-2">
                                            <label class="form-label">Amount (₦)</label>
                                            <input type="number" class="form-control" name="amount" min="0" step="50" value="0">
                                        </div>
                                        <div class="mb-2">
                                            <label class="form-label">Narration (optional)</label>
                                            <input type="text" class="form-control" name="narration" placeholder="e.g. Adjustment">
                                        </div>
                                    </div>

                                    <div id="wm_flags_block" style="display:none;">
                                        <div class="mb-2">
                                            <label class="form-label">Wallet Status</label>
                                            <select class="form-select" name="wallet_status" id="wm_status">
                                                <option value="">(no change)</option>
                                                <option value="Active">Active</option>
                                                <option value="Suspended">Suspended</option>
                                                <option value="Blocked">Blocked</option>
                                            </select>
                                        </div>
                                        <div class="form-check form-switch">
                                            <input class="form-check-input" type="checkbox" id="wm_enabled" name="wallet_is_enabled">
                                            <label class="form-check-label" for="wm_enabled">Enable Wallet for Transactions</label>
                                        </div>
                                    </div>

                                    <div class="alert alert-info mt-3 mb-0 small">
                                        Note: "Suspended/Blocked" prevents students from paying with wallet. Admin can still credit/debit.
                                    </div>
                                </div>
                                <div class="modal-footer">
                                    <button class="btn btn-secondary" type="button" data-bs-dismiss="modal">Close</button>
                                    <button class="btn btn-primary" type="submit">Save</button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>

                <script>
                (function(){
                    const m = document.getElementById('walletModal');
                    if(!m) return;
                    m.addEventListener('show.bs.modal', function (event) {
                        const btn = event.relatedTarget;
                        if(!btn) return;
                        document.getElementById('wm_student_id').value = btn.getAttribute('data-student-id') || '';
                        document.getElementById('wm_student_name').value = btn.getAttribute('data-student-name') || '';
                        document.getElementById('wm_status').value = btn.getAttribute('data-wallet-status') || '';
                        const en = parseInt(btn.getAttribute('data-wallet-enabled') || '1', 10);
                        document.getElementById('wm_enabled').checked = (en === 1);
                        document.getElementById('wm_op').value = 'credit';
                        document.getElementById('wm_flags_block').style.display = 'none';
                        document.getElementById('wm_amount_block').style.display = 'block';
                    });
                    document.getElementById('wm_op').addEventListener('change', function(){
                        const op = this.value;
                        const flags = document.getElementById('wm_flags_block');
                        const amt = document.getElementById('wm_amount_block');
                        if(op === 'update_flags'){
                            flags.style.display = 'block';
                            amt.style.display = 'none';
                        } else {
                            flags.style.display = 'none';
                            amt.style.display = 'block';
                        }
                    });
                })();
                </script>
                <?php
                break;
            case 'admin_numbering_settings':
                if (isAdmin()) {
                    requireStaffPermission($pdo, 'academics_manage');
                    portalEnsureNumberingSchema($pdo);
                    try { $pdo->exec("UPDATE auto_number_formats SET scope_session = '' WHERE scope_session IS NULL"); } catch (Throwable $e) {}
                    $faculties = [];
                    $departments = [];
                    $programmes = [];
                    $academicSessions = [];
                    $formats = [];
                    $availableLevels = portalAvailableAcademicLevels($pdo);
                    $sampleLevel = (string)($availableLevels[0] ?? '100');
                    $currentSessionName = portalCurrentAcademicSessionName($pdo);
                    $currentSemesterName = portalCurrentAcademicSemesterName($pdo);
                    $editRow = [
                        'id' => 0,
                        'number_type' => 'matric',
                        'scope_type' => 'global',
                        'scope_id' => 0,
                        'scope_session' => '',
                        'prefix' => '{FACULTY_CODE}/{YEAR}/',
                        'affix' => '',
                        'serial_digits' => 4,
                        'start_from' => 1,
                        'is_active' => 1,
                        'faculty_id' => 0,
                        'department_id' => 0,
                        'programme_id' => 0,
                    ];

                    try { $faculties = $pdo->query("SELECT id, name, code FROM faculties WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
                    try { $departments = $pdo->query("SELECT d.id, d.name, d.code, d.faculty_id, f.name AS faculty_name FROM departments d LEFT JOIN faculties f ON f.id = d.faculty_id WHERE d.is_active = 1 ORDER BY f.name ASC, d.name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
                    try { $programmes = $pdo->query("SELECT p.id, p.name, p.code, p.faculty_id, p.department_id, d.name AS department_name, f.name AS faculty_name FROM programmes p LEFT JOIN departments d ON d.id = p.department_id LEFT JOIN faculties f ON f.id = p.faculty_id WHERE p.is_active = 1 ORDER BY f.name ASC, d.name ASC, p.name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
                    try { $academicSessions = $pdo->query("SELECT id, name FROM academic_sessions ORDER BY is_current DESC, start_date DESC, id DESC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
                    if (!$academicSessions && $currentSessionName !== '') {
                        $academicSessions = [['id' => 0, 'name' => $currentSessionName]];
                    }

                    $editId = (int)($_GET['edit'] ?? 0);
                    if ($editId > 0) {
                        try {
                            $st = $pdo->prepare("SELECT * FROM auto_number_formats WHERE id = ? LIMIT 1");
                            $st->execute([$editId]);
                            if ($row = $st->fetch(PDO::FETCH_ASSOC)) {
                                $editRow = array_merge($editRow, $row);
                                if (($editRow['scope_type'] ?? '') === 'faculty') {
                                    $editRow['faculty_id'] = (int)($editRow['scope_id'] ?? 0);
                                } elseif (($editRow['scope_type'] ?? '') === 'department') {
                                    $editRow['department_id'] = (int)($editRow['scope_id'] ?? 0);
                                    foreach ($departments as $dep) {
                                        if ((int)$dep['id'] === (int)$editRow['department_id']) { $editRow['faculty_id'] = (int)($dep['faculty_id'] ?? 0); break; }
                                    }
                                } elseif (($editRow['scope_type'] ?? '') === 'programme') {
                                    $editRow['programme_id'] = (int)($editRow['scope_id'] ?? 0);
                                    foreach ($programmes as $prog) {
                                        if ((int)$prog['id'] === (int)$editRow['programme_id']) {
                                            $editRow['faculty_id'] = (int)($prog['faculty_id'] ?? 0);
                                            $editRow['department_id'] = (int)($prog['department_id'] ?? 0);
                                            break;
                                        }
                                    }
                                }
                            }
                        } catch (Throwable $e) {}
                    }

                    try {
                        $sql = "SELECT anf.*, 
                                   CASE
                                     WHEN anf.scope_type = 'faculty' THEN f.name
                                     WHEN anf.scope_type = 'department' THEN d.name
                                     WHEN anf.scope_type = 'programme' THEN p.name
                                     WHEN anf.scope_type = 'session' THEN anf.scope_session
                                     ELSE 'Global Default'
                                   END AS scope_name,
                                   f.name AS faculty_name, d.name AS department_name, p.name AS programme_name
                                FROM auto_number_formats anf
                                LEFT JOIN faculties f ON anf.scope_type = 'faculty' AND f.id = anf.scope_id
                                LEFT JOIN departments d ON anf.scope_type = 'department' AND d.id = anf.scope_id
                                LEFT JOIN programmes p ON anf.scope_type = 'programme' AND p.id = anf.scope_id
                                ORDER BY FIELD(anf.number_type, 'application', 'registration', 'admission', 'matric'), FIELD(anf.scope_type, 'global', 'session', 'faculty', 'department', 'programme'), scope_name ASC";
                        $formats = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    } catch (Throwable $e) { $formats = []; }

                    $previewSession = (string)(($editRow['scope_type'] ?? '') === 'session' ? ($editRow['scope_session'] ?? '') : $currentSessionName);
                    if ($previewSession === '') $previewSession = $currentSessionName;
                    $numberingPreviewSource = [
                        'faculty_id' => (int)($editRow['faculty_id'] ?? 0),
                        'department_id' => (int)($editRow['department_id'] ?? 0),
                        'programme_id' => (int)($editRow['programme_id'] ?? 0),
                        'session_name' => $previewSession,
                        'session' => $previewSession,
                        'semester' => $currentSemesterName,
                        'level' => $sampleLevel,
                    ];
                    $numberingPreview = portalNumberingPreview($pdo, (string)($editRow['number_type'] ?? 'matric'), $numberingPreviewSource, [
                        'prefix' => (string)($editRow['prefix'] ?? ''),
                        'affix' => (string)($editRow['affix'] ?? ''),
                        'serial_digits' => (int)($editRow['serial_digits'] ?? 4),
                        'start_from' => (int)($editRow['start_from'] ?? 1),
                    ]);
                    $applicationPreview = portalNumberingPreview($pdo, 'application', $numberingPreviewSource);
                    $registrationPreview = portalNumberingPreview($pdo, 'registration', $numberingPreviewSource);

                    $numberingSearch = trim((string)($_GET['nfmt_q'] ?? ''));
                    $numberingFilterType = trim((string)($_GET['nfmt_type'] ?? ''));
                    $numberingFilterScope = trim((string)($_GET['nfmt_scope'] ?? ''));
                    $numberingFilterStatus = trim((string)($_GET['nfmt_status'] ?? ''));
                    $numberingHasSavedFilters = ($numberingSearch !== '' || $numberingFilterType !== '' || $numberingFilterScope !== '' || $numberingFilterStatus !== '');
                    $requestedNumberingTab = trim((string)($_GET['numbering_tab'] ?? ''));
                    if (!in_array($requestedNumberingTab, ['setup', 'formats'], true)) {
                        $requestedNumberingTab = $numberingHasSavedFilters ? 'formats' : 'setup';
                    }
                    if ($editId > 0 && !$numberingHasSavedFilters && $requestedNumberingTab !== 'formats') {
                        $requestedNumberingTab = 'setup';
                    }
                    $numberingActiveTab = $requestedNumberingTab !== '' ? $requestedNumberingTab : 'setup';

                    $numberingTypeOptions = ['application' => 'Application', 'registration' => 'Registration', 'admission' => 'Admission', 'matric' => 'Matric'];
                    $numberingScopeOptions = ['global' => 'Global', 'session' => 'Session', 'faculty' => 'Faculty', 'department' => 'Department', 'programme' => 'Programme'];

                    $numberingTotalFormats = count($formats);
                    $numberingActiveCount = 0;
                    foreach ($formats as $formatRow) {
                        if ((int)($formatRow['is_active'] ?? 1) === 1) $numberingActiveCount++;
                        $rowTypeKey = strtolower(trim((string)($formatRow['number_type'] ?? '')));
                        if ($rowTypeKey !== '' && !isset($numberingTypeOptions[$rowTypeKey])) {
                            $numberingTypeOptions[$rowTypeKey] = ucwords(str_replace('_', ' ', $rowTypeKey));
                        }
                        $rowScopeKey = strtolower(trim((string)($formatRow['scope_type'] ?? '')));
                        if ($rowScopeKey !== '' && !isset($numberingScopeOptions[$rowScopeKey])) {
                            $numberingScopeOptions[$rowScopeKey] = ucwords(str_replace('_', ' ', $rowScopeKey));
                        }
                    }
                    $numberingInactiveCount = max(0, $numberingTotalFormats - $numberingActiveCount);
                    ksort($numberingTypeOptions, SORT_NATURAL | SORT_FLAG_CASE);
                    ksort($numberingScopeOptions, SORT_NATURAL | SORT_FLAG_CASE);

                    $filteredFormats = array_values(array_filter($formats, static function(array $row) use ($numberingSearch, $numberingFilterType, $numberingFilterScope, $numberingFilterStatus): bool {
                        $rowType = strtolower(trim((string)($row['number_type'] ?? '')));
                        $rowScope = strtolower(trim((string)($row['scope_type'] ?? '')));
                        $rowStatus = ((int)($row['is_active'] ?? 1) === 1) ? 'active' : 'disabled';

                        if ($numberingFilterType !== '' && $rowType !== strtolower($numberingFilterType)) return false;
                        if ($numberingFilterScope !== '' && $rowScope !== strtolower($numberingFilterScope)) return false;
                        if ($numberingFilterStatus !== '' && $rowStatus !== strtolower($numberingFilterStatus)) return false;

                        if ($numberingSearch !== '') {
                            $needle = function_exists('mb_strtolower') ? mb_strtolower($numberingSearch, 'UTF-8') : strtolower($numberingSearch);
                            $haystack = implode(' ', [
                                (string)($row['number_type'] ?? ''),
                                (string)($row['scope_type'] ?? ''),
                                (string)($row['scope_name'] ?? ''),
                                (string)($row['prefix'] ?? ''),
                                (string)($row['affix'] ?? ''),
                            ]);
                            $haystack = function_exists('mb_strtolower') ? mb_strtolower($haystack, 'UTF-8') : strtolower($haystack);
                            if ($needle !== '' && !str_contains($haystack, $needle)) return false;
                        }

                        return true;
                    }));
                    $numberingShowingCount = count($filteredFormats);

                    $numberingFormatsReturnParams = ['page' => 'admin_numbering_settings', 'numbering_tab' => 'formats'];
                    if ($numberingSearch !== '') $numberingFormatsReturnParams['nfmt_q'] = $numberingSearch;
                    if ($numberingFilterType !== '') $numberingFormatsReturnParams['nfmt_type'] = $numberingFilterType;
                    if ($numberingFilterScope !== '') $numberingFormatsReturnParams['nfmt_scope'] = $numberingFilterScope;
                    if ($numberingFilterStatus !== '') $numberingFormatsReturnParams['nfmt_status'] = $numberingFilterStatus;
                    $numberingFormatsReturnUrl = '?' . http_build_query($numberingFormatsReturnParams);
                    ?>

                    <style>
                        .numbering-settings-page{padding-left:.35rem;padding-right:.35rem;}
                        .numbering-settings-page .nav-tabs{margin-bottom:.75rem !important;}
                        .numbering-settings-page .nav-link{padding:.6rem .95rem;}
                        .numbering-settings-page .numbering-setup-card{border:1px solid #d8e1ea;box-shadow:0 .125rem .35rem rgba(15,23,42,.04);}
                        .numbering-settings-page .numbering-setup-card .card-header{padding:.8rem 1rem;}
                        .numbering-settings-page .numbering-setup-card .card-body{padding:1rem 1rem .95rem;}
                        .numbering-settings-page .numbering-setup-form{row-gap:.75rem;}
                        .numbering-settings-page .numbering-setup-form .form-label{margin-bottom:.35rem;font-weight:500;}
                        .numbering-settings-page .numbering-setup-form .form-text{margin-top:.35rem;font-size:.82rem;line-height:1.35;}
                        .numbering-settings-page .numbering-setup-form .form-check{margin-top:.15rem;}
                        .numbering-settings-page .numbering-preview-card{padding:.9rem 1rem !important;background:#f8fafc !important;min-height:0 !important;}
                        .numbering-settings-page .numbering-preview-card .numbering-preview-title{margin-bottom:.35rem !important;}
                        .numbering-settings-page .numbering-preview-card .numbering-preview-note{margin-bottom:.5rem !important;line-height:1.4;}
                        .numbering-settings-page .numbering-preview-meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.25rem 1rem;}
                        .numbering-settings-page .numbering-preview-error{margin-top:.45rem !important;}
                        .numbering-settings-page .numbering-help-block{padding:.75rem .95rem !important;font-size:.92rem;line-height:1.45;border-radius:.5rem;}
                        .numbering-settings-page .numbering-actions{padding-top:.15rem;gap:.6rem !important;}
                        .numbering-settings-page .numbering-actions .btn{padding:.5rem .95rem;}
                        @media (max-width: 767.98px){
                            .numbering-settings-page{padding-left:.15rem;padding-right:.15rem;}
                            .numbering-settings-page .numbering-setup-card .card-header,
                            .numbering-settings-page .numbering-setup-card .card-body{padding:.85rem .85rem .8rem;}
                            .numbering-settings-page .numbering-preview-card{padding:.8rem .85rem !important;}
                            .numbering-settings-page .numbering-help-block{padding:.7rem .8rem !important;}
                            .numbering-settings-page .numbering-preview-meta{grid-template-columns:1fr;}
                            .numbering-settings-page .numbering-actions .btn{width:100%;justify-content:center;}
                        }
                    </style>

                    <div class="container-fluid numbering-settings-page">
                        <?php renderFlashes(); ?>
                        <ul class="nav nav-tabs mb-2" id="numberingSettingsTabs" role="tablist">
                            <li class="nav-item" role="presentation">
                                <a class="nav-link <?php echo $numberingActiveTab === 'setup' ? 'active' : ''; ?>" href="?page=admin_numbering_settings&numbering_tab=setup<?php echo $editId > 0 ? '&edit=' . (int)$editId : ''; ?>" role="tab" aria-selected="<?php echo $numberingActiveTab === 'setup' ? 'true' : 'false'; ?>">
                                    <i class="bi bi-hash"></i> Admission / Matric / Application Numbering
                                </a>
                            </li>
                            <li class="nav-item" role="presentation">
                                <a class="nav-link <?php echo $numberingActiveTab === 'formats' ? 'active' : ''; ?>" href="<?php echo e($numberingFormatsReturnUrl); ?>" role="tab" aria-selected="<?php echo $numberingActiveTab === 'formats' ? 'true' : 'false'; ?>">
                                    <i class="bi bi-list-ul"></i> Saved Numbering Formats
                                </a>
                            </li>
                        </ul>

                        <div class="tab-content">
                            <div class="tab-pane fade <?php echo $numberingActiveTab === 'setup' ? 'show active' : ''; ?>" id="numbering-tab-setup" role="tabpanel">
                                <div class="card mb-2 numbering-setup-card">
                                    <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                        <span><i class="bi bi-hash"></i> Admission / Matric / Application Numbering</span>
                                        <?php if ($editId > 0): ?><a class="btn btn-sm btn-outline-secondary" href="?page=admin_numbering_settings&numbering_tab=setup">New</a><?php endif; ?>
                                    </div>
                                    <div class="card-body">
                                        <form method="post" class="row g-2 g-md-3 numbering-setup-form" id="numberingSettingsForm">
                                            <input type="hidden" name="action" value="save_auto_number_format">
                                            <input type="hidden" name="edit_id" value="<?php echo (int)($editRow['id'] ?? 0); ?>">
                                            <input type="hidden" name="numbering_tab" value="setup">

                                            <div class="col-md-6">
                                                <label class="form-label mb-1">Number Type</label>
                                                <select class="form-select" name="number_type" id="number_type_select" required>
                                                    <option value="application" <?php echo (($editRow['number_type'] ?? 'matric') === 'application') ? 'selected' : ''; ?>>Application Number</option>
                                                    <option value="registration" <?php echo (($editRow['number_type'] ?? 'matric') === 'registration') ? 'selected' : ''; ?>>Registration Number</option>
                                                    <option value="admission" <?php echo (($editRow['number_type'] ?? 'matric') === 'admission') ? 'selected' : ''; ?>>Admission Number</option>
                                                    <option value="matric" <?php echo (($editRow['number_type'] ?? 'matric') === 'matric') ? 'selected' : ''; ?>>Matric Number</option>
                                                </select>
                                            </div>
                                            <div class="col-md-6">
                                                <label class="form-label mb-1">Apply To</label>
                                                <select class="form-select" name="scope_type" id="number_scope_type">
                                                    <option value="global" <?php echo (($editRow['scope_type'] ?? 'global') === 'global') ? 'selected' : ''; ?>>Global Default</option>
                                                    <option value="session" <?php echo (($editRow['scope_type'] ?? '') === 'session') ? 'selected' : ''; ?>>Academic Session</option>
                                                    <option value="faculty" <?php echo (($editRow['scope_type'] ?? '') === 'faculty') ? 'selected' : ''; ?>>Faculty / School</option>
                                                    <option value="department" <?php echo (($editRow['scope_type'] ?? '') === 'department') ? 'selected' : ''; ?>>Department</option>
                                                    <option value="programme" <?php echo (($editRow['scope_type'] ?? '') === 'programme') ? 'selected' : ''; ?>>Programme</option>
                                                </select>
                                            </div>

                                            <div class="col-md-6 numbering-scope numbering-scope-session">
                                                <label class="form-label mb-1">Academic Session</label>
                                                <select class="form-select" name="scope_session" id="number_scope_session">
                                                    <option value="">Select Session</option>
                                                    <?php foreach ($academicSessions as $sessionRow): ?>
                                                        <option value="<?php echo e((string)$sessionRow['name']); ?>" <?php echo ((string)($editRow['scope_session'] ?? '') === (string)$sessionRow['name']) ? 'selected' : ''; ?>><?php echo e((string)$sessionRow['name']); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>

                                            <div class="col-md-4 numbering-scope numbering-scope-faculty numbering-scope-department numbering-scope-programme">
                                                <label class="form-label">Faculty</label>
                                                <select class="form-select" name="faculty_id" id="number_faculty_select">
                                                    <option value="0" data-code="" data-name="">Select Faculty</option>
                                                    <?php foreach ($faculties as $f): ?>
                                                        <option value="<?php echo (int)$f['id']; ?>" data-code="<?php echo e((string)($f['code'] ?? '')); ?>" data-name="<?php echo e((string)$f['name']); ?>" <?php echo ((int)($editRow['faculty_id'] ?? 0) === (int)$f['id']) ? 'selected' : ''; ?>><?php echo e($f['name']); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-md-4 numbering-scope numbering-scope-department numbering-scope-programme">
                                                <label class="form-label">Department</label>
                                                <select class="form-select" name="department_id" id="number_department_select">
                                                    <option value="0" data-code="" data-name="" data-faculty-id="0">Select Department</option>
                                                    <?php foreach ($departments as $d): ?>
                                                        <option value="<?php echo (int)$d['id']; ?>" data-code="<?php echo e((string)($d['code'] ?? '')); ?>" data-name="<?php echo e((string)$d['name']); ?>" data-faculty-id="<?php echo (int)($d['faculty_id'] ?? 0); ?>" <?php echo ((int)($editRow['department_id'] ?? 0) === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e($d['name']); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-md-4 numbering-scope numbering-scope-programme">
                                                <label class="form-label">Programme</label>
                                                <select class="form-select" name="programme_id" id="number_programme_select">
                                                    <option value="0" data-code="" data-name="" data-faculty-id="0" data-department-id="0">Select Programme</option>
                                                    <?php foreach ($programmes as $p): ?>
                                                        <option value="<?php echo (int)$p['id']; ?>" data-code="<?php echo e((string)($p['code'] ?? '')); ?>" data-name="<?php echo e((string)$p['name']); ?>" data-faculty-id="<?php echo (int)($p['faculty_id'] ?? 0); ?>" data-department-id="<?php echo (int)($p['department_id'] ?? 0); ?>" <?php echo ((int)($editRow['programme_id'] ?? 0) === (int)$p['id']) ? 'selected' : ''; ?>><?php echo e($p['name']); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>

                                            <div class="col-md-6">
                                                <label class="form-label mb-1">Prefix</label>
                                                <input class="form-control" name="prefix" id="number_prefix_input" value="<?php echo e((string)($editRow['prefix'] ?? '')); ?>" placeholder="e.g. APP/{YEAR}/ or {FACULTY_CODE}/{YEAR}/">
                                                <div class="form-text mt-1">Supports placeholders such as <code>{YEAR}</code> and <code>{FACULTY_CODE}</code>.</div>
                                            </div>
                                            <div class="col-md-6">
                                                <label class="form-label mb-1">Affix</label>
                                                <input class="form-control" name="affix" id="number_affix_input" value="<?php echo e((string)($editRow['affix'] ?? '')); ?>" placeholder="Optional text added after the serial number">
                                            </div>
                                            <div class="col-md-6">
                                                <label class="form-label mb-1">Serial Digits</label>
                                                <input class="form-control" type="number" min="1" max="12" name="serial_digits" id="number_serial_digits" value="<?php echo (int)($editRow['serial_digits'] ?? 4); ?>">
                                            </div>
                                            <div class="col-md-6">
                                                <label class="form-label mb-1">Starting Serial</label>
                                                <input class="form-control" type="number" min="1" name="start_from" id="number_start_from" value="<?php echo (int)($editRow['start_from'] ?? 1); ?>">
                                            </div>
                                            <div class="col-12">
                                                <div class="form-check">
                                                    <input class="form-check-input" type="checkbox" name="is_active" id="numbering_is_active" <?php echo ((int)($editRow['is_active'] ?? 1) === 1) ? 'checked' : ''; ?>>
                                                    <label class="form-check-label" for="numbering_is_active">Active</label>
                                                </div>
                                            </div>
                                            <div class="col-12">
                                                <div class="border rounded p-3 bg-light h-100">
                                                    <div class="fw-semibold text-uppercase small text-muted numbering-preview-title">Format Preview</div>
                                                    <div class="fs-5 fw-bold text-primary mb-2" id="numberingPreviewValue"><?php echo e($numberingPreview); ?></div>
                                                    <div class="small text-muted numbering-preview-note">Preview is based on the current format settings, active academic session, selected scope, and a sample serial value.</div>
                                                    <div class="numbering-preview-meta"><div class="small mb-1"><strong>Application Preview:</strong> <span id="numberingApplicationPreview"><?php echo e($applicationPreview); ?></span></div>
                                                    </div><div class="small"><strong>Registration Preview:</strong> <span id="numberingRegistrationPreview"><?php echo e($registrationPreview); ?></span></div>
                                                    </div><div class="small text-danger d-none numbering-preview-error" id="numberingPreviewError"></div>
                                                </div>
                                            </div>
                                            <div class="col-12">
                                                <div class="alert alert-info mb-0">
                                                    <strong>Available placeholders:</strong> <code>{YEAR}</code>, <code>{FACULTY_CODE}</code>, <code>{DEPARTMENT_CODE}</code>, <code>{PROGRAMME_CODE}</code>, <code>{LEVEL}</code>. Existing assigned numbers are preserved while new records follow the active rule that matches their scope.
                                                </div>
                                            </div>
                                            <div class="col-12 d-flex flex-wrap align-items-center numbering-actions">
                                                <button class="btn btn-primary" type="submit"><i class="bi bi-save"></i> Save Numbering Form</button>
                                                <a class="btn btn-outline-secondary" href="?page=admin_numbering_settings&numbering_tab=setup">Reset</a>
                                            </div>
                                        </form>
                                    </div>
                                </div>
                            </div>

                            <div class="tab-pane fade <?php echo $numberingActiveTab === 'formats' ? 'show active' : ''; ?>" id="numbering-tab-formats" role="tabpanel">
                                <div class="card mb-3">
                                    <div class="card-header">
                                        <i class="bi bi-list-ul"></i> Saved Numbering Formats
                                    </div>
                                    <div class="card-body">
                                        <form method="get" class="row g-2 align-items-end mb-3">
                                            <input type="hidden" name="page" value="admin_numbering_settings">
                                            <input type="hidden" name="numbering_tab" value="formats">
                                            <div class="col-lg-4 col-md-6">
                                                <label class="form-label">Search</label>
                                                <input type="text" class="form-control" name="nfmt_q" value="<?php echo e($numberingSearch); ?>" placeholder="Search type, scope, prefix or affix">
                                            </div>
                                            <div class="col-lg-2 col-md-6">
                                                <label class="form-label">Number Type</label>
                                                <select class="form-select" name="nfmt_type">
                                                    <option value="">All Types</option>
                                                    <?php foreach ($numberingTypeOptions as $typeKey => $typeLabel): ?>
                                                        <option value="<?php echo e((string)$typeKey); ?>" <?php echo ((string)$numberingFilterType === (string)$typeKey) ? 'selected' : ''; ?>><?php echo e((string)$typeLabel); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-lg-2 col-md-6">
                                                <label class="form-label">Scope</label>
                                                <select class="form-select" name="nfmt_scope">
                                                    <option value="">All Scopes</option>
                                                    <?php foreach ($numberingScopeOptions as $scopeKey => $scopeLabel): ?>
                                                        <option value="<?php echo e((string)$scopeKey); ?>" <?php echo ((string)$numberingFilterScope === (string)$scopeKey) ? 'selected' : ''; ?>><?php echo e((string)$scopeLabel); ?></option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                            <div class="col-lg-2 col-md-6">
                                                <label class="form-label">Status</label>
                                                <select class="form-select" name="nfmt_status">
                                                    <option value="">All Statuses</option>
                                                    <option value="active" <?php echo $numberingFilterStatus === 'active' ? 'selected' : ''; ?>>Active</option>
                                                    <option value="disabled" <?php echo $numberingFilterStatus === 'disabled' ? 'selected' : ''; ?>>Disabled</option>
                                                </select>
                                            </div>
                                            <div class="col-lg-2 col-md-12 d-flex gap-2">
                                                <button class="btn btn-primary w-100" type="submit">Apply</button>
                                                <a class="btn btn-outline-secondary w-100" href="?page=admin_numbering_settings&numbering_tab=formats">Reset</a>
                                            </div>
                                        </form>

                                        <div class="d-flex flex-wrap gap-2 mb-3">
                                            <span class="badge text-bg-light border">Total Formats: <?php echo (int)$numberingTotalFormats; ?></span>
                                            <span class="badge text-bg-light border">Active: <?php echo (int)$numberingActiveCount; ?></span>
                                            <span class="badge text-bg-light border">Inactive / Disabled: <?php echo (int)$numberingInactiveCount; ?></span>
                                            <span class="badge text-bg-light border">Showing: <?php echo (int)$numberingShowingCount; ?></span>
                                        </div>

                                        <?php if ($filteredFormats): ?>
                                            <div class="table-responsive">
                                                <table class="table table-bordered table-sm align-middle">
                                                    <thead>
                                                        <tr>
                                                            <th>Type</th>
                                                            <th>Scope</th>
                                                            <th>Prefix</th>
                                                            <th>Affix</th>
                                                            <th>Digits</th>
                                                            <th>Start</th>
                                                            <th>Status</th>
                                                            <th style="width:190px">Action</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <?php foreach ($filteredFormats as $row): ?>
                                                            <tr>
                                                                <td><?php echo e(ucfirst((string)$row['number_type'])); ?></td>
                                                                <td>
                                                                    <?php echo e(ucfirst((string)$row['scope_type'])); ?>
                                                                    <div class="small text-muted"><?php echo e(portalNumberingScopeLabel($row)); ?></div>
                                                                </td>
                                                                <td><code><?php echo e((string)$row['prefix']); ?></code></td>
                                                                <td><code><?php echo e((string)$row['affix']); ?></code></td>
                                                                <td><?php echo (int)($row['serial_digits'] ?? 4); ?></td>
                                                                <td><?php echo (int)($row['start_from'] ?? 1); ?></td>
                                                                <td><?php echo ((int)($row['is_active'] ?? 1) === 1) ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Disabled</span>'; ?></td>
                                                                <td class="text-nowrap">
                                                                    <a class="btn btn-sm btn-outline-primary" href="?page=admin_numbering_settings&numbering_tab=setup&edit=<?php echo (int)$row['id']; ?>">Edit</a>
                                                                    <form method="post" class="d-inline">
                                                                        <input type="hidden" name="action" value="toggle_auto_number_format_status">
                                                                        <input type="hidden" name="id" value="<?php echo (int)$row['id']; ?>">
                                                                        <input type="hidden" name="return_to" value="<?php echo e($numberingFormatsReturnUrl); ?>">
                                                                        <button class="btn btn-sm btn-outline-<?php echo ((int)($row['is_active'] ?? 1) === 1) ? 'secondary' : 'success'; ?>" type="submit"><?php echo ((int)($row['is_active'] ?? 1) === 1) ? 'Disable' : 'Activate'; ?></button>
                                                                    </form>
                                                                    <form method="post" class="d-inline" onsubmit="return confirm('Delete this numbering format?');">
                                                                        <input type="hidden" name="action" value="delete_auto_number_format">
                                                                        <input type="hidden" name="id" value="<?php echo (int)$row['id']; ?>">
                                                                        <input type="hidden" name="return_to" value="<?php echo e($numberingFormatsReturnUrl); ?>">
                                                                        <button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
                                                                    </form>
                                                                </td>
                                                            </tr>
                                                        <?php endforeach; ?>
                                                    </tbody>
                                                </table>
                                            </div>
                                        <?php else: ?>
                                            <div class="border rounded p-4 text-center text-muted bg-light">
                                                <?php echo $numberingHasSavedFilters ? 'No numbering formats match the selected filters.' : 'No saved numbering formats found.'; ?>
                                            </div>
                                        <?php endif; ?>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <script>
                    (function(){
                        const currentSession = <?php echo json_encode((string)$currentSessionName); ?>;
                        const sampleLevel = <?php echo json_encode((string)$sampleLevel); ?>;
                        const allowedTokens = <?php echo json_encode(array_values(portalNumberingAllowedPlaceholders())); ?>;
                        const scope = document.getElementById('number_scope_type');
                        const sessionSelect = document.getElementById('number_scope_session');
                        const facultySelect = document.getElementById('number_faculty_select');
                        const departmentSelect = document.getElementById('number_department_select');
                        const programmeSelect = document.getElementById('number_programme_select');
                        const prefixInput = document.getElementById('number_prefix_input');
                        const affixInput = document.getElementById('number_affix_input');
                        const digitsInput = document.getElementById('number_serial_digits');
                        const startInput = document.getElementById('number_start_from');
                        const previewValue = document.getElementById('numberingPreviewValue');
                        const appPreview = document.getElementById('numberingApplicationPreview');
                        const regPreview = document.getElementById('numberingRegistrationPreview');
                        const previewError = document.getElementById('numberingPreviewError');

                        function toggleNumberingScope(){
                            if(!scope) return;
                            const selected = scope.value || 'global';
                            document.querySelectorAll('.numbering-scope').forEach(function(el){ el.style.display = 'none'; });
                            if (selected === 'session') document.querySelectorAll('.numbering-scope-session').forEach(function(el){ el.style.display = ''; });
                            if (selected === 'faculty') document.querySelectorAll('.numbering-scope-faculty').forEach(function(el){ el.style.display = ''; });
                            if (selected === 'department') document.querySelectorAll('.numbering-scope-faculty, .numbering-scope-department').forEach(function(el){ el.style.display = ''; });
                            if (selected === 'programme') document.querySelectorAll('.numbering-scope-faculty, .numbering-scope-department, .numbering-scope-programme').forEach(function(el){ el.style.display = ''; });
                        }

                        function filterAcademicOptions(){
                            const facultyId = facultySelect ? facultySelect.value : '0';
                            const departmentId = departmentSelect ? departmentSelect.value : '0';
                            if (departmentSelect) {
                                Array.from(departmentSelect.options).forEach(function(opt, idx){
                                    if (idx === 0) { opt.hidden = false; return; }
                                    opt.hidden = facultyId !== '0' && (opt.dataset.facultyId || '0') !== facultyId;
                                });
                                if (departmentSelect.selectedOptions[0] && departmentSelect.selectedOptions[0].hidden) departmentSelect.value = '0';
                            }
                            if (programmeSelect) {
                                Array.from(programmeSelect.options).forEach(function(opt, idx){
                                    if (idx === 0) { opt.hidden = false; return; }
                                    const facultyMatch = facultyId === '0' || (opt.dataset.facultyId || '0') === facultyId;
                                    const departmentMatch = departmentId === '0' || (opt.dataset.departmentId || '0') === departmentId;
                                    opt.hidden = !(facultyMatch && departmentMatch);
                                });
                                if (programmeSelect.selectedOptions[0] && programmeSelect.selectedOptions[0].hidden) programmeSelect.value = '0';
                            }
                        }

                        function academicYearFromSession(sessionValue){
                            const match = String(sessionValue || '').match(/(20\d{2})/);
                            return match ? match[1] : String(new Date().getFullYear());
                        }

                        function validateTemplate(template){
                            template = String(template || '');
                            const openCount = (template.match(/\{/g) || []).length;
                            const closeCount = (template.match(/\}/g) || []).length;
                            if (openCount !== closeCount) return 'Broken placeholder usage detected.';
                            const matches = template.match(/\{[A-Z_]+\}/g) || [];
                            for (const token of matches) {
                                if (!allowedTokens.includes(token)) return 'Invalid placeholder used: ' + token;
                            }
                            const remaining = template.replace(/\{[A-Z_]+\}/g, '');
                            if (remaining.includes('{') || remaining.includes('}')) return 'Broken placeholder usage detected.';
                            return '';
                        }

                        function tokenMap(){
                            const facultyOpt = facultySelect && facultySelect.selectedOptions.length ? facultySelect.selectedOptions[0] : null;
                            const departmentOpt = departmentSelect && departmentSelect.selectedOptions.length ? departmentSelect.selectedOptions[0] : null;
                            const programmeOpt = programmeSelect && programmeSelect.selectedOptions.length ? programmeSelect.selectedOptions[0] : null;
                            const sessionValue = (scope && scope.value === 'session' ? (sessionSelect ? sessionSelect.value : '') : currentSession) || currentSession;
                            const year = academicYearFromSession(sessionValue);
                            return {
                                '{YEAR}': year,
                                '{CURRENT_YEAR}': year,
                                '{SESSION_YEAR}': year,
                                '{FACULTY_CODE}': ((facultyOpt && facultyOpt.dataset.code) || 'STD').toUpperCase(),
                                '{DEPARTMENT_CODE}': ((departmentOpt && departmentOpt.dataset.code) || 'DPT').toUpperCase(),
                                '{PROGRAMME_CODE}': ((programmeOpt && programmeOpt.dataset.code) || 'PRG').toUpperCase(),
                                '{FACULTY}': ((facultyOpt && facultyOpt.dataset.name) || '').toUpperCase(),
                                '{DEPARTMENT}': ((departmentOpt && departmentOpt.dataset.name) || '').toUpperCase(),
                                '{PROGRAMME}': ((programmeOpt && programmeOpt.dataset.name) || '').toUpperCase(),
                                '{LEVEL}': String(sampleLevel || '100').toUpperCase()
                            };
                        }

                        function applyTokens(template, map){
                            let out = String(template || '');
                            Object.keys(map).forEach(function(key){ out = out.split(key).join(map[key]); });
                            return out;
                        }

                        function updatePreview(){
                            const map = tokenMap();
                            const prefix = prefixInput ? prefixInput.value : '';
                            const affix = affixInput ? affixInput.value : '';
                            const digits = Math.max(1, parseInt(digitsInput && digitsInput.value ? digitsInput.value : '4', 10) || 4);
                            const start = Math.max(1, parseInt(startInput && startInput.value ? startInput.value : '1', 10) || 1);
                            const serial = String(start).padStart(digits, '0');
                            const err = validateTemplate(prefix) || validateTemplate(affix);
                            if (previewError) {
                                previewError.textContent = err;
                                previewError.classList.toggle('d-none', !err);
                            }
                            if (previewValue) previewValue.textContent = applyTokens(prefix, map) + serial + applyTokens(affix, map);
                            if (appPreview) appPreview.textContent = applyTokens('APP/{YEAR}/', map) + String(1).padStart(6, '0');
                            if (regPreview) regPreview.textContent = applyTokens('REG/{YEAR}/', map) + String(1).padStart(6, '0');
                        }

                        document.addEventListener('DOMContentLoaded', function(){
                            toggleNumberingScope();
                            filterAcademicOptions();
                            updatePreview();
                            [scope, sessionSelect, facultySelect, departmentSelect, programmeSelect, prefixInput, affixInput, digitsInput, startInput].forEach(function(el){
                                if (!el) return;
                                el.addEventListener('change', function(){ toggleNumberingScope(); filterAcademicOptions(); updatePreview(); });
                                el.addEventListener('input', updatePreview);
                            });
                        });
                    })();
                    </script>
                    <?php

    }
    break;

case 'admin_matric_verifier':
    if (isAdmin()) {
        requireStaffPermission($pdo, 'academics_manage');
        $matricQuery = trim((string)($_GET['matric_number'] ?? ''));
        $matricSearchPerformed = array_key_exists('matric_number', $_GET);
        $verifierTab = trim((string)($_GET['verifier_tab'] ?? 'overview'));
        if (!in_array($verifierTab, ['overview','history'], true)) { $verifierTab = 'overview'; }
        $verifiedStudent = null;
        $verificationState = 'secondary';
        $verificationMessage = 'Enter a matric number or admission number to verify a student record.';
        if ($matricSearchPerformed) {
            if ($matricQuery === '') {
                $verificationState = 'warning';
                $verificationMessage = 'Please enter a matric number or admission number to verify.';
            } else {
                $verifiedStudent = portalAdminFindStudentByIdentifier($pdo, $matricQuery);
                if ($verifiedStudent) {
                    $verificationState = 'success';
                    $verificationMessage = 'Student identifier verified successfully. Student record found.';
                } else {
                    $verificationState = 'danger';
                    $verificationMessage = 'Invalid matric number or admission number, or student record not found.';
                }
            }
        }
        $defaultVerifierPhoto = 'data:image/svg+xml;utf8,' . rawurlencode('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160"><rect width="160" height="160" rx="24" fill="#e9ecef"/><circle cx="80" cy="62" r="32" fill="#adb5bd"/><path d="M32 134c10-26 31-40 48-40s38 14 48 40" fill="#adb5bd"/></svg>');
        ?>
        <style>
            .portal-matric-verifier-shell{width:100%;max-width:1640px;margin:0 auto;padding-inline:clamp(.15rem,.9vw,.75rem);}
            @media (max-width: 1199.98px){.portal-matric-verifier-shell{max-width:100%;padding-inline:.25rem;}}
            @media (max-width: 767.98px){.portal-matric-verifier-shell{padding-inline:0;}}
        </style>
        <div class="container-fluid">
            <div class="portal-matric-verifier-shell">
                    <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
                        <div>
                            <h2 class="mb-1">Change Department/Matric No.</h2>
                            <p class="text-muted mb-0">Verify student details inside admin, then change Faculty/Department placement or Matric Number without leaving this page.</p>
                        </div>
                        <a href="?page=admin_students" class="btn btn-outline-secondary"><i class="bi bi-people"></i> Students</a>
                    </div>
                    <div class="card shadow-sm border-0 mb-4">
                        <div class="card-header bg-primary text-white">
                            <i class="bi bi-search"></i> Verify Student Identifier
                        </div>
                        <div class="card-body">
                            <form method="get" class="row g-3 align-items-end">
                                <input type="hidden" name="page" value="admin_matric_verifier">
                                <input type="hidden" name="verifier_tab" value="<?php echo e($verifierTab); ?>">
                                <div class="col-lg-9">
                                    <label class="form-label">Matric Number</label>
                                    <input type="text" name="matric_number" class="form-control" value="<?php echo e($matricQuery); ?>" placeholder="Enter matric number or admission number" autocomplete="off">
                                    <div class="form-text">Enter Matric Number or Admission Number to verify student details. Spaces are ignored during verification and matching is handled safely.</div>
                                </div>
                                <div class="col-lg-3 d-grid">
                                    <button type="submit" class="btn btn-primary"><i class="bi bi-search"></i> Verify</button>
                                </div>
                            </form>
                        </div>
                    </div>

                    <div class="alert alert-<?php echo e($verificationState); ?> d-flex align-items-center gap-2 mb-4">
                        <i class="bi <?php echo $verificationState === 'success' ? 'bi-patch-check-fill' : ($verificationState === 'danger' ? 'bi-x-octagon-fill' : ($verificationState === 'warning' ? 'bi-exclamation-triangle-fill' : 'bi-info-circle-fill')); ?>"></i>
                        <div><?php echo e($verificationMessage); ?></div>
                    </div>

                    <?php renderFlashes(); ?>

                    <?php if ($verifiedStudent): ?>
                        <?php
                            $studentPhoto = trim((string)studentIdentityPhotoPath($verifiedStudent));
                            $studentFullName = studentFullName($verifiedStudent);
                            $studentFaculty = trim((string)($verifiedStudent['faculty_name'] ?? $verifiedStudent['school_name'] ?? $verifiedStudent['faculty'] ?? ''));
                            $studentDepartment = trim((string)($verifiedStudent['department_name'] ?? $verifiedStudent['course_of_study'] ?? $verifiedStudent['course_in_view'] ?? $verifiedStudent['department'] ?? ''));
                            $studentProgramme = trim((string)($verifiedStudent['programme_name'] ?? $verifiedStudent['programme_name_cache'] ?? $verifiedStudent['programme'] ?? ''));
                            $studentModeOfStudy = trim((string)($verifiedStudent['mode_of_study'] ?? portalInferModeOfStudy((array)$verifiedStudent)));
                            $studentLevel = trim((string)($verifiedStudent['current_level'] ?? $verifiedStudent['level'] ?? ''));
                            $studentSession = trim((string)($verifiedStudent['current_session'] ?? $verifiedStudent['academic_session'] ?? $verifiedStudent['session'] ?? ''));
                            $accountStatus = trim((string)($verifiedStudent['account_status'] ?? ''));
                            $applicationStatus = trim((string)($verifiedStudent['application_status'] ?? ''));
                            $studentMatricRaw = trim((string)($verifiedStudent['matric_no'] ?? ''));
                            $studentMatricDisplay = $studentMatricRaw !== '' ? $studentMatricRaw : 'Matric No: Processing';
                            $studentApplicationNumber = trim((string)($verifiedStudent['application_no'] ?? ''));
                            $studentAdmissionNumber = trim((string)($verifiedStudent['admission_no'] ?? ''));
                            $studentRegistrationNumber = trim((string)($verifiedStudent['registration_no'] ?? ''));
                            $studentAcademicState = portalStudentApplicationAcademicState($pdo, $verifiedStudent);
                            $studentFaculty = trim((string)($studentAcademicState['school'] ?? $studentFaculty));
                            $studentDepartment = trim((string)($studentAcademicState['course_of_study'] ?? $studentDepartment));
                            $studentProgramme = trim((string)($studentAcademicState['programme'] ?? $studentProgramme));
                            $studentModeOfStudy = trim((string)($verifiedStudent['mode_of_study'] ?? $studentAcademicState['mode_of_study'] ?? $studentModeOfStudy));
                            $studentFacultyId = (int)($studentAcademicState['faculty_id'] ?? ($verifiedStudent['faculty_id'] ?? 0));
                            $studentDepartmentId = (int)($studentAcademicState['department_id'] ?? ($verifiedStudent['department_id'] ?? 0));
                            $studentProgrammeId = (int)($studentAcademicState['programme_id'] ?? ($verifiedStudent['programme_id'] ?? 0));
                            $verifierFaculties = (array)($studentAcademicState['faculties'] ?? []);
                            $verifierDepartments = (array)($studentAcademicState['departments'] ?? []);
                            $verifierProgrammes = (array)($studentAcademicState['programmes'] ?? []);
                            $verifierLevelOptions = portalAcademicLevelOptions($pdo);
                            if ($studentLevel !== '' && !in_array($studentLevel, array_map('strval', $verifierLevelOptions), true)) {
                                $verifierLevelOptions[] = $studentLevel;
                            }
                            $studentModeOfStudyType = in_array($studentModeOfStudy, ['Full-Time', 'Part-Time'], true) ? $studentModeOfStudy : 'Others';
                            $studentModeOfStudyCustom = $studentModeOfStudyType === 'Others' ? $studentModeOfStudy : '';
                            $displayFaculty = $studentFaculty !== '' && strcasecmp($studentFaculty, 'Not Set') !== 0 ? $studentFaculty : 'Not Assigned';
                            $displayDepartment = $studentDepartment !== '' && strcasecmp($studentDepartment, 'Not Set') !== 0 ? $studentDepartment : 'Not Assigned';
                            $displayProgramme = $studentProgramme !== '' && strcasecmp($studentProgramme, 'Not Set') !== 0 ? $studentProgramme : 'Not Assigned';
                            $displayModeOfStudy = $studentModeOfStudy !== '' ? $studentModeOfStudy : 'Not Set';
                            $displayLevel = $studentLevel !== '' ? $studentLevel : 'Not Set';
                            $displaySession = $studentSession !== '' ? $studentSession : 'Not Set';
                            $displayApplicationNumber = $studentApplicationNumber !== '' ? $studentApplicationNumber : 'Not Assigned';
                            $displayAdmissionNumber = $studentAdmissionNumber !== '' ? $studentAdmissionNumber : 'Pending';
                            $displayRegistrationNumber = $studentRegistrationNumber !== '' ? $studentRegistrationNumber : 'Pending';
                            $displayEmail = trim((string)($verifiedStudent['email'] ?? '')) !== '' ? trim((string)($verifiedStudent['email'] ?? '')) : 'Not Set';
                            $displayPhone = trim((string)($verifiedStudent['mobile_phone'] ?? '')) !== '' ? trim((string)($verifiedStudent['mobile_phone'] ?? '')) : 'Not Set';
                            $hasStudentIdentifier = (int)($verifiedStudent['id'] ?? 0) > 0;
                            $applicationQrSrc = '';
                            if ($hasStudentIdentifier) {
                                $applicationQrPayload = json_encode([
                                    'doc' => 'application_form',
                                    'reg_no' => $studentRegistrationNumber,
                                    'student_id' => (int)($verifiedStudent['id'] ?? 0),
                                    'ts' => date('c')
                                ]);
                                if ($applicationQrPayload !== false && $applicationQrPayload !== '') {
                                    $applicationQrSrc = generateQRCode($applicationQrPayload);
                                }
                            }
                            $admissionQrSrc = '';
                            $admissionAllowed = $studentAdmissionNumber !== ''
                                || $studentRegistrationNumber !== ''
                                || in_array(strtolower($applicationStatus), ['approved','accepted','cleared','verified','processing'], true);
                            if ($hasStudentIdentifier && $admissionAllowed) {
                                $admissionQrPayload = json_encode([
                                    'doc' => 'admission_letter',
                                    'reg_no' => $studentRegistrationNumber,
                                    'student_id' => (int)($verifiedStudent['id'] ?? 0),
                                    'ts' => date('c')
                                ]);
                                if ($admissionQrPayload !== false && $admissionQrPayload !== '') {
                                    $admissionQrSrc = generateQRCode($admissionQrPayload);
                                }
                            }
                            try {
                                $studentVerifierHistory = portalFetchStudentVerifierChangeHistory($pdo, (int)($verifiedStudent['id'] ?? 0));
                                $studentVerifierHistoryCount = count($studentVerifierHistory);
                            } catch (Throwable $e) {
                                $studentVerifierHistory = [];
                                $studentVerifierHistoryCount = 0;
                            }
                            $verifierBaseIdentifier = trim((string)($verifiedStudent['matric_no'] ?? $verifiedStudent['admission_no'] ?? $matricQuery));
                            if ($verifierBaseIdentifier === '') {
                                $verifierBaseIdentifier = trim((string)$matricQuery);
                            }
                            $verifierTabLinks = [
                                'overview' => '?' . http_build_query(['page' => 'admin_matric_verifier', 'matric_number' => $verifierBaseIdentifier, 'verifier_tab' => 'overview']),
                                'history' => '?' . http_build_query(['page' => 'admin_matric_verifier', 'matric_number' => $verifierBaseIdentifier, 'verifier_tab' => 'history']),
                            ];
                        ?>
                        <div class="card border-0 shadow-sm mb-4">
                            <div class="card-body py-3">
                                <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
                                    <div>
                                        <div class="small text-uppercase text-muted fw-semibold">Verified Student Workspace</div>
                                        <div class="fw-semibold">Switch between the current record and change history without reloading this verified result.</div>
                                    </div>
                                    <span class="badge text-bg-primary px-3 py-2">Total Changes: <?php echo (int)$studentVerifierHistoryCount; ?></span>
                                </div>
                                <div class="d-flex flex-wrap gap-2 mt-3 js-verifier-tab-nav" role="tablist" aria-label="Verified student workspace tabs">
                                    <button type="button" class="btn <?php echo $verifierTab === 'history' ? 'btn-outline-primary' : 'btn-primary'; ?> js-verifier-tab-btn" data-verifier-tab="overview" aria-selected="<?php echo $verifierTab === 'history' ? 'false' : 'true'; ?>">Current Record</button>
                                    <button type="button" class="btn <?php echo $verifierTab === 'history' ? 'btn-primary' : 'btn-outline-primary'; ?> js-verifier-tab-btn" data-verifier-tab="history" aria-selected="<?php echo $verifierTab === 'history' ? 'true' : 'false'; ?>">Change History <span class="badge text-bg-light border ms-1"><?php echo (int)$studentVerifierHistoryCount; ?></span></button>
                                </div>
                            </div>
                        </div>
                        <div class="js-verifier-tab-panel<?php echo $verifierTab === 'history' ? ' d-none' : ''; ?>" data-verifier-panel="overview">
                        <div class="card border-0 shadow-sm">
                            <div class="card-header bg-white">
                                <div class="row g-4 align-items-start">
                                    <div class="col-12 col-xl-6">
                                        <h4 class="mb-1 text-primary"><?php echo e($studentFullName); ?></h4>
                                        <div class="text-muted small">Student record verified against the existing student database.</div>
                                        <div class="d-flex flex-wrap gap-2 mt-3">
                                            <span class="badge bg-success-subtle text-success-emphasis border border-success-subtle px-3 py-2">Valid / Genuine</span>
                                            <?php if ($accountStatus !== ''): ?><span class="badge bg-light text-dark border px-3 py-2">Account: <?php echo e($accountStatus); ?></span><?php endif; ?>
                                            <?php if ($applicationStatus !== ''): ?><span class="badge bg-light text-dark border px-3 py-2">Application: <?php echo e($applicationStatus); ?></span><?php endif; ?>
                                        </div>
                                    </div>
                                    <div class="col-12 col-xl-6">
                                        <div class="border rounded-3 p-3 bg-light w-100">
                                            <form method="post" class="row g-2 align-items-end w-100 m-0">
                                                <input type="hidden" name="action" value="admin_change_student_matric_number">
                                                <input type="hidden" name="student_id" value="<?php echo (int)($verifiedStudent['id'] ?? 0); ?>">
                                                <input type="hidden" name="current_matric_lookup" value="<?php echo e((string)($verifiedStudent['matric_no'] ?? $verifiedStudent['admission_no'] ?? $matricQuery)); ?>">
                                                <input type="hidden" name="verifier_tab" value="<?php echo e($verifierTab); ?>">
                                                <div class="col-12 px-0">
                                                    <label class="form-label fw-semibold mb-1 text-primary d-block" style="white-space:normal;">CHANGE MATRIC NUMBER</label>
                                                    <div class="small text-muted">Current: <strong><?php echo e($studentMatricDisplay); ?></strong></div>
                                                </div>
                                                <div class="col-md-8 px-0 pe-md-2">
                                                    <input type="text" name="new_matric_number" class="form-control" value="<?php echo e($studentMatricRaw); ?>" placeholder="Enter new matric number" autocomplete="off" required>
                                                </div>
                                                <div class="col-md-4 px-0 d-grid">
                                                    <button type="submit" class="btn btn-primary"><i class="bi bi-pencil-square"></i> Save Change</button>
                                                </div>
                                            </form>
                                        </div>
                                    </div>
                                    <div class="col-12">
                                        <div class="border rounded-3 p-3 bg-light js-verifier-academic-panel w-100">
                                            <form method="post" class="w-100 m-0">
                                                <input type="hidden" name="action" value="admin_change_student_academic_details">
                                                <input type="hidden" name="student_id" value="<?php echo (int)($verifiedStudent['id'] ?? 0); ?>">
                                                <input type="hidden" name="current_matric_lookup" value="<?php echo e((string)($verifiedStudent['matric_no'] ?? $verifiedStudent['admission_no'] ?? $matricQuery)); ?>">
                                                <input type="hidden" name="verifier_tab" value="<?php echo e($verifierTab); ?>">
                                                <div class="row g-3 w-100 m-0">
                                                    <div class="col-12 px-0">
                                                        <label class="form-label fw-semibold mb-1 text-primary d-block" style="white-space:normal;">CHANGE ACADEMIC DETAILS</label>
                                                        <div class="small text-muted">Update Faculty, Department, Programme, Mode of Study, and Level from this verified record without leaving the verifier page.</div>
                                                    </div>
                                                    <div class="col-12 col-md-6 col-lg">
                                                        <label class="form-label small text-muted mb-1">Faculty</label>
                                                        <select name="faculty_id" class="form-select js-verifier-faculty" required>
                                                            <option value="">Select Faculty</option>
                                                            <?php foreach ($verifierFaculties as $vf): ?>
                                                                <option value="<?php echo (int)($vf['id'] ?? 0); ?>" <?php echo ((int)($vf['id'] ?? 0) === $studentFacultyId) ? 'selected' : ''; ?>><?php echo e((string)($vf['name'] ?? '')); ?></option>
                                                            <?php endforeach; ?>
                                                        </select>
                                                    </div>
                                                    <div class="col-12 col-md-6 col-lg">
                                                        <label class="form-label small text-muted mb-1">Department</label>
                                                        <select name="department_id" class="form-select js-verifier-department" required>
                                                            <option value="">Select Department</option>
                                                            <?php foreach ($verifierDepartments as $vd): ?>
                                                                <option value="<?php echo (int)($vd['id'] ?? 0); ?>" data-faculty-id="<?php echo (int)($vd['faculty_id'] ?? 0); ?>" <?php echo ((int)($vd['id'] ?? 0) === $studentDepartmentId) ? 'selected' : ''; ?>><?php echo e((string)($vd['name'] ?? '')); ?></option>
                                                            <?php endforeach; ?>
                                                        </select>
                                                    </div>
                                                    <div class="col-12 col-md-6 col-lg">
                                                        <label class="form-label small text-muted mb-1">Programme</label>
                                                        <select name="programme_id" class="form-select js-verifier-programme" required>
                                                            <option value="">Select Programme</option>
                                                            <?php foreach ($verifierProgrammes as $vp): ?>
                                                                <option value="<?php echo (int)($vp['id'] ?? 0); ?>" data-faculty-id="<?php echo (int)($vp['faculty_id'] ?? 0); ?>" data-department-id="<?php echo (int)($vp['department_id'] ?? 0); ?>" <?php echo ((int)($vp['id'] ?? 0) === $studentProgrammeId) ? 'selected' : ''; ?>><?php echo e((string)($vp['name'] ?? '')); ?></option>
                                                            <?php endforeach; ?>
                                                        </select>
                                                    </div>
                                                    <div class="col-12 col-md-6 col-lg">
                                                        <label class="form-label small text-muted mb-1">Mode of Study</label>
                                                        <select name="mode_of_study_type" class="form-select js-verifier-mode" required>
                                                            <option value="Full-Time" <?php echo $studentModeOfStudyType === 'Full-Time' ? 'selected' : ''; ?>>Full-Time</option>
                                                            <option value="Part-Time" <?php echo $studentModeOfStudyType === 'Part-Time' ? 'selected' : ''; ?>>Part-Time</option>
                                                            <option value="Others" <?php echo $studentModeOfStudyType === 'Others' ? 'selected' : ''; ?>>Others</option>
                                                        </select>
                                                        <div class="mt-2 js-verifier-mode-custom-wrap" <?php echo $studentModeOfStudyType === 'Others' ? '' : 'style="display:none;"'; ?>>
                                                            <input type="text" name="mode_of_study_custom" class="form-control js-verifier-mode-custom" value="<?php echo e($studentModeOfStudyCustom); ?>" placeholder="Enter custom mode of study" <?php echo $studentModeOfStudyType === 'Others' ? 'required' : ''; ?>>
                                                        </div>
                                                    </div>
                                                    <div class="col-12 col-md-6 col-lg">
                                                        <label class="form-label small text-muted mb-1">Level</label>
                                                        <select name="level" class="form-select js-verifier-level" required>
                                                            <option value="">Select Level</option>
                                                            <?php foreach ($verifierLevelOptions as $levelOption): $levelOption = trim((string)$levelOption); if ($levelOption === '') continue; ?>
                                                                <option value="<?php echo e($levelOption); ?>" <?php echo strcasecmp((string)$studentLevel, (string)$levelOption) === 0 ? 'selected' : ''; ?>><?php echo e($levelOption); ?></option>
                                                            <?php endforeach; ?>
                                                        </select>
                                                    </div>
                                                    <div class="col-12 px-0">
                                                        <div class="small text-muted mb-2">Current: <strong><?php echo e($displayFaculty); ?></strong> / <strong><?php echo e($displayDepartment); ?></strong> / <strong><?php echo e($displayProgramme); ?></strong> / <strong><?php echo e($displayModeOfStudy); ?></strong> / <strong><?php echo e($displayLevel); ?></strong></div>
                                                        <div class="d-grid d-sm-flex justify-content-sm-end">
                                                            <button type="submit" class="btn btn-primary"><i class="bi bi-save2"></i> Save Academic Details</button>
                                                        </div>
                                                    </div>
                                                </div>
                                            </form>
                                        </div>
                                    </div>
                                    <div class="col-12">
                                        <div class="row g-3 align-items-stretch">
                                            <div class="col-sm-6 col-lg-4">
                                                <div class="border rounded-4 bg-light p-3 text-center h-100 d-flex flex-column justify-content-center">
                                                    <img src="<?php echo e($studentPhoto !== '' ? $studentPhoto : $defaultVerifierPhoto); ?>" alt="Student Photo" class="img-fluid rounded-4 border mx-auto" style="width:100%;max-width:220px;aspect-ratio:1/1;object-fit:cover;" onerror="this.onerror=null;this.src='<?php echo e($defaultVerifierPhoto); ?>';">
                                                    <div class="text-muted small mt-2"><?php echo $studentPhoto !== '' ? 'Student photo' : 'Default placeholder used'; ?></div>
                                                </div>
                                            </div>
                                            <div class="col-sm-6 col-lg-8">
                                                <div class="row g-3 h-100 align-items-stretch">
                                                    <div class="col-12 col-md-6">
                                                        <div class="border rounded-4 bg-light p-3 h-100 d-flex flex-column justify-content-center text-center">
                                                            <div class="small text-muted fw-semibold mb-2">Application Form QR Code</div>
                                                            <?php if ($applicationQrSrc !== ''): ?>
                                                                <img src="<?php echo e($applicationQrSrc); ?>" alt="Application Form QR Code" class="img-fluid mx-auto border rounded-3 bg-white p-2" style="width:100%;max-width:132px;aspect-ratio:1/1;object-fit:contain;">
                                                                <div class="small text-muted mt-2">Application Form verification QR</div>
                                                            <?php else: ?>
                                                                <div class="small text-muted py-4">Application Form QR Code is not available for this student.</div>
                                                            <?php endif; ?>
                                                        </div>
                                                    </div>
                                                    <div class="col-12 col-md-6">
                                                        <div class="border rounded-4 bg-light p-3 h-100 d-flex flex-column justify-content-center text-center">
                                                            <div class="small text-muted fw-semibold mb-2">Admission Letter QR Code</div>
                                                            <?php if ($admissionQrSrc !== ''): ?>
                                                                <img src="<?php echo e($admissionQrSrc); ?>" alt="Admission Letter QR Code" class="img-fluid mx-auto border rounded-3 bg-white p-2" style="width:100%;max-width:132px;aspect-ratio:1/1;object-fit:contain;">
                                                                <div class="small text-muted mt-2">Admission Letter verification QR</div>
                                                            <?php else: ?>
                                                                <div class="small text-muted py-4">Admission Letter QR Code is not available for this student.</div>
                                                            <?php endif; ?>
                                                        </div>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="card-body">
                                <div class="row g-3">
                                    <div class="col-12 col-xl-6">
                                        <div class="border rounded-4 p-3 h-100 bg-light-subtle">
                                            <div class="small text-uppercase text-muted fw-semibold mb-3">Academic Profile</div>
                                            <div class="row g-3">
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Full Name</div><div class="fw-semibold"><?php echo e($studentFullName); ?></div></div></div>
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Session</div><div class="fw-semibold"><?php echo e($displaySession); ?></div></div></div>
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Faculty</div><div class="fw-semibold"><?php echo e($displayFaculty); ?></div></div></div>
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Department</div><div class="fw-semibold"><?php echo e($displayDepartment); ?></div></div></div>
                                                <div class="col-md-4"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Programme</div><div class="fw-semibold"><?php echo e($displayProgramme); ?></div></div></div>
                                                <div class="col-md-4"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Mode of Study</div><div class="fw-semibold"><?php echo e($displayModeOfStudy); ?></div></div></div>
                                                <div class="col-md-4"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Level</div><div class="fw-semibold"><?php echo e($displayLevel); ?></div></div></div>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-12 col-xl-6">
                                        <div class="border rounded-4 p-3 h-100 bg-light-subtle">
                                            <div class="small text-uppercase text-muted fw-semibold mb-3">Identifiers</div>
                                            <div class="row g-3">
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Matric Number</div><div class="fw-bold text-primary"><?php echo e($studentMatricDisplay); ?></div></div></div>
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Admission Number</div><div class="fw-semibold"><?php echo e($displayAdmissionNumber); ?></div></div></div>
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Application Number</div><div class="fw-semibold"><?php echo e($displayApplicationNumber); ?></div></div></div>
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Registration Number</div><div class="fw-semibold"><?php echo e($displayRegistrationNumber); ?></div></div></div>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-12">
                                        <div class="border rounded-4 p-3 bg-light-subtle">
                                            <div class="small text-uppercase text-muted fw-semibold mb-3">Contact Information</div>
                                            <div class="row g-3">
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Email</div><div class="fw-semibold"><?php echo e($displayEmail); ?></div></div></div>
                                                <div class="col-md-6"><div class="border rounded-3 p-3 h-100 bg-white"><div class="text-muted small">Phone</div><div class="fw-semibold"><?php echo e($displayPhone); ?></div></div></div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        </div>
                        <div class="js-verifier-tab-panel<?php echo $verifierTab === 'history' ? '' : ' d-none'; ?>" data-verifier-panel="history">
                        <div class="card border-0 shadow-sm">
                            <div class="card-body">
                                <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
                                    <div>
                                        <h5 class="mb-1">Student Change History</h5>
                                        <div class="text-muted small">Full movement and matric-number history for the currently verified student, sorted from newest to oldest.</div>
                                    </div>
                                    <span class="badge text-bg-primary px-3 py-2">Total Changes: <?php echo (int)$studentVerifierHistoryCount; ?></span>
                                </div>
                                <?php if ($studentVerifierHistory): ?>
                                    <div class="table-responsive">
                                        <table class="table table-bordered align-middle table-sm">
                                            <thead class="table-light">
                                                <tr>
                                                    <th style="min-width:64px;">S/N</th>
                                                    <th style="min-width:170px;">Action Type</th>
                                                    <th style="min-width:260px;">Old Value(s)</th>
                                                    <th style="min-width:260px;">New Value(s)</th>
                                                    <th style="min-width:120px;">Date</th>
                                                    <th style="min-width:110px;">Time</th>
                                                    <th style="min-width:80px;">Year</th>
                                                    <th style="min-width:150px;">Changed By</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <?php foreach ($studentVerifierHistory as $historyIndex => $historyRow): ?>
                                                    <?php
                                                        $historyActionType = trim((string)($historyRow['action_type'] ?? 'academic_details_updated'));
                                                        $historyBadgeClass = 'text-bg-secondary';
                                                        $historyActionLabel = 'Academic Details Updated';
                                                        switch ($historyActionType) {
                                                            case 'matric_number_changed':
                                                                $historyBadgeClass = 'text-bg-primary';
                                                                $historyActionLabel = 'Matric Number Changed';
                                                                break;
                                                            case 'faculty_changed':
                                                                $historyBadgeClass = 'text-bg-info';
                                                                $historyActionLabel = 'Faculty Changed';
                                                                break;
                                                            case 'department_changed':
                                                                $historyBadgeClass = 'text-bg-warning';
                                                                $historyActionLabel = 'Department Changed';
                                                                break;
                                                            case 'programme_changed':
                                                                $historyBadgeClass = 'text-bg-dark';
                                                                $historyActionLabel = 'Programme Changed';
                                                                break;
                                                            case 'mode_of_study_changed':
                                                                $historyBadgeClass = 'text-bg-success';
                                                                $historyActionLabel = 'Mode of Study Changed';
                                                                break;
                                                            case 'level_changed':
                                                                $historyBadgeClass = 'text-bg-secondary';
                                                                $historyActionLabel = 'Level Changed';
                                                                break;
                                                        }
                                                        $oldHistoryLines = [];
                                                        $newHistoryLines = [];
                                                        $historyPairs = [
                                                            'Matric No.' => ['old_matric_no', 'new_matric_no'],
                                                            'Faculty' => ['old_faculty', 'new_faculty'],
                                                            'Department' => ['old_department', 'new_department'],
                                                            'Programme' => ['old_programme', 'new_programme'],
                                                            'Mode of Study' => ['old_mode_of_study', 'new_mode_of_study'],
                                                            'Level' => ['old_level', 'new_level'],
                                                        ];
                                                        foreach ($historyPairs as $historyLabel => $historyColumns) {
                                                            $oldValue = trim((string)($historyRow[$historyColumns[0]] ?? ''));
                                                            $newValue = trim((string)($historyRow[$historyColumns[1]] ?? ''));
                                                            if ($oldValue === '' && $newValue === '') continue;
                                                            $oldHistoryLines[] = $historyLabel . ': ' . ($oldValue !== '' ? $oldValue : '—');
                                                            $newHistoryLines[] = $historyLabel . ': ' . ($newValue !== '' ? $newValue : '—');
                                                        }
                                                        if (!$oldHistoryLines) $oldHistoryLines[] = '—';
                                                        if (!$newHistoryLines) $newHistoryLines[] = '—';
                                                        $historyDate = trim((string)($historyRow['change_date'] ?? ''));
                                                        $historyTime = trim((string)($historyRow['change_time'] ?? ''));
                                                        $historyYear = trim((string)($historyRow['change_year'] ?? ''));
                                                        $historyBy = trim((string)($historyRow['changed_by_name'] ?? ''));
                                                        if ($historyBy === '') $historyBy = 'System';
                                                    ?>
                                                    <tr>
                                                        <td><?php echo (int)($historyIndex + 1); ?></td>
                                                        <td>
                                                            <span class="badge <?php echo e($historyBadgeClass); ?> px-3 py-2"><?php echo e($historyActionLabel); ?></span>
                                                            <?php if (trim((string)($historyRow['remark'] ?? '')) !== ''): ?>
                                                                <div class="small text-muted mt-2"><?php echo e((string)$historyRow['remark']); ?></div>
                                                            <?php endif; ?>
                                                        </td>
                                                        <td>
                                                            <?php foreach ($oldHistoryLines as $oldHistoryLine): ?>
                                                                <div class="small mb-1"><?php echo e($oldHistoryLine); ?></div>
                                                            <?php endforeach; ?>
                                                        </td>
                                                        <td>
                                                            <?php foreach ($newHistoryLines as $newHistoryLine): ?>
                                                                <div class="small mb-1"><?php echo e($newHistoryLine); ?></div>
                                                            <?php endforeach; ?>
                                                        </td>
                                                        <td><?php echo e($historyDate !== '' ? $historyDate : '—'); ?></td>
                                                        <td><?php echo e($historyTime !== '' ? $historyTime : '—'); ?></td>
                                                        <td><?php echo e($historyYear !== '' ? $historyYear : '—'); ?></td>
                                                        <td><?php echo e($historyBy); ?></td>
                                                    </tr>
                                                <?php endforeach; ?>
                                            </tbody>
                                        </table>
                                    </div>
                                <?php else: ?>
                                    <div class="alert alert-light border mb-0">No change history has been recorded for this verified student yet.</div>
                                <?php endif; ?>
                            </div>
                        </div>
                        </div>

                        <script>
                        document.addEventListener('DOMContentLoaded', function () {
                            var verifierTabButtons = Array.from(document.querySelectorAll('.js-verifier-tab-btn'));
                            var verifierTabPanels = Array.from(document.querySelectorAll('.js-verifier-tab-panel'));
                            var verifierTabInputs = Array.from(document.querySelectorAll('input[name="verifier_tab"]'));
                            var activateVerifierTab = function (tabName) {
                                var normalizedTab = tabName === 'history' ? 'history' : 'overview';
                                verifierTabButtons.forEach(function (btn) {
                                    var isActive = btn.getAttribute('data-verifier-tab') === normalizedTab;
                                    btn.classList.toggle('btn-primary', isActive);
                                    btn.classList.toggle('btn-outline-primary', !isActive);
                                    btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
                                });
                                verifierTabPanels.forEach(function (panel) {
                                    panel.classList.toggle('d-none', panel.getAttribute('data-verifier-panel') !== normalizedTab);
                                });
                                verifierTabInputs.forEach(function (input) {
                                    input.value = normalizedTab;
                                });
                                try {
                                    var tabUrl = new URL(window.location.href);
                                    tabUrl.searchParams.set('verifier_tab', normalizedTab);
                                    window.history.replaceState({}, '', tabUrl.toString());
                                } catch (err) {}
                            };
                            verifierTabButtons.forEach(function (btn) {
                                btn.addEventListener('click', function () {
                                    activateVerifierTab(btn.getAttribute('data-verifier-tab') || 'overview');
                                });
                            });
                            if (verifierTabButtons.length && verifierTabPanels.length) {
                                activateVerifierTab(<?php echo json_encode($verifierTab); ?>);
                            }

                            document.querySelectorAll('.js-verifier-academic-panel').forEach(function (panel) {
                                var facultySelect = panel.querySelector('.js-verifier-faculty');
                                var departmentSelect = panel.querySelector('.js-verifier-department');
                                var programmeSelect = panel.querySelector('.js-verifier-programme');
                                var modeSelect = panel.querySelector('.js-verifier-mode');
                                var modeCustomWrap = panel.querySelector('.js-verifier-mode-custom-wrap');
                                var modeCustomInput = panel.querySelector('.js-verifier-mode-custom');
                                if (!facultySelect || !departmentSelect || !programmeSelect) return;

                                var departmentOptions = Array.from(departmentSelect.options).map(function (opt) { return opt.cloneNode(true); });
                                var programmeOptions = Array.from(programmeSelect.options).map(function (opt) { return opt.cloneNode(true); });
                                var selectedDepartment = departmentSelect.value;
                                var selectedProgramme = programmeSelect.value;

                                function rebuildDepartmentOptions() {
                                    var facultyId = String(facultySelect.value || '');
                                    var keepValue = String(departmentSelect.value || selectedDepartment || '');
                                    departmentSelect.innerHTML = '';
                                    departmentOptions.forEach(function (opt) {
                                        var optionValue = String(opt.value || '');
                                        var optionFacultyId = String(opt.getAttribute('data-faculty-id') || '');
                                        if (optionValue === '' || facultyId === '' || optionFacultyId === facultyId) {
                                            departmentSelect.appendChild(opt.cloneNode(true));
                                        }
                                    });
                                    if (keepValue !== '') {
                                        departmentSelect.value = keepValue;
                                    }
                                    if (departmentSelect.value === '' && departmentSelect.options.length > 1) {
                                        var firstReal = Array.from(departmentSelect.options).find(function (opt) { return String(opt.value || '') !== ''; });
                                        if (firstReal) departmentSelect.value = String(firstReal.value || '');
                                    }
                                }

                                function rebuildProgrammeOptions() {
                                    var facultyId = String(facultySelect.value || '');
                                    var departmentId = String(departmentSelect.value || '');
                                    var keepValue = String(programmeSelect.value || selectedProgramme || '');
                                    programmeSelect.innerHTML = '';
                                    programmeOptions.forEach(function (opt) {
                                        var optionValue = String(opt.value || '');
                                        var optionFacultyId = String(opt.getAttribute('data-faculty-id') || '');
                                        var optionDepartmentId = String(opt.getAttribute('data-department-id') || '');
                                        var facultyMatches = (facultyId === '' || optionFacultyId === facultyId);
                                        var departmentMatches = (departmentId === '' || optionDepartmentId === departmentId);
                                        if (optionValue === '' || (facultyMatches && departmentMatches)) {
                                            programmeSelect.appendChild(opt.cloneNode(true));
                                        }
                                    });
                                    if (keepValue !== '') {
                                        programmeSelect.value = keepValue;
                                    }
                                    if (programmeSelect.value === '' && programmeSelect.options.length > 1) {
                                        var firstReal = Array.from(programmeSelect.options).find(function (opt) { return String(opt.value || '') !== ''; });
                                        if (firstReal) programmeSelect.value = String(firstReal.value || '');
                                    }
                                }

                                function syncModeCustom() {
                                    if (!modeSelect || !modeCustomWrap || !modeCustomInput) return;
                                    var isCustom = String(modeSelect.value || '') === 'Others';
                                    modeCustomWrap.style.display = isCustom ? '' : 'none';
                                    modeCustomInput.required = isCustom;
                                    if (!isCustom) {
                                        modeCustomInput.value = '';
                                    }
                                }

                                facultySelect.addEventListener('change', function () {
                                    selectedDepartment = '';
                                    selectedProgramme = '';
                                    rebuildDepartmentOptions();
                                    rebuildProgrammeOptions();
                                });
                                departmentSelect.addEventListener('change', function () {
                                    selectedProgramme = '';
                                    rebuildProgrammeOptions();
                                });
                                if (modeSelect) {
                                    modeSelect.addEventListener('change', syncModeCustom);
                                }

                                rebuildDepartmentOptions();
                                rebuildProgrammeOptions();
                                syncModeCustom();
                            });
                        });
                        </script>
                    <?php elseif ($matricSearchPerformed && $matricQuery !== ''): ?>
                        <div class="card border-0 shadow-sm">
                            <div class="card-body text-center py-5">
                                <div class="display-6 text-danger mb-3"><i class="bi bi-person-x"></i></div>
                                <h4 class="mb-2">Student Record Not Found</h4>
                                <p class="text-muted mb-0">No student record matched the supplied matric number or admission number. Check the value and try again.</p>
                            </div>
                        </div>
                    <?php endif; ?>
            </div>
        </div>
        <?php
    }
    break;

case 'admin_settings':
                if (isAdmin()) {
                    // Get current settings
                    $stmt = $pdo->query("SELECT * FROM system_settings");
                    $settings = [];
                    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                        $settings[$row['setting_key']] = $row['setting_value'];
                    }

                    // Get current assets - Modified to handle separate logos
                    $leftLogoAsset = getSystemAsset('left_logo');
                    $rightLogoAsset = getSystemAsset('right_logo');
                    $faviconAsset = getSystemAsset('favicon');

                    // Document signature assets
                    $idcardSignatureAsset = getSystemAsset('idcard_signature');
                    $admissionSignatureAsset = getSystemAsset('admission_signature');
                    $applicationSignatureAsset = getSystemAsset('application_signature');
                    $accountOfficerSignatureAsset = getSystemAsset('account_officer_signature');
                    $stampAsset = getSystemAsset('official_stamp');
?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header">
                                    <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
                                        <span>System Settings</span>
                                        <ul class="nav nav-pills small gap-2">
                                            <li class="nav-item"><a class="nav-link" href="#settings-general">General</a></li>
                                            <li class="nav-item"><a class="nav-link" href="?page=theme_branding">Theme/Branding</a></li>
                                            <li class="nav-item"><a class="nav-link" href="?page=admin_fees_manager">Fees Manager</a></li><li class="nav-item"><a class="nav-link" href="?page=admin_accommodation_verification">Accommodation</a></li>
                                            <li class="nav-item"><a class="nav-link" href="#settings-signatures">Signatures</a></li>
                                            <li class="nav-item"><a class="nav-link" href="#settings-assets">Assets</a></li>
                                        </ul>
                                    </div>
                                </div>
                                <div class="card-body">
                                    <form method="post" action="">
                                        <input type="hidden" name="action" value="update_settings">
                                        <?php portalRenderUpdateSettingsHiddenInputs($settings, ['application_deadline','portal_title','idcard_signatory_name','admission_signatory_name','application_signatory_name','account_officer_signatory_name','lazy_loader_enabled','resume_user_progress_enabled','theme_mode_enabled']); ?>
                                        <div class="alert alert-info border" id="settings-general">
                                            <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
                                                <div>
                                                    <strong>Payment settings moved to Fees Manager.</strong>
                                                    Duplicate payment, assigned/custom fee, gateway, wallet, late-fee, and receipt notification controls have been removed from System Settings so they are managed in one place only.
                                                </div>
                                                <a class="btn btn-sm btn-primary" href="?page=admin_fees_manager">Open Fees Manager</a>
                                            </div>
                                        </div>

                                        <h5 class="mt-2">Portal</h5>
<div class="row">
    <div class="col-md-6 mb-3">
        <label for="portal_title" class="form-label">Portal Title</label>
        <input type="text" class="form-control" id="portal_title" name="portal_title" value="<?php echo htmlspecialchars($settings['portal_title'] ?? 'HontTech System'); ?>" required>
    </div>
</div>

<hr class="my-3">
<hr class="my-3">
<div class="alert alert-light border">
    <strong>Payment gateways, receipts, assigned/custom fees, wallet controls, and payment notifications now live in Fees Manager only.</strong>
    <div class="small text-muted mt-1">This removes duplicate payment configuration from System Settings while preserving all saved values and current workflows.</div>
    <div class="mt-2"><a class="btn btn-outline-primary btn-sm" href="?page=admin_fees_manager">Open Fees Manager</a></div>
</div>

<hr class="my-3">
<h5 class="mt-2">Fee / Promotion / Accommodation Workflow Controls</h5>
<div class="row g-3 mb-2">
    <div class="col-xl-6">
        <div class="card border-0 bg-light-subtle h-100">
            <div class="card-body">
                <div class="small text-uppercase text-muted mb-2">School Fee Workflow</div>
                <div class="form-check form-switch mb-2">
                    <input class="form-check-input" type="checkbox" id="school_fee_enforcement_on_promotion" name="school_fee_enforcement_on_promotion" <?php echo (($settings['school_fee_enforcement_on_promotion'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>>
                    <label class="form-check-label" for="school_fee_enforcement_on_promotion">Enforce school fee after promotion / level change</label>
                </div>
                <div class="form-check form-switch mb-2">
                    <input class="form-check-input" type="checkbox" id="school_fee_enforcement_on_session_change" name="school_fee_enforcement_on_session_change" <?php echo (($settings['school_fee_enforcement_on_session_change'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>>
                    <label class="form-check-label" for="school_fee_enforcement_on_session_change">Enforce school fee on session change</label>
                </div>
                <div class="form-check form-switch mb-2">
                    <input class="form-check-input" type="checkbox" id="fee_difference_auto_calculation" name="fee_difference_auto_calculation" <?php echo (($settings['fee_difference_auto_calculation'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>>
                    <label class="form-check-label" for="fee_difference_auto_calculation">Auto-calculate fee difference / carry-over coverage</label>
                </div>
                <div class="form-check form-switch mb-0">
                    <input class="form-check-input" type="checkbox" id="previous_debt_blocking" name="previous_debt_blocking" <?php echo (($settings['previous_debt_blocking'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>>
                    <label class="form-check-label" for="previous_debt_blocking">Block course registration when previous debt exists</label>
                </div>
            </div>
        </div>
    </div>
    <div class="col-xl-6">
        <div class="card border-0 bg-light-subtle h-100">
            <div class="card-body">
                <div class="small text-uppercase text-muted mb-2">Accommodation / Hostel Workflow</div>
                <div class="form-check form-switch mb-2">
                    <input class="form-check-input" type="checkbox" id="hostel_fee_depend_on_accommodation_status" name="hostel_fee_depend_on_accommodation_status" <?php echo (($settings['hostel_fee_depend_on_accommodation_status'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>>
                    <label class="form-check-label" for="hostel_fee_depend_on_accommodation_status">Make hostel fee depend on accommodation status</label>
                </div>
                <div class="form-check form-switch mb-2">
                    <input class="form-check-input" type="checkbox" id="off_campus_verification_required" name="off_campus_verification_required" <?php echo (($settings['off_campus_verification_required'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>>
                    <label class="form-check-label" for="off_campus_verification_required">Require admin verification for Off-Campus claims</label>
                </div>
                <div class="mb-2">
                    <label class="form-label" for="pending_claim_behavior">Pending claim behavior</label>
                    <select class="form-select" id="pending_claim_behavior" name="pending_claim_behavior">
                        <option value="exempt_until_review" <?php echo (($settings['pending_claim_behavior'] ?? 'exempt_until_review') === 'exempt_until_review') ? 'selected' : ''; ?>>Do not require hostel fee until review</option>
                        <option value="require_hostel_until_review" <?php echo (($settings['pending_claim_behavior'] ?? 'exempt_until_review') === 'require_hostel_until_review') ? 'selected' : ''; ?>>Require hostel fee until claim is approved</option>
                    </select>
                </div>
                <div class="form-check form-switch mb-2">
                    <input class="form-check-input" type="checkbox" id="dashboard_payment_popup_enabled" name="dashboard_payment_popup_enabled" <?php echo (($settings['dashboard_payment_popup_enabled'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>>
                    <label class="form-check-label" for="dashboard_payment_popup_enabled">Show dashboard payment / accommodation popup</label>
                </div>
                <div class="mt-3"><a class="btn btn-outline-primary btn-sm" href="?page=admin_accommodation_verification"><i class="bi bi-house-check"></i> Review Off-Campus Claims</a></div>
            </div>
        </div>
    </div>
</div>

<hr class="my-3">
<h5 class="mt-2">Document Signatory Names</h5>
<div class="row">
    <div class="col-md-4 mb-3">
        <label class="form-label" for="idcard_signatory_name">ID Card Signatory Name</label>
        <input type="text" class="form-control" id="idcard_signatory_name" name="idcard_signatory_name" value="<?php echo htmlspecialchars($settings['idcard_signatory_name'] ?? ''); ?>">
    </div>
    <div class="col-md-4 mb-3">
        <label class="form-label" for="admission_signatory_name">Admission Letter Signatory Name</label>
        <input type="text" class="form-control" id="admission_signatory_name" name="admission_signatory_name" value="<?php echo htmlspecialchars($settings['admission_signatory_name'] ?? ''); ?>">
    </div>
    <div class="col-md-4 mb-3">
        <label class="form-label" for="application_signatory_name">Application Form Signatory Name</label>
        <input type="text" class="form-control" id="application_signatory_name" name="application_signatory_name" value="<?php echo htmlspecialchars($settings['application_signatory_name'] ?? ''); ?>">
                                            </div>
                                            <div class="col-md-4 mb-3">
                                                <label for="account_officer_signatory_name" class="form-label">Clearance Account Officer Name</label>
                                                <input type="text" class="form-control" id="account_officer_signatory_name" name="account_officer_signatory_name" value="<?php echo htmlspecialchars($settings['account_officer_signatory_name'] ?? 'Account Officer'); ?>">
    </div>

</div>

<hr class="my-3">
<h5 class="mt-2" id="user-progress-controls">User Progress Controls</h5>
<div class="row g-3">
    <div class="col-xl-4 col-md-6">
        <div class="card border-0 bg-light-subtle h-100 mb-3">
            <div class="card-body">
                <div class="d-flex justify-content-between align-items-start gap-2 mb-2">
                    <div>
                        <h6 class="mb-1">Lazy Loader</h6>
                        <div class="small text-muted">Track and safely remember the last allowed page, tab, workflow step, and context a logged-in user visited.</div>
                    </div>
                    <span class="badge <?php echo (($settings['lazy_loader_enabled'] ?? (($settings['resume_user_progress_enabled'] ?? 'disabled'))) === 'enabled') ? 'bg-success-subtle text-success border border-success-subtle' : 'bg-secondary-subtle text-secondary border border-secondary-subtle'; ?>" id="lazy_loader_enabled_status_badge"><?php echo (($settings['lazy_loader_enabled'] ?? (($settings['resume_user_progress_enabled'] ?? 'disabled'))) === 'enabled') ? 'Enabled' : 'Disabled'; ?></span>
                </div>
                <input type="hidden" name="lazy_loader_enabled" id="lazy_loader_enabled" value="<?php echo (($settings['lazy_loader_enabled'] ?? (($settings['resume_user_progress_enabled'] ?? 'disabled'))) === 'enabled') ? 'enabled' : 'disabled'; ?>">
                <div class="btn-group" role="group" aria-label="Lazy Loader action buttons" data-progress-setting="lazy_loader_enabled">
                    <button type="button" class="btn btn-sm <?php echo (($settings['lazy_loader_enabled'] ?? (($settings['resume_user_progress_enabled'] ?? 'disabled'))) === 'enabled') ? 'btn-primary active' : 'btn-outline-primary'; ?>" data-progress-target="lazy_loader_enabled" data-progress-value="enabled">Enable</button>
                    <button type="button" class="btn btn-sm <?php echo (($settings['lazy_loader_enabled'] ?? (($settings['resume_user_progress_enabled'] ?? 'disabled'))) === 'disabled') ? 'btn-danger active' : 'btn-outline-danger'; ?>" data-progress-target="lazy_loader_enabled" data-progress-value="disabled">Disable</button>
                </div>
                <div class="small text-muted mt-2">This controls progress tracking only. It does not auto-submit forms, payments, or destructive actions.</div>
            </div>
        </div>
    </div>
    <div class="col-xl-4 col-md-6">
        <div class="card border-0 bg-light-subtle h-100 mb-3">
            <div class="card-body">
                <div class="d-flex justify-content-between align-items-start gap-2 mb-2">
                    <div>
                        <h6 class="mb-1">Resume Progress</h6>
                        <div class="small text-muted">Return users to their last safe saved location after the next successful login, subject to role and permission checks.</div>
                    </div>
                    <span class="badge <?php echo (($settings['resume_user_progress_enabled'] ?? 'disabled') === 'enabled') ? 'bg-success-subtle text-success border border-success-subtle' : 'bg-secondary-subtle text-secondary border border-secondary-subtle'; ?>" id="resume_user_progress_enabled_status_badge"><?php echo (($settings['resume_user_progress_enabled'] ?? 'disabled') === 'enabled') ? 'Enabled' : 'Disabled'; ?></span>
                </div>
                <input type="hidden" name="resume_user_progress_enabled" id="resume_user_progress_enabled" value="<?php echo (($settings['resume_user_progress_enabled'] ?? 'disabled') === 'enabled') ? 'enabled' : 'disabled'; ?>">
                <div class="btn-group" role="group" aria-label="Resume Progress action buttons" data-progress-setting="resume_user_progress_enabled">
                    <button type="button" class="btn btn-sm <?php echo (($settings['resume_user_progress_enabled'] ?? 'disabled') === 'enabled') ? 'btn-primary active' : 'btn-outline-primary'; ?>" data-progress-target="resume_user_progress_enabled" data-progress-value="enabled">Enable</button>
                    <button type="button" class="btn btn-sm <?php echo (($settings['resume_user_progress_enabled'] ?? 'disabled') === 'disabled') ? 'btn-danger active' : 'btn-outline-danger'; ?>" data-progress-target="resume_user_progress_enabled" data-progress-value="disabled">Disable</button>
                </div>
                <div class="small text-muted mt-2">Safe restore only: login, logout, payment verification callbacks, delete actions, and duplicate-trigger routes are excluded automatically.</div>
            </div>
        </div>
    </div>
    <div class="col-xl-4 col-md-6">
        <div class="card border-0 bg-light-subtle h-100 mb-3">
            <div class="card-body">
                <div class="d-flex justify-content-between align-items-start gap-2 mb-2">
                    <div>
                        <h6 class="mb-1">Light / Dark Mode</h6>
                        <div class="small text-muted">Show or hide the Day/Night mode switcher for students and other allowed logged-in portal users.</div>
                    </div>
                    <span class="badge <?php echo (($settings['theme_mode_enabled'] ?? 'enabled') === 'enabled') ? 'bg-success-subtle text-success border border-success-subtle' : 'bg-secondary-subtle text-secondary border border-secondary-subtle'; ?>" id="theme_mode_enabled_status_badge"><?php echo (($settings['theme_mode_enabled'] ?? 'enabled') === 'enabled') ? 'Enabled' : 'Disabled'; ?></span>
                </div>
                <input type="hidden" name="theme_mode_enabled" id="theme_mode_enabled" value="<?php echo (($settings['theme_mode_enabled'] ?? 'enabled') === 'enabled') ? 'enabled' : 'disabled'; ?>">
                <div class="btn-group" role="group" aria-label="Light/Dark Mode action buttons" data-progress-setting="theme_mode_enabled">
                    <button type="button" class="btn btn-sm <?php echo (($settings['theme_mode_enabled'] ?? 'enabled') === 'enabled') ? 'btn-primary active' : 'btn-outline-primary'; ?>" data-progress-target="theme_mode_enabled" data-progress-value="enabled"><i class="bi bi-moon-stars me-1"></i>Enable</button>
                    <button type="button" class="btn btn-sm <?php echo (($settings['theme_mode_enabled'] ?? 'enabled') === 'disabled') ? 'btn-danger active' : 'btn-outline-danger'; ?>" data-progress-target="theme_mode_enabled" data-progress-value="disabled"><i class="bi bi-slash-circle me-1"></i>Disable</button>
                </div>
                <div class="small text-muted mt-2">When enabled, users can switch between <strong>Day</strong> and <strong>Night</strong> mode, and the selected theme is remembered safely across logins.</div>
            </div>
        </div>
    </div>
    
    <div class="col-xl-4 col-md-6">
        <div class="card border-0 bg-light-subtle h-100 mb-3">
            <div class="card-body">
                <div class="d-flex justify-content-between align-items-start gap-2 mb-2">
                    <div>
                        <h6 class="mb-1">Portal Search Bar</h6>
                        <div class="small text-muted">Show or hide the live search bar for admins and students so users can quickly find menus, pages, and tools they can access.</div>
                    </div>
                    <span class="badge <?php echo (($settings['portal_search_bar_enabled'] ?? 'enabled') === 'enabled') ? 'bg-success-subtle text-success border border-success-subtle' : 'bg-secondary-subtle text-secondary border border-secondary-subtle'; ?>" id="portal_search_bar_enabled_status_badge"><?php echo (($settings['portal_search_bar_enabled'] ?? 'enabled') === 'enabled') ? 'Enabled' : 'Disabled'; ?></span>
                </div>
                <input type="hidden" name="portal_search_bar_enabled" id="portal_search_bar_enabled" value="<?php echo (($settings['portal_search_bar_enabled'] ?? 'enabled') === 'enabled') ? 'enabled' : 'disabled'; ?>">
                <div class="btn-group" role="group" aria-label="Portal Search Bar action buttons" data-progress-setting="portal_search_bar_enabled">
                    <button type="button" class="btn btn-sm <?php echo (($settings['portal_search_bar_enabled'] ?? 'enabled') === 'enabled') ? 'btn-primary active' : 'btn-outline-primary'; ?>" data-progress-target="portal_search_bar_enabled" data-progress-value="enabled"><i class="bi bi-search me-1"></i>Enable</button>
                    <button type="button" class="btn btn-sm <?php echo (($settings['portal_search_bar_enabled'] ?? 'enabled') === 'disabled') ? 'btn-danger active' : 'btn-outline-danger'; ?>" data-progress-target="portal_search_bar_enabled" data-progress-value="disabled"><i class="bi bi-slash-circle me-1"></i>Disable</button>
                </div>
                <div class="small text-muted mt-2">When enabled, the search bar appears in the sidebar and suggests only pages available to the active role.</div>
            </div>
        </div>
    </div>
    <div class="col-12">
        <div class="small text-muted">These action buttons now save immediately. Use <strong>Save General Settings</strong> only for the other general settings fields on this page.</div>
    </div>
</div>
<script>
(function(){
  function updateState(target, value){
    if (!target) return;
    var input = document.getElementById(target);
    if (!input) return;
    input.value = value === 'enabled' ? 'enabled' : 'disabled';

    var wrap = document.querySelector('[data-progress-setting="' + target + '"]');
    if (wrap) {
      wrap.querySelectorAll('[data-progress-target="' + target + '"]').forEach(function(btn){
        var enabledBtn = btn.getAttribute('data-progress-value') === 'enabled';
        var active = btn.getAttribute('data-progress-value') === input.value;
        btn.classList.remove('btn-primary','btn-outline-primary','btn-danger','btn-outline-danger','active');
        btn.classList.add(enabledBtn ? (active ? 'btn-primary' : 'btn-outline-primary') : (active ? 'btn-danger' : 'btn-outline-danger'));
        btn.setAttribute('aria-pressed', active ? 'true' : 'false');
        btn.disabled = false;
        if (active) btn.classList.add('active');
      });
    }

    var badge = document.getElementById(target + '_status_badge');
    if (badge) {
      var enabled = input.value === 'enabled';
      badge.textContent = enabled ? 'Enabled' : 'Disabled';
      badge.className = 'badge ' + (enabled ? 'bg-success-subtle text-success border border-success-subtle' : 'bg-secondary-subtle text-secondary border border-secondary-subtle');
    }
  }

  function submitQuickAction(target, value, clickedButton){
    var quickForm = document.getElementById('user_progress_quick_action_form');
    if (!quickForm) return false;
    var keyInput = quickForm.querySelector('input[name="control_key"]');
    var valueInput = quickForm.querySelector('input[name="control_value"]');
    if (!keyInput || !valueInput) return false;

    keyInput.value = target;
    valueInput.value = value === 'enabled' ? 'enabled' : 'disabled';

    document.querySelectorAll('[data-progress-target][data-progress-value]').forEach(function(btn){
      btn.disabled = true;
    });
    if (clickedButton) {
      clickedButton.classList.add('disabled');
    }
    quickForm.submit();
    return true;
  }

  document.addEventListener('click', function(e){
    var btn = e.target.closest('[data-progress-target][data-progress-value]');
    if (!btn) return;
    e.preventDefault();
    var target = btn.getAttribute('data-progress-target') || '';
    var value = btn.getAttribute('data-progress-value') || 'disabled';
    updateState(target, value);
    submitQuickAction(target, value, btn);
  });

  document.querySelectorAll('[data-progress-setting]').forEach(function(wrap){
    var target = wrap.getAttribute('data-progress-setting') || '';
    var input = document.getElementById(target);
    if (target && input) {
      updateState(target, input.value || 'disabled');
    }
  });
})();
</script>

<button type="submit" class="btn btn-primary">Save General Settings</button>
                                    </form>

<div class="alert alert-light border d-flex flex-wrap justify-content-between align-items-center gap-2 mb-4">
    <div>
        <strong>Branding settings moved.</strong>
        <div class="small text-muted">Open the new <strong>Theme/Branding</strong> menu to manage portal colors and branding controls.</div>
    </div>
    <a href="?page=theme_branding" class="btn btn-outline-primary btn-sm"><i class="bi bi-palette"></i> Open Theme/Branding</a>
</div>



<form method="post" action="" id="user_progress_quick_action_form" class="d-none">
    <input type="hidden" name="action" value="update_user_progress_control">
    <input type="hidden" name="control_key" value="">
    <input type="hidden" name="control_value" value="">
    <input type="hidden" name="return_url" value="?page=settings#user-progress-controls">
</form>

<hr class="my-3">
<h5 class="mt-2">Document Signatures</h5>
<div class="row">
    <div class="col-md-4">
        <div class="asset-upload-section">
            <h6>ID Card Signature</h6>
            <?php if (!empty($idcardSignatureAsset)): ?>
                <div class="mb-2">
                    <img src="<?php echo $idcardSignatureAsset; ?>" alt="ID Card Signature" style="max-width: 220px; max-height: 90px; border: 1px solid #ddd; border-radius: 5px; object-fit: contain;">
                </div>
            <?php endif; ?>
            <form method="post" action="" enctype="multipart/form-data">
                <input type="hidden" name="action" value="upload_asset">
                <input type="hidden" name="asset_type" value="idcard_signature">
                <div class="mb-2">
                    <input type="file" class="form-control" name="asset_file" accept="image/*" required>
                    <div class="form-text">JPG/PNG. Max 2MB.</div>
                </div>
                <button type="submit" class="btn btn-outline-primary btn-sm">Upload</button>
            </form>
        </div>
    </div>
    <div class="col-md-4">
        <div class="asset-upload-section">
            <h6>Admission Letter Signature</h6>
            <?php if (!empty($admissionSignatureAsset)): ?>
                <div class="mb-2">
                    <img src="<?php echo $admissionSignatureAsset; ?>" alt="Admission Signature" style="max-width: 220px; max-height: 90px; border: 1px solid #ddd; border-radius: 5px; object-fit: contain;">
                </div>
            <?php endif; ?>
            <form method="post" action="" enctype="multipart/form-data">
                <input type="hidden" name="action" value="upload_asset">
                <input type="hidden" name="asset_type" value="admission_signature">
                <div class="mb-2">
                    <input type="file" class="form-control" name="asset_file" accept="image/*" required>
                    <div class="form-text">JPG/PNG. Max 2MB.</div>
                </div>
                <button type="submit" class="btn btn-outline-primary btn-sm">Upload</button>
            </form>
        </div>
    </div>
    <div class="col-md-4">
        <div class="asset-upload-section">
            <h6>Application Form Signature</h6>
            <?php if (!empty($applicationSignatureAsset)): ?>
                <div class="mb-2">
                    <img src="<?php echo $applicationSignatureAsset; ?>" alt="Application Signature" style="max-width: 220px; max-height: 90px; border: 1px solid #ddd; border-radius: 5px; object-fit: contain;">
                </div>
            <?php endif; ?>
            <form method="post" action="" enctype="multipart/form-data">
                <input type="hidden" name="action" value="upload_asset">
                <input type="hidden" name="asset_type" value="application_signature">
                <div class="mb-2">
                    <input type="file" class="form-control" name="asset_file" accept="image/*" required>
                    <div class="form-text">JPG/PNG. Max 2MB.</div>
                </div>
                <button type="submit" class="btn btn-outline-primary btn-sm">Upload</button>
            </form>
        </div>
    </div>
</div>


<hr class="my-3">
<h5 class="mt-2" id="settings-signatures">Clearance Account Officer Signature</h5>
<div class="row">
    <div class="col-md-4">
        <div class="asset-upload-section">
            <h6>Reusable Clearance Signature</h6>
            <?php if (!empty($accountOfficerSignatureAsset)): ?>
                <div class="mb-2">
                    <img src="<?php echo $accountOfficerSignatureAsset; ?>" alt="Clearance Signature" style="max-width: 220px; max-height: 90px; border: 1px solid #ddd; border-radius: 5px; object-fit: contain;">
                </div>
            <?php endif; ?>
            <form method="post" action="" enctype="multipart/form-data">
                <input type="hidden" name="action" value="upload_asset">
                <input type="hidden" name="asset_type" value="account_officer_signature">
                <div class="mb-2">
                    <input type="file" class="form-control" name="asset_file" accept="image/*" required>
                    <div class="form-text">JPG/PNG. Max 2MB. This is used automatically on clearance receipts for all students, while the signature pad remains available as a per-record override.</div>
                </div>
                <button type="submit" class="btn btn-outline-primary btn-sm">Upload</button>
            </form>
        </div>
    </div>
</div>

<hr class="my-3">
<h5 class="mt-2">Official Stamp (for Admission Letter, Application Form & Receipts)</h5>
<div class="row">
    <div class="col-md-4">
        <div class="asset-upload-section">
            <h6>Upload Stamp</h6>
            <?php if (!empty($stampAsset)): ?>
                <div class="mb-2">
                    <img src="<?php echo $stampAsset; ?>" alt="Official Stamp" style="max-width: 220px; max-height: 220px; border: 1px solid #ddd; border-radius: 5px; object-fit: contain;">
                </div>
            <?php endif; ?>
            <form method="post" action="" enctype="multipart/form-data">
                <input type="hidden" name="action" value="upload_asset">
                <input type="hidden" name="asset_type" value="official_stamp">
                <div class="mb-2">
                    <input type="file" class="form-control" name="asset_file" accept="image/*" required>
                    <div class="form-text">PNG recommended (transparent background). Max 2MB.</div>
                </div>
                <button type="submit" class="btn btn-outline-primary btn-sm">Upload</button>
            </form>
        </div>
    </div>
</div>

<hr class="my-3">
<div class="alert alert-secondary border">
    <strong>Wallet settings are managed from Fees Manager.</strong>
    <div class="small text-muted mt-1">Duplicate wallet controls have been removed from System Settings so wallet funding and payment behavior stay synchronized in one place.</div>
    <div class="mt-2"><a class="btn btn-outline-primary btn-sm" href="?page=admin_fees_manager&fees_tab=wallet_settings">Open Wallet Settings</a></div>
</div>

<hr class="my-4">

                                    <!-- Logo and Favicon Upload Section - Modified to handle separate logos -->
                                    <h4>System Assets</h4>
                                    <div class="row">
                                        <div class="col-md-6">
                                            <div class="asset-upload-section">
                                                <h5>Left Logo</h5>
                                                <p>Upload the left organization logo to display on the website and receipts.</p>

                                                <?php if ($leftLogoAsset): ?>
                                                <div class="mb-3">
                                                    <img src="<?php echo $leftLogoAsset; ?>" alt="Current Left Logo" style="max-width: 150px; max-height: 150px; border: 1px solid #ddd; border-radius: 5px;">
                                                </div>
                                                <?php endif; ?>

                                                <form method="post" action="" enctype="multipart/form-data">
                                                    <input type="hidden" name="action" value="upload_asset">
                                                    <input type="hidden" name="asset_type" value="left_logo">

                                                    <div class="mb-3">
                                                        <label for="left_logo_file" class="form-label">Choose Left Logo File</label>
                                                        <input type="file" class="form-control" id="left_logo_file" name="asset_file" accept="image/*" required>
                                                        <div class="form-text">Allowed formats: JPG, JPEG, PNG. Maximum size: 2MB.</div>
                                                    </div>

                                                    <button type="submit" class="btn btn-primary">Upload Left Logo</button>
                                                </form>
                                            </div>
                                        </div>

                                        <div class="col-md-6">
                                            <div class="asset-upload-section">
                                                <h5>Right Logo</h5>
                                                <p>Upload the right organization logo to display on the website and receipts.</p>

                                                <?php if ($rightLogoAsset): ?>
                                                <div class="mb-3">
                                                    <img src="<?php echo $rightLogoAsset; ?>" alt="Current Right Logo" style="max-width: 150px; max-height: 150px; border: 1px solid #ddd; border-radius: 5px;">
                                                </div>
                                                <?php endif; ?>

                                                <form method="post" action="" enctype="multipart/form-data">
                                                    <input type="hidden" name="action" value="upload_asset">
                                                    <input type="hidden" name="asset_type" value="right_logo">

                                                    <div class="mb-3">
                                                        <label for="right_logo_file" class="form-label">Choose Right Logo File</label>
                                                        <input type="file" class="form-control" id="right_logo_file" name="asset_file" accept="image/*" required>
                                                        <div class="form-text">Allowed formats: JPG, JPEG, PNG. Maximum size: 2MB.</div>
                                                    </div>

                                                    <button type="submit" class="btn btn-primary">Upload Right Logo</button>
                                                </form>
                                            </div>
                                        </div>
                                    </div>

                                    <div class="row mt-4">
                                        <div class="col-md-6">
                                            <div class="asset-upload-section">
                                                <h5>Website Favicon</h5>
                                                <p>Upload a favicon for your website (appears in browser tab).</p>

                                                <?php if ($faviconAsset): ?>
                                                <div class="mb-3">
                                                    <img src="<?php echo $faviconAsset; ?>" alt="Current Favicon" style="max-width: 32px; max-height: 32px; border: 1px solid #ddd; border-radius: 5px;">
                                                </div>
                                                <?php endif; ?>

                                                <form method="post" action="" enctype="multipart/form-data">
                                                    <input type="hidden" name="action" value="upload_asset">
                                                    <input type="hidden" name="asset_type" value="favicon">

                                                    <div class="mb-3">
                                                        <label for="favicon_file" class="form-label">Choose Favicon File</label>
                                                        <input type="file" class="form-control" id="favicon_file" name="asset_file" accept="image/x-icon, image/png, image/jpeg" required>
                                                        <div class="form-text">Recommended size: 32x32 pixels. Formats: ICO, PNG, JPG.</div>
                                                    </div>

                                                    <button type="submit" class="btn btn-primary">Upload Favicon</button>
                                                </form>
                                            </div>
                                        </div>
                                    </div>

                                    <hr class="my-4">

                                    <h4>Admin Accounts</h4>
                                    <div class="table-responsive">
                                        <table class="table table-striped">
                                            <thead>
                                                <tr>
                                                    <th>Username</th>
                                                    <th>Full Name</th>
                                                    <th>Email</th>
                                                    <th>Created At</th>
                                                    <th>Actions</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <?php
                                                $stmt = $pdo->query("SELECT * FROM admins ORDER BY created_at DESC");
                                                while ($admin = $stmt->fetch(PDO::FETCH_ASSOC)) {
                                                    echo "<tr data-student-id=\"" . (int)$admin['id'] . "\">";
                                                    echo "<td>" . $admin['username'] . "</td>";
                                                    echo "<td>" . $admin['full_name'] . "</td>";
                                                    echo "<td>" . $admin['email'] . "</td>";
                                                    echo "<td>" . date('d M Y', strtotime($admin['created_at'])) . "</td>";
                                                    echo "<td>
                                                            <button class=\"btn btn-sm btn-primary\" onclick=\"editAdmin(" . $admin['id'] . ")\">Edit</button>";
                                                    if ($admin['id'] != $_SESSION['user_id']) {
                                                        echo "<button class=\"btn btn-sm btn-danger ms-1\" onclick=\"deleteAdmin(" . $admin['id'] . ")\">Delete</button>";
                                                    }
                                                    echo "</td>";
                                                    echo "</tr>";
                                                }
                                                ?>
                                            </tbody>
                                        </table>
                                    </div>

                                    <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addAdminModal">Add New Admin</button>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- Add Admin Modal -->
                    <div class="modal fade" id="addAdminModal" tabindex="-1">
                        <div class="modal-dialog">
                            <div class="modal-content">
                                <div class="modal-header">
                                    <h5 class="modal-title">Add New Admin</h5>
                                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                                </div>
                                <div class="modal-body">
                                    <form id="addAdminForm">
                                        <div class="mb-3">
                                            <label for="new_admin_username" class="form-label">Username</label>
                                            <input type="text" class="form-control" id="new_admin_username" required>
                                        </div>
                                        <div class="mb-3">
                                            <label for="new_admin_email" class="form-label">Email</label>
                                            <input type="email" class="form-control" id="new_admin_email" required>
                                        </div>
                                        <div class="mb-3">
                                            <label for="new_admin_full_name" class="form-label">Full Name</label>
                                            <input type="text" class="form-control" id="new_admin_full_name" required>
                                        </div>
                                        <div class="mb-3">
                                            <label for="new_admin_password" class="form-label">Password</label>
                                            <input type="password" class="form-control" id="new_admin_password" required>
                                        </div>
                                        <div class="mb-3">
                                            <label for="new_admin_confirm_password" class="form-label">Confirm Password</label>
                                            <input type="password" class="form-control" id="new_admin_confirm_password" required>
                                        </div>
                                    </form>
                                </div>
                                <div class="modal-footer">
                                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
                                    <button type="button" class="btn btn-primary" onclick="addAdmin()">Add Admin</button>
                                </div>
                            </div>
                        </div>
                    </div>

                    <script>
                    document.addEventListener("DOMContentLoaded", function() {
                        // Add admin
                        window.addAdmin = function() {
                            const username = document.getElementById("new_admin_username").value;
                            const email = document.getElementById("new_admin_email").value;
                            const fullName = document.getElementById("new_admin_full_name").value;
                            const password = document.getElementById("new_admin_password").value;
                            const confirmPassword = document.getElementById("new_admin_confirm_password").value;

                            if (password !== confirmPassword) {
                                alert("Passwords do not match");
                                return;
                            }

                            const xhr = new XMLHttpRequest();
                            xhr.open("POST", "", true);
                            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                            xhr.onreadystatechange = function() {
                                if (xhr.readyState === 4 && xhr.status === 200) {
                                    if (xhr.responseText === "success") {
                                        alert("Admin added successfully");
                                        location.reload();
                                    } else {
                                        alert("Error: " + xhr.responseText);
                                    }
                                }
                            };
                            xhr.send("action=add_admin&username=" + encodeURIComponent(username) + "&email=" + encodeURIComponent(email) + "&full_name=" + encodeURIComponent(fullName) + "&password=" + encodeURIComponent(password));
                        };

                        // Edit admin
                        window.editAdmin = function(id) {
                            // This would open a modal with admin details
                            alert("Edit functionality would be implemented here");
                        };

                        // Delete admin
                        window.deleteAdmin = function(id) {
                            if (confirm("Are you sure you want to delete this admin account?")) {
                                const xhr = new XMLHttpRequest();
                                xhr.open("POST", "", true);
                                xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                                xhr.onreadystatechange = function() {
                                    if (xhr.readyState === 4 && xhr.status === 200) {
                                        if (xhr.responseText === "success") {
                                            alert("Admin deleted successfully");
                                            location.reload();
                                        } else {
                                            alert("Error: " + xhr.responseText);
                                        }
                                    }
                                };
                                xhr.send("action=delete_admin&id=" + id);
                            }
                        };
                    });
                    </script>

                    <?php
                    // Handle AJAX requests for admin management
                    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
                        if ($_POST['action'] === 'add_admin') {
                            $username = $_POST['username'] ?? '';
                            $email = $_POST['email'] ?? '';
                            $fullName = $_POST['full_name'] ?? '';
                            $password = $_POST['password'] ?? '';

                            // Check if username already exists
                            $stmt = $pdo->prepare("SELECT COUNT(*) FROM admins WHERE username = ?");
                            $stmt->execute([$username]);
                            if ($stmt->fetchColumn() > 0) {
                                echo "Username already exists";
                                exit;
                            }

                            // Check if email already exists
                            $stmt = $pdo->prepare("SELECT COUNT(*) FROM admins WHERE email = ?");
                            $stmt->execute([$email]);
                            if ($stmt->fetchColumn() > 0) {
                                echo "Email already exists";
                                exit;
                            }

                            // Hash password
                            $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

                            // Insert new admin
                            $stmt = $pdo->prepare("INSERT INTO admins (username, email, full_name, password) VALUES (?, ?, ?, ?)");
                            $stmt->execute([$username, $email, $fullName, $hashedPassword]);

                            echo "success";
                            exit;
                        } elseif ($_POST['action'] === 'delete_admin') {
                            $id = $_POST['id'] ?? 0;

                            if ($id > 0) {
                                $stmt = $pdo->prepare("DELETE FROM admins WHERE id = ?");
                                $stmt->execute([$id]);
                                echo "success";
                            } else {
                                echo "Invalid admin ID";
                            }

                            exit;
                        }
                    }
                } else {
                    redirect('?page=login');
                }
                break;


            case 'admin_accommodation_verification':
                if (isAdmin()) {
                    portalEnsureAccommodationVerificationSupport($pdo);
                    $claimStatusFilter = trim((string)($_GET['status'] ?? 'Pending'));
                    $allowedClaimStatuses = ['Pending','Approved','Rejected','Cancelled'];
                    if ($claimStatusFilter !== '' && !in_array($claimStatusFilter, $allowedClaimStatuses, true)) $claimStatusFilter = 'Pending';
                    $claims = [];
                    try {
                        $sql = "SELECT acr.*, s.matric_no, s.registration_no, s.application_no, s.surname, s.first_name, s.other_name, s.email, s.mobile_phone, s.department, s.level, s.programme
                                FROM accommodation_change_requests acr
                                LEFT JOIN students s ON s.id = acr.student_id";
                        $params = [];
                        if ($claimStatusFilter !== '') { $sql .= " WHERE acr.request_status = ?"; $params[] = $claimStatusFilter; }
                        $sql .= " ORDER BY CASE acr.request_status WHEN 'Pending' THEN 0 ELSE 1 END, acr.id DESC";
                        $st = $pdo->prepare($sql);
                        $st->execute($params);
                        $claims = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    } catch (Throwable $e) { $claims = []; }
                    ?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
                                    <span>Off-Campus Accommodation Verification</span>
                                    <div class="d-flex gap-2">
                                        <a href="?page=admin_settings" class="btn btn-outline-secondary btn-sm"><i class="bi bi-gear"></i> Settings</a>
                                    </div>
                                </div>
                                <div class="card-body">
                                    <?php renderFlashes(); ?>
                                    <form method="get" class="row g-2 align-items-end mb-3">
                                        <input type="hidden" name="page" value="admin_accommodation_verification">
                                        <div class="col-md-4">
                                            <label class="form-label">Filter Status</label>
                                            <select class="form-select" name="status">
                                                <?php foreach ($allowedClaimStatuses as $statusOpt): ?>
                                                    <option value="<?php echo e($statusOpt); ?>" <?php echo ($claimStatusFilter === $statusOpt) ? 'selected' : ''; ?>><?php echo e($statusOpt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-md-2 d-grid"><button class="btn btn-outline-primary" type="submit">Load</button></div>
                                    </form>
                                    <div class="table-responsive">
                                        <table class="table table-bordered align-middle">
                                            <thead>
                                                <tr>
                                                    <th>Student</th>
                                                    <th>Scope</th>
                                                    <th>Request</th>
                                                    <th>Status</th>
                                                    <th>Student Note</th>
                                                    <th>Admin Review</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                            <?php if (!$claims): ?>
                                                <tr><td colspan="6" class="text-center text-muted py-4">No accommodation claims found for the selected filter.</td></tr>
                                            <?php else: ?>
                                                <?php foreach ($claims as $claim): ?>
                                                    <tr>
                                                        <td>
                                                            <div class="fw-semibold"><?php echo e(trim(full_name((array)$claim)) !== '' ? full_name((array)$claim) : ('Student #' . (int)($claim['student_id'] ?? 0))); ?></div>
                                                            <div class="small text-muted"><?php echo e((string)($claim['matric_no'] ?? $claim['registration_no'] ?? $claim['application_no'] ?? '')); ?></div>
                                                            <div class="small text-muted"><?php echo e((string)($claim['email'] ?? '')); ?></div>
                                                        </td>
                                                        <td>
                                                            <div><strong>Session:</strong> <?php echo e((string)($claim['session_name'] ?? '—')); ?></div>
                                                            <div><strong>Semester:</strong> <?php echo e((string)($claim['semester'] ?? '—')); ?></div>
                                                            <div><strong>Level:</strong> <?php echo e((string)($claim['level'] ?? '—')); ?></div>
                                                        </td>
                                                        <td>
                                                            <div><strong>Requested:</strong> <?php echo e((string)($claim['requested_status'] ?? 'Off-Campus')); ?></div>
                                                            <div class="small text-muted">Submitted: <?php echo e((string)($claim['created_at'] ?? '')); ?></div>
                                                        </td>
                                                        <td><span class="badge <?php echo ((string)($claim['request_status'] ?? '') === 'Approved') ? 'bg-success' : ((((string)($claim['request_status'] ?? '') === 'Rejected') || ((string)($claim['request_status'] ?? '') === 'Cancelled')) ? 'bg-danger' : 'bg-warning text-dark'); ?>"><?php echo e((string)($claim['request_status'] ?? 'Pending')); ?></span></td>
                                                        <td><?php echo nl2br(e((string)($claim['student_note'] ?? '—'))); ?></td>
                                                        <td style="min-width:280px;">
                                                            <form method="post" class="row g-2">
                                                                <input type="hidden" name="action" value="review_off_campus_claim">
                                                                <input type="hidden" name="claim_id" value="<?php echo (int)($claim['id'] ?? 0); ?>">
                                                                <input type="hidden" name="return_to" value="?page=admin_accommodation_verification&status=<?php echo urlencode($claimStatusFilter); ?>">
                                                                <div class="col-12">
                                                                    <select class="form-select form-select-sm" name="decision">
                                                                        <option value="pending" <?php echo ((string)($claim['request_status'] ?? 'Pending') === 'Pending') ? 'selected' : ''; ?>>Leave Pending</option>
                                                                        <option value="approved" <?php echo ((string)($claim['request_status'] ?? '') === 'Approved') ? 'selected' : ''; ?>>Approve Off-Campus</option>
                                                                        <option value="rejected" <?php echo ((string)($claim['request_status'] ?? '') === 'Rejected') ? 'selected' : ''; ?>>Reject / Require Hostel Fee</option>
                                                                    </select>
                                                                </div>
                                                                <div class="col-12"><textarea class="form-control form-control-sm" name="verification_note" rows="2" placeholder="Verification note"><?php echo e((string)($claim['admin_note'] ?? '')); ?></textarea></div>
                                                                <div class="col-12 d-grid"><button type="submit" class="btn btn-primary btn-sm">Save Review</button></div>
                                                            </form>
                                                        </td>
                                                    </tr>
                                                <?php endforeach; ?>
                                            <?php endif; ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <?php
                } else {
                    redirect('?page=login');
                }
                break;

            case 'theme_branding':
                if (isAdmin()) {
?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
                                <div>
                                    <h4 class="mb-1">Theme/Branding</h4>
                                    <div class="small text-muted">Change portal colors and branding safely from one dedicated workspace.</div>
                                </div>
                                <div class="d-flex gap-2">
                                    <a href="?page=admin_settings" class="btn btn-outline-secondary btn-sm"><i class="bi bi-gear"></i> Settings</a>
                                </div>
                            </div>
                            <?php
    $brandingSettings = function_exists('portalBrandingSettings') ? portalBrandingSettings($pdo) : [];
    $adminThemeSettings = function_exists('portalAdminThemeSettings') ? portalAdminThemeSettings($pdo) : [];
    $dashboardRuleEditId = (int)($_GET['edit_dashboard_rule'] ?? 0);
    $dashboardRuleEdit = $dashboardRuleEditId > 0 && function_exists('portalDashboardDesignRuleById') ? portalDashboardDesignRuleById($pdo, $dashboardRuleEditId) : null;
    $dashboardRuleDefaults = function_exists('portalDefaultStudentDashboardTheme') ? portalDefaultStudentDashboardTheme($pdo, []) : [];
    $dashboardRuleLayoutOptions = function_exists('portalDashboardDesignRuleLayoutOptions') ? portalDashboardDesignRuleLayoutOptions() : ['standard' => 'Standard Layout'];
    $dashboardRuleRows = function_exists('portalFetchDashboardDesignRules') ? portalFetchDashboardDesignRules($pdo, false) : [];
    $dashboardProgrammeOptions = function_exists('portalFetchDistinctProgrammeOptions') ? portalFetchDistinctProgrammeOptions($pdo) : [];
    try { $dashboardFacultyOptions = $pdo->query("SELECT id, name FROM faculties WHERE COALESCE(is_active,1)=1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $dashboardFacultyOptions = []; }
    try { $dashboardDepartmentOptions = $pdo->query("SELECT id, faculty_id, name FROM departments WHERE COALESCE(is_active,1)=1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $dashboardDepartmentOptions = []; }
    $dashboardModeOptions = function_exists('portalModeOfStudyOptionValues') ? portalModeOfStudyOptionValues($pdo) : ['Full-Time', 'Part-Time'];
    $dashboardLevelOptions = function_exists('portalAcademicLevelOptions') ? portalAcademicLevelOptions($pdo) : ['100','200','300','400'];
    $themeBrandingWorkspaceTab = trim((string)($_GET['branding_tab'] ?? ''));
    if (!in_array($themeBrandingWorkspaceTab, ['admin_theme', 'dashboard_rules'], true)) {
        $themeBrandingWorkspaceTab = ($dashboardRuleEditId > 0 || isset($_GET['dashboard_rules_tab'])) ? 'dashboard_rules' : 'admin_theme';
    }
?>
<div class="theme-branding-workspace mb-4" id="theme-branding-workspace">
    <ul class="nav nav-tabs theme-branding-main-nav mb-3" id="themeBrandingMainTabs" role="tablist">
        <li class="nav-item" role="presentation">
            <button class="nav-link <?php echo $themeBrandingWorkspaceTab === 'admin_theme' ? 'active' : ''; ?>" id="themeBrandingMainTabAdminTheme" data-bs-toggle="tab" data-bs-target="#themeBrandingMainPaneAdminTheme" type="button" role="tab" aria-controls="themeBrandingMainPaneAdminTheme" aria-selected="<?php echo $themeBrandingWorkspaceTab === 'admin_theme' ? 'true' : 'false'; ?>" data-theme-branding-tab="admin_theme">
                Admin Theme Settings
            </button>
        </li>
        <li class="nav-item" role="presentation">
            <button class="nav-link <?php echo $themeBrandingWorkspaceTab === 'dashboard_rules' ? 'active' : ''; ?>" id="themeBrandingMainTabDashboardRules" data-bs-toggle="tab" data-bs-target="#themeBrandingMainPaneDashboardRules" type="button" role="tab" aria-controls="themeBrandingMainPaneDashboardRules" aria-selected="<?php echo $themeBrandingWorkspaceTab === 'dashboard_rules' ? 'true' : 'false'; ?>" data-theme-branding-tab="dashboard_rules">
                Student Dashboard Design Rules
            </button>
        </li>
    </ul>
    <div class="tab-content" id="themeBrandingMainTabContent">
        <div class="tab-pane fade <?php echo $themeBrandingWorkspaceTab === 'admin_theme' ? 'show active' : ''; ?>" id="themeBrandingMainPaneAdminTheme" role="tabpanel" aria-labelledby="themeBrandingMainTabAdminTheme" tabindex="0">
<div class="card border-0 shadow-sm mb-4" id="settings-admin-theme">
    <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
        <div>
            <strong>Admin Theme Settings</strong>
            <div class="small opacity-75">Manage admin colours independently for cards, titles, tables, tabs, buttons, sidebar, alerts, and dashboard stat tiles.</div>
        </div>
        <span class="badge text-bg-light border">Admin Theme</span>
    </div>
    <div class="card-body">
        <form method="post" action="" class="row g-3" id="portalAdminThemeForm">
            <input type="hidden" name="action" value="save_admin_theme_settings">
            <input type="hidden" name="return_to" value="?page=theme_branding&branding_tab=admin_theme#settings-admin-theme">
            <?php foreach ([
                'admin_theme_background_color' => 'Admin Page Background Colour',
                'admin_theme_card_background_color' => 'Admin Card Background Colour',
                'admin_theme_border_color' => 'Admin Border Colour',
                'admin_theme_section_header_bg_color' => 'Admin Section/Header Bar Background Colour',
                'admin_theme_sidebar_bg_color' => 'Admin Sidebar Background Colour',
                'admin_theme_sidebar_active_color' => 'Admin Sidebar Active Menu Colour',
                'admin_theme_sidebar_text_color' => 'Admin Sidebar Text Colour',
                'admin_theme_stat_card_background_color' => 'Admin Stat Card Background Colour',
                'admin_theme_stat_card_number_color' => 'Admin Stat Card Number Colour',
                'admin_theme_stat_card_label_color' => 'Admin Stat Card Label/Title Colour',
                'admin_theme_card_title_color' => 'Admin General Card Title Colour',
                'admin_theme_heading_color' => 'Admin Heading Colour',
                'admin_theme_body_text_color' => 'Admin Body Text Colour',
                'admin_theme_muted_text_color' => 'Admin Muted Text Colour',
                'admin_theme_button_color' => 'Admin Primary Button Colour',
                'admin_theme_button_text_color' => 'Admin Button Text Colour',
                'admin_theme_tab_active_color' => 'Admin Tab Active Colour',
                'admin_theme_tab_inactive_color' => 'Admin Tab Inactive Colour',
                'admin_theme_tab_text_color' => 'Admin Tab Text Colour',
                'admin_theme_table_header_bg_color' => 'Admin Table Header Background Colour',
                'admin_theme_table_header_text_color' => 'Admin Table Header Text Colour',
                'admin_theme_badge_accent_color' => 'Admin Badge / Alert Accent Colour',
            ] as $adminThemeKey => $adminThemeLabel): ?>
                <div class="col-12 col-md-6 col-xl-4">
                    <label class="form-label" for="<?php echo e($adminThemeKey); ?>"><?php echo e($adminThemeLabel); ?></label>
                    <div class="input-group portal-brand-color-group" data-admin-theme-color-sync="<?php echo e($adminThemeKey); ?>">
                        <input type="color" class="form-control form-control-color" id="<?php echo e($adminThemeKey); ?>_picker" name="<?php echo e($adminThemeKey); ?>_picker" value="<?php echo e((string)($adminThemeSettings[$adminThemeKey] ?? '')); ?>" title="Choose <?php echo e($adminThemeLabel); ?>">
                        <input type="text" class="form-control portal-brand-color-text" id="<?php echo e($adminThemeKey); ?>" name="<?php echo e($adminThemeKey); ?>" value="<?php echo e((string)($adminThemeSettings[$adminThemeKey] ?? '')); ?>" maxlength="7" pattern="^#?[A-Fa-f0-9]{3,6}$" placeholder="#003366">
                    </div>
                </div>
            <?php endforeach; ?>

            <div class="col-12">
                <div class="border rounded-4 p-3 branding-preview-wrap admin-branding-preview-wrap" id="portalAdminThemePreview" style="background:<?php echo e((string)($adminThemeSettings['admin_theme_background_color'] ?? '#F4F7F6')); ?>;">
                    <div class="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-3">
                        <div>
                            <div class="fw-semibold">Live Admin Theme Preview</div>
                            <div class="small text-muted">Preview updates as you change the colours below. This preview is only a sample and does not affect saved data until you click save.</div>
                        </div>
                        <span class="badge portal-admin-preview-badge">Admin Preview</span>
                    </div>
                    <div class="border rounded-4 overflow-hidden admin-branding-preview-shell">
                        <div class="p-3 portal-admin-preview-sidebar text-white">Sidebar / Menu</div>
                        <div class="p-3 portal-admin-preview-header d-flex justify-content-between align-items-center">
                            <div>Admin Dashboard</div>
                            <button class="btn btn-sm portal-admin-preview-btn">Primary Button</button>
                        </div>
                        <div class="p-3 admin-branding-preview-surface">
                            <div class="row g-3">
                                <div class="col-md-4">
                                    <div class="portal-admin-preview-stat-card">
                                        <div class="value">245</div>
                                        <div class="label">Total Students</div>
                                    </div>
                                </div>
                                <div class="col-md-4">
                                    <div class="portal-admin-preview-stat-card">
                                        <div class="value">18</div>
                                        <div class="label">Pending Payments</div>
                                    </div>
                                </div>
                                <div class="col-md-4">
                                    <div class="portal-admin-preview-stat-card">
                                        <div class="value">7</div>
                                        <div class="label">Awaiting Clearance</div>
                                    </div>
                                </div>
                                <div class="col-md-7">
                                    <div class="card border-0 shadow-sm h-100 portal-admin-preview-card">
                                        <div class="card-header portal-admin-preview-card-header">Admissions Summary</div>
                                        <div class="card-body">
                                            <h6 class="portal-admin-preview-card-title">Current Session Overview</h6>
                                            <p class="mb-2 portal-admin-preview-body">This sample shows how admin card text, titles, borders, buttons, alerts, and table headers will look after saving.</p>
                                            <p class="mb-0 portal-admin-preview-muted">Muted helper text remains readable and balanced.</p>
                                        </div>
                                    </div>
                                </div>
                                <div class="col-md-5">
                                    <div class="alert mb-0 portal-admin-preview-alert">Badge / Alert Accent Preview</div>
                                </div>
                                <div class="col-12">
                                    <ul class="nav nav-tabs admin-branding-preview-tabs">
                                        <li class="nav-item"><span class="nav-link active">Active Tab</span></li>
                                        <li class="nav-item"><span class="nav-link">Inactive Tab</span></li>
                                    </ul>
                                    <div class="table-responsive border rounded-bottom-4 overflow-hidden">
                                        <table class="table mb-0">
                                            <thead>
                                                <tr>
                                                    <th class="portal-admin-preview-table-header">Table Header</th>
                                                    <th class="portal-admin-preview-table-header">Status</th>
                                                    <th class="portal-admin-preview-table-header">Action</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <tr>
                                                    <td>Payments</td>
                                                    <td><span class="badge portal-admin-preview-badge">Active</span></td>
                                                    <td><a href="#" class="portal-admin-preview-link">View Details</a></td>
                                                </tr>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="alert alert-light border small mt-3 mb-0">
                    <strong>Admin theme tips:</strong> These colours are scoped to admin-facing pages only, so student, applicant, login, print, and public pages remain unchanged.
                </div>
            </div>

            <div class="col-12 d-flex flex-wrap gap-2 justify-content-between align-items-center">
                <div class="small text-muted">Admin theme values are stored separately from the general portal theme and load automatically on every admin page refresh.</div>
                <div class="d-flex flex-wrap gap-2">
                    <button type="submit" class="btn btn-primary"><i class="bi bi-sliders"></i> Save Admin Theme</button>
                </div>
            </div>
        </form>
        <form method="post" action="" class="mt-2 d-flex justify-content-end">
            <input type="hidden" name="action" value="reset_admin_theme_settings">
            <input type="hidden" name="return_to" value="?page=theme_branding&branding_tab=admin_theme#settings-admin-theme">
            <button type="submit" class="btn btn-outline-danger" onclick="return confirm('Reset admin theme to the default admin colours?');"><i class="bi bi-arrow-counterclockwise"></i> Reset Admin Theme</button>
        </form>
    </div>
</div>

<script>
(function(){
    const pairs = Array.from(document.querySelectorAll('[data-admin-theme-color-sync]'));
    const preview = document.getElementById('portalAdminThemePreview');
    if (!pairs.length || !preview) return;
    function safeHex(value, fallback){
        const raw = String(value || '').trim();
        const prefixed = raw.startsWith('#') ? raw : ('#' + raw);
        return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(prefixed) ? prefixed : fallback;
    }
    function syncPair(wrap){
        const picker = wrap.querySelector('input[type="color"]');
        const text = wrap.querySelector('.portal-brand-color-text');
        if (!picker || !text) return;
        picker.addEventListener('input', function(){ text.value = picker.value.toUpperCase(); updatePreview(); });
        text.addEventListener('input', function(){
            const val = safeHex(text.value, picker.value || '#003366');
            if (/^#([0-9a-fA-F]{6})$/.test(val)) picker.value = val;
            updatePreview();
        });
    }
    function val(id, fallback){
        const el = document.getElementById(id);
        return safeHex(el ? el.value : fallback, fallback);
    }
    function updatePreview(){
        const vars = {
            '--admin-preview-bg': val('admin_theme_background_color', '#F4F7F6'),
            '--admin-preview-card-bg': val('admin_theme_card_background_color', '#FFFFFF'),
            '--admin-preview-border': val('admin_theme_border_color', '#D9E2EC'),
            '--admin-preview-header': val('admin_theme_section_header_bg_color', '#003366'),
            '--admin-preview-sidebar': val('admin_theme_sidebar_bg_color', '#003366'),
            '--admin-preview-sidebar-active': val('admin_theme_sidebar_active_color', '#0B5ED7'),
            '--admin-preview-sidebar-text': val('admin_theme_sidebar_text_color', '#FFFFFF'),
            '--admin-preview-stat-bg': val('admin_theme_stat_card_background_color', '#003366'),
            '--admin-preview-stat-number': val('admin_theme_stat_card_number_color', '#FFFFFF'),
            '--admin-preview-stat-label': val('admin_theme_stat_card_label_color', '#FFFFFF'),
            '--admin-preview-card-title': val('admin_theme_card_title_color', '#003366'),
            '--admin-preview-heading': val('admin_theme_heading_color', '#003366'),
            '--admin-preview-text': val('admin_theme_body_text_color', '#333333'),
            '--admin-preview-muted': val('admin_theme_muted_text_color', '#64748B'),
            '--admin-preview-btn': val('admin_theme_button_color', '#003366'),
            '--admin-preview-btn-text': val('admin_theme_button_text_color', '#FFFFFF'),
            '--admin-preview-tab-active': val('admin_theme_tab_active_color', '#003366'),
            '--admin-preview-tab-inactive': val('admin_theme_tab_inactive_color', '#E8EEF7'),
            '--admin-preview-tab-text': val('admin_theme_tab_text_color', '#003366'),
            '--admin-preview-table-bg': val('admin_theme_table_header_bg_color', '#003366'),
            '--admin-preview-table-text': val('admin_theme_table_header_text_color', '#FFFFFF'),
            '--admin-preview-badge': val('admin_theme_badge_accent_color', '#198754')
        };
        Object.entries(vars).forEach(([key, value]) => preview.style.setProperty(key, value));
        preview.style.background = vars['--admin-preview-bg'];
    }
    pairs.forEach(syncPair);
    updatePreview();
})();
</script>
        </div>
        <div class="tab-pane fade <?php echo $themeBrandingWorkspaceTab === 'dashboard_rules' ? 'show active' : ''; ?>" id="themeBrandingMainPaneDashboardRules" role="tabpanel" aria-labelledby="themeBrandingMainTabDashboardRules" tabindex="0">

<div class="card border-0 shadow-sm mb-4" id="dashboard-design-rules">
    <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
        <div>
            <strong>Student Dashboard Design Rules</strong>
            <div class="small opacity-75">Control the student dashboard theme and layout by Faculty, Department, Programme, Mode of Study, and Level without changing the shared dashboard engine.</div>
        </div>
        <div class="d-flex flex-wrap gap-2 align-items-center">
            <span class="badge text-bg-light border"><?php echo count($dashboardRuleRows); ?> rule(s)</span>
            <form method="post" action="" class="d-inline" onsubmit="return confirm('Reset Student Dashboard Design Rules to safe defaults? This will remove only saved student dashboard rules and will not affect Admin Theme Settings or other branding settings.');">
                <input type="hidden" name="action" value="reset_student_dashboard_theme">
                <input type="hidden" name="return_to" value="?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=add#dashboard-design-rules">
                <button type="submit" class="btn btn-outline-danger btn-sm"><i class="bi bi-arrow-counterclockwise"></i> Reset Student Theme</button>
            </form>
        </div>
    </div>
    <div class="card-body">
        <div class="dashboard-design-rule-workspace">
            <ul class="nav nav-tabs dashboard-design-rule-nav mb-3" id="dashboardDesignRulesTabs" role="tablist">
                <li class="nav-item" role="presentation">
                    <button class="nav-link active" id="dashboardDesignRulesAddTab" data-bs-toggle="tab" data-bs-target="#dashboardDesignRulesAddPane" type="button" role="tab" aria-controls="dashboardDesignRulesAddPane" aria-selected="true" data-dashboard-rules-tab="add">
                        Saved Dashboard Rules
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="dashboardDesignRulesSavedTab" data-bs-toggle="tab" data-bs-target="#dashboardDesignRulesSavedPane" type="button" role="tab" aria-controls="dashboardDesignRulesSavedPane" aria-selected="false" data-dashboard-rules-tab="saved">
                        Add Dashboard Rules
                    </button>
                </li>
            </ul>

            <div class="tab-content dashboard-design-rule-tab-content" id="dashboardDesignRulesTabContent">
                <div class="tab-pane fade" id="dashboardDesignRulesSavedPane" role="tabpanel" aria-labelledby="dashboardDesignRulesSavedTab" tabindex="0" data-dashboard-rules-pane="saved">
                    <div class="border rounded-4 p-3 bg-light-subtle dashboard-design-rule-pane-card">
                        <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
                            <div>
                                <div class="fw-semibold"><?php echo $dashboardRuleEdit ? 'Edit Dashboard Rule' : 'Add Dashboard Rule'; ?></div>
                                <div class="small text-muted">Match student dashboard appearance by Faculty, Department, Programme, Mode of Study, and Level.</div>
                            </div>
                            <?php if ($dashboardRuleEdit): ?>
                                <a href="?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=saved#dashboard-design-rules" class="btn btn-outline-secondary btn-sm"><i class="bi bi-x-circle"></i> Clear Edit</a>
                            <?php endif; ?>
                        </div>
                        <form method="post" action="" class="row g-3" id="dashboardDesignRuleForm">
                            <input type="hidden" name="action" value="save_dashboard_design_rule">
                            <input type="hidden" name="rule_id" value="<?php echo (int)($dashboardRuleEdit['id'] ?? 0); ?>">
                            <input type="hidden" name="return_to" value="?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=saved#dashboard-design-rules" id="dashboardDesignRuleReturnTo">

                            <div class="col-12">
                                <label class="form-label">Rule Name</label>
                                <input type="text" class="form-control" name="rule_name" maxlength="190" value="<?php echo e((string)($dashboardRuleEdit['rule_name'] ?? '')); ?>" placeholder="e.g. NCE Full-Time 100 Dashboard">
                            </div>

                            
                            <div class="col-md-6">
                                <label class="form-label">Faculty</label>
                                <select class="form-select" name="faculty_id" id="dashboard_rule_faculty_id">
                                    <option value="">All Faculties</option>
                                    <?php foreach ($dashboardFacultyOptions as $facultyOption): ?>
                                        <option value="<?php echo (int)($facultyOption['id'] ?? 0); ?>" <?php echo ((int)($dashboardRuleEdit['faculty_id'] ?? 0) === (int)($facultyOption['id'] ?? 0)) ? 'selected' : ''; ?>>
                                            <?php echo e((string)($facultyOption['name'] ?? 'Faculty')); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                            </div>

                            <div class="col-md-6">
                                <label class="form-label">Department</label>
                                <select class="form-select" name="department_id" id="dashboard_rule_department_id">
                                    <option value="">All Departments</option>
                                    <?php foreach ($dashboardDepartmentOptions as $departmentOption): ?>
                                        <option value="<?php echo (int)($departmentOption['id'] ?? 0); ?>" data-faculty-id="<?php echo (int)($departmentOption['faculty_id'] ?? 0); ?>" <?php echo ((int)($dashboardRuleEdit['department_id'] ?? 0) === (int)($departmentOption['id'] ?? 0)) ? 'selected' : ''; ?>>
                                            <?php echo e((string)($departmentOption['name'] ?? 'Department')); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                            </div>

                            <div class="col-md-6">
                                <label class="form-label">Programme</label>
                                <select class="form-select" name="programme_id" id="dashboard_rule_programme_id">
                                    <option value="">All / Default</option>
                                    <?php foreach ($dashboardProgrammeOptions as $programmeOption): ?>
                                        <option value="<?php echo (int)($programmeOption['id'] ?? 0); ?>" data-faculty-id="<?php echo (int)($programmeOption['faculty_id'] ?? 0); ?>" data-department-id="<?php echo (int)($programmeOption['department_id'] ?? 0); ?>" <?php echo ((int)($dashboardRuleEdit['programme_id'] ?? 0) === (int)($programmeOption['id'] ?? 0)) ? 'selected' : ''; ?>>
                                            <?php echo e((string)($programmeOption['name'] ?? 'Programme')); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <div class="form-text">Leave all scope fields blank for a global fallback rule.</div>
                            </div>

                            <div class="col-md-6">
                                <label class="form-label">Mode of Study</label>
                                <select class="form-select" name="mode_of_study" id="dashboard_rule_mode_of_study">
                                    <option value="">All Modes</option>
                                    <?php foreach ($dashboardModeOptions as $modeOption): ?>
                                        <?php $modeOption = trim((string)$modeOption); if ($modeOption === '') continue; ?>
                                        <option value="<?php echo e($modeOption); ?>" <?php echo (strcasecmp((string)($dashboardRuleEdit['mode_of_study'] ?? ''), $modeOption) === 0) ? 'selected' : ''; ?>>
                                            <?php echo e($modeOption); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                            </div>

                            <div class="col-md-6">
                                <label class="form-label">Level</label>
                                <select class="form-select" name="level" id="dashboard_rule_level">
                                    <option value="">All Levels</option>
                                    <?php foreach ($dashboardLevelOptions as $levelOption): ?>
                                        <?php $levelOption = trim((string)$levelOption); if ($levelOption === '') continue; ?>
                                        <option value="<?php echo e($levelOption); ?>" <?php echo ((string)($dashboardRuleEdit['level'] ?? '') === (string)$levelOption) ? 'selected' : ''; ?>>
                                            <?php echo e($levelOption); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                            </div>

                            <div class="col-md-6">
                                <label class="form-label">Template Layout</label>
                                <select class="form-select" name="layout_type" id="dashboard_rule_layout_type">
                                    <?php foreach ($dashboardRuleLayoutOptions as $layoutKey => $layoutLabel): ?>
                                        <option value="<?php echo e($layoutKey); ?>" <?php echo ((string)($dashboardRuleEdit['layout_type'] ?? 'standard') === (string)$layoutKey) ? 'selected' : ''; ?>>
                                            <?php echo e($layoutLabel); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                            </div>


                            <?php
                                $dashboardHeaderColorValue = (string)($dashboardRuleEdit['header_color'] ?? $dashboardRuleDefaults['header_color'] ?? '#003366');
                                $dashboardHeaderTextColorValue = (string)($dashboardRuleEdit['header_text_color'] ?? $dashboardRuleDefaults['header_text_color'] ?? '#FFFFFF');
                            ?>
                            <div class="col-12">
                                <div class="border rounded-4 p-3 bg-white">
                                    <div class="fw-semibold mb-1">Student Dashboard Header Bar Colours</div>
                                    <div class="small text-muted mb-3">Controls only the top STUDENT DASHBOARD title/header strip without affecting the sidebar, welcome card, stat cards, or other dashboard sections.</div>
                                    <div class="row g-3">
                                        <div class="col-md-6">
                                            <label class="form-label" for="dashboard_header_color">Header Background Colour</label>
                                            <div class="input-group" data-dashboard-color-sync="header_color">
                                                <input type="color" class="form-control form-control-color" id="dashboard_header_color_picker" value="<?php echo e($dashboardHeaderColorValue); ?>" title="Header Background Colour">
                                                <input type="text" class="form-control dashboard-rule-color-text" id="dashboard_header_color" name="header_color" value="<?php echo e($dashboardHeaderColorValue); ?>" maxlength="7" pattern="^#?[A-Fa-f0-9]{3,6}$" placeholder="#003366">
                                            </div>
                                            <div class="form-text">Applies to the top student dashboard header bar that contains the "STUDENT DASHBOARD" title and the status badge.</div>
                                        </div>
                                        <div class="col-md-6">
                                            <label class="form-label" for="dashboard_header_text_color">Header Text Colour</label>
                                            <div class="input-group" data-dashboard-color-sync="header_text_color">
                                                <input type="color" class="form-control form-control-color" id="dashboard_header_text_color_picker" value="<?php echo e($dashboardHeaderTextColorValue); ?>" title="Header Text Colour">
                                                <input type="text" class="form-control dashboard-rule-color-text" id="dashboard_header_text_color" name="header_text_color" value="<?php echo e($dashboardHeaderTextColorValue); ?>" maxlength="7" pattern="^#?[A-Fa-f0-9]{3,6}$" placeholder="#FFFFFF">
                                            </div>
                                            <div class="form-text">Controls the text colour for the top "STUDENT DASHBOARD" title/header strip. Default is white.</div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <?php foreach ([
                                'primary_color' => 'Primary Colour',
                                'secondary_color' => 'Secondary Colour',
                                'accent_color' => 'Accent / Highlight Colour',
                                'background_color' => 'Student Dashboard Page Background Colour',
                                'card_background_color' => 'Student Dashboard Card Background Colour',
                                'card_border_color' => 'Student Dashboard Card Border Colour',
                                'card_title_color' => 'Student Dashboard Card Title Colour',
                                'body_text_color' => 'Student Dashboard Card Body Text Colour',
                                'heading_color' => 'Student Dashboard Heading Colour',
                                'muted_text_color' => 'Student Dashboard Muted Text Colour',
                                'header_text_color' => 'Student Header Text Colour',
                                'section_header_color' => 'Student Section / Header Strip Colour',
                                'card_header_color' => 'Student Inner Card Header Colour',
                                'sidebar_color' => 'Student Sidebar Background / Highlight Colour',
                                'sidebar_text_color' => 'Student Sidebar Text Colour',
                                'sidebar_inactive_color' => 'Student Sidebar Inactive Menu Colour',
                                'button_color' => 'Student Button Colour',
                                'button_text_color' => 'Student Button Text Colour',
                                'link_color' => 'Student Link / Highlight Colour',
                                'stat_card_background_color' => 'Dashboard Stat Card Background Colour',
                                'stat_number_color' => 'Dashboard Stat Number Colour',
                                'stat_label_color' => 'Dashboard Stat Label Colour',
                                'tab_active_color' => 'Student Tab Active Colour',
                                'tab_inactive_color' => 'Student Tab Inactive Colour',
                                'tab_text_color' => 'Student Tab Text Colour',
                                'table_header_bg_color' => 'Student Table Header Background Colour',
                                'table_header_text_color' => 'Student Table Header Text Colour',
                                'badge_success_color' => 'Student Badge Success Colour',
                                'badge_warning_color' => 'Student Badge Warning Colour',
                                'badge_danger_color' => 'Student Badge Danger Colour',
                                'alert_info_color' => 'Student Alert / Info Colour',
                            ] as $dashboardColorKey => $dashboardColorLabel): ?>
                                <?php
                                    $dashboardColorValue = (string)($dashboardRuleEdit[$dashboardColorKey] ?? $dashboardRuleDefaults[$dashboardColorKey] ?? '#003366');
                                ?>
                                <div class="col-md-6">
                                    <label class="form-label" for="dashboard_<?php echo e($dashboardColorKey); ?>"><?php echo e($dashboardColorLabel); ?></label>
                                    <div class="input-group" data-dashboard-color-sync="<?php echo e($dashboardColorKey); ?>">
                                        <input type="color" class="form-control form-control-color" id="dashboard_<?php echo e($dashboardColorKey); ?>_picker" value="<?php echo e($dashboardColorValue); ?>" title="<?php echo e($dashboardColorLabel); ?>">
                                        <input type="text" class="form-control dashboard-rule-color-text" id="dashboard_<?php echo e($dashboardColorKey); ?>" name="<?php echo e($dashboardColorKey); ?>" value="<?php echo e($dashboardColorValue); ?>" maxlength="7" pattern="^#?[A-Fa-f0-9]{3,6}$" placeholder="#003366">
                                    </div>
                                </div>
                            <?php endforeach; ?>

                            <div class="col-12">
                                <label class="form-label">Optional Notice Banner Text</label>
                                <input type="text" class="form-control" name="notice_banner_text" maxlength="255" value="<?php echo e((string)($dashboardRuleEdit['notice_banner_text'] ?? '')); ?>" placeholder="Optional dashboard notice text for this scope">
                            </div>

                            <div class="col-12">
                                <div class="border rounded-4 p-3 bg-white">
                                    <div class="fw-semibold mb-2">Optional Visibility Toggles</div>
                                    <div class="row g-2">
                                        <?php foreach ((function_exists('portalDashboardDesignRuleVisibilityFields') ? portalDashboardDesignRuleVisibilityFields() : []) as $toggleKey => $toggleLabel): ?>
                                            <div class="col-md-6">
                                                <div class="form-check">
                                                    <input class="form-check-input" type="checkbox" name="<?php echo e($toggleKey); ?>" id="<?php echo e($toggleKey); ?>" value="1" <?php echo ((int)($dashboardRuleEdit[$toggleKey] ?? 1) === 1) ? 'checked' : ''; ?>>
                                                    <label class="form-check-label" for="<?php echo e($toggleKey); ?>"><?php echo e($toggleLabel); ?></label>
                                                </div>
                                            </div>
                                        <?php endforeach; ?>
                                    </div>
                                    <div class="small text-muted mt-2">These toggles only affect safe dashboard cards already handled by the shared student dashboard page.</div>
                                </div>
                            </div>

                            <div class="col-md-4">
                                <label class="form-label">Sort Order</label>
                                <input type="number" class="form-control" name="sort_order" value="<?php echo (int)($dashboardRuleEdit['sort_order'] ?? 0); ?>">
                            </div>
                            <div class="col-md-4 d-flex align-items-end">
                                <div class="form-check form-switch">
                                    <input class="form-check-input" type="checkbox" name="is_active" id="dashboard_rule_is_active" value="1" <?php echo ((int)($dashboardRuleEdit['is_active'] ?? 1) === 1) ? 'checked' : ''; ?>>
                                    <label class="form-check-label" for="dashboard_rule_is_active">Rule Active</label>
                                </div>
                            </div>
                            <div class="col-md-4 d-flex align-items-end">
                                <div class="form-check form-switch">
                                    <input class="form-check-input" type="checkbox" name="is_default" id="dashboard_rule_is_default" value="1" <?php echo ((int)($dashboardRuleEdit['is_default'] ?? 0) === 1) ? 'checked' : ''; ?>>
                                    <label class="form-check-label" for="dashboard_rule_is_default">Set as Default Fallback</label>
                                </div>
                            </div>

                            <div class="col-12">
                                <div class="border rounded-4 p-3 dashboard-rule-preview-wrap" id="dashboardRulePreview">
                                    <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
                                        <div>
                                            <div class="fw-semibold">Dashboard Rule Preview</div>
                                            <div class="small text-muted">This sample shows the selected colors and layout style before saving.</div>
                                        </div>
                                        <span class="badge rounded-pill px-3 py-2 dashboard-rule-preview-badge">Preview</span>
                                    </div>
                                    <div class="rounded-4 overflow-hidden border">
                                        <div class="dashboard-rule-preview-banner p-3">
                                            <div class="small text-uppercase fw-bold opacity-75">Student Dashboard</div>
                                            <div class="fw-bold fs-5" id="dashboardRulePreviewTitle">Programme + Mode + Level Theme</div>
                                            <div class="small mt-2 opacity-75">Banner / header sample</div>
                                        </div>
                                        <div class="p-3 dashboard-rule-preview-surface">
                                            <ul class="nav dashboard-rule-preview-tabs mb-3 gap-2">
                                                <li class="nav-item"><button type="button" class="nav-link active">Overview</button></li>
                                                <li class="nav-item"><button type="button" class="nav-link">Payments</button></li>
                                            </ul>
                                            <div class="row g-3">
                                                <div class="col-md-4">
                                                    <div class="rounded-4 p-3 dashboard-rule-preview-stat">
                                                        <div class="small opacity-75">Primary Card</div>
                                                        <div class="fs-4 fw-bold">12</div>
                                                    </div>
                                                </div>
                                                <div class="col-md-4">
                                                    <div class="rounded-4 p-3 dashboard-rule-preview-stat dashboard-rule-preview-stat-secondary">
                                                        <div class="small opacity-75">Secondary Card</div>
                                                        <div class="fs-4 fw-bold">24</div>
                                                    </div>
                                                </div>
                                                <div class="col-md-4">
                                                    <div class="rounded-4 p-3 border dashboard-rule-preview-widget">
                                                        <div class="fw-semibold">Widget Card</div>
                                                        <div class="small text-muted">Card header, links, and buttons preview.</div>
                                                        <div class="d-flex flex-wrap gap-2 mt-3">
                                                            <button type="button" class="btn btn-sm dashboard-rule-preview-btn">Action</button>
                                                            <a href="#" class="small fw-semibold dashboard-rule-preview-link" onclick="return false;">Open link</a>
                                                        </div>
                                                    </div>
                                                </div>
                                            </div>
                                            <div class="mt-3 border rounded-4 overflow-hidden dashboard-rule-preview-table-wrap">
                                                <div class="dashboard-rule-preview-card-header px-3 py-2 fw-semibold">Section Header Strip</div>
                                                <div class="p-3">
                                                    <div class="small text-muted mb-2">Layout preview: <span id="dashboardRulePreviewLayoutLabel">Standard Layout</span></div>
                                                    <div class="d-flex flex-wrap gap-2 mb-3">
                                                        <span class="badge rounded-pill dashboard-rule-preview-badge-success">Success Badge</span>
                                                        <span class="badge rounded-pill dashboard-rule-preview-badge-warning">Warning Badge</span>
                                                        <span class="badge rounded-pill dashboard-rule-preview-badge-danger">Danger Badge</span>
                                                    </div>
                                                    <div class="border rounded-3 p-3 mb-3 dashboard-rule-preview-alert">
                                                        <div class="fw-semibold">Info Alert Sample</div>
                                                        <div class="small text-muted">Notice, scoped hint, or dashboard information preview.</div>
                                                    </div>
                                                    <div class="table-responsive">
                                                        <table class="table table-sm mb-0 dashboard-rule-preview-table">
                                                            <thead><tr><th>Column</th><th>Status</th></tr></thead>
                                                            <tbody><tr><td>Student Row</td><td>Ready</td></tr></tbody>
                                                        </table>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <div class="col-12 d-flex flex-wrap gap-2 justify-content-between align-items-center">
                                <div class="small text-muted">Matching priority automatically prefers exact Faculty + Department + Programme + Mode of Study + Level rules, then less specific matches, then the default fallback.</div>
                                <div class="d-flex flex-wrap gap-2">
                                    <button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> <?php echo $dashboardRuleEdit ? 'Update Rule' : 'Save Rule'; ?></button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>

                <div class="tab-pane fade show active" id="dashboardDesignRulesAddPane" role="tabpanel" aria-labelledby="dashboardDesignRulesAddTab" tabindex="0" data-dashboard-rules-pane="add">
                    <div class="border rounded-4 p-3 dashboard-design-rule-pane-card">
                        <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
                            <div>
                                <div class="fw-semibold">Saved Dashboard Rules</div>
                                <div class="small text-muted">Search, review, edit, enable, disable, or delete saved dashboard scope rules.</div>
                            </div>
                            <div class="d-flex gap-2 flex-wrap">
                                <input type="search" class="form-control form-control-sm" id="dashboardRuleFilterInput" placeholder="Filter by faculty, department, programme, mode, level, layout..." style="min-width:260px;">
                            </div>
                        </div>

                        <div class="table-responsive">
                            <table class="table align-middle table-sm" id="dashboardRulesTable">
                                <thead>
                                    <tr>
                                        <th>Rule</th>
                                        <th>Scope</th>
                                        <th>Layout</th>
                                        <th>Colors</th>
                                        <th>Status</th>
                                        <th class="text-end">Actions</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <?php if (!$dashboardRuleRows): ?>
                                        <tr><td colspan="6" class="text-center text-muted py-4">No dashboard design rules have been created yet.</td></tr>
                                    <?php else: ?>
                                        <?php foreach ($dashboardRuleRows as $dashboardRuleRow): ?>
                                            <?php
                                                $dashboardRuleScope = function_exists('portalDashboardDesignRuleScopeText') ? portalDashboardDesignRuleScopeText($pdo, $dashboardRuleRow) : 'Global Default';
                                                $dashboardRuleLayoutLabel = $dashboardRuleLayoutOptions[(string)($dashboardRuleRow['layout_type'] ?? 'standard')] ?? ucfirst(str_replace('-', ' ', (string)($dashboardRuleRow['layout_type'] ?? 'standard')));
                                                $dashboardRuleFilterText = strtolower(trim(($dashboardRuleRow['rule_name'] ?? '') . ' ' . ($dashboardRuleRow['faculty_name'] ?? '') . ' ' . ($dashboardRuleRow['department_name'] ?? '') . ' ' . $dashboardRuleScope . ' ' . $dashboardRuleLayoutLabel));
                                            ?>
                                            <tr data-dashboard-rule-filter="<?php echo e($dashboardRuleFilterText); ?>">
                                                <td>
                                                    <div class="fw-semibold"><?php echo e((string)($dashboardRuleRow['rule_name'] ?? 'Dashboard Rule')); ?></div>
                                                    <div class="small text-muted">
                                                        <?php echo ((int)($dashboardRuleRow['specificity_score'] ?? 0)); ?> pts specificity
                                                        <?php if ((int)($dashboardRuleRow['is_default'] ?? 0) === 1): ?>
                                                            • Default Fallback
                                                        <?php endif; ?>
                                                    </div>
                                                </td>
                                                <td>
                                                    <div><?php echo e($dashboardRuleScope); ?></div>
                                                </td>
                                                <td>
                                                    <span class="badge text-bg-light border"><?php echo e($dashboardRuleLayoutLabel); ?></span>
                                                </td>
                                                <td>
                                                    <div class="d-flex flex-wrap gap-1">
                                                        <?php foreach (['primary_color','accent_color','header_color','header_text_color','button_color'] as $miniColorKey): ?>
                                                            <span class="border rounded-circle d-inline-block" style="width:20px;height:20px;background:<?php echo e((string)($dashboardRuleRow[$miniColorKey] ?? '#003366')); ?>;" title="<?php echo e(ucwords(str_replace('_', ' ', $miniColorKey))); ?>"></span>
                                                        <?php endforeach; ?>
                                                    </div>
                                                </td>
                                                <td>
                                                    <?php if ((int)($dashboardRuleRow['is_active'] ?? 0) === 1): ?>
                                                        <span class="badge text-bg-success">Active</span>
                                                    <?php else: ?>
                                                        <span class="badge text-bg-secondary">Inactive</span>
                                                    <?php endif; ?>
                                                </td>
                                                <td class="text-end">
                                                    <div class="d-flex flex-wrap gap-2 justify-content-end">
                                                        <a href="?page=theme_branding&branding_tab=dashboard_rules&edit_dashboard_rule=<?php echo (int)($dashboardRuleRow['id'] ?? 0); ?>&dashboard_rules_tab=saved#dashboard-design-rules" class="btn btn-outline-primary btn-sm"><i class="bi bi-pencil-square"></i> Edit</a>
                                                        <form method="post" action="" class="d-inline">
                                                            <input type="hidden" name="action" value="toggle_dashboard_design_rule">
                                                            <input type="hidden" name="rule_id" value="<?php echo (int)($dashboardRuleRow['id'] ?? 0); ?>">
                                                            <input type="hidden" name="set_active" value="<?php echo ((int)($dashboardRuleRow['is_active'] ?? 0) === 1) ? 0 : 1; ?>">
                                                            <input type="hidden" name="return_to" value="?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=add#dashboard-design-rules">
                                                            <button type="submit" class="btn btn-outline-secondary btn-sm"><?php echo ((int)($dashboardRuleRow['is_active'] ?? 0) === 1) ? 'Disable' : 'Enable'; ?></button>
                                                        </form>
                                                        <form method="post" action="" class="d-inline" onsubmit="return confirm('Delete this dashboard design rule?');">
                                                            <input type="hidden" name="action" value="delete_dashboard_design_rule">
                                                            <input type="hidden" name="rule_id" value="<?php echo (int)($dashboardRuleRow['id'] ?? 0); ?>">
                                                            <input type="hidden" name="return_to" value="?page=theme_branding&branding_tab=dashboard_rules&dashboard_rules_tab=add#dashboard-design-rules">
                                                            <button type="submit" class="btn btn-outline-danger btn-sm"><i class="bi bi-trash"></i></button>
                                                        </form>
                                                    </div>
                                                </td>
                                            </tr>
                                        <?php endforeach; ?>
                                    <?php endif; ?>
                                </tbody>
                            </table>
                        </div>

                        <div class="small text-muted mt-3">
                            A blank scope acts as the global fallback. Students always stay on the same shared dashboard route; only the student dashboard colours, emphasis, scoped cards, and safe layout style change.
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<style>

#dashboardRulePreview{
    --dash-prev-primary: <?php echo e((string)($dashboardRuleEdit['primary_color'] ?? $dashboardRuleDefaults['primary_color'] ?? '#003366')); ?>;
    --dash-prev-secondary: <?php echo e((string)($dashboardRuleEdit['secondary_color'] ?? $dashboardRuleDefaults['secondary_color'] ?? '#0B5ED7')); ?>;
    --dash-prev-accent: <?php echo e((string)($dashboardRuleEdit['accent_color'] ?? $dashboardRuleDefaults['accent_color'] ?? '#F0A500')); ?>;
    --dash-prev-page-bg: <?php echo e((string)($dashboardRuleEdit['background_color'] ?? $dashboardRuleDefaults['background_color'] ?? '#F4F7F6')); ?>;
    --dash-prev-card-bg: <?php echo e((string)($dashboardRuleEdit['card_background_color'] ?? $dashboardRuleDefaults['card_background_color'] ?? '#FFFFFF')); ?>;
    --dash-prev-card-border: <?php echo e((string)($dashboardRuleEdit['card_border_color'] ?? $dashboardRuleDefaults['card_border_color'] ?? '#D9E2EC')); ?>;
    --dash-prev-card-title: <?php echo e((string)($dashboardRuleEdit['card_title_color'] ?? $dashboardRuleDefaults['card_title_color'] ?? '#003366')); ?>;
    --dash-prev-text: <?php echo e((string)($dashboardRuleEdit['body_text_color'] ?? $dashboardRuleDefaults['body_text_color'] ?? '#333333')); ?>;
    --dash-prev-heading: <?php echo e((string)($dashboardRuleEdit['heading_color'] ?? $dashboardRuleDefaults['heading_color'] ?? '#003366')); ?>;
    --dash-prev-muted: <?php echo e((string)($dashboardRuleEdit['muted_text_color'] ?? $dashboardRuleDefaults['muted_text_color'] ?? '#64748B')); ?>;
    --dash-prev-header: <?php echo e((string)($dashboardRuleEdit['header_color'] ?? $dashboardRuleDefaults['header_color'] ?? '#003366')); ?>;
    --dash-prev-header-text: <?php echo e((string)($dashboardRuleEdit['header_text_color'] ?? $dashboardRuleDefaults['header_text_color'] ?? '#FFFFFF')); ?>;
    --dash-prev-section-header: <?php echo e((string)($dashboardRuleEdit['section_header_color'] ?? $dashboardRuleDefaults['section_header_color'] ?? '#003366')); ?>;
    --dash-prev-card-header: <?php echo e((string)($dashboardRuleEdit['card_header_color'] ?? $dashboardRuleDefaults['card_header_color'] ?? '#003366')); ?>;
    --dash-prev-sidebar-text: <?php echo e((string)($dashboardRuleEdit['sidebar_text_color'] ?? $dashboardRuleDefaults['sidebar_text_color'] ?? '#FFFFFF')); ?>;
    --dash-prev-sidebar-inactive: <?php echo e((string)($dashboardRuleEdit['sidebar_inactive_color'] ?? $dashboardRuleDefaults['sidebar_inactive_color'] ?? '#D9E2EC')); ?>;
    --dash-prev-button: <?php echo e((string)($dashboardRuleEdit['button_color'] ?? $dashboardRuleDefaults['button_color'] ?? '#003366')); ?>;
    --dash-prev-button-text: <?php echo e((string)($dashboardRuleEdit['button_text_color'] ?? $dashboardRuleDefaults['button_text_color'] ?? '#FFFFFF')); ?>;
    --dash-prev-link: <?php echo e((string)($dashboardRuleEdit['link_color'] ?? $dashboardRuleDefaults['link_color'] ?? '#F0A500')); ?>;
    --dash-prev-stat-bg: <?php echo e((string)($dashboardRuleEdit['stat_card_background_color'] ?? $dashboardRuleDefaults['stat_card_background_color'] ?? '#003366')); ?>;
    --dash-prev-stat-number: <?php echo e((string)($dashboardRuleEdit['stat_number_color'] ?? $dashboardRuleDefaults['stat_number_color'] ?? '#FFFFFF')); ?>;
    --dash-prev-stat-label: <?php echo e((string)($dashboardRuleEdit['stat_label_color'] ?? $dashboardRuleDefaults['stat_label_color'] ?? '#FFFFFF')); ?>;
    --dash-prev-tab-active: <?php echo e((string)($dashboardRuleEdit['tab_active_color'] ?? $dashboardRuleDefaults['tab_active_color'] ?? '#003366')); ?>;
    --dash-prev-tab-inactive: <?php echo e((string)($dashboardRuleEdit['tab_inactive_color'] ?? $dashboardRuleDefaults['tab_inactive_color'] ?? '#E8EEF7')); ?>;
    --dash-prev-tab-text: <?php echo e((string)($dashboardRuleEdit['tab_text_color'] ?? $dashboardRuleDefaults['tab_text_color'] ?? '#003366')); ?>;
    --dash-prev-table-header-bg: <?php echo e((string)($dashboardRuleEdit['table_header_bg_color'] ?? $dashboardRuleDefaults['table_header_bg_color'] ?? '#003366')); ?>;
    --dash-prev-table-header-text: <?php echo e((string)($dashboardRuleEdit['table_header_text_color'] ?? $dashboardRuleDefaults['table_header_text_color'] ?? '#FFFFFF')); ?>;
    --dash-prev-badge-success: <?php echo e((string)($dashboardRuleEdit['badge_success_color'] ?? $dashboardRuleDefaults['badge_success_color'] ?? '#198754')); ?>;
    --dash-prev-badge-warning: <?php echo e((string)($dashboardRuleEdit['badge_warning_color'] ?? $dashboardRuleDefaults['badge_warning_color'] ?? '#F59E0B')); ?>;
    --dash-prev-badge-danger: <?php echo e((string)($dashboardRuleEdit['badge_danger_color'] ?? $dashboardRuleDefaults['badge_danger_color'] ?? '#DC3545')); ?>;
    --dash-prev-alert-info: <?php echo e((string)($dashboardRuleEdit['alert_info_color'] ?? $dashboardRuleDefaults['alert_info_color'] ?? '#0B5ED7')); ?>;
}

.dashboard-design-rule-workspace{max-width:100%;}
.dashboard-design-rule-nav{border-bottom:1px solid rgba(13,110,253,.16);gap:.5rem;}
.dashboard-design-rule-nav .nav-link{border:none;border-radius:999px;padding:.7rem 1rem;font-weight:600;color:#334155;background:#f8fafc;}
.dashboard-design-rule-nav .nav-link:hover,.dashboard-design-rule-nav .nav-link:focus{color:#0d3b84;background:#eef4ff;}
.dashboard-design-rule-nav .nav-link.active{color:#fff;background:linear-gradient(135deg,#0b2e6f,#0b5ed7);box-shadow:0 10px 20px rgba(11,94,215,.18);}
.dashboard-design-rule-tab-content{min-height:0;}
.dashboard-design-rule-pane-card{width:100%;}
.dashboard-design-rule-pane-card .table-responsive{margin-bottom:0;}
.dashboard-rule-preview-banner{
    background: linear-gradient(135deg, var(--dash-prev-header), var(--dash-prev-secondary));
    color:var(--dash-prev-header-text);
}
.dashboard-rule-preview-stat{
    background: linear-gradient(135deg, var(--dash-prev-primary), var(--dash-prev-secondary));
    color:#fff;
    min-height:110px;
}
.dashboard-rule-preview-stat-secondary{
    background: linear-gradient(135deg, var(--dash-prev-accent), var(--dash-prev-secondary));
}
.dashboard-rule-preview-card-header{
    background: var(--dash-prev-card-header);
    color:#fff;
}
.dashboard-rule-preview-btn{
    background: var(--dash-prev-button);
    color:#fff;
    border-color: var(--dash-prev-button);
}
.dashboard-rule-preview-link{
    color: var(--dash-prev-link);
    text-decoration:none;
}
#dashboardRulePreview{background:var(--dash-prev-page-bg);color:var(--dash-prev-text);}
#dashboardRulePreview .dashboard-rule-preview-surface{background:var(--dash-prev-page-bg);color:var(--dash-prev-text);}
#dashboardRulePreview .dashboard-rule-preview-widget,
#dashboardRulePreview .dashboard-rule-preview-table-wrap,
#dashboardRulePreview .dashboard-rule-preview-alert{background:var(--dash-prev-card-bg);border-color:var(--dash-prev-card-border) !important;color:var(--dash-prev-text);}
#dashboardRulePreview .dashboard-rule-preview-widget .fw-semibold,
#dashboardRulePreview .dashboard-rule-preview-table-wrap .fw-semibold{color:var(--dash-prev-card-title);}
#dashboardRulePreview .dashboard-rule-preview-widget .text-muted,
#dashboardRulePreview .dashboard-rule-preview-table-wrap .text-muted,
#dashboardRulePreview .dashboard-rule-preview-alert .text-muted{color:var(--dash-prev-muted) !important;}
.dashboard-rule-preview-banner{background:linear-gradient(135deg,var(--dash-prev-header), var(--dash-prev-secondary));color:var(--dash-prev-header-text);}
.dashboard-rule-preview-card-header{background:var(--dash-prev-section-header);color:#fff;}
.dashboard-rule-preview-stat{background:var(--dash-prev-stat-bg);color:var(--dash-prev-stat-number);}
.dashboard-rule-preview-stat .small{color:var(--dash-prev-stat-label) !important;opacity:1 !important;}
.dashboard-rule-preview-btn{background:var(--dash-prev-button);color:var(--dash-prev-button-text);border-color:var(--dash-prev-button);}
.dashboard-rule-preview-tabs .nav-link{border:none;border-radius:999px;padding:.45rem .8rem;background:var(--dash-prev-tab-inactive);color:var(--dash-prev-tab-text);font-weight:600;}
.dashboard-rule-preview-tabs .nav-link.active{background:var(--dash-prev-tab-active);color:#fff;}
.dashboard-rule-preview-badge-success{background:var(--dash-prev-badge-success);color:#fff;}
.dashboard-rule-preview-badge-warning{background:var(--dash-prev-badge-warning);color:#fff;}
.dashboard-rule-preview-badge-danger{background:var(--dash-prev-badge-danger);color:#fff;}
.dashboard-rule-preview-alert{border-left:4px solid var(--dash-prev-alert-info);}
.dashboard-rule-preview-alert .fw-semibold{color:var(--dash-prev-heading);}
.dashboard-rule-preview-table thead th{background:var(--dash-prev-table-header-bg);color:var(--dash-prev-table-header-text);}
@media (max-width: 767.98px){
    .dashboard-design-rule-nav{gap:.35rem;}
    .dashboard-design-rule-nav .nav-link{width:100%;text-align:center;}
}
</style>

<script>
(function(){
    const preview = document.getElementById('dashboardRulePreview');
    const titleEl = document.getElementById('dashboardRulePreviewTitle');
    const layoutLabelEl = document.getElementById('dashboardRulePreviewLayoutLabel');
    const filterInput = document.getElementById('dashboardRuleFilterInput');
    const filterRows = Array.from(document.querySelectorAll('#dashboardRulesTable tbody tr[data-dashboard-rule-filter]'));
    const colorGroups = Array.from(document.querySelectorAll('[data-dashboard-color-sync]'));
    const facultySelect = document.getElementById('dashboard_rule_faculty_id');
    const departmentSelect = document.getElementById('dashboard_rule_department_id');
    const programmeSelect = document.getElementById('dashboard_rule_programme_id');
    const modeSelect = document.getElementById('dashboard_rule_mode_of_study');
    const levelSelect = document.getElementById('dashboard_rule_level');
    const layoutSelect = document.getElementById('dashboard_rule_layout_type');
    const tabsRoot = document.getElementById('dashboardDesignRulesTabs');
    const tabButtons = tabsRoot ? Array.from(tabsRoot.querySelectorAll('[data-dashboard-rules-tab]')) : [];
    const tabStorageKey = 'dashboard_design_rules_active_tab';
    if (!preview) return;
    function safeHex(value, fallback){
        const raw = String(value || '').trim();
        const prefixed = raw.startsWith('#') ? raw : ('#' + raw);
        return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(prefixed) ? prefixed : fallback;
    }
    function textValue(id, fallback){
        const el = document.getElementById(id);
        return safeHex(el ? el.value : fallback, fallback);
    }
    function selectedText(select){
        if (!select || !select.options.length) return '';
        const opt = select.options[select.selectedIndex];
        return opt ? String(opt.text || '').trim() : '';
    }
    function toggleOptionVisibility(option, visible){
        if (!option || option.value === '') return;
        option.hidden = !visible;
        option.disabled = !visible;
        option.style.display = visible ? '' : 'none';
    }
    function syncScopedOptions(){
        const selectedFaculty = facultySelect ? String(facultySelect.value || '') : '';
        const selectedDepartment = departmentSelect ? String(departmentSelect.value || '') : '';
        if (departmentSelect) {
            Array.from(departmentSelect.options).forEach(function(option){
                if (!option.value) return;
                const optionFaculty = String(option.getAttribute('data-faculty-id') || '');
                const visible = !selectedFaculty || !optionFaculty || optionFaculty === selectedFaculty;
                toggleOptionVisibility(option, visible);
            });
            const current = departmentSelect.options[departmentSelect.selectedIndex] || null;
            if (current && current.hidden) departmentSelect.value = '';
        }
        if (programmeSelect) {
            Array.from(programmeSelect.options).forEach(function(option){
                if (!option.value) return;
                const optionFaculty = String(option.getAttribute('data-faculty-id') || '');
                const optionDepartment = String(option.getAttribute('data-department-id') || '');
                const facultyOk = !selectedFaculty || !optionFaculty || optionFaculty === selectedFaculty;
                const departmentOk = !selectedDepartment || !optionDepartment || optionDepartment === selectedDepartment;
                toggleOptionVisibility(option, facultyOk && departmentOk);
            });
            const current = programmeSelect.options[programmeSelect.selectedIndex] || null;
            if (current && current.hidden) programmeSelect.value = '';
        }
    }
    function autoFillParentsFromSelection(source){
        const selectedOption = source && source.options ? source.options[source.selectedIndex] : null;
        if (!selectedOption) return;
        const facultyId = String(selectedOption.getAttribute('data-faculty-id') || '');
        const departmentId = String(selectedOption.getAttribute('data-department-id') || '');
        if (source === departmentSelect && facultySelect && facultyId) facultySelect.value = facultyId;
        if (source === programmeSelect) {
            if (facultySelect && facultyId) facultySelect.value = facultyId;
            if (departmentSelect && departmentId) departmentSelect.value = departmentId;
        }
    }
    function syncColorGroups(){
        colorGroups.forEach(function(group){
            const picker = group.querySelector('input[type="color"]');
            const text = group.querySelector('input[type="text"]');
            if (!picker || !text) return;
            picker.addEventListener('input', function(){ text.value = picker.value.toUpperCase(); updatePreview(); });
            text.addEventListener('input', function(){
                const normalized = safeHex(text.value, picker.value || '#003366');
                if (/^#([0-9a-fA-F]{6})$/.test(normalized)) picker.value = normalized;
                updatePreview();
            });
        });
    }
    function updatePreview(){
        const vars = {
            '--dash-prev-primary': textValue('dashboard_primary_color', '#003366'),
            '--dash-prev-secondary': textValue('dashboard_secondary_color', '#0B5ED7'),
            '--dash-prev-accent': textValue('dashboard_accent_color', '#F0A500'),
            '--dash-prev-page-bg': textValue('dashboard_background_color', '#F4F7F6'),
            '--dash-prev-card-bg': textValue('dashboard_card_background_color', '#FFFFFF'),
            '--dash-prev-card-border': textValue('dashboard_card_border_color', '#D9E2EC'),
            '--dash-prev-card-title': textValue('dashboard_card_title_color', '#003366'),
            '--dash-prev-text': textValue('dashboard_body_text_color', '#333333'),
            '--dash-prev-heading': textValue('dashboard_heading_color', '#003366'),
            '--dash-prev-muted': textValue('dashboard_muted_text_color', '#64748B'),
            '--dash-prev-header': textValue('dashboard_header_color', '#003366'),
            '--dash-prev-header-text': textValue('dashboard_header_text_color', '#FFFFFF'),
            '--dash-prev-section-header': textValue('dashboard_section_header_color', '#003366'),
            '--dash-prev-card-header': textValue('dashboard_card_header_color', '#003366'),
            '--dash-prev-sidebar-text': textValue('dashboard_sidebar_text_color', '#FFFFFF'),
            '--dash-prev-sidebar-inactive': textValue('dashboard_sidebar_inactive_color', '#D9E2EC'),
            '--dash-prev-button': textValue('dashboard_button_color', '#003366'),
            '--dash-prev-button-text': textValue('dashboard_button_text_color', '#FFFFFF'),
            '--dash-prev-link': textValue('dashboard_link_color', '#F0A500'),
            '--dash-prev-stat-bg': textValue('dashboard_stat_card_background_color', '#003366'),
            '--dash-prev-stat-number': textValue('dashboard_stat_number_color', '#FFFFFF'),
            '--dash-prev-stat-label': textValue('dashboard_stat_label_color', '#FFFFFF'),
            '--dash-prev-tab-active': textValue('dashboard_tab_active_color', '#003366'),
            '--dash-prev-tab-inactive': textValue('dashboard_tab_inactive_color', '#E8EEF7'),
            '--dash-prev-tab-text': textValue('dashboard_tab_text_color', '#003366'),
            '--dash-prev-table-header-bg': textValue('dashboard_table_header_bg_color', '#003366'),
            '--dash-prev-table-header-text': textValue('dashboard_table_header_text_color', '#FFFFFF'),
            '--dash-prev-badge-success': textValue('dashboard_badge_success_color', '#198754'),
            '--dash-prev-badge-warning': textValue('dashboard_badge_warning_color', '#F59E0B'),
            '--dash-prev-badge-danger': textValue('dashboard_badge_danger_color', '#DC3545'),
            '--dash-prev-alert-info': textValue('dashboard_alert_info_color', '#0B5ED7')
        };
        Object.entries(vars).forEach(([key, value]) => preview.style.setProperty(key, value));
        preview.style.background = vars['--dash-prev-page-bg'];
        const scopeParts = [];
        if (facultySelect && facultySelect.value) scopeParts.push(selectedText(facultySelect));
        if (departmentSelect && departmentSelect.value) scopeParts.push(selectedText(departmentSelect));
        if (programmeSelect && programmeSelect.value) scopeParts.push(selectedText(programmeSelect));
        if (modeSelect && modeSelect.value) scopeParts.push(selectedText(modeSelect));
        if (levelSelect && levelSelect.value) scopeParts.push(selectedText(levelSelect));
        titleEl.textContent = scopeParts.length ? scopeParts.join(' + ') : 'Global Default Dashboard';
        if (layoutLabelEl && layoutSelect) layoutLabelEl.textContent = selectedText(layoutSelect);
    }
    function activateTab(tabName){
        const targetButton = tabButtons.find(function(button){
            return String(button.getAttribute('data-dashboard-rules-tab') || '') === String(tabName || '');
        }) || tabButtons[0] || null;
        if (!targetButton) return;
        if (window.bootstrap && bootstrap.Tab) {
            bootstrap.Tab.getOrCreateInstance(targetButton).show();
        } else {
            tabButtons.forEach(function(button){
                const isActive = button === targetButton;
                button.classList.toggle('active', isActive);
                button.setAttribute('aria-selected', isActive ? 'true' : 'false');
                const selector = button.getAttribute('data-bs-target');
                const pane = selector ? document.querySelector(selector) : null;
                if (pane) {
                    pane.classList.toggle('show', isActive);
                    pane.classList.toggle('active', isActive);
                }
            });
        }
    }
    function resolveInitialTab(){
        try {
            const params = new URLSearchParams(window.location.search || '');
            const requested = String(params.get('dashboard_rules_tab') || '').trim().toLowerCase();
            if (requested === 'saved' || requested === 'add') return requested;
        } catch (err) {}
        try {
            const stored = String(window.localStorage.getItem(tabStorageKey) || '').trim().toLowerCase();
            if (stored === 'saved' || stored === 'add') return stored;
        } catch (err) {}
        return 'add';
    }
    if (filterInput) {
        filterInput.addEventListener('input', function(){
            const term = String(filterInput.value || '').trim().toLowerCase();
            filterRows.forEach(function(row){
                const hay = String(row.getAttribute('data-dashboard-rule-filter') || '');
                row.style.display = !term || hay.indexOf(term) !== -1 ? '' : 'none';
            });
        });
    }
    [facultySelect, departmentSelect, programmeSelect, modeSelect, levelSelect, layoutSelect].forEach(function(el){
        if (!el) return;
        el.addEventListener('change', function(){
            autoFillParentsFromSelection(el);
            syncScopedOptions();
            updatePreview();
        });
    });
    tabButtons.forEach(function(button){
        button.addEventListener('shown.bs.tab', function(){
            const tabName = String(button.getAttribute('data-dashboard-rules-tab') || '').trim().toLowerCase();
            if (!tabName) return;
            try { window.localStorage.setItem(tabStorageKey, tabName); } catch (err) {}
        });
        button.addEventListener('click', function(){
            const tabName = String(button.getAttribute('data-dashboard-rules-tab') || '').trim().toLowerCase();
            if (!tabName) return;
            try { window.localStorage.setItem(tabStorageKey, tabName); } catch (err) {}
        });
    });
    syncColorGroups();
    syncScopedOptions();
    updatePreview();
    activateTab(resolveInitialTab());
})();
</script>
        </div>
    </div>
</div>

<style>
.theme-branding-main-nav{border-bottom:1px solid rgba(13,110,253,.16);gap:.5rem;}
.theme-branding-main-nav .nav-link{border:none;border-radius:999px;padding:.75rem 1rem;font-weight:600;color:#334155;background:#f8fafc;}
.theme-branding-main-nav .nav-link:hover,.theme-branding-main-nav .nav-link:focus{color:#0d3b84;background:#eef4ff;}
.theme-branding-main-nav .nav-link.active{color:#fff;background:linear-gradient(135deg,#0b2e6f,#0b5ed7);box-shadow:0 10px 20px rgba(11,94,215,.18);}
@media (max-width: 767.98px){
    .theme-branding-main-nav{gap:.35rem;}
    .theme-branding-main-nav .nav-link{width:100%;text-align:center;}
}
</style>

<script>
(function(){
    const tabsRoot = document.getElementById('themeBrandingMainTabs');
    const tabButtons = tabsRoot ? Array.from(tabsRoot.querySelectorAll('[data-theme-branding-tab]')) : [];
    const storageKey = 'theme_branding_main_active_tab';
    if (!tabButtons.length) return;

    function activateTab(tabName){
        const targetButton = tabButtons.find(function(button){
            return String(button.getAttribute('data-theme-branding-tab') || '') === String(tabName || '');
        }) || tabButtons[0] || null;
        if (!targetButton) return;
        if (window.bootstrap && bootstrap.Tab) {
            bootstrap.Tab.getOrCreateInstance(targetButton).show();
        } else {
            tabButtons.forEach(function(button){
                const isActive = button === targetButton;
                button.classList.toggle('active', isActive);
                button.setAttribute('aria-selected', isActive ? 'true' : 'false');
                const selector = button.getAttribute('data-bs-target');
                const pane = selector ? document.querySelector(selector) : null;
                if (pane) {
                    pane.classList.toggle('show', isActive);
                    pane.classList.toggle('active', isActive);
                }
            });
        }
    }
    function persistTab(tabName){
        if (!tabName) return;
        try { window.localStorage.setItem(storageKey, tabName); } catch (err) {}
        try {
            const url = new URL(window.location.href);
            url.searchParams.set('branding_tab', tabName);
            window.history.replaceState({}, '', url.toString());
        } catch (err) {}
    }
    function resolveInitialTab(){
        const allowedTabs = ['admin_theme', 'dashboard_rules'];
        try {
            const params = new URLSearchParams(window.location.search || '');
            const requested = String(params.get('branding_tab') || '').trim().toLowerCase();
            if (allowedTabs.indexOf(requested) !== -1) return requested;
        } catch (err) {}
        try {
            const stored = String(window.localStorage.getItem(storageKey) || '').trim().toLowerCase();
            if (allowedTabs.indexOf(stored) !== -1) return stored;
        } catch (err) {}
        return '<?php echo e($themeBrandingWorkspaceTab); ?>';
    }
    tabButtons.forEach(function(button){
        button.addEventListener('shown.bs.tab', function(){
            persistTab(String(button.getAttribute('data-theme-branding-tab') || '').trim().toLowerCase());
        });
        button.addEventListener('click', function(){
            persistTab(String(button.getAttribute('data-theme-branding-tab') || '').trim().toLowerCase());
        });
    });
    activateTab(resolveInitialTab());
})();
</script>

                        </div>
                    </div>
<?php
                } else {
                    redirect('?page=login');
                }
                break;


            case 'notifications':
                if (isset($_SESSION['user_type']) && in_array($_SESSION['user_type'], ['admin', 'student'], true)) {
                    $role = currentUserRole();
                    $uid  = currentUserId();

                    // Housekeeping: auto-delete archived notifications older than 1 month
                    try { cleanupOldNotifications($pdo); } catch (Throwable $e) {}

                    $unread = [];
                    $archived = [];

                    // Fetch recent notifications (safe helper). Then split into unread + archived.
                    $allNotifications = getNotificationsForUser($pdo, $role, $uid, 200);
                    if (!empty($allNotifications)) {
                        foreach ($allNotifications as $n) {
                            $isArchived = !empty($n['archived']) && (int)$n['archived'] === 1;
                            $isRead = !empty($n['is_read']) && (int)$n['is_read'] === 1;
                            if ($isArchived) {
                                $archived[] = $n;
                            } elseif (!$isRead) {
                                $unread[] = $n;
                            }
                        }
                    }
                    ?>
                    <div class="card">
                        <div class="card-header d-flex align-items-center justify-content-between">
                            <h5 class="mb-0"><i class="bi bi-bell"></i> Notifications</h5>
                            <span class="badge bg-primary"><?php echo count($unread); ?> unread</span>
                        </div>
                        <div class="card-body">
                            <h6 class="mb-3">Unread</h6>
                            <?php if (empty($unread)): ?>
                                <div class="alert alert-info">No unread notifications.</div>
                            <?php else: ?>
                                <div class="list-group">
                                    <?php foreach ($unread as $n): ?>
                                        <a href="?page=view_notification&id=<?php echo (int)($n['id'] ?? 0); ?>" class="list-group-item list-group-item-action">
                                            <div class="d-flex w-100 justify-content-between">
                                                <h6 class="mb-1"><?php echo htmlspecialchars($n['title'] ?? 'Notification'); ?></h6>
                                                <small class="text-muted"><?php echo htmlspecialchars($n['created_at'] ?? ''); ?></small>
                                            </div>
                                            <?php if (!empty($n['body'])): ?>
                                                <p class="mb-1 text-muted"><?php echo htmlspecialchars($n['body']); ?></p>
                                            <?php endif; ?>
                                        </a>
                                    <?php endforeach; ?>
                                </div>
                            <?php endif; ?>

                            <hr>

                            <h6 class="mb-3">Archived</h6>
                            <?php if (empty($archived)): ?>
                                <div class="alert alert-secondary">No archived notifications.</div>
                            <?php else: ?>
                                <div class="list-group">
                                    <?php foreach ($archived as $n): ?>
                                        <div class="list-group-item">
                                            <div class="d-flex w-100 justify-content-between">
                                                <h6 class="mb-1"><?php echo htmlspecialchars($n['title'] ?? 'Notification'); ?></h6>
                                                <small class="text-muted"><?php echo htmlspecialchars($n['created_at'] ?? ''); ?></small>
                                            </div>
                                            <?php if (!empty($n['body'])): ?>
                                                <p class="mb-1 text-muted"><?php echo htmlspecialchars($n['body']); ?></p>
                                            <?php endif; ?>
                                        </div>
                                    <?php endforeach; ?>
                                </div>
                                <p class="text-muted small mt-2">Archived notifications are automatically deleted after 1 month.</p>
                            <?php endif; ?>
                        </div>
                    </div>
                    <?php
                } else {
                    redirect('?page=home');
                }
                break;


            case 'view_notification':
                if (isset($_SESSION['user_type']) && in_array($_SESSION['user_type'], ['admin','student'], true)) {
                    $role = currentUserRole();
                    $uid  = currentUserId();
                    $nid  = (int)($_GET['id'] ?? 0);

                    if ($nid > 0) {
                        $stmt = $pdo->prepare("SELECT * FROM notifications WHERE id = ? AND user_role = ? AND user_id = ?");
                        $stmt->execute([$nid, $role, $uid]);
                        $n = $stmt->fetch(PDO::FETCH_ASSOC);

                        if ($n) {
                            markNotificationReadAndArchive($pdo, $nid, $role, $uid);
                            if (!empty($n['link'])) {
                                redirect($n['link']);
                            }
                        }
                    }
                    redirect('?page=notifications');
                } else {
                    redirect('?page=home');
                }
                break;

            case 'messages':
                if (isset($_SESSION['user_type']) && in_array($_SESSION['user_type'], ['admin','student'], true)) {
                    $role = currentUserRole();
                    $uid  = currentUserId();
                    $view = $_GET['view'] ?? 'inbox';

                    ?>
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header d-flex align-items-center justify-content-between">
                                    <h5 class="mb-0"><i class="bi bi-chat-dots"></i> Messages</h5>
                                    <div class="d-flex gap-2">
                                        <a class="btn btn-outline-primary btn-sm" href="?page=messages&view=inbox">Inbox</a>
                                        <a class="btn btn-outline-secondary btn-sm" href="?page=messages&view=sent">Sent</a>
                                        <a class="btn btn-primary btn-sm" href="?page=messages&view=compose"><i class="bi bi-pencil-square"></i> Compose</a>
                                    </div>
                                </div>
                                <div class="card-body">

                                    <?php if (!empty($_SESSION['message_sent'])): ?>
                                        <div class="alert alert-success">Message sent successfully.</div>
                                        <?php unset($_SESSION['message_sent']); ?>
                                    <?php endif; ?>

                                    <?php if ($view === 'compose'): ?>
                                        <form method="post">
                                            <input type="hidden" name="send_message" value="1">
                                            <?php if ($role === 'admin'): ?>
                                                <div class="mb-3">
                                                    <label class="form-label">Recipient</label>
                                                    <select class="form-select" name="recipient_mode" id="recipient_mode" onchange="toggleStudentSelect()">
                                                        <option value="student">Single Student</option>
                                                        <option value="all_students">All Students</option>
                                                    </select>
                                                </div>
                                                <div class="mb-3" id="student_select_wrap">
                                                    <label class="form-label">Select Student</label>
                                                    <select class="form-select" name="student_id">
                                                        <?php
                                                            $students = $pdo->query("SELECT id, registration_no, surname, first_name, other_name, CONCAT(surname, ', ', first_name, IF(other_name IS NULL OR other_name = '', '', CONCAT(' ', other_name))) AS full_name FROM students ORDER BY surname ASC, first_name ASC, other_name ASC")->fetchAll(PDO::FETCH_ASSOC);
                                                            foreach ($students as $s):
                                                        ?>
                                                            <option value="<?php echo (int)$s['id']; ?>">
                                                                <?php echo htmlspecialchars(($s['full_name'] ?? 'Student') . " (" . ($s['registration_no'] ?? '') . ")"); ?>
                                                            </option>
                                                        <?php endforeach; ?>
                                                    </select>
                                                </div>
                                            <?php else: ?>
                                                <div class="alert alert-info">
                                                    Your message will be delivered to the admin(s).
                                                </div>
                                            <?php endif; ?>

                                            <div class="mb-3">
                                                <label class="form-label">Subject</label>
                                                <input type="text" class="form-control" name="subject" required>
                                            </div>

                                            <div class="mb-3">
                                                <label class="form-label">Message</label>
                                                <textarea class="form-control" name="body" rows="6" required></textarea>
                                            </div>

                                            <button class="btn btn-success" type="submit"><i class="bi bi-send"></i> Send</button>
                                        </form>

                                        <script>
                                            function toggleStudentSelect() {
                                                const mode = document.getElementById('recipient_mode');
                                                const wrap = document.getElementById('student_select_wrap');
                                                if (!mode || !wrap) return;
                                                wrap.style.display = (mode.value === 'student') ? 'block' : 'none';
                                            }
                                            toggleStudentSelect();
                                        </script>

                                    <?php elseif ($view === 'sent'): ?>
                                        <?php
                                            $stmt = $pdo->prepare("SELECT * FROM messages WHERE sender_role = ? AND sender_id = ? ORDER BY created_at DESC LIMIT 100");
                                            $stmt->execute([$role, $uid]);
                                            $sent = $stmt->fetchAll(PDO::FETCH_ASSOC);
                                        ?>
                                        <?php if (empty($sent)): ?>
                                            <div class="alert alert-info">No sent messages.</div>
                                        <?php else: ?>
                                            <div class="list-group">
                                                <?php foreach ($sent as $m): ?>
                                                    <a class="list-group-item list-group-item-action" href="?page=view_message&id=<?php echo (int)$m['id']; ?>">
                                                        <div class="d-flex w-100 justify-content-between">
                                                            <h6 class="mb-1"><?php echo htmlspecialchars($m['subject']); ?></h6>
                                                            <small><?php echo date('d M Y, g:i A', strtotime($m['created_at'])); ?></small>
                                                        </div>
                                                        <p class="mb-1 text-muted"><?php echo htmlspecialchars(substr($m['body'], 0, 120)); ?>...</p>
                                                    </a>
                                                <?php endforeach; ?>
                                            </div>
                                        <?php endif; ?>

                                    <?php else: ?>
                                        <?php
                                            $stmt = $pdo->prepare("SELECT m.*, mr.is_read, mr.read_at
                                                                  FROM message_recipients mr
                                                                  JOIN messages m ON m.id = mr.message_id
                                                                  WHERE mr.recipient_role = ? AND mr.recipient_id = ?
                                                                  ORDER BY m.created_at DESC
                                                                  LIMIT 100");
                                            $stmt->execute([$role, $uid]);
                                            $inbox = $stmt->fetchAll(PDO::FETCH_ASSOC);
                                        ?>
                                        <?php if (empty($inbox)): ?>
                                            <div class="alert alert-info">No messages in your inbox.</div>
                                        <?php else: ?>
                                            <div class="list-group">
                                                <?php foreach ($inbox as $m): ?>
                                                    <a class="list-group-item list-group-item-action" href="?page=view_message&id=<?php echo (int)$m['id']; ?>">
                                                        <div class="d-flex w-100 justify-content-between">
                                                            <h6 class="mb-1">
                                                                <?php echo htmlspecialchars($m['subject']); ?>
                                                                <?php if ((int)$m['is_read'] === 0): ?>
                                                                    <span class="badge bg-danger ms-2">New</span>
                                                                <?php endif; ?>
                                                            </h6>
                                                            <small><?php echo date('d M Y, g:i A', strtotime($m['created_at'])); ?></small>
                                                        </div>
                                                        <p class="mb-1 text-muted"><?php echo htmlspecialchars(substr($m['body'], 0, 120)); ?>...</p>
                                                    </a>
                                                <?php endforeach; ?>
                                            </div>
                                        <?php endif; ?>
                                    <?php endif; ?>

                                </div>
                            </div>
                        </div>
                    </div>
                    <?php
                } else {
                    redirect('?page=home');
                }
                break;

            case 'view_message':
                if (isset($_SESSION['user_type']) && in_array($_SESSION['user_type'], ['admin','student'], true)) {
                    $role = currentUserRole();
                    $uid  = currentUserId();
                    $mid  = (int)($_GET['id'] ?? 0);

                    if ($mid <= 0) redirect('?page=messages');

                    // Access check: recipient OR sender
                    $stmt = $pdo->prepare("SELECT * FROM messages WHERE id = ?");
                    $stmt->execute([$mid]);
                    $msg = $stmt->fetch(PDO::FETCH_ASSOC);
                    if (!$msg) redirect('?page=messages');

                    $isSender = ($msg['sender_role'] === $role && (int)$msg['sender_id'] === (int)$uid);

                    $stmt = $pdo->prepare("SELECT * FROM message_recipients WHERE message_id = ? AND recipient_role = ? AND recipient_id = ?");
                    $stmt->execute([$mid, $role, $uid]);
                    $rec = $stmt->fetch(PDO::FETCH_ASSOC);

                    if (!$isSender && !$rec) {
                        redirect('?page=messages');
                    }

                    if ($rec && (int)$rec['is_read'] === 0) {
                        $stmt = $pdo->prepare("UPDATE message_recipients SET is_read = 1, read_at = NOW() WHERE id = ?");
                        $stmt->execute([(int)$rec['id']]);

                        // Archive any notification pointing to this message
                        try {
                            $stmt = $pdo->prepare("UPDATE notifications
                                                   SET is_read = 1, read_at = NOW(), archived = 1, archived_at = NOW()
                                                   WHERE user_role = ? AND user_id = ? AND link = ? AND is_read = 0");
                            $stmt->execute([$role, $uid, "?page=view_message&id=".$mid]);
                        } catch (Exception $e) {}
                    }

                    ?>
                    <div class="card">
                        <div class="card-header d-flex align-items-center justify-content-between">
                            <h5 class="mb-0"><i class="bi bi-envelope-open"></i> <?php echo htmlspecialchars($msg['subject']); ?></h5>
                            <a class="btn btn-outline-secondary btn-sm" href="?page=messages"><i class="bi bi-arrow-left"></i> Back</a>
                        </div>
                        <div class="card-body">
                            <p class="text-muted mb-2">
                                <strong>From:</strong> <?php echo htmlspecialchars($msg['sender_role']); ?> |
                                <strong>Date:</strong> <?php echo date('d M Y, g:i A', strtotime($msg['created_at'])); ?>
                            </p>
                            <hr>
                            <div style="white-space: pre-wrap;"><?php echo htmlspecialchars($msg['body']); ?></div>
                        </div>
                    </div>
                    <?php
                } else {
                    redirect('?page=home');
                }
                break;



case 'admin_structure_manager':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    $tab = (string)($_GET['tab'] ?? 'faculties');
    if (!in_array($tab, ['faculties','departments','programmes'], true)) $tab = 'faculties';

    portalEnsureProgrammeModeOfStudySupport($pdo);

    // Load dropdown data
    $faculties = [];
    $departments = [];
    try { $faculties = $pdo->query("SELECT * FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) {}
    try { $departments = $pdo->query("SELECT d.*, f.name AS faculty_name FROM departments d LEFT JOIN faculties f ON f.id=d.faculty_id ORDER BY f.name ASC, d.name ASC")->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) {}
    $programmes = [];
    try {
        $programmes = $pdo->query("SELECT p.*, f.name AS faculty_name, d.name AS department_name
                                   FROM programmes p
                                   LEFT JOIN faculties f ON f.id=p.faculty_id
                                   LEFT JOIN departments d ON d.id=p.department_id
                                   ORDER BY f.name ASC, d.name ASC, p.name ASC")->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable $e) {}

    $structureSanitizeText = static function($value): string {
        $value = trim((string)$value);
        return preg_replace('/\s+/u', ' ', $value) ?: $value;
    };
    $structureBuildQuery = static function(array $params): string {
        $clean = [];
        foreach ($params as $key => $value) {
            if ($value === null) continue;
            if (is_string($value)) {
                $value = trim($value);
                if ($value == '') continue;
            }
            $clean[$key] = $value;
        }
        return http_build_query($clean);
    };

    $facultyFilterName = $structureSanitizeText($_GET['faculty_search_name'] ?? '');
    $facultyFilterCode = $structureSanitizeText($_GET['faculty_search_code'] ?? '');
    $facultyFilterStatus = trim((string)($_GET['faculty_search_status'] ?? ''));
    if (!in_array($facultyFilterStatus, ['active', 'inactive'], true)) {
        $facultyFilterStatus = '';
    }
    $facultySummary = ['total' => count($faculties), 'active' => 0, 'inactive' => 0];
    foreach ($faculties as $facultyRow) {
        if ((int)($facultyRow['is_active'] ?? 1) === 1) {
            $facultySummary['active']++;
        } else {
            $facultySummary['inactive']++;
        }
    }
    $filteredFaculties = array_values(array_filter($faculties, static function(array $row) use ($facultyFilterName, $facultyFilterCode, $facultyFilterStatus): bool {
        if ($facultyFilterName !== '' && stripos((string)($row['name'] ?? ''), $facultyFilterName) === false) {
            return false;
        }
        if ($facultyFilterCode !== '' && stripos((string)($row['code'] ?? ''), $facultyFilterCode) === false) {
            return false;
        }
        if ($facultyFilterStatus !== '') {
            $rowStatus = ((int)($row['is_active'] ?? 1) === 1) ? 'active' : 'inactive';
            if ($rowStatus !== $facultyFilterStatus) {
                return false;
            }
        }
        return true;
    }));
    $facultyFiltersApplied = ($facultyFilterName !== '' || $facultyFilterCode !== '' || $facultyFilterStatus !== '');
    $facultyReturnQuery = $structureBuildQuery([
        'page' => 'admin_structure_manager',
        'tab' => 'faculties',
        'subtab' => 'manage_faculties',
        'faculty_search_name' => $facultyFilterName,
        'faculty_search_code' => $facultyFilterCode,
        'faculty_search_status' => $facultyFilterStatus,
    ]);
    $facultyResetUrl = '?page=admin_structure_manager&tab=faculties&subtab=manage_faculties';

    $facultyIds = array_map(static function(array $row): int {
        return (int)($row['id'] ?? 0);
    }, $faculties);

    $departmentFilterName = $structureSanitizeText($_GET['department_search_name'] ?? '');
    $departmentFilterCode = $structureSanitizeText($_GET['department_search_code'] ?? '');
    $departmentFilterFacultyId = max(0, (int)($_GET['department_filter_faculty_id'] ?? 0));
    $departmentFilterStatus = trim((string)($_GET['department_filter_status'] ?? ''));
    if (!in_array($departmentFilterStatus, ['active', 'inactive'], true)) {
        $departmentFilterStatus = '';
    }
    if ($departmentFilterFacultyId > 0 && !in_array($departmentFilterFacultyId, $facultyIds, true)) {
        $departmentFilterFacultyId = 0;
    }
    $departmentSummary = ['total' => count($departments), 'active' => 0, 'inactive' => 0];
    foreach ($departments as $departmentRow) {
        if ((int)($departmentRow['is_active'] ?? 1) === 1) {
            $departmentSummary['active']++;
        } else {
            $departmentSummary['inactive']++;
        }
    }
    $filteredDepartments = array_values(array_filter($departments, static function(array $row) use ($departmentFilterName, $departmentFilterCode, $departmentFilterFacultyId, $departmentFilterStatus): bool {
        if ($departmentFilterName !== '' && stripos((string)($row['name'] ?? ''), $departmentFilterName) === false) {
            return false;
        }
        if ($departmentFilterCode !== '' && stripos((string)($row['code'] ?? ''), $departmentFilterCode) === false) {
            return false;
        }
        if ($departmentFilterFacultyId > 0 && (int)($row['faculty_id'] ?? 0) !== $departmentFilterFacultyId) {
            return false;
        }
        if ($departmentFilterStatus !== '') {
            $rowStatus = ((int)($row['is_active'] ?? 1) === 1) ? 'active' : 'inactive';
            if ($rowStatus !== $departmentFilterStatus) {
                return false;
            }
        }
        return true;
    }));
    $departmentFiltersApplied = ($departmentFilterName !== '' || $departmentFilterCode !== '' || $departmentFilterFacultyId > 0 || $departmentFilterStatus !== '');
    $departmentReturnQuery = $structureBuildQuery([
        'page' => 'admin_structure_manager',
        'tab' => 'departments',
        'subtab' => 'manage_department',
        'department_search_name' => $departmentFilterName,
        'department_search_code' => $departmentFilterCode,
        'department_filter_faculty_id' => $departmentFilterFacultyId > 0 ? $departmentFilterFacultyId : null,
        'department_filter_status' => $departmentFilterStatus,
    ]);
    $departmentResetUrl = '?page=admin_structure_manager&tab=departments&subtab=manage_department';

    $programmeFilterName = $structureSanitizeText($_GET['programme_search_name'] ?? '');
    $programmeFilterCode = $structureSanitizeText($_GET['programme_search_code'] ?? '');
    $programmeFilterFacultyId = max(0, (int)($_GET['programme_filter_faculty_id'] ?? 0));
    $programmeFilterDepartmentId = max(0, (int)($_GET['programme_filter_department_id'] ?? 0));
    $programmeFilterMode = $structureSanitizeText($_GET['programme_filter_mode'] ?? '');
    $programmeFilterStatus = trim((string)($_GET['programme_filter_status'] ?? ''));
    if (!in_array($programmeFilterStatus, ['active', 'inactive'], true)) {
        $programmeFilterStatus = '';
    }

    $facultyIds = array_map(static function(array $row): int {
        return (int)($row['id'] ?? 0);
    }, $faculties);
    if ($programmeFilterFacultyId > 0 && !in_array($programmeFilterFacultyId, $facultyIds, true)) {
        $programmeFilterFacultyId = 0;
    }

    $programmeFilterDepartments = array_values(array_filter($departments, static function(array $row) use ($programmeFilterFacultyId): bool {
        return $programmeFilterFacultyId <= 0 || (int)($row['faculty_id'] ?? 0) === $programmeFilterFacultyId;
    }));
    if ($programmeFilterDepartmentId > 0) {
        $departmentFound = false;
        foreach ($programmeFilterDepartments as $departmentRow) {
            if ((int)($departmentRow['id'] ?? 0) === $programmeFilterDepartmentId) {
                $departmentFound = true;
                break;
            }
        }
        if (!$departmentFound) {
            $programmeFilterDepartmentId = 0;
        }
    }

    $programmeModeOptions = ['Full-Time' => 'Full-Time', 'Part-Time' => 'Part-Time'];
    foreach ($programmes as $programmeRow) {
        $modeLabel = trim((string)portalProgrammeModeOfStudyDisplay((array)$programmeRow));
        if ($modeLabel !== '') {
            $programmeModeOptions[$modeLabel] = $modeLabel;
        }
    }
    natcasesort($programmeModeOptions);

    $programmeSummary = ['total' => count($programmes), 'active' => 0, 'inactive' => 0];
    foreach ($programmes as $programmeRow) {
        if ((int)($programmeRow['is_active'] ?? 1) === 1) {
            $programmeSummary['active']++;
        } else {
            $programmeSummary['inactive']++;
        }
    }
    $filteredProgrammes = array_values(array_filter($programmes, static function(array $row) use ($programmeFilterName, $programmeFilterCode, $programmeFilterFacultyId, $programmeFilterDepartmentId, $programmeFilterMode, $programmeFilterStatus): bool {
        if ($programmeFilterName !== '' && stripos((string)($row['name'] ?? ''), $programmeFilterName) === false) {
            return false;
        }
        if ($programmeFilterCode !== '' && stripos((string)($row['code'] ?? ''), $programmeFilterCode) === false) {
            return false;
        }
        if ($programmeFilterFacultyId > 0 && (int)($row['faculty_id'] ?? 0) !== $programmeFilterFacultyId) {
            return false;
        }
        if ($programmeFilterDepartmentId > 0 && (int)($row['department_id'] ?? 0) !== $programmeFilterDepartmentId) {
            return false;
        }
        $rowMode = trim((string)portalProgrammeModeOfStudyDisplay((array)$row));
        if ($programmeFilterMode !== '' && strcasecmp($rowMode, $programmeFilterMode) !== 0) {
            return false;
        }
        if ($programmeFilterStatus !== '') {
            $rowStatus = ((int)($row['is_active'] ?? 1) === 1) ? 'active' : 'inactive';
            if ($rowStatus !== $programmeFilterStatus) {
                return false;
            }
        }
        return true;
    }));
    $programmeFiltersApplied = ($programmeFilterName !== '' || $programmeFilterCode !== '' || $programmeFilterFacultyId > 0 || $programmeFilterDepartmentId > 0 || $programmeFilterMode !== '' || $programmeFilterStatus !== '');
    $programmeReturnQuery = $structureBuildQuery([
        'page' => 'admin_structure_manager',
        'tab' => 'programmes',
        'subtab' => 'manage_programmes',
        'programme_search_name' => $programmeFilterName,
        'programme_search_code' => $programmeFilterCode,
        'programme_filter_faculty_id' => $programmeFilterFacultyId > 0 ? $programmeFilterFacultyId : null,
        'programme_filter_department_id' => $programmeFilterDepartmentId > 0 ? $programmeFilterDepartmentId : null,
        'programme_filter_mode' => $programmeFilterMode,
        'programme_filter_status' => $programmeFilterStatus,
    ]);
    $programmeResetUrl = '?page=admin_structure_manager&tab=programmes&subtab=manage_programmes';

    ?>
    <div class="card">
        <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
            <div><i class="bi bi-diagram-3"></i> Faculty / Department / Programme Manager</div>
            <div class="d-flex gap-2 flex-wrap">
                <a class="btn btn-sm <?php echo $tab==='faculties'?'btn-primary':'btn-outline-primary'; ?>" href="?page=admin_structure_manager&tab=faculties">Faculties</a>
                <a class="btn btn-sm <?php echo $tab==='departments'?'btn-primary':'btn-outline-primary'; ?>" href="?page=admin_structure_manager&tab=departments">Departments</a>
                <a class="btn btn-sm <?php echo $tab==='programmes'?'btn-primary':'btn-outline-primary'; ?>" href="?page=admin_structure_manager&tab=programmes">Programmes</a>
                <a class="btn btn-sm <?php echo ($page==='admin_sessions_manager')?'btn-primary':'btn-outline-primary'; ?>" href="?page=admin_sessions_manager">Sessions</a>
                <a class="btn btn-sm <?php echo ($page==='admin_levels')?'btn-primary':'btn-outline-primary'; ?>" href="?page=admin_levels">Levels</a>
            </div>
        </div>
        <div class="card-body">
            <?php renderFlashes(); ?>

            <style>
                .structure-manager-shell .nav-tabs .nav-link{font-weight:600;}
                .structure-manager-shell .tab-pane{padding-top:1rem;}
                .structure-manager-shell .structure-pane-card{border:1px solid #dee2e6;border-radius:.75rem;background:#fff;padding:1rem;}
                .structure-manager-shell .structure-pane-card.table-pane{padding:0;overflow:hidden;}
                .structure-manager-shell .structure-pane-head{padding:1rem 1rem .5rem;}
                .structure-manager-shell .structure-form-shell{max-width:780px;}
                .structure-manager-shell .table-responsive{margin-bottom:0;}
                .structure-manager-shell .table{margin-bottom:0;}
                .structure-manager-shell .structure-filter-wrap{padding:1rem 1rem .85rem;border-top:1px solid #eef2f7;border-bottom:1px solid #eef2f7;background:#fbfcfe;}
                .structure-manager-shell .structure-filter-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.75rem;align-items:end;}
                .structure-manager-shell .structure-filter-actions{display:flex;gap:.5rem;flex-wrap:wrap;align-items:end;}
                .structure-manager-shell .structure-summary{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.85rem;}
                .structure-manager-shell .structure-summary-pill{display:inline-flex;align-items:center;gap:.35rem;padding:.35rem .7rem;border:1px solid #dbe5f0;border-radius:999px;background:#fff;font-size:.85rem;color:#495057;}
                .structure-manager-shell .structure-manager-table{margin-bottom:0;}
                .structure-manager-shell .structure-manager-table thead th{white-space:nowrap;}
                .structure-manager-shell .structure-manager-table > :not(caption) > * > *{padding:.55rem .75rem;vertical-align:middle;line-height:1.25;}
                .structure-manager-shell .structure-manager-table tbody tr{border-top:1px solid #eef2f7;}
                .structure-manager-shell .structure-manager-table tbody tr:first-child{border-top:0;}
                .structure-manager-shell .structure-manager-table .actions-cell{min-width:320px;width:1%;white-space:normal;}
                .structure-manager-shell .structure-inline-form{display:grid;gap:.45rem;align-items:center;margin:0;}
                .structure-manager-shell .structure-inline-form .form-control,
                .structure-manager-shell .structure-inline-form .form-select{min-height:34px;padding-top:.25rem;padding-bottom:.25rem;}
                .structure-manager-shell .structure-inline-form .form-check{margin-bottom:0;}
                .structure-manager-shell .faculty-inline-form{grid-template-columns:minmax(0,1.5fr) minmax(88px,.8fr);}
                .structure-manager-shell .department-inline-form{grid-template-columns:minmax(0,1.25fr) minmax(0,1fr) minmax(92px,.68fr);}
                .structure-manager-shell .programme-inline-form{grid-template-columns:repeat(2,minmax(0,1fr));}
                .structure-manager-shell .structure-field-full{grid-column:1 / -1;}
                .structure-manager-shell .faculty-inline-form .structure-inline-footer,
                .structure-manager-shell .department-inline-form .structure-inline-footer,
                .structure-manager-shell .programme-inline-form .structure-inline-footer{grid-column:1 / -1;display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex-wrap:wrap;}
                .structure-manager-shell .structure-btn-group{display:flex;gap:.45rem;flex-wrap:wrap;}
                .structure-manager-shell .structure-empty-state{padding:1.45rem 1rem;text-align:center;color:#6c757d;}
                .structure-manager-shell .structure-table-note{font-size:.8rem;color:#6c757d;}
                .structure-manager-shell .structure-status-badge{min-width:74px;text-align:center;}
                @media (max-width: 1199.98px){
                    .structure-manager-shell .structure-filter-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
                }
                @media (max-width: 991.98px){
                    .structure-manager-shell .nav-tabs{flex-wrap:wrap;}
                    .structure-manager-shell .structure-manager-table .actions-cell{min-width:280px;}
                }
                @media (max-width: 767.98px){
                    .structure-manager-shell .structure-filter-grid{grid-template-columns:1fr;}
                    .structure-manager-shell .structure-manager-table > :not(caption) > * > *{padding:.65rem .5rem;}
                    .structure-manager-shell .faculty-inline-form,
                    .structure-manager-shell .department-inline-form,
                    .structure-manager-shell .programme-inline-form{grid-template-columns:1fr;}
                    .structure-manager-shell .structure-inline-footer{align-items:flex-start;}
                    .structure-manager-shell .structure-btn-group{width:100%;}
                    .structure-manager-shell .structure-btn-group .btn{flex:1 1 auto;}
                }
            </style>
            <?php
                $structureAllowedSubtabs = [
                    'faculties' => ['manage_faculties', 'add_faculty'],
                    'departments' => ['manage_department', 'add_department'],
                    'programmes' => ['manage_programmes', 'add_programme'],
                ];
                $structureDefaultSubtabs = [
                    'faculties' => 'manage_faculties',
                    'departments' => 'manage_department',
                    'programmes' => 'manage_programmes',
                ];
                $structureSubtab = trim((string)($_GET['subtab'] ?? $structureDefaultSubtabs[$tab] ?? 'manage_faculties'));
                if (!in_array($structureSubtab, $structureAllowedSubtabs[$tab] ?? [], true)) {
                    $structureSubtab = $structureDefaultSubtabs[$tab] ?? 'manage_faculties';
                }
            ?>

            <div class="structure-manager-shell">
            <?php if ($tab === 'faculties'): ?>
                <ul class="nav nav-tabs flex-wrap" role="tablist">
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($structureSubtab === 'manage_faculties') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#structureManageFacultiesPane" type="button" role="tab" aria-selected="<?php echo ($structureSubtab === 'manage_faculties') ? 'true' : 'false'; ?>">Manage Faculties</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($structureSubtab === 'add_faculty') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#structureAddFacultyPane" type="button" role="tab" aria-selected="<?php echo ($structureSubtab === 'add_faculty') ? 'true' : 'false'; ?>">Add Faculty</button>
                    </li>
                </ul>
                <div class="tab-content">
                    <div class="tab-pane fade <?php echo ($structureSubtab === 'manage_faculties') ? 'show active' : ''; ?>" id="structureManageFacultiesPane" role="tabpanel">
                        <div class="structure-pane-card table-pane">
                            <div class="structure-pane-head">
                                <h6 class="mb-0">Manage Faculties</h6>
                            </div>
                            <div class="structure-filter-wrap">
                                <form method="get">
                                    <input type="hidden" name="page" value="admin_structure_manager">
                                    <input type="hidden" name="tab" value="faculties">
                                    <input type="hidden" name="subtab" value="manage_faculties">
                                    <div class="structure-filter-grid">
                                        <div>
                                            <label class="form-label">Search by Faculty Name</label>
                                            <input type="text" class="form-control form-control-sm" name="faculty_search_name" value="<?php echo e($facultyFilterName); ?>" placeholder="e.g. School of Business">
                                        </div>
                                        <div>
                                            <label class="form-label">Search by Code</label>
                                            <input type="text" class="form-control form-control-sm" name="faculty_search_code" value="<?php echo e($facultyFilterCode); ?>" placeholder="e.g. SSEB">
                                        </div>
                                        <div>
                                            <label class="form-label">Status</label>
                                            <select class="form-select form-select-sm" name="faculty_search_status">
                                                <option value="">All</option>
                                                <option value="active" <?php echo ($facultyFilterStatus === 'active') ? 'selected' : ''; ?>>Active</option>
                                                <option value="inactive" <?php echo ($facultyFilterStatus === 'inactive') ? 'selected' : ''; ?>>Inactive</option>
                                            </select>
                                        </div>
                                        <div class="structure-filter-actions">
                                            <button class="btn btn-sm btn-primary" type="submit"><i class="bi bi-funnel"></i> Apply</button>
                                            <a class="btn btn-sm btn-outline-secondary" href="<?php echo e($facultyResetUrl); ?>"><i class="bi bi-arrow-counterclockwise"></i> Reset</a>
                                        </div>
                                    </div>
                                </form>
                                <div class="structure-summary">
                                    <span class="structure-summary-pill"><strong>Total Faculties:</strong> <?php echo (int)$facultySummary['total']; ?></span>
                                    <span class="structure-summary-pill"><strong>Active:</strong> <?php echo (int)$facultySummary['active']; ?></span>
                                    <span class="structure-summary-pill"><strong>Inactive:</strong> <?php echo (int)$facultySummary['inactive']; ?></span>
                                    <span class="structure-summary-pill"><strong>Showing:</strong> <?php echo count($filteredFaculties); ?> result<?php echo count($filteredFaculties) === 1 ? '' : 's'; ?></span>
                                </div>
                            </div>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle structure-manager-table">
                                    <thead>
                                        <tr>
                                            <th>Name</th>
                                            <th>Code</th>
                                            <th>Status</th>
                                            <th style="width:340px;">Actions</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                    <?php foreach ($filteredFaculties as $f): ?>
                                        <tr>
                                            <td class="fw-medium"><?php echo e($f['name']); ?></td>
                                            <td><?php echo e($f['code'] ?? ''); ?></td>
                                            <td><?php echo ((int)($f['is_active'] ?? 1)===1) ? '<span class="badge bg-success structure-status-badge">Active</span>' : '<span class="badge bg-secondary structure-status-badge">Inactive</span>'; ?></td>
                                            <td class="actions-cell">
                                                <form method="post" class="structure-inline-form faculty-inline-form">
                                                    <input type="hidden" name="return_subtab" value="manage_faculties">
                                                    <input type="hidden" name="return_query" value="<?php echo e($facultyReturnQuery); ?>">
                                                    <input type="hidden" name="id" value="<?php echo (int)$f['id']; ?>">
                                                    <input class="form-control form-control-sm" name="name" value="<?php echo e($f['name']); ?>" required>
                                                    <input class="form-control form-control-sm" name="code" value="<?php echo e($f['code'] ?? ''); ?>" placeholder="Code">
                                                    <div class="structure-inline-footer">
                                                        <div class="form-check form-check-inline mb-0">
                                                            <input class="form-check-input" type="checkbox" name="is_active" id="faculty_active_<?php echo (int)$f['id']; ?>" <?php echo ((int)($f['is_active'] ?? 1)===1)?'checked':''; ?>>
                                                            <label class="form-check-label" for="faculty_active_<?php echo (int)$f['id']; ?>">Active</label>
                                                        </div>
                                                        <div class="structure-btn-group">
                                                            <button type="submit" name="action" value="edit_faculty" class="btn btn-sm btn-outline-primary">Update</button>
                                                            <button type="submit" name="action" value="delete_faculty" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this faculty? This will also delete its departments/programmes.');">Delete</button>
                                                        </div>
                                                    </div>
                                                </form>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$filteredFaculties): ?>
                                        <tr>
                                            <td colspan="4" class="structure-empty-state">
                                                <?php echo $facultyFiltersApplied ? 'No faculties match the selected filters.' : 'No faculty records found.'; ?>
                                            </td>
                                        </tr>
                                    <?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                    <div class="tab-pane fade <?php echo ($structureSubtab === 'add_faculty') ? 'show active' : ''; ?>" id="structureAddFacultyPane" role="tabpanel">
                        <div class="structure-pane-card structure-form-shell">
                            <h6 class="mb-3">Add Faculty</h6>
                            <form method="post">
                                <input type="hidden" name="action" value="add_faculty">
                                <input type="hidden" name="return_subtab" value="add_faculty">
                                <div class="mb-2">
                                    <label class="form-label">Name</label>
                                    <input class="form-control" name="name" required>
                                </div>
                                <div class="mb-2 programme-mode-wrap">
                                    <label class="form-label">Mode of Study</label>
                                    <select class="form-select" name="mode_of_study_type" data-mode-select required>
                                        <option value="Full-Time">Full-Time</option>
                                        <option value="Part-Time">Part-Time</option>
                                        <option value="Others">Others</option>
                                    </select>
                                </div>
                                <div class="mb-2 programme-mode-custom-wrap" data-mode-custom-wrap style="display:none;">
                                    <label class="form-label">Custom Mode of Study</label>
                                    <input class="form-control" name="mode_of_study_custom" data-mode-custom-input placeholder="Enter custom mode of study">
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Code (optional)</label>
                                    <input class="form-control" name="code">
                                </div>
                                <div class="form-check mb-3">
                                    <input class="form-check-input" type="checkbox" name="is_active" id="fac_active" checked>
                                    <label class="form-check-label" for="fac_active">Active</label>
                                </div>
                                <button class="btn btn-primary">Save</button>
                            </form>
                        </div>
                    </div>
                </div>

            <?php elseif ($tab === 'departments'): ?>
                <ul class="nav nav-tabs flex-wrap" role="tablist">
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($structureSubtab === 'manage_department') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#structureManageDepartmentPane" type="button" role="tab" aria-selected="<?php echo ($structureSubtab === 'manage_department') ? 'true' : 'false'; ?>">Manage Department</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($structureSubtab === 'add_department') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#structureAddDepartmentPane" type="button" role="tab" aria-selected="<?php echo ($structureSubtab === 'add_department') ? 'true' : 'false'; ?>">Add Department</button>
                    </li>
                </ul>
                <div class="tab-content">
                    <div class="tab-pane fade <?php echo ($structureSubtab === 'manage_department') ? 'show active' : ''; ?>" id="structureManageDepartmentPane" role="tabpanel">
                        <div class="structure-pane-card table-pane">
                            <div class="structure-pane-head">
                                <h6 class="mb-0">Manage Department</h6>
                            </div>
                            <div class="structure-filter-wrap">
                                <form method="get">
                                    <input type="hidden" name="page" value="admin_structure_manager">
                                    <input type="hidden" name="tab" value="departments">
                                    <input type="hidden" name="subtab" value="manage_department">
                                    <div class="structure-filter-grid">
                                        <div>
                                            <label class="form-label">Search by Department Name</label>
                                            <input type="text" class="form-control form-control-sm" name="department_search_name" value="<?php echo e($departmentFilterName); ?>" placeholder="e.g. Accounting">
                                        </div>
                                        <div>
                                            <label class="form-label">Search by Code</label>
                                            <input type="text" class="form-control form-control-sm" name="department_search_code" value="<?php echo e($departmentFilterCode); ?>" placeholder="e.g. SSEB">
                                        </div>
                                        <div>
                                            <label class="form-label">Faculty</label>
                                            <select class="form-select form-select-sm" name="department_filter_faculty_id">
                                                <option value="0">All Faculties</option>
                                                <?php foreach ($faculties as $f): ?>
                                                    <option value="<?php echo (int)$f['id']; ?>" <?php echo ($departmentFilterFacultyId === (int)($f['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e($f['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div>
                                            <label class="form-label">Status</label>
                                            <select class="form-select form-select-sm" name="department_filter_status">
                                                <option value="">All</option>
                                                <option value="active" <?php echo ($departmentFilterStatus === 'active') ? 'selected' : ''; ?>>Active</option>
                                                <option value="inactive" <?php echo ($departmentFilterStatus === 'inactive') ? 'selected' : ''; ?>>Inactive</option>
                                            </select>
                                        </div>
                                        <div class="structure-filter-actions" style="grid-column:1 / -1;">
                                            <button class="btn btn-sm btn-primary" type="submit"><i class="bi bi-funnel"></i> Apply</button>
                                            <a class="btn btn-sm btn-outline-secondary" href="<?php echo e($departmentResetUrl); ?>"><i class="bi bi-arrow-counterclockwise"></i> Reset</a>
                                        </div>
                                    </div>
                                </form>
                                <div class="structure-summary">
                                    <span class="structure-summary-pill"><strong>Total Departments:</strong> <?php echo (int)$departmentSummary['total']; ?></span>
                                    <span class="structure-summary-pill"><strong>Active:</strong> <?php echo (int)$departmentSummary['active']; ?></span>
                                    <span class="structure-summary-pill"><strong>Inactive:</strong> <?php echo (int)$departmentSummary['inactive']; ?></span>
                                    <span class="structure-summary-pill"><strong>Showing:</strong> <?php echo count($filteredDepartments); ?> result<?php echo count($filteredDepartments) === 1 ? '' : 's'; ?></span>
                                </div>
                            </div>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle structure-manager-table">
                                    <thead>
                                        <tr>
                                            <th>Faculty</th>
                                            <th>Department</th>
                                            <th>Code</th>
                                            <th>Status</th>
                                            <th style="width:360px;">Actions</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                    <?php foreach ($filteredDepartments as $d): ?>
                                        <tr>
                                            <td><?php echo e($d['faculty_name'] ?? ''); ?></td>
                                            <td class="fw-medium"><?php echo e($d['name']); ?></td>
                                            <td><?php echo e($d['code'] ?? ''); ?></td>
                                            <td><?php echo ((int)($d['is_active'] ?? 1)===1) ? '<span class="badge bg-success structure-status-badge">Active</span>' : '<span class="badge bg-secondary structure-status-badge">Inactive</span>'; ?></td>
                                            <td class="actions-cell">
                                                <form method="post" class="structure-inline-form department-inline-form">
                                                    <input type="hidden" name="action" value="edit_department">
                                                    <input type="hidden" name="return_subtab" value="manage_department">
                                                    <input type="hidden" name="return_query" value="<?php echo e($departmentReturnQuery); ?>">
                                                    <input type="hidden" name="id" value="<?php echo (int)$d['id']; ?>">
                                                    <select class="form-select form-select-sm" name="faculty_id" required>
                                                        <?php foreach ($faculties as $f): ?>
                                                            <option value="<?php echo (int)$f['id']; ?>" <?php echo ((int)$d['faculty_id']===(int)$f['id'])?'selected':''; ?>><?php echo e($f['name']); ?></option>
                                                        <?php endforeach; ?>
                                                    </select>
                                                    <input class="form-control form-control-sm" name="name" value="<?php echo e($d['name']); ?>" required>
                                                    <input class="form-control form-control-sm" name="code" value="<?php echo e($d['code'] ?? ''); ?>" placeholder="Code">
                                                    <div class="structure-inline-footer">
                                                        <div class="form-check form-check-inline mb-0">
                                                            <input class="form-check-input" type="checkbox" name="is_active" id="department_active_<?php echo (int)$d['id']; ?>" <?php echo ((int)($d['is_active'] ?? 1)===1)?'checked':''; ?>>
                                                            <label class="form-check-label" for="department_active_<?php echo (int)$d['id']; ?>">Active</label>
                                                        </div>
                                                        <div class="structure-btn-group">
                                                            <button type="submit" class="btn btn-sm btn-outline-primary">Update</button>
                                                            <button type="submit" name="action" value="delete_department" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this department? This will also delete its programmes.');">Delete</button>
                                                        </div>
                                                    </div>
                                                </form>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$filteredDepartments): ?>
                                        <tr>
                                            <td colspan="5" class="structure-empty-state"><?php echo $departmentFiltersApplied ? 'No departments match the selected filters.' : 'No department records found.'; ?></td>
                                        </tr>
                                    <?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                    <div class="tab-pane fade <?php echo ($structureSubtab === 'add_department') ? 'show active' : ''; ?>" id="structureAddDepartmentPane" role="tabpanel">
                        <div class="structure-pane-card structure-form-shell">
                            <h6 class="mb-3">Add Department</h6>
                            <form method="post">
                                <input type="hidden" name="action" value="add_department">
                                <input type="hidden" name="return_subtab" value="add_department">
                                <div class="mb-2">
                                    <label class="form-label">Faculty</label>
                                    <select class="form-select" name="faculty_id" data-structure-faculty required>
                                        <option value="">-- Select --</option>
                                        <?php foreach ($faculties as $f): ?>
                                            <option value="<?php echo (int)$f['id']; ?>"><?php echo e($f['name']); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Programme Name</label>
                                    <input class="form-control" name="name" required>
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Mode of Study</label>
                                    <select class="form-select" name="mode_of_study_type" data-mode-select required>
                                        <option value="Full-Time" selected>Full-Time</option>
                                        <option value="Part-Time">Part-Time</option>
                                        <option value="Others">Others</option>
                                    </select>
                                </div>
                                <div class="mb-2 programme-mode-custom-wrap" data-mode-custom-wrap style="display:none;">
                                    <label class="form-label">Custom Mode of Study</label>
                                    <input class="form-control" name="mode_of_study_custom" data-mode-custom-input placeholder="Enter custom mode when Others is selected">
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Code (optional)</label>
                                    <input class="form-control" name="code">
                                </div>
                                <div class="form-check mb-3">
                                    <input class="form-check-input" type="checkbox" name="is_active" id="dept_active" checked>
                                    <label class="form-check-label" for="dept_active">Active</label>
                                </div>
                                <button class="btn btn-primary">Save</button>
                            </form>
                        </div>
                    </div>
                </div>

            <?php else: ?>
                <ul class="nav nav-tabs flex-wrap" role="tablist">
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($structureSubtab === 'manage_programmes') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#structureManageProgrammesPane" type="button" role="tab" aria-selected="<?php echo ($structureSubtab === 'manage_programmes') ? 'true' : 'false'; ?>">Manage Programmes</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($structureSubtab === 'add_programme') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#structureAddProgrammePane" type="button" role="tab" aria-selected="<?php echo ($structureSubtab === 'add_programme') ? 'true' : 'false'; ?>">Add Programme</button>
                    </li>
                </ul>
                <div class="tab-content">
                    <div class="tab-pane fade <?php echo ($structureSubtab === 'manage_programmes') ? 'show active' : ''; ?>" id="structureManageProgrammesPane" role="tabpanel">
                        <div class="structure-pane-card table-pane">
                            <div class="structure-pane-head">
                                <h6 class="mb-0">Manage Programmes</h6>
                            </div>
                            <div class="structure-filter-wrap">
                                <form method="get" data-structure-programme-form>
                                    <input type="hidden" name="page" value="admin_structure_manager">
                                    <input type="hidden" name="tab" value="programmes">
                                    <input type="hidden" name="subtab" value="manage_programmes">
                                    <div class="structure-filter-grid">
                                        <div>
                                            <label class="form-label">Search by Programme Name</label>
                                            <input type="text" class="form-control form-control-sm" name="programme_search_name" value="<?php echo e($programmeFilterName); ?>" placeholder="e.g. Degree or NCE">
                                        </div>
                                        <div>
                                            <label class="form-label">Search by Programme Code</label>
                                            <input type="text" class="form-control form-control-sm" name="programme_search_code" value="<?php echo e($programmeFilterCode); ?>" placeholder="e.g. EDU">
                                        </div>
                                        <div>
                                            <label class="form-label">Faculty</label>
                                            <select class="form-select form-select-sm" name="programme_filter_faculty_id" data-structure-faculty>
                                                <option value="0">All Faculties</option>
                                                <?php foreach ($faculties as $f): ?>
                                                    <option value="<?php echo (int)$f['id']; ?>" <?php echo ($programmeFilterFacultyId === (int)$f['id']) ? 'selected' : ''; ?>><?php echo e($f['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div>
                                            <label class="form-label">Department</label>
                                            <select class="form-select form-select-sm" name="programme_filter_department_id" data-structure-department data-all-label="All Departments" data-placeholder-value="0">
                                                <option value="0">All Departments</option>
                                                <?php foreach ($departments as $d): ?>
                                                    <option value="<?php echo (int)$d['id']; ?>" data-faculty-id="<?php echo (int)($d['faculty_id'] ?? 0); ?>" <?php echo ($programmeFilterDepartmentId === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e($d['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div>
                                            <label class="form-label">Mode of Study</label>
                                            <select class="form-select form-select-sm" name="programme_filter_mode">
                                                <option value="">All Modes</option>
                                                <?php foreach ($programmeModeOptions as $modeOption): ?>
                                                    <option value="<?php echo e($modeOption); ?>" <?php echo (strcasecmp((string)$programmeFilterMode, (string)$modeOption) === 0) ? 'selected' : ''; ?>><?php echo e($modeOption); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div>
                                            <label class="form-label">Status</label>
                                            <select class="form-select form-select-sm" name="programme_filter_status">
                                                <option value="">All</option>
                                                <option value="active" <?php echo ($programmeFilterStatus === 'active') ? 'selected' : ''; ?>>Active</option>
                                                <option value="inactive" <?php echo ($programmeFilterStatus === 'inactive') ? 'selected' : ''; ?>>Inactive</option>
                                            </select>
                                        </div>
                                        <div class="structure-filter-actions" style="grid-column:1 / -1;">
                                            <button class="btn btn-sm btn-primary" type="submit"><i class="bi bi-funnel"></i> Apply</button>
                                            <a class="btn btn-sm btn-outline-secondary" href="<?php echo e($programmeResetUrl); ?>"><i class="bi bi-arrow-counterclockwise"></i> Reset</a>
                                        </div>
                                    </div>
                                </form>
                                <div class="structure-summary">
                                    <span class="structure-summary-pill"><strong>Total Programmes:</strong> <?php echo (int)$programmeSummary['total']; ?></span>
                                    <span class="structure-summary-pill"><strong>Active:</strong> <?php echo (int)$programmeSummary['active']; ?></span>
                                    <span class="structure-summary-pill"><strong>Inactive:</strong> <?php echo (int)$programmeSummary['inactive']; ?></span>
                                    <span class="structure-summary-pill"><strong>Showing:</strong> <?php echo count($filteredProgrammes); ?> result<?php echo count($filteredProgrammes) === 1 ? '' : 's'; ?></span>
                                </div>
                            </div>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle structure-manager-table">
                                    <thead>
                                        <tr>
                                            <th>Faculty</th>
                                            <th>Department</th>
                                            <th>Programme</th>
                                            <th>Mode of Study</th>
                                            <th>Code</th>
                                            <th>Status</th>
                                            <th style="width:390px;">Actions</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                    <?php foreach ($filteredProgrammes as $p): ?>
                                        <?php $programmeModeType = trim((string)($p['mode_of_study_type'] ?? '')); if (!in_array($programmeModeType, ['Full-Time','Part-Time','Others'], true)) { $programmeModeType = 'Full-Time'; } $programmeModeCustom = trim((string)($p['mode_of_study_custom'] ?? '')); ?>
                                        <tr>
                                            <td><?php echo e($p['faculty_name'] ?? ''); ?></td>
                                            <td><?php echo e($p['department_name'] ?? ''); ?></td>
                                            <td class="fw-medium"><?php echo e($p['name']); ?></td>
                                            <td><?php echo e(portalProgrammeModeOfStudyDisplay($p)); ?></td>
                                            <td><?php echo e($p['code'] ?? ''); ?></td>
                                            <td><?php echo ((int)($p['is_active'] ?? 1)===1) ? '<span class="badge bg-success structure-status-badge">Active</span>' : '<span class="badge bg-secondary structure-status-badge">Inactive</span>'; ?></td>
                                            <td class="actions-cell">
                                                <form method="post" class="structure-inline-form programme-inline-form" data-structure-programme-form>
                                                    <input type="hidden" name="return_subtab" value="manage_programmes">
                                                    <input type="hidden" name="return_query" value="<?php echo e($programmeReturnQuery); ?>">
                                                    <input type="hidden" name="id" value="<?php echo (int)$p['id']; ?>">
                                                    <select class="form-select form-select-sm" name="faculty_id" data-structure-faculty required>
                                                        <?php foreach ($faculties as $f): ?>
                                                            <option value="<?php echo (int)$f['id']; ?>" <?php echo ((int)$p['faculty_id']===(int)$f['id'])?'selected':''; ?>><?php echo e($f['name']); ?></option>
                                                        <?php endforeach; ?>
                                                    </select>
                                                    <select class="form-select form-select-sm" name="department_id" data-structure-department data-all-label="-- Select Department --" data-placeholder-value="" required>
                                                        <option value="">-- Select Department --</option>
                                                        <?php foreach ($departments as $d): ?>
                                                            <option value="<?php echo (int)$d['id']; ?>" data-faculty-id="<?php echo (int)($d['faculty_id'] ?? 0); ?>" <?php echo ((int)$p['department_id']===(int)$d['id'])?'selected':''; ?>><?php echo e($d['name']); ?></option>
                                                        <?php endforeach; ?>
                                                    </select>
                                                    <input class="form-control form-control-sm structure-field-full" name="name" value="<?php echo e($p['name']); ?>" required>
                                                    <select class="form-select form-select-sm" name="mode_of_study_type" data-mode-select required>
                                                        <option value="Full-Time" <?php echo ($programmeModeType==='Full-Time')?'selected':''; ?>>Full-Time</option>
                                                        <option value="Part-Time" <?php echo ($programmeModeType==='Part-Time')?'selected':''; ?>>Part-Time</option>
                                                        <option value="Others" <?php echo ($programmeModeType==='Others')?'selected':''; ?>>Others</option>
                                                    </select>
                                                    <input class="form-control form-control-sm" name="code" value="<?php echo e($p['code'] ?? ''); ?>" placeholder="Code">
                                                    <div class="programme-mode-custom-wrap structure-field-full" data-mode-custom-wrap style="<?php echo ($programmeModeType==='Others') ? '' : 'display:none;'; ?>">
                                                        <input class="form-control form-control-sm" name="mode_of_study_custom" data-mode-custom-input value="<?php echo e($programmeModeCustom); ?>" placeholder="Custom mode" <?php echo ($programmeModeType==='Others') ? 'required' : ''; ?>>
                                                    </div>
                                                    <div class="structure-inline-footer">
                                                        <div class="form-check form-check-inline mb-0">
                                                            <input class="form-check-input" type="checkbox" name="is_active" id="programme_active_<?php echo (int)$p['id']; ?>" <?php echo ((int)($p['is_active'] ?? 1)===1)?'checked':''; ?>>
                                                            <label class="form-check-label" for="programme_active_<?php echo (int)$p['id']; ?>">Active</label>
                                                        </div>
                                                        <div class="structure-btn-group">
                                                            <button type="submit" name="action" value="edit_programme" class="btn btn-sm btn-outline-primary">Update</button>
                                                            <button type="submit" name="action" value="delete_programme" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this programme?');">Delete</button>
                                                        </div>
                                                    </div>
                                                </form>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$filteredProgrammes): ?>
                                        <tr>
                                            <td colspan="7" class="structure-empty-state">
                                                <?php echo $programmeFiltersApplied ? 'No programmes match the selected filters.' : 'No programme records found.'; ?>
                                            </td>
                                        </tr>
                                    <?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                    <div class="tab-pane fade <?php echo ($structureSubtab === 'add_programme') ? 'show active' : ''; ?>" id="structureAddProgrammePane" role="tabpanel">
                        <div class="structure-pane-card structure-form-shell">
                            <h6 class="mb-3">Add Programme</h6>
                            <form method="post">
                                <input type="hidden" name="action" value="add_programme">
                                <input type="hidden" name="return_subtab" value="add_programme">
                                <div class="mb-2">
                                    <label class="form-label">Faculty</label>
                                    <select class="form-select" name="faculty_id" data-structure-faculty required>
                                        <option value="">-- Select --</option>
                                        <?php foreach ($faculties as $f): ?>
                                            <option value="<?php echo (int)$f['id']; ?>"><?php echo e($f['name']); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Department</label>
                                    <select class="form-select" name="department_id" data-structure-department data-all-label="-- Select Department --" data-placeholder-value="" required>
                                        <option value="">-- Select Department --</option>
                                        <?php foreach ($departments as $d): ?>
                                            <option value="<?php echo (int)$d['id']; ?>" data-faculty-id="<?php echo (int)($d['faculty_id'] ?? 0); ?>"><?php echo e($d['name']); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Programme Name</label>
                                    <input class="form-control" name="name" required>
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Mode of Study</label>
                                    <select class="form-select" name="mode_of_study_type" data-mode-select required>
                                        <option value="Full-Time">Full-Time</option>
                                        <option value="Part-Time">Part-Time</option>
                                        <option value="Others">Others</option>
                                    </select>
                                </div>
                                <div class="mb-2 programme-mode-custom-wrap" data-mode-custom-wrap style="display:none;">
                                    <label class="form-label">Custom Mode of Study</label>
                                    <input class="form-control" name="mode_of_study_custom" data-mode-custom-input placeholder="Enter custom mode when Others is selected">
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Code (optional)</label>
                                    <input class="form-control" name="code">
                                </div>
                                <div class="form-check mb-3">
                                    <input class="form-check-input" type="checkbox" name="is_active" id="prog_active" checked>
                                    <label class="form-check-label" for="prog_active">Active</label>
                                </div>
                                <button class="btn btn-primary">Save</button>
                            </form>
                            <div class="form-text mt-2">Tip: Create Faculty → Department → Programme in that order.</div>
                        </div>
                    </div>
                </div>
            <?php endif; ?>
            </div>

            <script>
            (function(){
                function syncModeState(select){
                    if (!select) return;
                    var form = select.closest('form');
                    var wrap = form ? form.querySelector('[data-mode-custom-wrap]') : null;
                    var input = wrap ? wrap.querySelector('[data-mode-custom-input]') : null;
                    var show = select.value === 'Others';
                    if (wrap) wrap.style.display = show ? '' : 'none';
                    if (input) {
                        input.required = show;
                        if (!show) input.value = '';
                    }
                }

                function syncDepartmentOptions(form){
                    if (!form) return;
                    var faculty = form.querySelector('[data-structure-faculty]');
                    var department = form.querySelector('[data-structure-department]');
                    if (!faculty || !department) return;

                    if (!department._allOptions) {
                        department._allOptions = Array.prototype.slice.call(department.querySelectorAll('option')).map(function(opt){
                            return opt.cloneNode(true);
                        });
                    }

                    var selectedValue = department.value || '';
                    var facultyValue = faculty.value || '';
                    var placeholderValue = department.getAttribute('data-placeholder-value');
                    if (placeholderValue === null) {
                        placeholderValue = department._allOptions.length ? department._allOptions[0].value : '';
                    }
                    var placeholderLabel = department.getAttribute('data-all-label') || (department._allOptions.length ? department._allOptions[0].textContent : '-- Select Department --');

                    department.innerHTML = '';
                    var placeholder = document.createElement('option');
                    placeholder.value = placeholderValue;
                    placeholder.textContent = placeholderLabel;
                    department.appendChild(placeholder);

                    department._allOptions.forEach(function(opt, index){
                        if (index === 0 && String(opt.value || '') === String(placeholderValue || '')) {
                            return;
                        }
                        var optionFacultyId = opt.getAttribute('data-faculty-id') || '';
                        if (facultyValue && optionFacultyId && String(optionFacultyId) !== String(facultyValue)) {
                            return;
                        }
                        department.appendChild(opt.cloneNode(true));
                    });

                    var hasSelected = Array.prototype.some.call(department.options, function(opt){
                        return String(opt.value || '') === String(selectedValue || '');
                    });
                    department.value = hasSelected ? selectedValue : placeholderValue;
                }

                document.querySelectorAll('[data-mode-select]').forEach(function(select){
                    if (!select.dataset.modeBound) {
                        select.dataset.modeBound = '1';
                        select.addEventListener('change', function(){ syncModeState(select); });
                    }
                    syncModeState(select);
                });

                document.querySelectorAll('.structure-manager-shell form').forEach(function(form){
                    var faculty = form.querySelector('[data-structure-faculty]');
                    var department = form.querySelector('[data-structure-department]');
                    if (!faculty || !department) return;
                    if (!faculty.dataset.structureBound) {
                        faculty.dataset.structureBound = '1';
                        faculty.addEventListener('change', function(){ syncDepartmentOptions(form); });
                    }
                    syncDepartmentOptions(form);
                });
            })();
            </script>

        </div>
    </div>

    

    <?php
    break;


case 'admin_levels':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'academics_manage');
    portalEnsureAcademicLevelsSchema($pdo);
    $levelRows = [];
    try {
        $levelRows = $pdo->query("SELECT id, level_name, sort_order, is_active, created_at FROM academic_levels ORDER BY sort_order ASC, level_name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {}
    ?>
    <div class="card">
        <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
            <div><i class="bi bi-sort-numeric-up"></i> Academic Levels Manager</div>
            <div class="d-flex gap-2 flex-wrap">
                <a class="btn btn-sm btn-outline-primary" href="?page=admin_structure_manager&tab=programmes"><i class="bi bi-diagram-3"></i> Structure</a>
                <a class="btn btn-sm btn-outline-primary" href="?page=admin_sessions_manager"><i class="bi bi-calendar2-week"></i> Sessions</a>
            </div>
        </div>
        <div class="card-body">
            <?php renderFlashes(); ?>
            <div class="row g-3">
                <div class="col-lg-4">
                    <div class="border rounded p-3">
                        <h6 class="mb-3">Add Academic Level</h6>
                        <form method="post">
                            <input type="hidden" name="action" value="add_level">
                            <div class="mb-2">
                                <label class="form-label">Level</label>
                                <input class="form-control" name="level_name" placeholder="100, 200, ND1, HND2" required>
                            </div>
                            <div class="mb-2">
                                <label class="form-label">Sort Order</label>
                                <input class="form-control" type="number" min="0" name="sort_order" value="0">
                            </div>
                            <div class="form-check mb-3">
                                <input class="form-check-input" type="checkbox" name="is_active" id="level_active" checked>
                                <label class="form-check-label" for="level_active">Active</label>
                            </div>
                            <button class="btn btn-primary">Save</button>
                        </form>
                    </div>
                </div>
                <div class="col-lg-8">
                    <div class="table-responsive">
                        <table class="table table-striped align-middle">
                            <thead>
                                <tr>
                                    <th>Current Level</th>
                                    <th>Sort Order</th>
                                    <th>Status</th>
                                    <th style="width:330px;">Edit Level</th>
                                    <th style="width:150px;">Delete</th>
                                </tr>
                            </thead>
                            <tbody>
                            <?php foreach ($levelRows as $row): ?>
                                <tr>
                                    <td>
                                        <div class="fw-semibold"><?php echo e($row['level_name']); ?></div>
                                        <div class="text-muted small">Created: <?php echo !empty($row['created_at']) ? e(date('d M Y', strtotime((string)$row['created_at']))) : '—'; ?></div>
                                    </td>
                                    <td><?php echo (int)($row['sort_order'] ?? 0); ?></td>
                                    <td><?php echo ((int)($row['is_active'] ?? 1)===1) ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Disabled</span>'; ?></td>
                                    <td>
                                        <form method="post" class="d-flex flex-wrap gap-2 align-items-center">
                                            <input type="hidden" name="action" value="edit_level">
                                            <input type="hidden" name="id" value="<?php echo (int)$row['id']; ?>">
                                            <input class="form-control form-control-sm" style="max-width:140px;" name="level_name" value="<?php echo e($row['level_name']); ?>" required>
                                            <input class="form-control form-control-sm" style="max-width:110px;" type="number" min="0" name="sort_order" value="<?php echo (int)($row['sort_order'] ?? 0); ?>">
                                            <div class="form-check form-check-inline mb-0">
                                                <input class="form-check-input" type="checkbox" name="is_active" id="edit_level_<?php echo (int)$row['id']; ?>" <?php echo ((int)($row['is_active'] ?? 1)===1)?'checked':''; ?>>
                                                <label class="form-check-label" for="edit_level_<?php echo (int)$row['id']; ?>">Active</label>
                                            </div>
                                            <button type="submit" class="btn btn-sm btn-outline-primary">Update</button>
                                        </form>
                                    </td>
                                    <td>
                                        <form method="post" onsubmit="return confirm('Delete this level permanently?');">
                                            <input type="hidden" name="action" value="delete_level">
                                            <input type="hidden" name="id" value="<?php echo (int)$row['id']; ?>">
                                            <button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
                                        </form>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                            <?php if (!$levelRows): ?>
                                <tr><td colspan="5" class="text-center text-muted">No academic levels found yet.</td></tr>
                            <?php endif; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'admin_sessions_manager':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    $sessions = [];
    try { portalSelfHealAcademicSessions($pdo); } catch (Throwable $e) {}
    try { portalEnsureAcademicSessionRegistrationControl($pdo); } catch (Throwable $e) {}
    try { $sessions = $pdo->query("SELECT * FROM academic_sessions ORDER BY name DESC")->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) {}
    $faculties = [];
    $departments = [];
    $programmes = [];
    try { $faculties = $pdo->query("SELECT * FROM faculties WHERE is_active=1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) {}
    try { $departments = $pdo->query("SELECT d.*, f.name AS faculty_name FROM departments d LEFT JOIN faculties f ON f.id=d.faculty_id WHERE d.is_active=1 ORDER BY f.name ASC, d.name ASC")->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) {}
    $programmes = portalFetchDistinctProgrammeOptions($pdo);
    $bulkPromotionProgrammes = portalFilterProgrammeOptionsForScope($programmes, 0, 0);
    $bulkPromotionModeOptions = portalModeOfStudyOptionValues($pdo);
    $bulkPromotionLevels = portalAcademicLevelOptions($pdo);
    $semesterAccessOptions = portalAcademicSessionRegistrationAccessOptions();
    $sessionSanitizeText = static function($value): string {
        $value = trim((string)$value);
        return preg_replace('/\s+/u', ' ', $value) ?: $value;
    };
    $sessionBuildQuery = static function(array $params): string {
        $clean = [];
        foreach ($params as $key => $value) {
            if ($value === null) continue;
            if (is_string($value)) {
                $value = trim($value);
                if ($value === '') continue;
            }
            $clean[$key] = $value;
        }
        return http_build_query($clean);
    };
    $sessionFilterName = $sessionSanitizeText($_GET['session_search_name'] ?? '');
    $sessionFilterCurrent = trim((string)($_GET['session_filter_current'] ?? ''));
    if (!in_array($sessionFilterCurrent, ['current', 'not_current'], true)) {
        $sessionFilterCurrent = '';
    }
    $sessionFilterStatus = trim((string)($_GET['session_filter_status'] ?? ''));
    if (!in_array($sessionFilterStatus, ['active', 'inactive'], true)) {
        $sessionFilterStatus = '';
    }
    $sessionFilterAccess = trim((string)($_GET['session_filter_access'] ?? ''));
    if ($sessionFilterAccess !== '' && !array_key_exists($sessionFilterAccess, $semesterAccessOptions)) {
        $sessionFilterAccess = '';
    }
    $sessionSummary = ['total' => count($sessions), 'current' => 0, 'active' => 0, 'inactive' => 0];
    foreach ($sessions as $sessionRow) {
        if ((int)($sessionRow['is_current'] ?? 0) === 1) {
            $sessionSummary['current']++;
        }
        if ((int)($sessionRow['is_active'] ?? 1) === 1) {
            $sessionSummary['active']++;
        } else {
            $sessionSummary['inactive']++;
        }
    }
    $filteredSessions = array_values(array_filter($sessions, static function(array $row) use ($sessionFilterName, $sessionFilterCurrent, $sessionFilterStatus, $sessionFilterAccess): bool {
        if ($sessionFilterName !== '' && stripos((string)($row['name'] ?? ''), $sessionFilterName) === false) {
            return false;
        }
        if ($sessionFilterCurrent !== '') {
            $rowCurrent = ((int)($row['is_current'] ?? 0) === 1) ? 'current' : 'not_current';
            if ($rowCurrent !== $sessionFilterCurrent) {
                return false;
            }
        }
        if ($sessionFilterStatus !== '') {
            $rowStatus = ((int)($row['is_active'] ?? 1) === 1) ? 'active' : 'inactive';
            if ($rowStatus !== $sessionFilterStatus) {
                return false;
            }
        }
        if ($sessionFilterAccess !== '') {
            $rowAccess = portalNormalizeRegistrationSemesterAccess((string)($row['registration_semester_access'] ?? 'both'));
            if ($rowAccess !== $sessionFilterAccess) {
                return false;
            }
        }
        return true;
    }));
    $sessionFiltersApplied = ($sessionFilterName !== '' || $sessionFilterCurrent !== '' || $sessionFilterStatus !== '' || $sessionFilterAccess !== '');
    $sessionReturnQuery = $sessionBuildQuery([
        'page' => 'admin_sessions_manager',
        'subtab' => 'manage_session',
        'session_search_name' => $sessionFilterName,
        'session_filter_current' => $sessionFilterCurrent,
        'session_filter_status' => $sessionFilterStatus,
        'session_filter_access' => $sessionFilterAccess,
    ]);
    $sessionResetUrl = '?page=admin_sessions_manager&subtab=manage_session';
    $bulkPromotionSessions = portalAvailableSessionOptions($pdo);
    $bulkPromotionCurrentSession = portalNormalizeSessionNameSafe((string)setting('academic_session',''));
    if ($bulkPromotionCurrentSession === '') {
        $bulkPromotionCurrentSession = portalNormalizeSessionNameSafe((string)portalCurrentAcademicSessionName($pdo));
    }
    try { portalEnsureBulkPromotionHistorySchema($pdo); } catch (Throwable $e) {}

    $bulkPromotionPersisted = $_SESSION['bulk_promotion_form_values'] ?? [];
    if (!is_array($bulkPromotionPersisted)) $bulkPromotionPersisted = [];
    $bulkPromotionFormState = array_merge([
        'from_level' => 'ALL',
        'to_level' => 'ALL',
        'to_session' => $bulkPromotionCurrentSession !== '' ? $bulkPromotionCurrentSession : '',
        'faculty_id' => 0,
        'department_id' => 0,
        'programme_id' => 0,
        'mode_of_study' => 'ALL',
        'mode_of_study_custom' => '',
        'superadmin_identity' => '',
    ], $bulkPromotionPersisted);
    unset($_SESSION['bulk_promotion_form_values']);

    $bulkPromotionFormState['faculty_id'] = max(0, (int)($bulkPromotionFormState['faculty_id'] ?? 0));
    $bulkPromotionFormState['department_id'] = max(0, (int)($bulkPromotionFormState['department_id'] ?? 0));
    $bulkPromotionFormState['programme_id'] = max(0, (int)($bulkPromotionFormState['programme_id'] ?? 0));
    $bulkPromotionFormState['from_level'] = trim((string)($bulkPromotionFormState['from_level'] ?? 'ALL')) ?: 'ALL';
    $bulkPromotionFormState['to_level'] = trim((string)($bulkPromotionFormState['to_level'] ?? 'ALL')) ?: 'ALL';
    $bulkPromotionFormState['to_session'] = portalNormalizeSessionNameSafe((string)($bulkPromotionFormState['to_session'] ?? ''));
    if ($bulkPromotionFormState['to_session'] === '') {
        $bulkPromotionFormState['to_session'] = $bulkPromotionCurrentSession;
    }
    $bulkPromotionFormState['mode_of_study'] = trim((string)($bulkPromotionFormState['mode_of_study'] ?? 'ALL')) ?: 'ALL';
    $bulkPromotionFormState['mode_of_study_custom'] = trim((string)($bulkPromotionFormState['mode_of_study_custom'] ?? ''));
    $bulkPromotionFormState['superadmin_identity'] = trim((string)($bulkPromotionFormState['superadmin_identity'] ?? ''));

    $bulkPromotionPreview = $_SESSION['bulk_promotion_preview'] ?? null;
    if (!is_array($bulkPromotionPreview)) $bulkPromotionPreview = null;
    unset($_SESSION['bulk_promotion_preview']);

    $promotionHistorySearch = trim((string)($_GET['promotion_history_q'] ?? ''));
    $promotionHistorySession = portalNormalizeSessionNameSafe((string)($_GET['promotion_history_session'] ?? ''));
    $promotionHistoryLevel = trim((string)($_GET['promotion_history_level'] ?? ''));
    $promotionHistoryStatus = trim((string)($_GET['promotion_history_status'] ?? ''));
    $promotionHistoryDateFrom = trim((string)($_GET['promotion_history_from'] ?? ''));
    $promotionHistoryDateTo = trim((string)($_GET['promotion_history_to'] ?? ''));
    $promotionHistoryPage = max(1, (int)($_GET['promotion_history_page'] ?? 1));
    $promotionHistoryPerPage = 15;
    $promotionHistoryRows = [];
    $promotionHistoryTotal = 0;
    $promotionHistoryAllCount = 0;
    $promotionHistoryStatusOptions = ['Successful', 'Partial', 'Failed', 'No Match'];

    try {
        $promotionHistoryAllCount = (int)($pdo->query("SELECT COUNT(*) FROM promotion_bulk_history")->fetchColumn() ?: 0);
    } catch (Throwable $e) {
        $promotionHistoryAllCount = 0;
    }

    try {
        $historyWhere = ["1=1"];
        $historyParams = [];
        if ($promotionHistorySearch !== '') {
            $historyWhere[] = "(COALESCE(performed_by_identity,'') LIKE ? OR COALESCE(acting_admin_name,'') LIKE ? OR COALESCE(message,'') LIKE ?)";
            $historyLike = '%' . $promotionHistorySearch . '%';
            $historyParams[] = $historyLike;
            $historyParams[] = $historyLike;
            $historyParams[] = $historyLike;
        }
        if ($promotionHistorySession !== '') {
            $historyWhere[] = "COALESCE(to_session,'') = ?";
            $historyParams[] = $promotionHistorySession;
        }
        if ($promotionHistoryLevel !== '') {
            $historyWhere[] = "(COALESCE(from_level,'') = ? OR COALESCE(to_level,'') = ?)";
            $historyParams[] = $promotionHistoryLevel;
            $historyParams[] = $promotionHistoryLevel;
        }
        if ($promotionHistoryStatus !== '') {
            $historyWhere[] = "COALESCE(status,'') = ?";
            $historyParams[] = $promotionHistoryStatus;
        }
        if ($promotionHistoryDateFrom !== '') {
            $historyWhere[] = "DATE(created_at) >= ?";
            $historyParams[] = $promotionHistoryDateFrom;
        }
        if ($promotionHistoryDateTo !== '') {
            $historyWhere[] = "DATE(created_at) <= ?";
            $historyParams[] = $promotionHistoryDateTo;
        }

        $historyWhereSql = implode(' AND ', $historyWhere);
        $countStmt = $pdo->prepare("SELECT COUNT(*) FROM promotion_bulk_history WHERE {$historyWhereSql}");
        $countStmt->execute($historyParams);
        $promotionHistoryTotal = (int)($countStmt->fetchColumn() ?: 0);

        $promotionHistoryOffset = ($promotionHistoryPage - 1) * $promotionHistoryPerPage;
        if ($promotionHistoryOffset < 0) $promotionHistoryOffset = 0;

        $rowsStmt = $pdo->prepare("SELECT * FROM promotion_bulk_history WHERE {$historyWhereSql} ORDER BY created_at DESC, id DESC LIMIT {$promotionHistoryPerPage} OFFSET {$promotionHistoryOffset}");
        $rowsStmt->execute($historyParams);
        $promotionHistoryRows = $rowsStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {
        $promotionHistoryRows = [];
        $promotionHistoryTotal = 0;
    }

    $promotionHistoryPages = max(1, (int)ceil($promotionHistoryTotal / max(1, $promotionHistoryPerPage)));
    if ($promotionHistoryPage > $promotionHistoryPages) $promotionHistoryPage = $promotionHistoryPages;

    $promotionHistoryBaseParams = [
        'page' => 'admin_sessions_manager',
        'subtab' => 'promotion_history',
        'promotion_history_q' => $promotionHistorySearch,
        'promotion_history_session' => $promotionHistorySession,
        'promotion_history_level' => $promotionHistoryLevel,
        'promotion_history_status' => $promotionHistoryStatus,
        'promotion_history_from' => $promotionHistoryDateFrom,
        'promotion_history_to' => $promotionHistoryDateTo,
    ];
    $promotionHistoryUrl = static function(array $overrides = []) use ($promotionHistoryBaseParams): string {
        $params = array_merge($promotionHistoryBaseParams, $overrides);
        return '?' . http_build_query($params);
    };

    ?>
    <div class="card">
        <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
            <div><i class="bi bi-calendar2-week"></i> Promotion / Academic Session Manager</div>
            <a class="btn btn-sm btn-outline-primary" href="?page=admin_structure_manager"><i class="bi bi-diagram-3"></i> Structure</a>
        </div>
        <div class="card-body">
            <?php renderFlashes(); ?>

            <style>
                .session-manager-shell .nav-tabs .nav-link{font-weight:600;}
                .session-manager-shell .nav-tabs .nav-link .badge{font-size:.7rem;vertical-align:middle;}
                .session-manager-shell .tab-pane{padding-top:1rem;}
                .session-manager-shell .session-pane-card{border:1px solid #dee2e6;border-radius:.85rem;background:#fff;padding:1rem;}
                .session-manager-shell .session-pane-card.table-pane{padding:0;overflow:hidden;}
                .session-manager-shell .session-pane-head{padding:1rem 1rem 0;}
                .session-manager-shell .session-form-shell{max-width:none;padding:1.25rem;}
                .session-manager-shell .session-add-form{max-width:none;}
                .session-manager-shell .session-add-form .form-label{font-weight:600;margin-bottom:.45rem;}
                .session-manager-shell .session-add-form .form-select,
                .session-manager-shell .session-add-form .form-control{min-height:46px;}
                .session-manager-shell .session-filter-wrap{padding:1rem 1rem .85rem;border-top:1px solid #eef2f7;border-bottom:1px solid #eef2f7;background:#fbfcfe;}
                .session-manager-shell .session-filter-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.75rem;align-items:end;}
                .session-manager-shell .session-filter-actions{display:flex;gap:.5rem;flex-wrap:wrap;align-items:end;}
                .session-manager-shell .session-summary{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.85rem;}
                .session-manager-shell .session-summary-pill{display:inline-flex;align-items:center;gap:.35rem;padding:.35rem .7rem;border:1px solid #dbe5f0;border-radius:999px;background:#fff;font-size:.85rem;color:#495057;}
                .session-manager-shell .session-manager-table{margin-bottom:0;}
                .session-manager-shell .session-manager-table thead th{white-space:nowrap;}
                .session-manager-shell .session-manager-table > :not(caption) > * > *{padding:.55rem .75rem;vertical-align:middle;line-height:1.25;}
                .session-manager-shell .session-manager-table tbody tr{border-top:1px solid #eef2f7;}
                .session-manager-shell .session-manager-table tbody tr:first-child{border-top:0;}
                .session-manager-shell .session-manager-table .actions-cell{min-width:430px;width:1%;white-space:normal;}
                .session-manager-shell .session-inline-form{display:grid;grid-template-columns:minmax(0,110px) minmax(0,140px) minmax(0,140px);gap:.45rem;align-items:center;margin:0;}
                .session-manager-shell .session-inline-form .form-control,
                .session-manager-shell .session-inline-form .form-select{min-height:34px;padding-top:.25rem;padding-bottom:.25rem;}
                .session-manager-shell .session-inline-footer{grid-column:1 / -1;display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex-wrap:wrap;}
                .session-manager-shell .session-inline-flags,
                .session-manager-shell .session-inline-actions{display:flex;gap:.45rem;flex-wrap:wrap;align-items:center;}
                .session-manager-shell .session-inline-flags .form-check{margin-bottom:0;}
                .session-manager-shell .session-inline-actions{justify-content:flex-end;}
                .session-manager-shell .session-inline-action-select{min-width:190px;max-width:100%;flex:0 1 220px;}
                .session-manager-shell .session-inline-action-select .form-select{min-width:100%;}
                .session-manager-shell .session-empty-state{padding:1.45rem 1rem;text-align:center;color:#6c757d;}
                .session-manager-shell .session-promote-shell{max-width:1180px;margin:0 auto;padding:1.1rem 1.1rem 1.2rem;}
                .session-manager-shell .session-promote-form .bulk-promo-row + .bulk-promo-row{margin-top:1rem;}
                .session-manager-shell .session-promote-form .form-label{font-weight:600;margin-bottom:.45rem;}
                .session-manager-shell .session-promote-form .form-select,
                .session-manager-shell .session-promote-form .form-control{min-height:46px;}
                .session-manager-shell .session-promote-form .bulk-promo-block{border:1px solid #e4eaf3;border-radius:1rem;background:linear-gradient(180deg,#ffffff 0%,#fbfcff 100%);padding:1rem 1rem .85rem;}
                .session-manager-shell .session-promote-form .bulk-promo-block-title{font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:#5f7591;font-weight:700;margin-bottom:.65rem;}
                .session-manager-shell .bulk-promo-summary{border:1px solid #d9e6fb;border-radius:1rem;background:#f7fbff;padding:1rem 1rem .85rem;}
                .session-manager-shell .bulk-promo-summary-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.8rem 1rem;}
                .session-manager-shell .bulk-promo-summary-item{min-width:0;}
                .session-manager-shell .bulk-promo-summary-item .label{font-size:.75rem;text-transform:uppercase;letter-spacing:.08em;color:#5f7591;font-weight:700;margin-bottom:.15rem;}
                .session-manager-shell .bulk-promo-summary-item .value{font-weight:600;color:#17324d;word-break:break-word;}
                .session-manager-shell .bulk-promo-summary-foot{margin-top:.85rem;padding-top:.75rem;border-top:1px solid #dbe7f7;color:#5f6f83;font-size:.9rem;}
                .session-manager-shell .bulk-promo-actions{display:flex;align-items:center;justify-content:space-between;gap:.85rem;flex-wrap:wrap;padding-top:.15rem;}
                .session-manager-shell .bulk-promo-actions .btn-wrap{display:flex;gap:.65rem;flex-wrap:wrap;}
                .session-manager-shell .bulk-promo-actions .btn{min-width:170px;}
                .session-manager-shell .session-promote-form .form-text{margin-top:.6rem;line-height:1.55;}
                .session-manager-shell .session-promote-form .bulk-promo-help{padding-top:.15rem;}
                .session-manager-shell .promotion-history-toolbar{border:1px solid #e4eaf3;border-radius:.9rem;background:#fbfcff;padding:1rem;margin-bottom:1rem;}
                .session-manager-shell .promotion-history-toolbar .form-label{font-weight:600;margin-bottom:.35rem;}
                .session-manager-shell .promotion-history-table thead th{white-space:nowrap;font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;color:#5a6a7c;background:#f6f8fb;}
                .session-manager-shell .promotion-history-table td{vertical-align:middle;}
                .session-manager-shell .promotion-history-meta{display:flex;flex-wrap:wrap;gap:.45rem;}
                .session-manager-shell .promotion-history-chip{display:inline-flex;align-items:center;gap:.35rem;border:1px solid #dbe5f2;background:#f8fbff;border-radius:999px;padding:.25rem .6rem;font-size:.78rem;color:#425569;}
                .session-manager-shell .promotion-history-detail{background:#fcfdff;}
                .session-manager-shell .promotion-history-detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem 1rem;}
                .session-manager-shell .promotion-history-detail-grid .label{font-size:.74rem;text-transform:uppercase;letter-spacing:.08em;color:#66788f;font-weight:700;margin-bottom:.12rem;}
                .session-manager-shell .promotion-history-detail-grid .value{word-break:break-word;}
                .session-manager-shell .table-responsive{margin-bottom:0;}
                .session-manager-shell .table{margin-bottom:0;}
                @media (max-width: 1199.98px){
                    .session-manager-shell .session-filter-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
                }
                @media (max-width: 991.98px){
                    .session-manager-shell .nav-tabs{flex-wrap:wrap;}
                    .session-manager-shell .session-promote-shell{padding:1rem;}
                    .session-manager-shell .bulk-promo-summary-grid,
                    .session-manager-shell .promotion-history-detail-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
                    .session-manager-shell .bulk-promo-actions{align-items:stretch;}
                    .session-manager-shell .bulk-promo-actions .btn-wrap{width:100%;}
                    .session-manager-shell .bulk-promo-actions .btn{width:100%;}
                    .session-manager-shell .session-manager-table .actions-cell{min-width:340px;}
                }
                @media (max-width: 767.98px){
                    .session-manager-shell .session-filter-grid{grid-template-columns:1fr;}
                    .session-manager-shell .session-inline-form{grid-template-columns:1fr;}
                    .session-manager-shell .session-inline-footer{align-items:flex-start;}
                    .session-manager-shell .session-inline-actions{width:100%;justify-content:flex-start;}
                    .session-manager-shell .session-inline-action-select{flex:1 1 100%;min-width:100%;}
                    .session-manager-shell .session-inline-actions .btn{flex:1 1 auto;}
                }
                @media (max-width: 575.98px){
                    .session-manager-shell .bulk-promo-summary-grid,
                    .session-manager-shell .promotion-history-detail-grid{grid-template-columns:1fr;}
                }
            </style>
            <?php
                $sessionAllowedSubtabs = ['manage_session', 'add_session', 'promote_students', 'promotion_history'];
                $sessionSubtab = trim((string)($_GET['subtab'] ?? 'manage_session'));
                if (!in_array($sessionSubtab, $sessionAllowedSubtabs, true)) {
                    $sessionSubtab = 'manage_session';
                }
            ?>

            <div class="session-manager-shell">
                <ul class="nav nav-tabs flex-wrap" role="tablist">
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($sessionSubtab === 'manage_session') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#sessionManagePane" type="button" role="tab" aria-selected="<?php echo ($sessionSubtab === 'manage_session') ? 'true' : 'false'; ?>">Manage Academic Session</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($sessionSubtab === 'add_session') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#sessionAddPane" type="button" role="tab" aria-selected="<?php echo ($sessionSubtab === 'add_session') ? 'true' : 'false'; ?>">Add Academic Session</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($sessionSubtab === 'promote_students') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#sessionPromotePane" type="button" role="tab" aria-selected="<?php echo ($sessionSubtab === 'promote_students') ? 'true' : 'false'; ?>">Promote Students (Bulk)</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php echo ($sessionSubtab === 'promotion_history') ? 'active' : ''; ?>" data-bs-toggle="tab" data-bs-target="#sessionPromotionHistoryPane" type="button" role="tab" aria-selected="<?php echo ($sessionSubtab === 'promotion_history') ? 'true' : 'false'; ?>">Promotion History <span class="badge rounded-pill text-bg-light border ms-1"><?php echo (int)$promotionHistoryAllCount; ?></span></button>
                    </li>
                </ul>

                <div class="tab-content">
                    <div class="tab-pane fade <?php echo ($sessionSubtab === 'manage_session') ? 'show active' : ''; ?>" id="sessionManagePane" role="tabpanel">
                        <div class="session-pane-card table-pane">
                            <div class="session-pane-head">
                                <h6 class="mb-0">Manage Academic Session</h6>
                            </div>
                            <div class="session-filter-wrap">
                                <form method="get">
                                    <input type="hidden" name="page" value="admin_sessions_manager">
                                    <input type="hidden" name="subtab" value="manage_session">
                                    <div class="session-filter-grid">
                                        <div>
                                            <label class="form-label">Search by Session Name</label>
                                            <input type="text" class="form-control form-control-sm" name="session_search_name" value="<?php echo e($sessionFilterName); ?>" placeholder="e.g. 2026/2027">
                                        </div>
                                        <div>
                                            <label class="form-label">Current</label>
                                            <select class="form-select form-select-sm" name="session_filter_current">
                                                <option value="">All</option>
                                                <option value="current" <?php echo ($sessionFilterCurrent === 'current') ? 'selected' : ''; ?>>Current</option>
                                                <option value="not_current" <?php echo ($sessionFilterCurrent === 'not_current') ? 'selected' : ''; ?>>Not Current</option>
                                            </select>
                                        </div>
                                        <div>
                                            <label class="form-label">Status</label>
                                            <select class="form-select form-select-sm" name="session_filter_status">
                                                <option value="">All</option>
                                                <option value="active" <?php echo ($sessionFilterStatus === 'active') ? 'selected' : ''; ?>>Active</option>
                                                <option value="inactive" <?php echo ($sessionFilterStatus === 'inactive') ? 'selected' : ''; ?>>Inactive</option>
                                            </select>
                                        </div>
                                        <div>
                                            <label class="form-label">Semester Access</label>
                                            <select class="form-select form-select-sm" name="session_filter_access">
                                                <option value="">All</option>
                                                <?php foreach ($semesterAccessOptions as $accessValue => $accessLabel): ?>
                                                    <option value="<?php echo e((string)$accessValue); ?>" <?php echo ($sessionFilterAccess === (string)$accessValue) ? 'selected' : ''; ?>><?php echo e((string)$accessLabel); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="session-filter-actions" style="grid-column:1 / -1;">
                                            <button class="btn btn-sm btn-primary" type="submit"><i class="bi bi-funnel"></i> Apply</button>
                                            <a class="btn btn-sm btn-outline-secondary" href="<?php echo e($sessionResetUrl); ?>"><i class="bi bi-arrow-counterclockwise"></i> Reset</a>
                                        </div>
                                    </div>
                                </form>
                                <div class="session-summary">
                                    <span class="session-summary-pill"><strong>Total Sessions:</strong> <?php echo (int)$sessionSummary['total']; ?></span>
                                    <span class="session-summary-pill"><strong>Current:</strong> <?php echo (int)$sessionSummary['current']; ?></span>
                                    <span class="session-summary-pill"><strong>Active:</strong> <?php echo (int)$sessionSummary['active']; ?></span>
                                    <span class="session-summary-pill"><strong>Showing:</strong> <?php echo count($filteredSessions); ?> result<?php echo count($filteredSessions) === 1 ? '' : 's'; ?></span>
                                </div>
                            </div>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle session-manager-table">
                                    <thead>
                                        <tr>
                                            <th>Session</th>
                                            <th>Start</th>
                                            <th>End</th>
                                            <th>Current</th>
                                            <th>Status</th>
                                            <th>Semester Access</th>
                                            <th style="width:430px;">Actions</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                    <?php foreach ($filteredSessions as $s): ?>
                                        <tr>
                                            <td class="fw-medium"><?php echo e($s['name']); ?></td>
                                            <td><?php echo e($s['start_date'] ?? ''); ?></td>
                                            <td><?php echo e($s['end_date'] ?? ''); ?></td>
                                            <td><?php echo ((int)($s['is_current'] ?? 0)===1) ? '<span class="badge bg-primary">Yes</span>' : '<span class="badge bg-secondary">No</span>'; ?></td>
                                            <td><?php echo ((int)($s['is_active'] ?? 1)===1) ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Inactive</span>'; ?></td>
                                            <td><span class="badge bg-info text-dark"><?php echo e(portalAcademicSessionRegistrationAccessLabel((string)($s['registration_semester_access'] ?? 'both'))); ?></span></td>
                                            <td class="actions-cell">
                                                <form method="post" class="session-inline-form">
                                                    <input type="hidden" name="action" value="edit_session">
                                                    <input type="hidden" name="return_subtab" value="manage_session">
                                                    <input type="hidden" name="return_query" value="<?php echo e($sessionReturnQuery); ?>">
                                                    <input type="hidden" name="id" value="<?php echo (int)$s['id']; ?>">
                                                    <input class="form-control form-control-sm" name="name" value="<?php echo e($s['name']); ?>" required>
                                                    <input class="form-control form-control-sm" type="date" name="start_date" value="<?php echo e($s['start_date'] ?? ''); ?>">
                                                    <input class="form-control form-control-sm" type="date" name="end_date" value="<?php echo e($s['end_date'] ?? ''); ?>">
                                                    <?php $sessionAccessValue = portalNormalizeRegistrationSemesterAccess((string)($s['registration_semester_access'] ?? 'both')); ?>
                                                    <div class="session-inline-footer">
                                                        <div class="session-inline-flags">
                                                            <div class="form-check form-check-inline mb-0">
                                                                <input class="form-check-input" type="checkbox" name="is_current" id="session_current_<?php echo (int)$s['id']; ?>" <?php echo ((int)($s['is_current'] ?? 0)===1)?'checked':''; ?>>
                                                                <label class="form-check-label" for="session_current_<?php echo (int)$s['id']; ?>">Current</label>
                                                            </div>
                                                            <div class="form-check form-check-inline mb-0">
                                                                <input class="form-check-input" type="checkbox" name="is_active" id="session_active_<?php echo (int)$s['id']; ?>" <?php echo ((int)($s['is_active'] ?? 1)===1)?'checked':''; ?>>
                                                                <label class="form-check-label" for="session_active_<?php echo (int)$s['id']; ?>">Active</label>
                                                            </div>
                                                        </div>
                                                        <div class="session-inline-actions">
                                                            <div class="session-inline-action-select">
                                                                <select class="form-select form-select-sm" name="registration_semester_access" aria-label="Semester registration access for <?php echo e($s['name']); ?>">
                                                                    <?php foreach ($semesterAccessOptions as $accessValue => $accessLabel): ?>
                                                                        <option value="<?php echo e((string)$accessValue); ?>" <?php echo ($sessionAccessValue === $accessValue) ? 'selected' : ''; ?>><?php echo e((string)$accessLabel); ?></option>
                                                                    <?php endforeach; ?>
                                                                </select>
                                                            </div>
                                                            <button type="submit" class="btn btn-sm btn-outline-primary">Update</button>
                                                            <button type="submit" name="action" value="set_current_session" class="btn btn-sm btn-outline-success" <?php echo ((int)($s['is_current'] ?? 0)===1)?'disabled':''; ?>>Make Current</button>
                                                            <button type="submit" name="action" value="delete_session" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this session?');">Delete</button>
                                                        </div>
                                                    </div>
                                                </form>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$filteredSessions): ?>
                                        <tr><td colspan="7" class="session-empty-state"><?php echo $sessionFiltersApplied ? 'No academic sessions match the selected filters.' : 'No academic sessions found.'; ?></td></tr>
                                    <?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>

                    <div class="tab-pane fade <?php echo ($sessionSubtab === 'add_session') ? 'show active' : ''; ?>" id="sessionAddPane" role="tabpanel">
                        <div class="session-pane-card session-form-shell session-add-shell">
                            <h6 class="mb-3">Add Academic Session</h6>
                            <form method="post" class="session-add-form">
                                <input type="hidden" name="action" value="add_session">
                                <input type="hidden" name="return_subtab" value="add_session">
                                <div class="mb-3">
                                    <label class="form-label">Session Name</label>
                                    <input class="form-control" name="name" placeholder="2026/2027" required>
                                </div>
                                <div class="row g-3">
                                    <div class="col-md-6">
                                        <label class="form-label">Start Date</label>
                                        <input class="form-control" type="date" name="start_date">
                                    </div>
                                    <div class="col-md-6">
                                        <label class="form-label">End Date</label>
                                        <input class="form-control" type="date" name="end_date">
                                    </div>
                                </div>
                                <div class="form-check mb-3">
                                    <input class="form-check-input" type="checkbox" name="is_current" id="sess_current">
                                    <label class="form-check-label" for="sess_current">Set as current</label>
                                </div>
                                <div class="mb-3">
                                    <label class="form-label">Semester Registration Access</label>
                                    <select class="form-select" name="registration_semester_access">
                                        <?php foreach ($semesterAccessOptions as $accessValue => $accessLabel): ?>
                                            <option value="<?php echo e((string)$accessValue); ?>" <?php echo ($accessValue === 'both') ? 'selected' : ''; ?>><?php echo e((string)$accessLabel); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                    <div class="form-text">Controls which semester students can register for in this academic session.</div>
                                </div>
                                <div class="form-check mb-3">
                                    <input class="form-check-input" type="checkbox" name="is_active" id="sess_active" checked>
                                    <label class="form-check-label" for="sess_active">Active</label>
                                </div>
                                <button class="btn btn-primary">Save</button>
                            </form>
                        </div>
                    </div>

                    <div class="tab-pane fade <?php echo ($sessionSubtab === 'promote_students') ? 'show active' : ''; ?>" id="sessionPromotePane" role="tabpanel">
                        <div id="promotion" class="session-pane-card session-promote-shell">
                            <div class="d-flex flex-wrap justify-content-between align-items-start gap-2">
                                <div>
                                    <h6 class="mb-1"><i class="bi bi-arrow-up-right-circle"></i> Promote Students (Bulk)</h6>
                                    <div class="text-muted small">Review the scope, preview the expected impact, then confirm with the Superadmin credentials used for the update.</div>
                                </div>
                                <?php if (!empty($bulkPromotionPreview) && is_array($bulkPromotionPreview)): ?>
                                    <span class="promotion-history-chip"><i class="bi bi-clipboard-check"></i> Preview ready: <?php echo (int)($bulkPromotionPreview['matched_count'] ?? 0); ?> matched</span>
                                <?php endif; ?>
                            </div>

                            <?php
                                $bulkPromotionCustomModes = [];
                                foreach ($bulkPromotionModeOptions as $modeOpt) {
                                    $modeOpt = trim((string)$modeOpt);
                                    if ($modeOpt === '' || in_array(strtoupper($modeOpt), ['FULL-TIME','PART-TIME','OTHERS'], true)) continue;
                                    $bulkPromotionCustomModes[$modeOpt] = $modeOpt;
                                }
                                ksort($bulkPromotionCustomModes, SORT_NATURAL | SORT_FLAG_CASE);
                            ?>

                            <form method="post" class="session-promote-form mt-3" id="bulkPromotionForm">
                                <input type="hidden" name="action" id="bulkPromotionAction" value="promote_students">
                                <input type="hidden" name="return_subtab" value="promote_students">

                                <div class="bulk-promo-block">
                                    <div class="bulk-promo-block-title">Promotion Scope</div>

                                    <div class="row g-3 bulk-promo-row">
                                        <div class="col-xl-4 col-md-6">
                                            <label class="form-label">From Level</label>
                                            <select class="form-select" name="from_level" id="bulkPromotionFromLevel" required>
                                                <option value="ALL" <?php echo (strcasecmp((string)($bulkPromotionFormState['from_level'] ?? 'ALL'), 'ALL') === 0) ? 'selected' : ''; ?>>All</option>
                                                <?php foreach ($bulkPromotionLevels as $levelOpt): ?>
                                                    <?php $levelOpt = (string)$levelOpt; ?>
                                                    <option value="<?php echo e($levelOpt); ?>" <?php echo (strcasecmp((string)($bulkPromotionFormState['from_level'] ?? ''), $levelOpt) === 0) ? 'selected' : ''; ?>><?php echo e($levelOpt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-xl-4 col-md-6">
                                            <label class="form-label">To Level</label>
                                            <select class="form-select" name="to_level" id="bulkPromotionToLevel" required>
                                                <option value="ALL" <?php echo (strcasecmp((string)($bulkPromotionFormState['to_level'] ?? 'ALL'), 'ALL') === 0) ? 'selected' : ''; ?>>All</option>
                                                <?php foreach ($bulkPromotionLevels as $levelOpt): ?>
                                                    <?php $levelOpt = (string)$levelOpt; ?>
                                                    <option value="<?php echo e($levelOpt); ?>" <?php echo (strcasecmp((string)($bulkPromotionFormState['to_level'] ?? ''), $levelOpt) === 0) ? 'selected' : ''; ?>><?php echo e($levelOpt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-xl-4 col-md-12">
                                            <label class="form-label">To Session</label>
                                            <select class="form-select" name="to_session" id="bulkPromotionToSession" required>
                                                <option value="ALL">All</option>
                                                <?php foreach ($bulkPromotionSessions as $sessionOpt): ?>
                                                    <?php $sessionOpt = portalNormalizeSessionNameSafe((string)$sessionOpt); if ($sessionOpt === '') continue; ?>
                                                    <option value="<?php echo e($sessionOpt); ?>" <?php echo (strcasecmp((string)($bulkPromotionFormState['to_session'] ?? ''), $sessionOpt) === 0) ? 'selected' : ''; ?>><?php echo e($sessionOpt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                    </div>

                                    <div class="row g-3 bulk-promo-row">
                                        <div class="col-xl-4 col-md-6">
                                            <label class="form-label">Faculty (optional)</label>
                                            <select class="form-select" name="faculty_id" id="bulkPromotionFaculty" data-rc-faculty data-all-label="All Faculty">
                                                <option value="0">All Faculty</option>
                                                <?php foreach ($faculties as $f): ?>
                                                    <option value="<?php echo (int)$f['id']; ?>" <?php echo ((int)($bulkPromotionFormState['faculty_id'] ?? 0) === (int)($f['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e($f['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-xl-4 col-md-6">
                                            <label class="form-label">Department (optional)</label>
                                            <select class="form-select" name="department_id" id="bulkPromotionDepartment" data-rc-department data-all-label="All Department">
                                                <option value="0">All Department</option>
                                                <?php foreach ($departments as $d): ?>
                                                    <option value="<?php echo (int)$d['id']; ?>" <?php echo ((int)($bulkPromotionFormState['department_id'] ?? 0) === (int)($d['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e(($d['faculty_name'] ?? '').' - '.$d['name']); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                        <div class="col-xl-4 col-md-12">
                                            <label class="form-label">Programme (optional)</label>
                                            <select class="form-select" name="programme_id" id="bulkPromotionProgramme">
                                                <option value="0">All Programmes</option>
                                                <?php foreach ($bulkPromotionProgrammes as $p): ?>
                                                    <option value="<?php echo (int)$p['id']; ?>" <?php echo ((int)($bulkPromotionFormState['programme_id'] ?? 0) === (int)($p['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e((string)($p['name'] ?? '')); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                        </div>
                                    </div>

                                    <div class="row g-3 bulk-promo-row">
                                        <div class="col-xl-4 col-lg-5 col-md-6">
                                            <label class="form-label">Mode of Study</label>
                                            <select class="form-select" name="mode_of_study" id="bulkPromotionModeOfStudy">
                                                <option value="ALL" <?php echo (strcasecmp((string)($bulkPromotionFormState['mode_of_study'] ?? 'ALL'), 'ALL') === 0) ? 'selected' : ''; ?>>All Modes of Study</option>
                                                <option value="Full-Time" <?php echo (strcasecmp((string)($bulkPromotionFormState['mode_of_study'] ?? ''), 'Full-Time') === 0) ? 'selected' : ''; ?>>Full-Time</option>
                                                <option value="Part-Time" <?php echo (strcasecmp((string)($bulkPromotionFormState['mode_of_study'] ?? ''), 'Part-Time') === 0) ? 'selected' : ''; ?>>Part-Time</option>
                                                <option value="Others" <?php echo (strcasecmp((string)($bulkPromotionFormState['mode_of_study'] ?? ''), 'Others') === 0) ? 'selected' : ''; ?>>Others</option>
                                                <?php foreach ($bulkPromotionModeOptions as $modeOpt): ?>
                                                    <?php $modeOpt = trim((string)$modeOpt); if ($modeOpt === '' || in_array(strtoupper($modeOpt), ['FULL-TIME','PART-TIME','OTHERS'], true)) continue; ?>
                                                    <option value="<?php echo e($modeOpt); ?>" <?php echo (strcasecmp((string)($bulkPromotionFormState['mode_of_study'] ?? ''), $modeOpt) === 0) ? 'selected' : ''; ?>><?php echo e($modeOpt); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                            <div class="mt-2" id="bulkPromotionModeOfStudyCustomWrap" style="display:none;">
                                                <input type="text" class="form-control" name="mode_of_study_custom" value="<?php echo e((string)($bulkPromotionFormState['mode_of_study_custom'] ?? '')); ?>" id="bulkPromotionModeOfStudyCustom" list="bulkPromotionModeOfStudyCustomList" placeholder="Enter custom mode of study">
                                                <datalist id="bulkPromotionModeOfStudyCustomList">
                                                    <?php foreach ($bulkPromotionCustomModes as $modeOpt): ?>
                                                        <option value="<?php echo e($modeOpt); ?>"></option>
                                                    <?php endforeach; ?>
                                                </datalist>
                                                <div class="form-text">Used only when Others is selected.</div>
                                            </div>
                                        </div>
                                    </div>
                                </div>

                                <div class="bulk-promo-summary mt-3" id="bulkPromotionSummaryCard">
                                    <div class="d-flex flex-wrap justify-content-between align-items-start gap-2 mb-2">
                                        <div>
                                            <div class="bulk-promo-block-title mb-1">Promotion Summary</div>
                                            <div class="text-muted small">Use <strong>Preview Summary</strong> to calculate the estimated number of students before the final update.</div>
                                        </div>
                                        <?php if (!empty($bulkPromotionPreview) && is_array($bulkPromotionPreview)): ?>
                                            <span class="promotion-history-chip"><i class="bi bi-people"></i> Estimated match: <?php echo (int)($bulkPromotionPreview['matched_count'] ?? 0); ?> student(s)</span>
                                        <?php else: ?>
                                            <span class="promotion-history-chip"><i class="bi bi-info-circle"></i> Estimated count pending preview</span>
                                        <?php endif; ?>
                                    </div>
                                    <div class="bulk-promo-summary-grid">
                                        <div class="bulk-promo-summary-item">
                                            <div class="label">From Level</div>
                                            <div class="value" id="bulkPromotionSummaryFromLevel"><?php echo e((string)($bulkPromotionPreview['from_level'] ?? ($bulkPromotionFormState['from_level'] ?? 'All'))); ?></div>
                                        </div>
                                        <div class="bulk-promo-summary-item">
                                            <div class="label">To Level</div>
                                            <div class="value" id="bulkPromotionSummaryToLevel"><?php echo e((string)($bulkPromotionPreview['to_level'] ?? ($bulkPromotionFormState['to_level'] ?? 'All'))); ?></div>
                                        </div>
                                        <div class="bulk-promo-summary-item">
                                            <div class="label">To Session</div>
                                            <div class="value" id="bulkPromotionSummaryToSession"><?php echo e((string)($bulkPromotionPreview['to_session'] ?? ($bulkPromotionFormState['to_session'] ?? $bulkPromotionCurrentSession))); ?></div>
                                        </div>
                                        <div class="bulk-promo-summary-item">
                                            <div class="label">Estimated Count</div>
                                            <div class="value" id="bulkPromotionSummaryCount"><?php echo !empty($bulkPromotionPreview) ? (int)($bulkPromotionPreview['matched_count'] ?? 0) . ' student(s)' : 'Run Preview Summary'; ?></div>
                                        </div>
                                        <div class="bulk-promo-summary-item">
                                            <div class="label">Faculty</div>
                                            <div class="value" id="bulkPromotionSummaryFaculty"><?php echo e((string)($bulkPromotionPreview['faculty_name'] ?? 'All Faculty')); ?></div>
                                        </div>
                                        <div class="bulk-promo-summary-item">
                                            <div class="label">Department</div>
                                            <div class="value" id="bulkPromotionSummaryDepartment"><?php echo e((string)($bulkPromotionPreview['department_name'] ?? 'All Department')); ?></div>
                                        </div>
                                        <div class="bulk-promo-summary-item">
                                            <div class="label">Programme</div>
                                            <div class="value" id="bulkPromotionSummaryProgramme"><?php echo e((string)($bulkPromotionPreview['programme_name'] ?? 'All Programmes')); ?></div>
                                        </div>
                                        <div class="bulk-promo-summary-item">
                                            <div class="label">Mode of Study</div>
                                            <div class="value" id="bulkPromotionSummaryMode"><?php echo e((string)($bulkPromotionPreview['mode_of_study'] ?? 'All Modes of Study')); ?></div>
                                        </div>
                                    </div>
                                    <div class="bulk-promo-summary-foot">
                                        <?php if (!empty($bulkPromotionPreview) && is_array($bulkPromotionPreview)): ?>
                                            Preview generated on <?php echo e((string)($bulkPromotionPreview['generated_at'] ?? '')); ?>.
                                            <?php if (trim((string)($bulkPromotionPreview['from_session'] ?? '')) !== ''): ?>
                                                Source session(s): <strong><?php echo e((string)$bulkPromotionPreview['from_session']); ?></strong>.
                                            <?php endif; ?>
                                            Refresh the preview after changing any filter.
                                        <?php else: ?>
                                            This summary updates live with your current form selections. Use the preview button to calculate the estimated student count safely before promotion.
                                        <?php endif; ?>
                                    </div>
                                </div>

                                <div class="bulk-promo-block mt-3">
                                    <div class="bulk-promo-block-title">Verification</div>
                                    <div class="row g-3 bulk-promo-row">
                                        <div class="col-xl-6 col-md-6">
                                            <label class="form-label">Superadmin Username / Email</label>
                                            <input class="form-control" name="superadmin_identity" value="<?php echo e((string)($bulkPromotionFormState['superadmin_identity'] ?? '')); ?>" placeholder="Superadmin username or email" required>
                                        </div>
                                        <div class="col-xl-6 col-md-6">
                                            <label class="form-label">Superadmin Password</label>
                                            <input type="password" class="form-control" name="superadmin_password" placeholder="Enter Superadmin password" required>
                                        </div>
                                    </div>
                                </div>

                                <div class="row g-3 bulk-promo-row">
                                    <div class="col-12 bulk-promo-actions">
                                        <div class="text-muted small">The system will verify the supplied Superadmin account, update the matching students, and log the transaction in Promotion History.</div>
                                        <div class="btn-wrap">
                                            <button type="submit" class="btn btn-outline-primary" data-bulk-promo-submit="preview" formnovalidate>Preview Summary</button>
                                            <button type="submit" class="btn btn-primary px-4" data-bulk-promo-submit="promote">Promote</button>
                                        </div>
                                    </div>
                                </div>

                                <div class="row g-3 bulk-promo-row">
                                    <div class="col-12 bulk-promo-help">
                                        <div class="form-text">This updates <code>students.level</code> and <code>students.session</code> only after Superadmin password verification succeeds. When Mode of Study is selected, only students matching that saved student mode are promoted. Promotion history, matched count, and fee re-synchronization outcomes are recorded below.</div>
                                    </div>
                                </div>
                            </form>
                        </div>

                        <script>
                        (function(){
                            var form = document.getElementById('bulkPromotionForm');
                            if (!form) return;

                            var actionInput = document.getElementById('bulkPromotionAction');
                            var modeSelect = document.getElementById('bulkPromotionModeOfStudy');
                            var customWrap = document.getElementById('bulkPromotionModeOfStudyCustomWrap');
                            var customInput = document.getElementById('bulkPromotionModeOfStudyCustom');
                            var summaryMap = {
                                fromLevel: document.getElementById('bulkPromotionSummaryFromLevel'),
                                toLevel: document.getElementById('bulkPromotionSummaryToLevel'),
                                toSession: document.getElementById('bulkPromotionSummaryToSession'),
                                faculty: document.getElementById('bulkPromotionSummaryFaculty'),
                                department: document.getElementById('bulkPromotionSummaryDepartment'),
                                programme: document.getElementById('bulkPromotionSummaryProgramme'),
                                mode: document.getElementById('bulkPromotionSummaryMode')
                            };

                            function optionText(el, fallback){
                                if (!el) return fallback || '';
                                var idx = el.selectedIndex >= 0 ? el.selectedIndex : 0;
                                var opt = el.options[idx];
                                var txt = opt ? String(opt.text || '') : '';
                                return txt || (fallback || '');
                            }

                            function syncModeField() {
                                if (!modeSelect || !customWrap || !customInput) return;
                                var needsCustom = String(modeSelect.value || '') === 'Others';
                                customWrap.style.display = needsCustom ? '' : 'none';
                                customInput.disabled = !needsCustom;
                                if (!needsCustom) customInput.value = '';
                            }

                            function syncSummary() {
                                var fromLevel = form.querySelector('[name="from_level"]');
                                var toLevel = form.querySelector('[name="to_level"]');
                                var toSession = form.querySelector('[name="to_session"]');
                                var faculty = form.querySelector('[name="faculty_id"]');
                                var department = form.querySelector('[name="department_id"]');
                                var programme = form.querySelector('[name="programme_id"]');
                                if (summaryMap.fromLevel) summaryMap.fromLevel.textContent = optionText(fromLevel, 'All');
                                if (summaryMap.toLevel) summaryMap.toLevel.textContent = optionText(toLevel, 'All');
                                if (summaryMap.toSession) summaryMap.toSession.textContent = optionText(toSession, 'Not Set');
                                if (summaryMap.faculty) summaryMap.faculty.textContent = optionText(faculty, 'All Faculty');
                                if (summaryMap.department) summaryMap.department.textContent = optionText(department, 'All Department');
                                if (summaryMap.programme) summaryMap.programme.textContent = optionText(programme, 'All Programmes');
                                if (summaryMap.mode) {
                                    var modeText = optionText(modeSelect, 'All Modes of Study');
                                    if (String(modeSelect && modeSelect.value || '') === 'Others' && customInput && String(customInput.value || '').trim() !== '') {
                                        modeText = String(customInput.value || '').trim();
                                    }
                                    summaryMap.mode.textContent = modeText;
                                }
                            }

                            form.querySelectorAll('select, input').forEach(function(el){
                                el.addEventListener('change', function(){ syncModeField(); syncSummary(); });
                                if (el.type === 'text' || el.type === 'password') {
                                    el.addEventListener('input', function(){ syncSummary(); });
                                }
                            });

                            form.querySelectorAll('[data-bulk-promo-submit]').forEach(function(btn){
                                btn.addEventListener('click', function(){
                                    if (!actionInput) return;
                                    var submitMode = String(btn.getAttribute('data-bulk-promo-submit') || 'promote');
                                    actionInput.value = (submitMode === 'preview') ? 'preview_promote_students' : 'promote_students';
                                });
                            });

                            form.addEventListener('submit', function(ev){
                                var currentAction = actionInput ? String(actionInput.value || 'promote_students') : 'promote_students';
                                if (currentAction === 'promote_students') {
                                    if (!window.confirm('Proceed with promotion? Superadmin verification will be checked before student level/session is updated.')) {
                                        ev.preventDefault();
                                        return false;
                                    }
                                }
                                return true;
                            });

                            syncModeField();
                            syncSummary();
                        })();
                        </script>
                    </div>

                    <div class="tab-pane fade <?php echo ($sessionSubtab === 'promotion_history') ? 'show active' : ''; ?>" id="sessionPromotionHistoryPane" role="tabpanel">
                        <div class="session-pane-card">
                            <div class="d-flex flex-wrap justify-content-between align-items-start gap-2">
                                <div>
                                    <h6 class="mb-1"><i class="bi bi-clock-history"></i> Promotion History</h6>
                                    <div class="text-muted small">Review each bulk-promotion transaction, the filters used, how many students were matched, how many were updated, and whether fee re-synchronization ran.</div>
                                </div>
                                <div class="promotion-history-meta">
                                    <span class="promotion-history-chip"><i class="bi bi-list-check"></i> Total Records: <?php echo (int)$promotionHistoryAllCount; ?></span>
                                    <span class="promotion-history-chip"><i class="bi bi-filter-circle"></i> Showing: <?php echo (int)$promotionHistoryTotal; ?></span>
                                </div>
                            </div>

                            <form method="get" class="promotion-history-toolbar mt-3">
                                <input type="hidden" name="page" value="admin_sessions_manager">
                                <input type="hidden" name="subtab" value="promotion_history">
                                <div class="row g-3">
                                    <div class="col-xl-3 col-md-6">
                                        <label class="form-label">Search Admin / Message</label>
                                        <input type="text" class="form-control" name="promotion_history_q" value="<?php echo e($promotionHistorySearch); ?>" placeholder="Admin username, email, or note">
                                    </div>
                                    <div class="col-xl-2 col-md-6">
                                        <label class="form-label">To Session</label>
                                        <select class="form-select" name="promotion_history_session">
                                            <option value="">All Sessions</option>
                                            <?php foreach ($bulkPromotionSessions as $sessionOpt): ?>
                                                <?php $sessionOpt = portalNormalizeSessionNameSafe((string)$sessionOpt); if ($sessionOpt === '') continue; ?>
                                                <option value="<?php echo e($sessionOpt); ?>" <?php echo (strcasecmp($promotionHistorySession, $sessionOpt) === 0) ? 'selected' : ''; ?>><?php echo e($sessionOpt); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-xl-2 col-md-6">
                                        <label class="form-label">Level</label>
                                        <select class="form-select" name="promotion_history_level">
                                            <option value="">All Levels</option>
                                            <?php foreach ($bulkPromotionLevels as $levelOpt): ?>
                                                <?php $levelOpt = (string)$levelOpt; ?>
                                                <option value="<?php echo e($levelOpt); ?>" <?php echo (strcasecmp($promotionHistoryLevel, $levelOpt) === 0) ? 'selected' : ''; ?>><?php echo e($levelOpt); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-xl-2 col-md-6">
                                        <label class="form-label">Status</label>
                                        <select class="form-select" name="promotion_history_status">
                                            <option value="">All Statuses</option>
                                            <?php foreach ($promotionHistoryStatusOptions as $statusOpt): ?>
                                                <option value="<?php echo e($statusOpt); ?>" <?php echo (strcasecmp($promotionHistoryStatus, $statusOpt) === 0) ? 'selected' : ''; ?>><?php echo e($statusOpt); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-xl-1 col-md-6">
                                        <label class="form-label">From</label>
                                        <input type="date" class="form-control" name="promotion_history_from" value="<?php echo e($promotionHistoryDateFrom); ?>">
                                    </div>
                                    <div class="col-xl-1 col-md-6">
                                        <label class="form-label">To</label>
                                        <input type="date" class="form-control" name="promotion_history_to" value="<?php echo e($promotionHistoryDateTo); ?>">
                                    </div>
                                    <div class="col-12 d-flex flex-wrap gap-2">
                                        <button class="btn btn-primary" type="submit">Apply Filters</button>
                                        <a class="btn btn-outline-secondary" href="?page=admin_sessions_manager&subtab=promotion_history">Reset</a>
                                    </div>
                                </div>
                            </form>

                            <div class="table-responsive">
                                <table class="table table-striped align-middle promotion-history-table">
                                    <thead>
                                        <tr>
                                            <th>Date / Time</th>
                                            <th>Admin / Superadmin</th>
                                            <th>From → To</th>
                                            <th>Scope Used</th>
                                            <th>Matched / Updated</th>
                                            <th>Status</th>
                                            <th>Re-sync</th>
                                            <th>Action</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <?php foreach ($promotionHistoryRows as $historyRow): ?>
                                            <?php
                                                $historyId = (int)($historyRow['id'] ?? 0);
                                                $historyStatus = trim((string)($historyRow['status'] ?? 'Successful'));
                                                $historyBadge = 'text-bg-success';
                                                if ($historyStatus === 'Partial') $historyBadge = 'text-bg-warning';
                                                elseif ($historyStatus === 'Failed') $historyBadge = 'text-bg-danger';
                                                elseif ($historyStatus === 'No Match') $historyBadge = 'text-bg-secondary';
                                                $historyWarnings = json_decode((string)($historyRow['warnings_json'] ?? ''), true);
                                                if (!is_array($historyWarnings)) $historyWarnings = [];
                                                $historyDetails = json_decode((string)($historyRow['details_json'] ?? ''), true);
                                                if (!is_array($historyDetails)) $historyDetails = [];
                                                $historyActor = trim((string)($historyRow['acting_admin_name'] ?? ''));
                                                if ($historyActor === '') $historyActor = trim((string)($historyRow['performed_by_identity'] ?? ''));
                                            ?>
                                            <tr>
                                                <td>
                                                    <div class="fw-semibold"><?php echo e(date('Y-m-d', strtotime((string)($historyRow['created_at'] ?? 'now')))); ?></div>
                                                    <div class="small text-muted"><?php echo e(date('H:i:s', strtotime((string)($historyRow['created_at'] ?? 'now')))); ?></div>
                                                </td>
                                                <td>
                                                    <div class="fw-semibold"><?php echo e($historyActor !== '' ? $historyActor : 'System'); ?></div>
                                                    <div class="small text-muted"><?php echo e((string)($historyRow['performed_by_identity'] ?? '')); ?></div>
                                                </td>
                                                <td>
                                                    <div><strong><?php echo e((string)($historyRow['from_level'] ?? 'All')); ?></strong> → <strong><?php echo e((string)($historyRow['to_level'] ?? 'All')); ?></strong></div>
                                                    <div class="small text-muted"><?php echo e((string)($historyRow['to_session'] ?? '')); ?></div>
                                                    <?php if (trim((string)($historyRow['from_session'] ?? '')) !== ''): ?>
                                                        <div class="small text-muted">Source: <?php echo e((string)$historyRow['from_session']); ?></div>
                                                    <?php endif; ?>
                                                </td>
                                                <td>
                                                    <div class="small"><strong>Faculty:</strong> <?php echo e((string)($historyRow['faculty_name'] ?? 'All Faculty')); ?></div>
                                                    <div class="small"><strong>Department:</strong> <?php echo e((string)($historyRow['department_name'] ?? 'All Department')); ?></div>
                                                    <div class="small"><strong>Programme:</strong> <?php echo e((string)($historyRow['programme_name'] ?? 'All Programmes')); ?></div>
                                                    <div class="small"><strong>Mode:</strong> <?php echo e((string)($historyRow['mode_of_study'] ?? 'All Modes of Study')); ?></div>
                                                </td>
                                                <td>
                                                    <div class="fw-semibold"><?php echo (int)($historyRow['matched_count'] ?? 0); ?> matched</div>
                                                    <div class="small text-muted"><?php echo (int)($historyRow['updated_count'] ?? 0); ?> updated</div>
                                                </td>
                                                <td>
                                                    <span class="badge <?php echo e($historyBadge); ?>"><?php echo e($historyStatus); ?></span>
                                                    <?php if (trim((string)($historyRow['message'] ?? '')) !== ''): ?>
                                                        <div class="small text-muted mt-2"><?php echo e((string)$historyRow['message']); ?></div>
                                                    <?php endif; ?>
                                                </td>
                                                <td>
                                                    <div class="small"><strong>School:</strong> <?php echo !empty($historyRow['school_fee_resync_triggered']) ? 'Yes' : 'No'; ?></div>
                                                    <div class="small"><strong>Hostel:</strong> <?php echo !empty($historyRow['hostel_fee_resync_triggered']) ? 'Yes' : 'No'; ?></div>
                                                </td>
                                                <td>
                                                    <button class="btn btn-sm btn-outline-primary" type="button" data-bs-toggle="collapse" data-bs-target="#promotionHistoryDetail<?php echo $historyId; ?>" aria-expanded="false" aria-controls="promotionHistoryDetail<?php echo $historyId; ?>">View Details</button>
                                                </td>
                                            </tr>
                                            <tr class="collapse promotion-history-detail" id="promotionHistoryDetail<?php echo $historyId; ?>">
                                                <td colspan="8">
                                                    <div class="promotion-history-detail-grid">
                                                        <div>
                                                            <div class="label">Notes / Message</div>
                                                            <div class="value"><?php echo e((string)($historyRow['message'] ?? 'No notes recorded.')); ?></div>
                                                        </div>
                                                        <div>
                                                            <div class="label">Warning Count</div>
                                                            <div class="value"><?php echo (int)($historyRow['warning_count'] ?? 0); ?></div>
                                                        </div>
                                                        <div>
                                                            <div class="label">Warnings</div>
                                                            <div class="value">
                                                                <?php if ($historyWarnings): ?>
                                                                    <ul class="mb-0 ps-3">
                                                                        <?php foreach ($historyWarnings as $warningItem): ?>
                                                                            <li><?php echo e((string)$warningItem); ?></li>
                                                                        <?php endforeach; ?>
                                                                    </ul>
                                                                <?php else: ?>
                                                                    No warnings recorded.
                                                                <?php endif; ?>
                                                            </div>
                                                        </div>
                                                        <div>
                                                            <div class="label">Stored Details</div>
                                                            <div class="value">
                                                                <div><strong>From Session:</strong> <?php echo e((string)($historyRow['from_session'] ?? '')); ?></div>
                                                                <div><strong>Target Session:</strong> <?php echo e((string)($historyRow['to_session'] ?? '')); ?></div>
                                                                <div><strong>Filter Type:</strong> <?php echo e((string)($historyRow['mode_of_study_filter_type'] ?? '')); ?></div>
                                                                <?php if (!empty($historyDetails['filters']) && is_array($historyDetails['filters'])): ?>
                                                                    <div class="small text-muted mt-2"><?php echo e(json_encode($historyDetails['filters'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); ?></div>
                                                                <?php endif; ?>
                                                            </div>
                                                        </div>
                                                    </div>
                                                </td>
                                            </tr>
                                        <?php endforeach; ?>
                                        <?php if (!$promotionHistoryRows): ?>
                                            <tr><td colspan="8" class="text-center text-muted py-4">No promotion history records found for the selected filters yet.</td></tr>
                                        <?php endif; ?>
                                    </tbody>
                                </table>
                            </div>

                            <?php if ($promotionHistoryPages > 1): ?>
                                <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 pt-3">
                                    <div class="small text-muted">Page <?php echo (int)$promotionHistoryPage; ?> of <?php echo (int)$promotionHistoryPages; ?></div>
                                    <div class="btn-group">
                                        <a class="btn btn-sm btn-outline-secondary <?php echo ($promotionHistoryPage <= 1) ? 'disabled' : ''; ?>" href="<?php echo e($promotionHistoryUrl(['promotion_history_page' => max(1, $promotionHistoryPage - 1)])); ?>">Previous</a>
                                        <a class="btn btn-sm btn-outline-secondary <?php echo ($promotionHistoryPage >= $promotionHistoryPages) ? 'disabled' : ''; ?>" href="<?php echo e($promotionHistoryUrl(['promotion_history_page' => min($promotionHistoryPages, $promotionHistoryPage + 1)])); ?>">Next</a>
                                    </div>
                                </div>
                            <?php endif; ?>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;




case 'admin_registration_summary_report':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    $reportMode = trim((string)($_GET['mode'] ?? 'saved'));
    $reportMode = in_array($reportMode, ['saved','yet_to_register'], true) ? $reportMode : 'saved';
    $reportPrint = ((string)($_GET['print'] ?? '') === '1');
    $reportDownload = ((string)($_GET['download'] ?? '') === '1');

    if ($reportMode === 'saved') {
        $currentRegistrationSession = portalNormalizeSessionNameSafe((string)portalCurrentAcademicSessionName($pdo));
        $filterSess  = portalNormalizeSessionNameSafe((string)($_GET['session_name'] ?? $currentRegistrationSession));
        $filterSem   = trim((string)($_GET['semester'] ?? ''));
        if ($filterSem !== '' && !in_array($filterSem, ['First','Second'], true)) { $filterSem = ''; }
        $filterFac   = max(0, (int)($_GET['faculty_id'] ?? 0));
        $filterDept  = max(0, (int)($_GET['department_id'] ?? 0));
        $filterProg  = max(0, (int)($_GET['programme_id'] ?? 0));
        $filterLevel = trim((string)($_GET['level'] ?? ''));
        $q = trim((string)($_GET['q'] ?? ''));

        $faculties = []; $departments = []; $programmes = [];
        try { $faculties = $pdo->query("SELECT * FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
        try { $departments = $pdo->query("SELECT d.*, f.name AS faculty_name FROM departments d LEFT JOIN faculties f ON f.id = d.faculty_id ORDER BY f.name ASC, d.name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
        try { $programmes = $pdo->query("SELECT p.*, d.name AS department_name FROM programmes p LEFT JOIN departments d ON d.id = p.department_id ORDER BY p.name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}

        $where = []; $params = [];
        if ($filterSess !== '') { $where[] = "COALESCE(cr.session_name,'') = ?"; $params[] = $filterSess; }
        if ($filterSem !== '') { $where[] = "cr.semester = ?"; $params[] = $filterSem; }
        if ($filterFac > 0) { $where[] = "cr.faculty_id = ?"; $params[] = $filterFac; }
        if ($filterDept > 0) { $where[] = "cr.department_id = ?"; $params[] = $filterDept; }
        if ($filterProg > 0) { $where[] = "cr.programme_id = ?"; $params[] = $filterProg; }
        if ($filterLevel !== '') { $where[] = "COALESCE(cr.level,'') = ?"; $params[] = $filterLevel; }
        if ($q !== '') {
            $studentFirstNameExpr = portalStudentFirstNameSql($pdo, 's');
            $studentOtherNameExpr = portalStudentOtherNameSql($pdo, 's');
            $where[] = "(COALESCE(s.matric_no,'') LIKE ? OR COALESCE(s.registration_no,'') LIKE ? OR COALESCE(s.application_no,'') LIKE ? OR COALESCE(s.email,'') LIKE ? OR COALESCE(s.surname,'') LIKE ? OR COALESCE({$studentFirstNameExpr},'') LIKE ? OR COALESCE({$studentOtherNameExpr},'') LIKE ?)";
            $like = '%' . $q . '%';
            array_push($params, $like, $like, $like, $like, $like, $like, $like);
        }
        $whereSql = $where ? implode(' AND ', $where) : '1=1';

        $regs = [];
        try {
            $studentSelect = portalCourseRegistrationStudentSelectList($pdo, 's');
            $stmt = $pdo->prepare("SELECT cr.*, {$studentSelect}, f.name AS faculty_name, d.name AS department_name, p.name AS programme_name FROM course_registrations cr LEFT JOIN students s ON s.id = cr.student_id LEFT JOIN faculties f ON f.id = cr.faculty_id LEFT JOIN departments d ON d.id = cr.department_id LEFT JOIN programmes p ON p.id = cr.programme_id WHERE {$whereSql} ORDER BY COALESCE(cr.created_at, cr.updated_at) DESC, cr.id DESC LIMIT 500");
            $stmt->execute($params);
            $regs = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) { $regs = []; }

        $selectedFacultyName = 'All Faculties';
        foreach ($faculties as $facultyRow) { if ((int)($facultyRow['id'] ?? 0) === $filterFac) { $selectedFacultyName = (string)($facultyRow['name'] ?? 'All Faculties'); break; } }
        $selectedDepartmentName = 'All Departments';
        foreach ($departments as $deptRow) { if ((int)($deptRow['id'] ?? 0) === $filterDept) { $selectedDepartmentName = (string)($deptRow['name'] ?? 'All Departments'); break; } }

        portalRenderRegistrationSummaryReport($pdo, 'Saved Course Registration Summary', $regs, [
            'faculty' => $selectedFacultyName,
            'department' => $selectedDepartmentName,
            'session_name' => $filterSess !== '' ? $filterSess : 'All Sessions',
            'semester' => $filterSem !== '' ? $filterSem : 'All Semesters',
            'level' => $filterLevel !== '' ? $filterLevel : 'All Levels',
            'scope_note' => 'This sheet summarizes the filtered saved course registration records in a compact printable/downloadable table.',
        ], $reportPrint, $reportDownload);
        break;
    }

    $filterSess  = trim((string)($_GET['session_name'] ?? ''));
    $filterSem   = (string)($_GET['semester'] ?? 'First');
    if (!in_array($filterSem, ['First','Second'], true)) $filterSem = 'First';
    $filterFac   = (int)($_GET['faculty_id'] ?? 0);
    $filterDept  = (int)($_GET['department_id'] ?? 0);
    $filterProg  = (int)($_GET['programme_id'] ?? 0);
    $filterLevel = trim((string)($_GET['level'] ?? ''));
    $q = trim((string)($_GET['q'] ?? ''));

    $faculties = []; $departments = []; $programmes = [];
    try { $faculties = $pdo->query("SELECT * FROM faculties ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $departments = $pdo->query("SELECT * FROM departments ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $programmes = $pdo->query("SELECT * FROM programmes ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}

    $rows = [];
    if ($filterSess !== '') {
        try {
            $sql = "SELECT s.*, f.name AS faculty_name, d.name AS department_name, p.name AS programme_name FROM students s LEFT JOIN faculties f ON f.id = s.faculty_id LEFT JOIN departments d ON d.id = s.department_id LEFT JOIN programmes p ON p.id = s.programme_id WHERE 1=1 AND NOT EXISTS (SELECT 1 FROM course_registrations cr WHERE cr.student_id = s.id AND COALESCE(cr.session_name,'') = ? AND cr.semester = ?)";
            $params = [(string)$filterSess, (string)$filterSem];
            if ($filterFac > 0) { $sql .= " AND s.faculty_id = ?"; $params[] = $filterFac; }
            if ($filterDept > 0) { $sql .= " AND s.department_id = ?"; $params[] = $filterDept; }
            if ($filterProg > 0) { $sql .= " AND s.programme_id = ?"; $params[] = $filterProg; }
            if ($filterLevel !== '') { $sql .= " AND COALESCE(s.level,'') = ?"; $params[] = $filterLevel; }
            $studentFirstNameExpr = portalStudentFirstNameSql($pdo, 's');
            if ($q !== '') {
                $sql .= " AND (s.matric_no LIKE ? OR s.reg_no LIKE ? OR s.email LIKE ? OR s.phone LIKE ? OR s.surname LIKE ? OR {$studentFirstNameExpr} LIKE ?)";
                $like = '%' . $q . '%';
                array_push($params, $like, $like, $like, $like, $like, $like);
            }
            $sql .= " ORDER BY s.surname ASC, {$studentFirstNameExpr} ASC, s.id DESC LIMIT 2000";
            $st = $pdo->prepare($sql);
            $st->execute($params);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) { $rows = []; }
    }

    $selectedFacultyName = 'All Faculties';
    foreach ($faculties as $facultyRow) { if ((int)($facultyRow['id'] ?? 0) === $filterFac) { $selectedFacultyName = (string)($facultyRow['name'] ?? 'All Faculties'); break; } }
    $selectedDepartmentName = 'All Departments';
    foreach ($departments as $deptRow) { if ((int)($deptRow['id'] ?? 0) === $filterDept) { $selectedDepartmentName = (string)($deptRow['name'] ?? 'All Departments'); break; } }

    portalRenderRegistrationSummaryReport($pdo, 'Yet To Register Summary', $rows, [
        'faculty' => $selectedFacultyName,
        'department' => $selectedDepartmentName,
        'session_name' => $filterSess !== '' ? $filterSess : 'All Sessions',
        'semester' => $filterSem !== '' ? $filterSem : 'All Semesters',
        'level' => $filterLevel !== '' ? $filterLevel : 'All Levels',
        'scope_note' => 'This sheet summarizes students who are still pending course registration for the selected academic scope.',
    ], $reportPrint, $reportDownload);
    break;

case 'admin_yet_to_register':
    if (!isAdmin()) { redirect('?page=admin_login'); }

    $filterSess  = trim((string)($_GET['session_name'] ?? ''));
    $filterSem   = (string)($_GET['semester'] ?? 'First');
    if (!in_array($filterSem, ['First','Second'], true)) $filterSem = 'First';
    $filterFac   = (int)($_GET['faculty_id'] ?? 0);
    $filterDept  = (int)($_GET['department_id'] ?? 0);
    $filterProg  = (int)($_GET['programme_id'] ?? 0);
    $filterLevel = trim((string)($_GET['level'] ?? ''));
    $q = trim((string)($_GET['q'] ?? ''));

    $faculties = [];
    $departments = [];
    $programmes = [];
    try { $faculties = $pdo->query("SELECT * FROM faculties ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $departments = $pdo->query("SELECT * FROM departments ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $programmes = $pdo->query("SELECT * FROM programmes ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}

    $rows = [];
    $count = 0;
    if ($filterSess !== '') {
        try {
            $sql = "
                SELECT s.*,
                       f.name AS faculty_name,
                       d.name AS department_name,
                       p.name AS programme_name
                FROM students s
                LEFT JOIN faculties f ON f.id = s.faculty_id
                LEFT JOIN departments d ON d.id = s.department_id
                LEFT JOIN programmes p ON p.id = s.programme_id
                WHERE 1=1
                  AND NOT EXISTS (
                    SELECT 1 FROM course_registrations cr
                    WHERE cr.student_id = s.id
                      AND COALESCE(cr.session_name,'') = ?
                      AND cr.semester = ?
                  )
            ";
            $params = [(string)$filterSess, (string)$filterSem];

            if ($filterFac > 0) { $sql .= " AND s.faculty_id = ?"; $params[] = $filterFac; }
            if ($filterDept > 0) { $sql .= " AND s.department_id = ?"; $params[] = $filterDept; }
            if ($filterProg > 0) { $sql .= " AND s.programme_id = ?"; $params[] = $filterProg; }
            if ($filterLevel !== '') { $sql .= " AND COALESCE(s.level,'') = ?"; $params[] = $filterLevel; }
            $studentFirstNameExpr = portalStudentFirstNameSql($pdo, 's');
            if ($q !== '') {
                $sql .= " AND (s.matric_no LIKE ? OR s.reg_no LIKE ? OR s.email LIKE ? OR s.phone LIKE ? OR s.surname LIKE ? OR {$studentFirstNameExpr} LIKE ?)";
                $like = '%' . $q . '%';
                array_push($params, $like, $like, $like, $like, $like, $like);
            }

            $sql .= " ORDER BY s.surname ASC, {$studentFirstNameExpr} ASC, s.id DESC LIMIT 2000";
            $st = $pdo->prepare($sql);
            $st->execute($params);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            $count = count($rows);
        } catch (Throwable $e) {
            flash('danger', 'Failed to load report: ' . ($e->getMessage() ?: 'Error'));
        }
    }
    $sessionOptions = portalAcademicSessionOptions($pdo);
    $currentSessionYtr = trim((string)portalCurrentAcademicSessionName($pdo));
    if ($currentSessionYtr !== '' && !in_array($currentSessionYtr, $sessionOptions, true)) {
        array_unshift($sessionOptions, $currentSessionYtr);
    }
    if (!in_array((string)$filterSess, $sessionOptions, true) && $filterSess !== '') { $sessionOptions[] = (string)$filterSess; }

    $selectedFacultyName = 'All Faculties';
    foreach ($faculties as $facultyRow) {
        if ((int)($facultyRow['id'] ?? 0) === $filterFac) { $selectedFacultyName = (string)($facultyRow['name'] ?? 'All Faculties'); break; }
    }
    $selectedDepartmentName = 'All Departments';
    foreach ($departments as $deptRow) {
        if ((int)($deptRow['id'] ?? 0) === $filterDept) { $selectedDepartmentName = (string)($deptRow['name'] ?? 'All Departments'); break; }
    }
    $selectedProgrammeName = 'All Programmes';
    foreach ($programmes as $programmeRow) {
        if ((int)($programmeRow['id'] ?? 0) === $filterProg) { $selectedProgrammeName = (string)($programmeRow['name'] ?? 'All Programmes'); break; }
    }

    $facultyScopeCount = 0;
    $departmentScopeCount = 0;
    $programmeScopeCount = 0;
    $levelScopeCount = 0;
    if ($rows) {
        $facultyBucket = [];
        $departmentBucket = [];
        $programmeBucket = [];
        $levelBucket = [];
        foreach ($rows as $reportRow) {
            $facultyLabel = trim((string)($reportRow['faculty_name'] ?? $reportRow['faculty'] ?? ''));
            $departmentLabel = trim((string)($reportRow['department_name'] ?? $reportRow['department'] ?? ''));
            $programmeLabel = trim((string)($reportRow['programme_name'] ?? $reportRow['programme'] ?? ''));
            $levelLabel = trim((string)($reportRow['level'] ?? ''));
            if ($facultyLabel !== '') $facultyBucket[$facultyLabel] = true;
            if ($departmentLabel !== '') $departmentBucket[$departmentLabel] = true;
            if ($programmeLabel !== '') $programmeBucket[$programmeLabel] = true;
            if ($levelLabel !== '') $levelBucket[$levelLabel] = true;
        }
        $facultyScopeCount = count($facultyBucket);
        $departmentScopeCount = count($departmentBucket);
        $programmeScopeCount = count($programmeBucket);
        $levelScopeCount = count($levelBucket);
    }
    ?>
    <style>
        .yet-register-page .ytr-hero,
        .yet-register-page .ytr-filter-card,
        .yet-register-page .ytr-kpi,
        .yet-register-page .ytr-results-card {
            border: 1px solid #d7e3f4;
            border-radius: 18px;
            box-shadow: 0 10px 28px rgba(10, 63, 126, .06);
        }
        .yet-register-page .ytr-hero {
            background: linear-gradient(135deg, #ffffff 0%, #f6faff 100%);
            padding: 1.25rem 1.25rem 1.1rem;
            margin-bottom: 1rem;
        }
        .yet-register-page .ytr-eyebrow,
        .yet-register-page .ytr-kpi .small-title {
            font-size: .74rem;
            letter-spacing: .12em;
            text-transform: uppercase;
            font-weight: 700;
            color: #5f7591;
        }
        .yet-register-page .ytr-scope-badge {
            display: inline-flex;
            align-items: center;
            gap: .35rem;
            border-radius: 999px;
            padding: .3rem .7rem;
            background: #edf5ff;
            color: #0b4f9b;
            font-size: .78rem;
            font-weight: 700;
            border: 1px solid #d3e4fb;
        }
        .yet-register-page .ytr-filter-card {
            background: #fff;
            overflow: hidden;
            margin-bottom: 1rem;
        }
        .yet-register-page .ytr-filter-head,
        .yet-register-page .ytr-results-head {
            background: #0b3f7e;
            color: #fff;
            padding: .95rem 1.15rem;
        }
        .yet-register-page .ytr-filter-body,
        .yet-register-page .ytr-results-body {
            padding: 1.15rem;
        }
        .yet-register-page .ytr-chip-wrap {
            display: flex;
            flex-wrap: wrap;
            gap: .45rem;
            margin-top: .9rem;
        }
        .yet-register-page .ytr-chip {
            display: inline-flex;
            align-items: center;
            gap: .35rem;
            padding: .35rem .7rem;
            border-radius: 999px;
            border: 1px solid #d7e3f4;
            background: #f8fbff;
            color: #204262;
            font-size: .82rem;
        }
        .yet-register-page .ytr-kpi {
            background: #fff;
            padding: 1rem 1.05rem;
            height: 100%;
        }
        .yet-register-page .ytr-kpi .value {
            font-size: 1.95rem;
            line-height: 1;
            font-weight: 800;
            color: #0b3f7e;
        }
        .yet-register-page .ytr-kpi .desc {
            color: #66788f;
            font-size: .92rem;
            margin-top: .3rem;
        }
        .yet-register-page .ytr-empty {
            border: 1px dashed #cddcf1;
            border-radius: 18px;
            padding: 2rem 1rem;
            text-align: center;
            background: linear-gradient(180deg, #f9fbfe 0%, #f3f8ff 100%);
        }
        .yet-register-page .ytr-empty .icon {
            width: 68px;
            height: 68px;
            border-radius: 50%;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            background: #e9f1ff;
            color: #0b4f9b;
            font-size: 1.6rem;
            margin-bottom: .9rem;
        }
        .yet-register-page .table thead th {
            white-space: nowrap;
            font-size: .84rem;
            text-transform: uppercase;
            letter-spacing: .04em;
            color: #4f6277;
            background: #f5f8fc;
            border-bottom-width: 1px;
        }
        .yet-register-page .table tbody td {
            vertical-align: middle;
        }
        .yet-register-page .ytr-print-note {
            color: #64748b;
            font-size: .88rem;
        }
        @media print {
            .yet-register-page .ytr-no-print,
            .yet-register-page .btn,
            .yet-register-page .sidebar,
            .yet-register-page .navbar,
            .yet-register-page .chat-widget,
            .yet-register-page .offcanvas,
            .yet-register-page .modal { display: none !important; }
            .yet-register-page .ytr-hero,
            .yet-register-page .ytr-filter-card,
            .yet-register-page .ytr-kpi,
            .yet-register-page .ytr-results-card {
                box-shadow: none !important;
                border-color: #d0d7e2 !important;
            }
            .yet-register-page .container-fluid { padding: 0 !important; }
        }
    </style>
    <div class="container-fluid yet-register-page">
        <div class="ytr-hero">
            <div class="d-flex flex-wrap align-items-start justify-content-between gap-3">
                <div>
                    <div class="ytr-eyebrow mb-2">Registration Monitoring</div>
                    <h4 class="mb-2">Yet To Register</h4>
                    <div class="text-muted">
                        <?php if ($filterSess !== ''): ?>
                            Students who have not completed course registration for <strong><?php echo e($filterSess); ?></strong> in the <strong><?php echo e($filterSem); ?></strong> semester.
                        <?php else: ?>
                            Select an academic session and filter the scope to load students who are still pending course registration.
                        <?php endif; ?>
                    </div>
                </div>
                <div class="d-flex flex-wrap gap-2 ytr-no-print">
                    <?php if ($currentSessionYtr !== ''): ?>
                        <span class="ytr-scope-badge"><i class="bi bi-calendar-event"></i> Current Session: <?php echo e($currentSessionYtr); ?></span>
                    <?php endif; ?>
                    <a class="btn btn-outline-secondary btn-sm" href="?page=admin_registrations_manager"><i class="bi bi-table"></i> View Registrations</a>
                    <a class="btn btn-outline-primary btn-sm" href="?page=admin_registration_settings"><i class="bi bi-sliders"></i> Registration Settings</a>
                </div>
            </div>
            <?php if ($filterSess !== '' || $filterFac > 0 || $filterDept > 0 || $filterProg > 0 || $filterLevel !== '' || $q !== ''): ?>
                <div class="ytr-chip-wrap">
                    <?php if ($filterSess !== ''): ?><span class="ytr-chip"><i class="bi bi-calendar3"></i> Session: <?php echo e($filterSess); ?></span><?php endif; ?>
                    <span class="ytr-chip"><i class="bi bi-book"></i> Semester: <?php echo e($filterSem); ?></span>
                    <?php if ($filterFac > 0): ?><span class="ytr-chip"><i class="bi bi-building"></i> <?php echo e($selectedFacultyName); ?></span><?php endif; ?>
                    <?php if ($filterDept > 0): ?><span class="ytr-chip"><i class="bi bi-diagram-3"></i> <?php echo e($selectedDepartmentName); ?></span><?php endif; ?>
                    <?php if ($filterProg > 0): ?><span class="ytr-chip"><i class="bi bi-mortarboard"></i> <?php echo e($selectedProgrammeName); ?></span><?php endif; ?>
                    <?php if ($filterLevel !== ''): ?><span class="ytr-chip"><i class="bi bi-bar-chart-steps"></i> Level: <?php echo e($filterLevel); ?></span><?php endif; ?>
                    <?php if ($q !== ''): ?><span class="ytr-chip"><i class="bi bi-search"></i> Search: <?php echo e($q); ?></span><?php endif; ?>
                </div>
            <?php endif; ?>
        </div>

        <div class="ytr-filter-card">
            <div class="ytr-filter-head d-flex flex-wrap align-items-center justify-content-between gap-2">
                <div>
                    <div class="fw-semibold">Filter Scope</div>
                    <div class="small opacity-75">Narrow the list by session, semester, academic structure, level, or student lookup.</div>
                </div>
                <div class="d-flex flex-wrap gap-2 ytr-no-print">
                    <a class="btn btn-light btn-sm" target="_blank" href="?page=admin_registration_summary_report&mode=yet_to_register&print=1&session_name=<?php echo urlencode((string)$filterSess); ?>&semester=<?php echo urlencode((string)$filterSem); ?>&faculty_id=<?php echo (int)$filterFac; ?>&department_id=<?php echo (int)$filterDept; ?>&programme_id=<?php echo (int)$filterProg; ?>&level=<?php echo urlencode((string)$filterLevel); ?>&q=<?php echo urlencode((string)$q); ?>"><i class="bi bi-printer"></i> Print Summary</a>
                    <a class="btn btn-outline-light btn-sm" target="_blank" href="?page=admin_registration_summary_report&mode=yet_to_register&download=1&session_name=<?php echo urlencode((string)$filterSess); ?>&semester=<?php echo urlencode((string)$filterSem); ?>&faculty_id=<?php echo (int)$filterFac; ?>&department_id=<?php echo (int)$filterDept; ?>&programme_id=<?php echo (int)$filterProg; ?>&level=<?php echo urlencode((string)$filterLevel); ?>&q=<?php echo urlencode((string)$q); ?>"><i class="bi bi-download"></i> Download Summary</a>
                </div>
            </div>
            <div class="ytr-filter-body">
                <form class="row g-3 align-items-end" method="get">
                    <input type="hidden" name="page" value="admin_yet_to_register">
                    <div class="col-xl-4 col-lg-5 col-md-6">
                        <label class="form-label fw-semibold">Session</label>
                        <select class="form-select" name="session_name">
                            <option value="">Select Session</option>
                            <?php foreach ($sessionOptions as $sn): ?>
                                <option value="<?php echo e($sn); ?>" <?php echo ((string)$filterSess === (string)$sn) ? 'selected' : ''; ?>><?php echo e($sn); ?></option>
                            <?php endforeach; ?>
                        </select>
                        <div class="form-text">Choose the academic session you want to audit for incomplete registration.</div>
                    </div>
                    <div class="col-xl-2 col-lg-3 col-md-3 col-sm-6">
                        <label class="form-label fw-semibold">Semester</label>
                        <select class="form-select" name="semester">
                            <option value="First" <?php echo $filterSem==='First'?'selected':''; ?>>First</option>
                            <option value="Second" <?php echo $filterSem==='Second'?'selected':''; ?>>Second</option>
                        </select>
                    </div>
                    <div class="col-xl-2 col-lg-4 col-md-3 col-sm-6">
                        <label class="form-label fw-semibold">Faculty</label>
                        <select class="form-select" name="faculty_id">
                            <option value="0">All Faculties</option>
                            <?php foreach ($faculties as $f): ?>
                                <option value="<?php echo (int)$f['id']; ?>" <?php echo $filterFac===(int)$f['id']?'selected':''; ?>><?php echo e($f['name']); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-xl-2 col-lg-4 col-md-4 col-sm-6">
                        <label class="form-label fw-semibold">Department</label>
                        <select class="form-select" name="department_id">
                            <option value="0">All Departments</option>
                            <?php foreach ($departments as $d): ?>
                                <option value="<?php echo (int)$d['id']; ?>" <?php echo $filterDept===(int)$d['id']?'selected':''; ?>><?php echo e($d['name']); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-xl-2 col-lg-4 col-md-4 col-sm-6">
                        <label class="form-label fw-semibold">Programme</label>
                        <select class="form-select" name="programme_id">
                            <option value="0">All Programmes</option>
                            <?php foreach ($programmes as $p): ?>
                                <option value="<?php echo (int)$p['id']; ?>" <?php echo $filterProg===(int)$p['id']?'selected':''; ?>><?php echo e($p['name']); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-xl-2 col-lg-3 col-md-3 col-sm-6">
                        <label class="form-label fw-semibold">Level</label>
                        <select class="form-select" name="level">
                            <option value="">All Levels</option>
                            <?php foreach (portalAcademicLevelOptions($pdo) as $lv): ?>
                                <option value="<?php echo e($lv); ?>" <?php echo ((string)$filterLevel === (string)$lv) ? 'selected' : ''; ?>><?php echo e($lv); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-xl-4 col-lg-5 col-md-5 col-sm-6">
                        <label class="form-label fw-semibold">Search Student</label>
                        <input class="form-control" name="q" value="<?php echo e($q); ?>" placeholder="matric no / reg no / name / email / phone">
                    </div>
                    <div class="col-12 d-flex flex-wrap gap-2 pt-1 ytr-no-print">
                        <button class="btn btn-primary" type="submit"><i class="bi bi-funnel"></i> Apply Filter</button>
                        <a class="btn btn-outline-secondary" href="?page=admin_yet_to_register"><i class="bi bi-arrow-counterclockwise"></i> Reset</a>
                    </div>
                </form>
            </div>
        </div>

        <div class="row g-3 mb-3">
            <div class="col-lg-3 col-sm-6">
                <div class="ytr-kpi">
                    <div class="small-title mb-2">Pending Students</div>
                    <div class="value"><?php echo number_format((int)$count); ?></div>
                    <div class="desc"><?php echo $filterSess !== '' ? 'Within the selected registration scope' : 'Waiting for a session filter'; ?></div>
                </div>
            </div>
            <div class="col-lg-3 col-sm-6">
                <div class="ytr-kpi">
                    <div class="small-title mb-2">Faculties in Scope</div>
                    <div class="value"><?php echo number_format((int)$facultyScopeCount); ?></div>
                    <div class="desc">Distinct faculties represented by the pending list</div>
                </div>
            </div>
            <div class="col-lg-3 col-sm-6">
                <div class="ytr-kpi">
                    <div class="small-title mb-2">Departments in Scope</div>
                    <div class="value"><?php echo number_format((int)$departmentScopeCount); ?></div>
                    <div class="desc">Departments captured after applying the current filters</div>
                </div>
            </div>
            <div class="col-lg-3 col-sm-6">
                <div class="ytr-kpi">
                    <div class="small-title mb-2">Programmes / Levels</div>
                    <div class="value"><?php echo number_format((int)$programmeScopeCount); ?><span style="font-size:1rem; font-weight:700; color:#678;"> / <?php echo number_format((int)$levelScopeCount); ?></span></div>
                    <div class="desc">Programme count versus active level spread in the current result</div>
                </div>
            </div>
        </div>

        <div class="ytr-results-card">
            <div class="ytr-results-head d-flex flex-wrap justify-content-between align-items-center gap-2">
                <div>
                    <div class="fw-semibold">Result</div>
                    <div class="small opacity-75">This list is generated from students without a saved <code>course_registrations</code> record in the selected session and semester.</div>
                </div>
                <div class="d-flex flex-wrap align-items-center gap-2 ytr-no-print">
                    <span class="badge bg-light text-dark rounded-pill px-3 py-2"><?php echo (int)$count; ?> student(s)</span>
                    <a class="btn btn-sm btn-outline-light" target="_blank" href="?page=admin_registration_summary_report&mode=yet_to_register&print=1&session_name=<?php echo urlencode((string)$filterSess); ?>&semester=<?php echo urlencode((string)$filterSem); ?>&faculty_id=<?php echo (int)$filterFac; ?>&department_id=<?php echo (int)$filterDept; ?>&programme_id=<?php echo (int)$filterProg; ?>&level=<?php echo urlencode((string)$filterLevel); ?>&q=<?php echo urlencode((string)$q); ?>"><i class="bi bi-printer"></i> Print Table Sheet</a>
                    <a class="btn btn-sm btn-light" target="_blank" href="?page=admin_registration_summary_report&mode=yet_to_register&download=1&session_name=<?php echo urlencode((string)$filterSess); ?>&semester=<?php echo urlencode((string)$filterSem); ?>&faculty_id=<?php echo (int)$filterFac; ?>&department_id=<?php echo (int)$filterDept; ?>&programme_id=<?php echo (int)$filterProg; ?>&level=<?php echo urlencode((string)$filterLevel); ?>&q=<?php echo urlencode((string)$q); ?>"><i class="bi bi-download"></i> Download Table Sheet</a>
                </div>
            </div>
            <div class="ytr-results-body">
                <?php if ($filterSess === ''): ?>
                    <div class="ytr-empty">
                        <div class="icon"><i class="bi bi-person-lines-fill"></i></div>
                        <div class="fw-semibold mb-2">Select a session to load the pending registration list.</div>
                        <div class="text-muted">Start with an academic session above, adjust the scope if needed, then click <strong>Apply Filter</strong> to view students who are yet to register.</div>
                    </div>
                <?php elseif (!$rows): ?>
                    <div class="ytr-empty">
                        <div class="icon"><i class="bi bi-check2-circle"></i></div>
                        <div class="fw-semibold mb-2">No students found for the selected scope.</div>
                        <div class="text-muted">There are currently no students pending registration for <strong><?php echo e($filterSess); ?></strong> in the <strong><?php echo e($filterSem); ?></strong> semester under the selected filters.</div>
                    </div>
                <?php else: ?>
                    <div class="table-responsive">
                        <table class="table table-hover align-middle mb-0">
                            <thead>
                                <tr>
                                    <th>#</th>
                                    <th>Matric No.</th>
                                    <th>Full Name</th>
                                    <th>Faculty</th>
                                    <th>Department</th>
                                    <th>Programme</th>
                                    <th>Mode of Study</th>
                                    <th>Level</th>
                                    <th>Email Address</th>
                                    <th>Phone</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php $i=0; foreach ($rows as $r): $i++; ?>
                                    <tr>
                                        <td><?php echo $i; ?></td>
                                        <td class="fw-semibold text-nowrap"><?php echo e($r['matric_no'] ?? ($r['reg_no'] ?? '')); ?></td>
                                        <td>
                                            <div class="fw-semibold"><?php echo e(full_name($r)); ?></div>
                                            <?php if (!empty($r['reg_no']) && (string)($r['reg_no'] ?? '') !== (string)($r['matric_no'] ?? '')): ?>
                                                <div class="small text-muted">Registration No.: <?php echo e($r['reg_no']); ?></div>
                                            <?php endif; ?>
                                        </td>
                                        <td><?php echo e($r['faculty_name'] ?? ($r['faculty'] ?? '')); ?></td>
                                        <td><?php echo e($r['department_name'] ?? ($r['department'] ?? '')); ?></td>
                                        <td><?php echo e($r['programme_name'] ?? ($r['programme'] ?? '')); ?></td>
                                        <td><?php echo e(portalInferModeOfStudy((array)$r)); ?></td>
                                        <td><?php echo e($r['level'] ?? ''); ?></td>
                                        <td><?php echo e($r['email'] ?? ''); ?></td>
                                        <td><?php echo e($r['phone'] ?? ''); ?></td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                <?php endif; ?>
                <div class="ytr-print-note mt-3">
                    Tip: Use this page to monitor incomplete registration quickly before following up with the students or adjusting registration scope/settings.
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;



case 'admin_school_fee_settings':
case 'admin_fees_manager':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    portalSchoolFeeSettingsSupport($pdo);
    portalEnsureAcademicLevelsSchema($pdo);
    $settings = portalSystemSettingsMap($pdo);
    $feesTab = trim((string)($_GET['fees_tab'] ?? 'school_fees'));
    $allowedFeesTabs = ['general_setup','school_fees','acceptance_fees','registration_fees','hostel_fees','assigned_custom_fees','late_fee','payment_gateways','wallet_settings','receipts_notifications','fee_scope_filters'];
    if (!in_array($feesTab, $allowedFeesTabs, true)) { $feesTab = 'school_fees'; }
    $feeSearch = portalNormalizeInlineWhitespace((string)($_GET['q'] ?? ''));
    $tabFeeTypeMap = [
        'school_fees' => 'school',
        'acceptance_fees' => 'acceptance',
        'registration_fees' => 'registration',
        'hostel_fees' => 'hostel',
        'assigned_custom_fees' => 'custom',
    ];

    $filterFeeTypeRaw = trim((string)($_GET['fee_type'] ?? ''));
    if ($filterFeeTypeRaw === '' && isset($tabFeeTypeMap[$feesTab])) {
        $filterFeeTypeRaw = $tabFeeTypeMap[$feesTab];
    }
    $filterFeeType = $filterFeeTypeRaw === '' ? '' : portalNormalizeManagedFeeType($filterFeeTypeRaw);
    $filterSession = portalNormalizeSessionNameSafe((string)($_GET['session_name'] ?? ''));
    $filterSemesterRaw = trim((string)($_GET['semester'] ?? ''));
    $filterSemester = $filterSemesterRaw !== '' ? portalNormalizeSemesterName($filterSemesterRaw) : '';
    $filterFacultyId = max(0, (int)($_GET['faculty_id'] ?? 0));
    $filterDepartmentId = max(0, (int)($_GET['department_id'] ?? 0));
    $filterProgrammeId = max(0, (int)($_GET['programme_id'] ?? 0));
    $filterLevel = strtoupper(trim((string)($_GET['level'] ?? '')));
    $sortBy = trim((string)($_GET['sort'] ?? 'specificity_desc'));
    if (!in_array($sortBy, ['specificity_desc','amount_asc','amount_desc','newest','session_desc'], true)) $sortBy = 'specificity_desc';

    $editId = max(0, (int)($_GET['edit_fee_id'] ?? 0));
    $currentFeesManagerUrl = portalManagedFeeCleanReturnUrl('?' . ltrim((string)($_SERVER['QUERY_STRING'] ?? ''), '?'), '?page=admin_fees_manager');
    $editRow = [
        'id' => 0,
        'fee_type' => ($filterFeeType !== '' ? $filterFeeType : 'school'),
        'session_name' => '',
        'semester' => '',
        'faculty_id' => 0,
        'department_id' => 0,
        'programme_id' => 0,
        'level' => '',
        'amount' => (float)portalSettingValueSafe($pdo, portalManagedFeeSettingKey('school'), '0'),
        'is_active' => 1,
        'notes' => '',
    ];
    if ($editId > 0) {
        try {
            $st = $pdo->prepare("SELECT * FROM school_fee_settings WHERE id = ? LIMIT 1");
            $st->execute([$editId]);
            $foundEditRow = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            if ($foundEditRow) {
                $editRow = array_merge($editRow, $foundEditRow);
                $editId = (int)($editRow['id'] ?? $editId);
            } else {
                $editId = 0;
            }
        } catch (Throwable $e) {
            $editId = 0;
        }
    }

    $faculties = [];
    $departments = [];
    $programmes = [];
    $sessions = portalAvailableSessionOptions($pdo);
    $levels = portalAcademicLevelOptions($pdo);
    try { $faculties = $pdo->query("SELECT id, name FROM faculties WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $departments = $pdo->query("SELECT id, faculty_id, name FROM departments WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $programmes = portalProgrammeSelectorOptions($pdo, 0, 0); } catch (Throwable $e) {}
    portalEnsureLateFeeRuleSchema($pdo);
    $lateFeeRuleRows = portalLateFeeRuleRows($pdo);
    $editLateFeeRuleId = max(0, (int)($_GET['edit_late_fee_rule_id'] ?? 0));
    $lateFeeEditRow = [
        'id' => 0,
        'session_name' => '',
        'semester' => '',
        'faculty_id' => 0,
        'department_id' => 0,
        'programme_id' => 0,
        'mode_of_study' => '',
        'mode_of_study_custom' => '',
        'course_of_study_id' => 0,
        'course_of_study' => '',
        'level' => '',
        'late_fee_type' => 'fixed',
        'late_fee_value' => 0,
        'trigger_condition' => 'session_closure',
        'due_date' => '',
        'is_enabled' => 1,
        'reason' => 'Late school fee charged after deadline.',
    ];
    if ($editLateFeeRuleId > 0) {
        foreach ($lateFeeRuleRows as $lateFeeRuleRow) {
            if ((int)($lateFeeRuleRow['id'] ?? 0) === $editLateFeeRuleId) {
                $lateFeeEditRow = array_merge($lateFeeEditRow, (array)$lateFeeRuleRow);
                break;
            }
        }
    }
    $lateFeeCourseOptions = portalLateFeeCourseOfStudyOptions(
        $pdo,
        (int)($lateFeeEditRow['faculty_id'] ?? 0),
        (int)($lateFeeEditRow['department_id'] ?? 0),
        (int)($lateFeeEditRow['programme_id'] ?? 0)
    );
    $lateFeeSettings = portalLateFeeSettings($pdo);

    $rows = [];
    try {
        $sql = "SELECT sfs.*, f.name AS faculty_name, d.name AS department_name, p.name AS programme_name
                FROM school_fee_settings sfs
                LEFT JOIN faculties f ON f.id = sfs.faculty_id
                LEFT JOIN departments d ON d.id = sfs.department_id
                LEFT JOIN programmes p ON p.id = sfs.programme_id
                WHERE 1=1";
        $params = [];
        if ($filterFeeType !== '') { $sql .= " AND COALESCE(sfs.fee_type,'school') = ?"; $params[] = $filterFeeType; }
        if ($filterSession !== '') { $sql .= " AND COALESCE(sfs.session_name,'') = ?"; $params[] = $filterSession; }
        if ($filterSemester !== '') { $sql .= " AND COALESCE(sfs.semester,'') = ?"; $params[] = $filterSemester; }
        if ($filterFacultyId > 0) { $sql .= " AND COALESCE(sfs.faculty_id,0) = ?"; $params[] = $filterFacultyId; }
        if ($filterDepartmentId > 0) { $sql .= " AND COALESCE(sfs.department_id,0) = ?"; $params[] = $filterDepartmentId; }
        if ($filterProgrammeId > 0) { $sql .= " AND COALESCE(sfs.programme_id,0) = ?"; $params[] = $filterProgrammeId; }
        if ($filterLevel !== '') { $sql .= " AND UPPER(COALESCE(sfs.level,'')) = ?"; $params[] = $filterLevel; }
        if ($feeSearch !== '') {
            $like = '%' . $feeSearch . '%';
            $sql .= " AND (COALESCE(sfs.custom_title,'') LIKE ? OR COALESCE(sfs.notes,'') LIKE ? OR COALESCE(sfs.session_name,'') LIKE ? OR COALESCE(sfs.semester,'') LIKE ? OR COALESCE(sfs.level,'') LIKE ? OR COALESCE(f.name,'') LIKE ? OR COALESCE(d.name,'') LIKE ? OR COALESCE(p.name,'') LIKE ?)";
            array_push($params, $like, $like, $like, $like, $like, $like, $like, $like);
        }
        $sql .= " ORDER BY sfs.id DESC";
        $st = $pdo->prepare($sql);
        $st->execute($params);
        $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) { $rows = []; }

    usort($rows, function(array $a, array $b) use ($sortBy) {
        if ($sortBy === 'amount_asc') return ((float)($a['amount'] ?? 0)) <=> ((float)($b['amount'] ?? 0));
        if ($sortBy === 'amount_desc') return ((float)($b['amount'] ?? 0)) <=> ((float)($a['amount'] ?? 0));
        if ($sortBy === 'session_desc') return strcmp((string)($b['session_name'] ?? ''), (string)($a['session_name'] ?? ''));
        if ($sortBy === 'newest') return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
        $scoreA = portalSchoolFeeRuleSpecificity($a);
        $scoreB = portalSchoolFeeRuleSpecificity($b);
        if ($scoreA === $scoreB) return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
        return $scoreB <=> $scoreA;
    });
    ?>
    <div class="container-fluid">
        <div class="d-flex align-items-center justify-content-between mb-3">
            <div>
                <h4 class="mb-0"><?php echo $feesTab === 'payment_gateways' ? 'Payment Gateway' : 'Fees Manager'; ?></h4>
                <div class="text-muted small"><?php echo $feesTab === 'payment_gateways' ? 'Manage online and manual payment gateways, default payment behavior, gateway activation state, configuration, and quick actions from one synchronized admin page.' : 'Manage Registration Fee, Acceptance Fee, Hostel Fee, School Fee, and Custom Fee centrally by session, semester, faculty, department, programme, level, session, and semester. Wallet settings, receipt notification controls, and scoped fee definitions are now organized here.'; ?></div>
            </div>
            <div class="d-flex gap-2">
                <?php if ($feesTab === 'payment_gateways'): ?>
                    <a class="btn btn-outline-secondary btn-sm" href="?page=admin_fees_manager">Fees Manager</a>
                <?php else: ?>
                    <a class="btn btn-outline-secondary btn-sm" href="?page=admin_settings">General Settings</a>
                <?php endif; ?>
                <a class="btn btn-outline-primary btn-sm" href="?page=admin_payments">Payments</a>
            </div>
        </div>

        <ul class="nav nav-tabs flex-wrap mb-3">
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'general_setup' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=general_setup">General Fee Setup</a></li>
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'school_fees' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=school_fees&fee_type=school">School Fees</a></li>
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'acceptance_fees' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=acceptance_fees&fee_type=acceptance">Acceptance Fees</a></li>
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'registration_fees' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=registration_fees&fee_type=registration">Registration Fees</a></li>
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'hostel_fees' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=hostel_fees&fee_type=hostel">Hostel Fees</a></li>
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'assigned_custom_fees' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=assigned_custom_fees&fee_type=custom">Assigned / Custom Fees</a></li>
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'late_fee' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=late_fee">Late Fee</a></li>
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'wallet_settings' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=wallet_settings">Wallet Settings</a></li>
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'receipts_notifications' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=receipts_notifications">Receipts / Payment Notifications</a></li>
            <li class="nav-item"><a class="nav-link <?php echo $feesTab === 'fee_scope_filters' ? 'active' : ''; ?>" href="?page=admin_fees_manager&fees_tab=fee_scope_filters">Fee Scope / Filters</a></li>
        </ul>

        <?php if ($feesTab === 'general_setup'): ?>
            <div class="card mb-3" id="fees-manager-settings">
                <div class="card-header">General Fee Setup</div>
                <div class="card-body">
                    <form method="post" class="row g-3">
                        <input type="hidden" name="action" value="update_settings">
                        <input type="hidden" name="return_to" value="<?php echo e('?page=admin_fees_manager&fees_tab=general_setup'); ?>"><?php portalRenderUpdateSettingsHiddenInputs($settings, ['academic_session','application_deadline','payment_description','payment_approval','email_notifications','installment_enabled','late_fee_enabled','late_fee_auto_generate','late_fee_type','late_fee_value','late_fee_grace_days','late_fee_apply_mode','late_fee_apply_to','late_fee_first_semester_end_date','late_fee_second_semester_end_date','late_fee_reason']); ?>
                        <div class="col-md-3">
                            <label class="form-label">Academic Session</label>
                            <select class="form-select" name="academic_session" required>
                                <?php $academicSessionSelected = (string)($settings['academic_session'] ?? (date('Y') . '/' . (date('Y') + 1))); foreach (portalAvailableSessionOptions($pdo) as $sessionOpt): ?>
                                    <option value="<?php echo e($sessionOpt); ?>" <?php echo ($academicSessionSelected === (string)$sessionOpt) ? 'selected' : ''; ?>><?php echo e($sessionOpt); ?></option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                        <div class="col-md-3">
                            <label class="form-label">Application Deadline</label>
                            <input type="date" class="form-control" name="application_deadline" value="<?php echo e((string)($settings['application_deadline'] ?? date('Y-m-d', strtotime('+6 months')))); ?>">
                        </div>
                        <div class="col-md-3">
                            <label class="form-label">Payment Description</label>
                            <input type="text" class="form-control" name="payment_description" value="<?php echo e((string)($settings['payment_description'] ?? 'HontTech Registration Fee')); ?>">
                        </div>
                        <div class="col-md-3">
                            <label class="form-label">Payment Approval Mode</label>
                            <select class="form-select" name="payment_approval">
                                <option value="automatic" <?php echo (($settings['payment_approval'] ?? 'automatic') === 'automatic') ? 'selected' : ''; ?>>Automatic</option>
                                <option value="manual" <?php echo (($settings['payment_approval'] ?? 'automatic') === 'manual') ? 'selected' : ''; ?>>Manual</option>
                            </select>
                        </div>
                        <div class="col-md-3"><div class="form-check form-switch mt-4"><input class="form-check-input" type="checkbox" name="email_notifications" <?php echo (($settings['email_notifications'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>><label class="form-check-label">Enable Email Notifications</label></div></div>
                        <div class="col-md-3"><div class="form-check form-switch mt-4"><input class="form-check-input" type="checkbox" name="installment_enabled" <?php echo (($settings['installment_enabled'] ?? 'disabled') === 'enabled') ? 'checked' : ''; ?>><label class="form-check-label">Enable Installment Payments</label></div></div>
                        <div class="col-md-3"><div class="form-check form-switch mt-4"><input class="form-check-input" type="checkbox" name="late_fee_enabled" <?php echo (($settings['late_fee_enabled'] ?? 'disabled') === 'enabled') ? 'checked' : ''; ?>><label class="form-check-label">Enable Late Fee System</label></div></div>
                        <div class="col-md-3"><div class="form-check form-switch mt-4"><input class="form-check-input" type="checkbox" name="late_fee_auto_generate" <?php echo (($settings['late_fee_auto_generate'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>><label class="form-check-label">Auto-generate Late Fee</label></div></div>
                        <div class="col-md-3">
                            <label class="form-label">Late Fee Mode</label>
                            <select class="form-select" name="late_fee_type">
                                <option value="fixed" <?php echo (($settings['late_fee_type'] ?? 'fixed') === 'fixed') ? 'selected' : ''; ?>>Fixed Amount</option>
                                <option value="percentage" <?php echo (($settings['late_fee_type'] ?? 'fixed') === 'percentage') ? 'selected' : ''; ?>>Percentage of Outstanding</option>
                            </select>
                        </div>
                        <div class="col-md-3"><label class="form-label">Late Fee Value</label><input type="number" min="0" step="0.01" class="form-control" name="late_fee_value" value="<?php echo e((string)($settings['late_fee_value'] ?? '0')); ?>"></div>
                        <div class="col-md-3"><label class="form-label">Grace Period (days)</label><input type="number" min="0" step="1" class="form-control" name="late_fee_grace_days" value="<?php echo e((string)($settings['late_fee_grace_days'] ?? '0')); ?>"></div>
                        <div class="col-md-3">
                            <label class="form-label">Apply Late Fee By</label>
                            <select class="form-select" name="late_fee_apply_mode">
                                <option value="session_closure" <?php echo (($settings['late_fee_apply_mode'] ?? 'session_closure') === 'session_closure') ? 'selected' : ''; ?>>Session Closure</option>
                                <option value="semester_end" <?php echo (($settings['late_fee_apply_mode'] ?? 'session_closure') === 'semester_end') ? 'selected' : ''; ?>>Semester End</option>
                                <option value="both" <?php echo (($settings['late_fee_apply_mode'] ?? 'session_closure') === 'both') ? 'selected' : ''; ?>>Both</option>
                            </select>
                        </div>
                        <div class="col-md-4">
                            <label class="form-label">Apply To</label>
                            <select class="form-select" name="late_fee_apply_to">
                                <option value="all_unpaid" <?php echo (($settings['late_fee_apply_to'] ?? 'all_unpaid') === 'all_unpaid') ? 'selected' : ''; ?>>All Students With Unpaid School Fee</option>
                                <option value="approved_only" <?php echo (($settings['late_fee_apply_to'] ?? 'all_unpaid') === 'approved_only') ? 'selected' : ''; ?>>Approved Admission Only</option>
                                <option value="active_only" <?php echo (($settings['late_fee_apply_to'] ?? 'all_unpaid') === 'active_only') ? 'selected' : ''; ?>>Active Students Only</option>
                            </select>
                        </div>
                        <div class="col-md-4"><label class="form-label">1st Semester End Date</label><input type="date" class="form-control" name="late_fee_first_semester_end_date" value="<?php echo e((string)($settings['late_fee_first_semester_end_date'] ?? '')); ?>"></div>
                        <div class="col-md-4"><label class="form-label">2nd Semester End Date</label><input type="date" class="form-control" name="late_fee_second_semester_end_date" value="<?php echo e((string)($settings['late_fee_second_semester_end_date'] ?? '')); ?>"></div>
                        <div class="col-12"><label class="form-label">Late Fee Reason / Remarks</label><textarea class="form-control" name="late_fee_reason" rows="2"><?php echo e((string)($settings['late_fee_reason'] ?? 'Late school fee charged after deadline.')); ?></textarea></div>
                        <div class="col-12 d-flex gap-2">
                            <button class="btn btn-primary" type="submit">Save General Fee Setup</button>
                            <button type="button" class="btn btn-outline-danger" onclick="(function(){var f=document.createElement('form');f.method='post';f.action='';var a=document.createElement('input');a.type='hidden';a.name='action';a.value='generate_late_fees_now';f.appendChild(a);var b=document.createElement('input');b.type='hidden';b.name='return_to';b.value=<?php echo json_encode('?page=admin_fees_manager&fees_tab=general_setup'); ?>;f.appendChild(b);var s=document.createElement('input');s.type='hidden';s.name='session_name';s.value=<?php echo json_encode((string)($settings['academic_session'] ?? portalCurrentAcademicSessionName($pdo))); ?>;f.appendChild(s);var m=document.createElement('input');m.type='hidden';m.name='semester';m.value=<?php echo json_encode((string)($settings['academic_semester'] ?? portalCurrentAcademicSemesterName($pdo))); ?>;f.appendChild(m);document.body.appendChild(f);f.submit();})();">Generate Late Fees Now</button>
                        </div>
                    </form>
                </div>
            </div>
        <?php elseif ($feesTab === 'payment_gateways'): ?>
            <?php
                $gatewayRegistry = portalPaymentGatewayRegistry();
                $gatewayKeyMap = portalGatewayConfigKeys();
                $gatewayHiddenExcludes = array_merge(
                    ['online_payment_enabled','default_payment_method','default_gateway','default_gateways','manual_payment_enabled','manual_payment_instructions'],
                    $gatewayKeyMap['scalar'] ?? [],
                    $gatewayKeyMap['checkbox'] ?? []
                );
                $defaultPaymentMethod = portalDefaultPaymentMethod($pdo);
                $defaultGateway = portalDefaultGatewayProvider($pdo);
                $gatewayDisplayRows = [];
                foreach ($gatewayRegistry as $providerKey => $providerDef) {
                    $gatewayDisplayRows[$providerKey] = [
                        'label' => (string)($providerDef['label'] ?? $providerKey),
                        'region' => (string)($providerDef['region'] ?? 'Gateway'),
                        'enabled' => (($settings[$providerKey . '_enabled'] ?? 'disabled') === 'enabled'),
                        'mode' => strtolower(trim((string)($settings[$providerKey . '_mode'] ?? ($providerDef['fields']['mode']['default'] ?? 'live')))),
                        'last_updated' => portalGatewayLastUpdated($pdo, $providerKey),
                        'state' => portalGatewayConfigurationState($pdo, $providerKey),
                        'native' => !empty($providerDef['native_checkout']),
                        'wallet' => !empty($providerDef['wallet_funding']),
                        'is_default' => ($defaultGateway === $providerKey),
                    ];
                }
            ?>
            <div class="card mb-3">
                <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
                    <div>
                        <strong>Payment Gateways / Methods</strong>
                        <div class="small text-light-emphasis">Manage Nigeria/Africa-facing and foreign/global gateways from one synchronized manager.</div>
                    </div>
                    <span class="badge bg-dark"><?php echo count($gatewayRegistry); ?> providers</span>
                </div>
                <div class="card-body">
                    <form method="post" class="row g-3" id="paymentGatewayManagerForm">
                        <input type="hidden" name="action" value="update_settings">
                        <input type="hidden" name="return_to" value="<?php echo e('?page=admin_fees_manager&fees_tab=payment_gateways'); ?>">
                        <?php portalRenderUpdateSettingsHiddenInputs($settings, $gatewayHiddenExcludes); ?>

                        <div class="col-md-3">
                            <div class="form-check form-switch mt-4">
                                <input class="form-check-input" type="checkbox" name="online_payment_enabled" <?php echo (($settings['online_payment_enabled'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>>
                                <label class="form-check-label">Enable Online Payment</label>
                            </div>
                        </div>
                        <div class="col-md-3">
                            <div class="form-check form-switch mt-4">
                                <input class="form-check-input" type="checkbox" name="manual_payment_enabled" <?php echo (($settings['manual_payment_enabled'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>>
                                <label class="form-check-label">Enable Manual Payment</label>
                            </div>
                        </div>
                        <div class="col-md-3">
                            <label class="form-label">Default Payment Method</label>
                            <select class="form-select" name="default_payment_method">
                                <option value="auto" <?php echo $defaultPaymentMethod === 'auto' ? 'selected' : ''; ?>>Auto Payment</option>
                                <option value="manual" <?php echo $defaultPaymentMethod === 'manual' ? 'selected' : ''; ?>>Manual Payment</option>
                            </select>
                            <div class="small text-muted mt-1">Choose the payment mode students should see as the default preference.</div>
                        </div>
                        <div class="col-md-3">
                            <label class="form-label">Default Online Gateway</label>
                            <select class="form-select" name="default_gateway">
                                <?php foreach ($gatewayRegistry as $providerKey => $providerDef): ?>
                                    <option value="<?php echo e($providerKey); ?>" <?php echo $defaultGateway === $providerKey ? 'selected' : ''; ?>><?php echo e((string)($providerDef['label'] ?? ucwords($providerKey))); ?></option>
                                <?php endforeach; ?>
                            </select>
                            <div class="small text-muted mt-1">Used as the first online gateway shown to students for payments and wallet funding.</div>
                        </div>

                        <div class="col-12">
                            <div class="table-responsive border rounded">
                                <table class="table table-sm table-striped align-middle mb-0">
                                    <thead class="table-light">
                                        <tr>
                                            <th>Provider</th>
                                            <th>Region / Type</th>
                                            <th>Status</th>
                                            <th>Mode</th>
                                            <th>Last Updated</th>
                                            <th>Student Checkout</th>
                                            <th>Wallet Funding</th>
                                            <th>Configuration</th>
                                            <th>Action</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <?php foreach ($gatewayDisplayRows as $providerKey => $row): ?>
                                            <tr>
                                                <td>
                                                    <strong><?php echo e($row['label']); ?></strong>
                                                    <?php if (!empty($row['is_default'])): ?>
                                                        <span class="badge bg-primary ms-1">Default Online</span>
                                                    <?php endif; ?>
                                                    <?php if ($row['native']): ?><div class="small text-muted">Native adapter</div><?php else: ?><div class="small text-muted">Hosted / middleware adapter</div><?php endif; ?>
                                                </td>
                                                <td><?php echo e($row['region']); ?></td>
                                                <td>
                                                    <span class="badge <?php echo $row['enabled'] ? 'bg-success' : 'bg-secondary'; ?>">
                                                        <?php echo $row['enabled'] ? 'Active' : 'Inactive'; ?>
                                                    </span>
                                                </td>
                                                <td><span class="badge bg-light text-dark border"><?php echo e(strtoupper($row['mode'])); ?></span></td>
                                                <td><?php echo e((string)($row['last_updated'] ?? '—')); ?></td>
                                                <td>
                                                    <?php if (portalGatewayExposedToStudents($pdo, $providerKey)): ?>
                                                        <span class="badge bg-primary">Shown</span>
                                                    <?php else: ?>
                                                        <span class="badge bg-warning text-dark">Hidden</span>
                                                    <?php endif; ?>
                                                </td>
                                                <td>
                                                    <?php if ($row['wallet']): ?>
                                                        <span class="badge bg-info text-dark">Supported</span>
                                                    <?php else: ?>
                                                        <span class="badge bg-light text-dark border">No</span>
                                                    <?php endif; ?>
                                                </td>
                                                <td>
                                                    <span class="badge <?php echo $row['state']['ok'] ? 'bg-success' : 'bg-warning text-dark'; ?>">
                                                        <?php echo $row['state']['ok'] ? 'Configured' : 'Needs Attention'; ?>
                                                    </span>
                                                    <?php if (!$row['state']['ok']): ?>
                                                        <div class="small text-muted mt-1"><?php echo e($row['state']['message']); ?></div>
                                                    <?php endif; ?>
                                                </td>
                                                <td class="text-nowrap">
                                                    <a class="btn btn-sm btn-outline-primary mb-1" href="#gateway-card-<?php echo e($providerKey); ?>">Edit</a>
                                                    <button type="button" class="btn btn-sm btn-outline-dark mb-1" onclick="(function(){var f=document.createElement('form');f.method='post';f.action='';var a=document.createElement('input');a.type='hidden';a.name='action';a.value='test_payment_gateway_connection';f.appendChild(a);var p=document.createElement('input');p.type='hidden';p.name='provider';p.value='<?php echo e($providerKey); ?>';f.appendChild(p);document.body.appendChild(f);f.submit();})();">Test</button>
                                                    <button type="button" class="btn btn-sm <?php echo $row['enabled'] ? 'btn-outline-warning' : 'btn-outline-success'; ?> mb-1" onclick="(function(){var f=document.createElement('form');f.method='post';f.action='';var a=document.createElement('input');a.type='hidden';a.name='action';a.value='toggle_payment_gateway_status';f.appendChild(a);var p=document.createElement('input');p.type='hidden';p.name='provider';p.value='<?php echo e($providerKey); ?>';f.appendChild(p);var s=document.createElement('input');s.type='hidden';s.name='next_state';s.value='<?php echo $row['enabled'] ? 'disable' : 'enable'; ?>';f.appendChild(s);document.body.appendChild(f);f.submit();})();"><?php echo $row['enabled'] ? 'Disable' : 'Enable'; ?></button>
                                                    <button type="button" class="btn btn-sm btn-outline-danger mb-1" onclick="if(!confirm('Delete this gateway configuration and disable it?')) return; (function(){var f=document.createElement('form');f.method='post';f.action='';var a=document.createElement('input');a.type='hidden';a.name='action';a.value='delete_payment_gateway_configuration';f.appendChild(a);var p=document.createElement('input');p.type='hidden';p.name='provider';p.value='<?php echo e($providerKey); ?>';f.appendChild(p);document.body.appendChild(f);f.submit();})();">Delete</button>
                                                </td>
                                            </tr>
                                        <?php endforeach; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>

                        <div class="col-12">
                            <div class="alert alert-light border mb-0">
                                <div class="fw-semibold mb-1">Hosted / middleware callback note</div>
                                <div class="small text-muted">
                                    Non-native providers become student-selectable only when they are enabled and a Hosted Checkout / Middleware URL is supplied. They can return safely through
                                    <code>?action=verify_payment&amp;gateway=provider&amp;reference=REF&amp;type=TYPE&amp;status=success&amp;amount=1234.00&amp;token=HMAC_SHA256(reference|status|amount, webhook_secret)</code>.
                                </div>
                            </div>
                        </div>

                        <div class="col-12">
                            <div class="accordion" id="gatewayManagerAccordion">
                                <?php $gatewayIndex = 0; foreach ($gatewayRegistry as $providerKey => $providerDef): $gatewayIndex++; $panelId = 'gateway-panel-' . $providerKey; $collapseId = 'gateway-collapse-' . $providerKey; ?>
                                    <div class="accordion-item mb-2" id="gateway-card-<?php echo e($providerKey); ?>">
                                        <h2 class="accordion-header" id="<?php echo e($panelId); ?>">
                                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#<?php echo e($collapseId); ?>" aria-expanded="false" aria-controls="<?php echo e($collapseId); ?>">
                                                <div class="d-flex flex-column">
                                                    <span><?php echo e((string)($providerDef['label'] ?? $providerKey)); ?></span>
                                                    <small class="text-muted"><?php echo e((string)($providerDef['region'] ?? 'Gateway')); ?><?php echo !empty($providerDef['native_checkout']) ? ' · Native adapter' : ' · Hosted / middleware adapter'; ?></small>
                                                </div>
                                            </button>
                                        </h2>
                                        <div id="<?php echo e($collapseId); ?>" class="accordion-collapse collapse" aria-labelledby="<?php echo e($panelId); ?>" data-bs-parent="#gatewayManagerAccordion">
                                            <div class="accordion-body">
                                                <div class="row g-3">
                                                    <div class="col-md-3">
                                                        <div class="form-check form-switch mt-4">
                                                            <input class="form-check-input" type="checkbox" name="<?php echo e($providerKey . '_enabled'); ?>" <?php echo (($settings[$providerKey . '_enabled'] ?? 'disabled') === 'enabled') ? 'checked' : ''; ?>>
                                                            <label class="form-check-label">Enable <?php echo e((string)($providerDef['label'] ?? $providerKey)); ?></label>
                                                        </div>
                                                    </div>
                                                    <div class="col-md-3">
                                                        <label class="form-label">Region / Type</label>
                                                        <input class="form-control" value="<?php echo e((string)($providerDef['region'] ?? 'Gateway')); ?>" readonly>
                                                    </div>
                                                    <div class="col-md-3">
                                                        <label class="form-label">Checkout Adapter</label>
                                                        <input class="form-control" value="<?php echo !empty($providerDef['native_checkout']) ? 'Native in portal' : 'Hosted / middleware'; ?>" readonly>
                                                    </div>
                                                    <div class="col-md-3">
                                                        <label class="form-label">Wallet Funding</label>
                                                        <input class="form-control" value="<?php echo !empty($providerDef['wallet_funding']) ? 'Supported' : 'Not exposed'; ?>" readonly>
                                                    </div>

                                                    <?php foreach ((array)($providerDef['fields'] ?? []) as $fieldKey => $fieldMeta): ?>
                                                        <div class="col-md-4">
                                                            <label class="form-label"><?php echo e((string)($fieldMeta['label'] ?? ucwords(str_replace('_', ' ', $fieldKey)))); ?><?php echo !empty($fieldMeta['required']) ? ' *' : ''; ?></label>
                                                            <?php $settingKey = $providerKey . '_' . $fieldKey; $fieldType = (string)($fieldMeta['type'] ?? 'text'); ?>
                                                            <?php if ($fieldType === 'select'): ?>
                                                                <select class="form-select" name="<?php echo e($settingKey); ?>">
                                                                    <?php foreach ((array)($fieldMeta['options'] ?? []) as $optValue => $optLabel): ?>
                                                                        <option value="<?php echo e((string)$optValue); ?>" <?php echo strtolower((string)($settings[$settingKey] ?? ($fieldMeta['default'] ?? ''))) === strtolower((string)$optValue) ? 'selected' : ''; ?>><?php echo e((string)$optLabel); ?></option>
                                                                    <?php endforeach; ?>
                                                                </select>
                                                            <?php else: ?>
                                                                <input class="form-control" type="<?php echo e($fieldType === 'password' ? 'password' : 'text'); ?>" name="<?php echo e($settingKey); ?>" value="<?php echo e((string)($settings[$settingKey] ?? ($fieldMeta['default'] ?? ''))); ?>" <?php echo ($fieldKey === 'callback_url' && trim((string)($settings[$settingKey] ?? '')) === '') ? 'placeholder="' . e(appBaseUrl() . '/index.php?action=verify_payment&gateway=' . $providerKey) . '"' : ''; ?>>
                                                            <?php endif; ?>
                                                        </div>
                                                    <?php endforeach; ?>

                                                    <div class="col-md-3">
                                                        <label class="form-label">Charge %</label>
                                                        <input class="form-control" name="<?php echo e($providerKey . '_charge_percent'); ?>" value="<?php echo e((string)($settings[$providerKey . '_charge_percent'] ?? '0')); ?>">
                                                    </div>
                                                    <div class="col-md-3">
                                                        <label class="form-label">Flat Charge</label>
                                                        <input class="form-control" name="<?php echo e($providerKey . '_charge_flat'); ?>" value="<?php echo e((string)($settings[$providerKey . '_charge_flat'] ?? '0')); ?>">
                                                    </div>
                                                    <div class="col-md-3">
                                                        <label class="form-label">Charge Cap</label>
                                                        <input class="form-control" name="<?php echo e($providerKey . '_charge_cap'); ?>" value="<?php echo e((string)($settings[$providerKey . '_charge_cap'] ?? '0')); ?>">
                                                    </div>
                                                    <div class="col-md-3">
                                                        <label class="form-label">Flat Charge Waive Below</label>
                                                        <input class="form-control" name="<?php echo e($providerKey . '_charge_flat_waive_below'); ?>" value="<?php echo e((string)($settings[$providerKey . '_charge_flat_waive_below'] ?? '0')); ?>">
                                                    </div>

                                                    <div class="col-12">
                                                        <div class="small text-muted">
                                                            <?php echo e((string)($providerDef['settings_help'] ?? 'Only the fields for this provider are shown in this block. Save to keep configuration synchronized with receipts, logs, wallet funding, and fee payment flows.')); ?>
                                                        </div>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                <?php endforeach; ?>
                            </div>
                        </div>

                        <div class="col-12">
                            <label class="form-label">Manual Payment Instructions</label>
                            <textarea class="form-control" name="manual_payment_instructions" rows="3"><?php echo e((string)($settings['manual_payment_instructions'] ?? 'Pay into the school account and upload your teller/receipt for verification.')); ?></textarea>
                        </div>

                        <div class="col-12">
                            <button class="btn btn-primary" type="submit">Save Payment Gateway Settings</button>
                        </div>
                    </form>
                </div>
            </div>

        <?php elseif ($feesTab === 'late_fee'): ?>
            <div class="card mb-3">
                <div class="card-header d-flex justify-content-between align-items-center">
                    <div>
                        <strong>Late Fee</strong>
                        <div class="small text-muted">Configure school-fee late charges by exact scope. Specific scope rules override broader rules, while unmatched students can still fall back to the existing global late-fee settings.</div>
                    </div>
                    <div class="d-flex gap-2 flex-wrap">
                        <button type="button" class="btn btn-outline-danger btn-sm" onclick="(function(){var f=document.createElement('form');f.method='post';f.action='';var a=document.createElement('input');a.type='hidden';a.name='action';a.value='generate_late_fees_now';f.appendChild(a);var b=document.createElement('input');b.type='hidden';b.name='return_to';b.value=<?php echo json_encode('?page=admin_fees_manager&fees_tab=late_fee'); ?>;f.appendChild(b);var s=document.createElement('input');s.type='hidden';s.name='session_name';s.value=<?php echo json_encode((string)($settings['academic_session'] ?? portalCurrentAcademicSessionName($pdo))); ?>;f.appendChild(s);var m=document.createElement('input');m.type='hidden';m.name='semester';m.value=<?php echo json_encode((string)($settings['academic_semester'] ?? portalCurrentAcademicSemesterName($pdo))); ?>;f.appendChild(m);document.body.appendChild(f);f.submit();})();">Generate Late Fees Now</button>
                    </div>
                </div>
                <div class="card-body">
                    <form method="post" class="row g-3 mb-4 border rounded p-3 bg-light-subtle">
                        <input type="hidden" name="action" value="update_settings">
                        <input type="hidden" name="return_to" value="<?php echo e('?page=admin_fees_manager&fees_tab=late_fee'); ?>"><?php portalRenderUpdateSettingsHiddenInputs($settings, ['academic_session','application_deadline','payment_description','payment_approval','email_notifications','installment_enabled','late_fee_enabled','late_fee_auto_generate','late_fee_type','late_fee_value','late_fee_grace_days','late_fee_apply_mode','late_fee_apply_to','late_fee_first_semester_end_date','late_fee_second_semester_end_date','late_fee_reason']); ?>
                        <div class="col-md-3"><div class="form-check form-switch mt-4"><input class="form-check-input" type="checkbox" name="late_fee_enabled" <?php echo (($lateFeeSettings['enabled'] ?? false) ? 'checked' : ''); ?>><label class="form-check-label">Enable Late Fee System</label></div></div>
                        <div class="col-md-3"><div class="form-check form-switch mt-4"><input class="form-check-input" type="checkbox" name="late_fee_auto_generate" <?php echo (($lateFeeSettings['auto_generate'] ?? false) ? 'checked' : ''); ?>><label class="form-check-label">Auto-generate Late Fee</label></div></div>
                        <div class="col-md-3"><label class="form-label">Grace Period (days)</label><input type="number" min="0" step="1" class="form-control" name="late_fee_grace_days" value="<?php echo e((string)($lateFeeSettings['grace_days'] ?? '0')); ?>"></div>
                        <div class="col-md-3"><label class="form-label">Apply To</label><select class="form-select" name="late_fee_apply_to"><option value="all_unpaid" <?php echo (($lateFeeSettings['apply_to'] ?? 'all_unpaid') === 'all_unpaid') ? 'selected' : ''; ?>>All Students With Unpaid School Fee</option><option value="approved_only" <?php echo (($lateFeeSettings['apply_to'] ?? 'all_unpaid') === 'approved_only') ? 'selected' : ''; ?>>Approved Admission Only</option><option value="active_only" <?php echo (($lateFeeSettings['apply_to'] ?? 'all_unpaid') === 'active_only') ? 'selected' : ''; ?>>Active Students Only</option></select></div>
                        <div class="col-md-4"><label class="form-label">1st Semester End Date</label><input type="date" class="form-control" name="late_fee_first_semester_end_date" value="<?php echo e((string)($lateFeeSettings['first_semester_end_date'] ?? '')); ?>"></div>
                        <div class="col-md-4"><label class="form-label">2nd Semester End Date</label><input type="date" class="form-control" name="late_fee_second_semester_end_date" value="<?php echo e((string)($lateFeeSettings['second_semester_end_date'] ?? '')); ?>"></div>
                        <div class="col-md-4"><label class="form-label">Default Late Fee Reason</label><input type="text" class="form-control" name="late_fee_reason" value="<?php echo e((string)($lateFeeSettings['reason'] ?? 'Late school fee charged after deadline.')); ?>"></div>
                        <div class="col-12"><button type="submit" class="btn btn-primary">Save Late Fee Settings</button></div>
                    </form>

                    <form method="post" class="row g-3 mb-3" id="lateFeeRuleForm">
                        <input type="hidden" name="action" value="save_late_fee_rule">
                        <input type="hidden" name="edit_late_fee_rule_id" value="<?php echo (int)($lateFeeEditRow['id'] ?? 0); ?>">
                        <input type="hidden" name="late_fee_rule_course_of_study_label" value="<?php echo e((string)($lateFeeEditRow['course_of_study'] ?? '')); ?>">

                        <div class="col-md-3">
                            <label class="form-label">Session</label>
                            <select class="form-select" name="late_fee_rule_session_name">
                                <option value="">All Sessions</option>
                                <?php foreach ($sessions as $sessionOpt): ?>
                                    <option value="<?php echo e($sessionOpt); ?>" <?php echo ((string)($lateFeeEditRow['session_name'] ?? '') === (string)$sessionOpt) ? 'selected' : ''; ?>><?php echo e($sessionOpt); ?></option>
                                <?php endforeach; ?>
                            </select>
                        </div>

                        <div class="col-md-3">
                            <label class="form-label">Semester</label>
                            <select class="form-select" name="late_fee_rule_semester">
                                <option value="">All Semesters</option>
                                <?php foreach (portalAvailableSemesterOptions() as $lateSemOpt): ?>
                                    <option value="<?php echo e((string)$lateSemOpt); ?>" <?php echo (strcasecmp((string)($lateFeeEditRow['semester'] ?? ''), (string)$lateSemOpt) === 0) ? 'selected' : ''; ?>><?php echo e((string)$lateSemOpt); ?></option>
                                <?php endforeach; ?>
                            </select>
                        </div>

                        <div class="col-md-3">
                            <label class="form-label">Faculty</label>
                            <select class="form-select" name="late_fee_rule_faculty_id" id="late_fee_rule_faculty_id">
                                <option value="0">All Faculties</option>
                                <?php foreach ($faculties as $frow): ?>
                                    <option value="<?php echo (int)($frow['id'] ?? 0); ?>" <?php echo ((int)($lateFeeEditRow['faculty_id'] ?? 0) === (int)($frow['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e((string)($frow['name'] ?? '')); ?></option>
                                <?php endforeach; ?>
                            </select>
                        </div>

                        <div class="col-md-3">
                            <label class="form-label">Department</label>
                            <select class="form-select" name="late_fee_rule_department_id" id="late_fee_rule_department_id">
                                <option value="0">All Departments</option>
                                <?php foreach ($departments as $drow): ?>
                                    <option value="<?php echo (int)($drow['id'] ?? 0); ?>"
                                            data-faculty-id="<?php echo (int)($drow['faculty_id'] ?? 0); ?>"
                                            <?php echo ((int)($lateFeeEditRow['department_id'] ?? 0) === (int)($drow['id'] ?? 0)) ? 'selected' : ''; ?>>
                                        <?php echo e((string)($drow['name'] ?? '')); ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>

                        <div class="col-md-3">
                            <label class="form-label">Programme</label>
                            <select class="form-select" name="late_fee_rule_programme_id" id="late_fee_rule_programme_id">
                                <option value="0">All Programmes</option>
                                <?php foreach ($programmes as $prow): ?>
                                    <option value="<?php echo (int)($prow['id'] ?? 0); ?>"
                                            data-faculty-id="<?php echo (int)($prow['faculty_id'] ?? 0); ?>"
                                            data-department-id="<?php echo (int)($prow['department_id'] ?? 0); ?>"
                                            data-mode-of-study="<?php echo e((string)($prow['mode_of_study'] ?? '')); ?>"
                                            <?php echo ((int)($lateFeeEditRow['programme_id'] ?? 0) === (int)($prow['id'] ?? 0)) ? 'selected' : ''; ?>>
                                        <?php echo e((string)($prow['name'] ?? '')); ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>

                        <div class="col-md-3">
                            <label class="form-label">Mode of Study</label>
                            <?php
                                $lateFeeModeValue = trim((string)($lateFeeEditRow['mode_of_study'] ?? ''));
                                $lateFeeModeCustomValue = trim((string)($lateFeeEditRow['mode_of_study_custom'] ?? ''));
                                $lateFeeModeSelectValue = $lateFeeModeValue;
                                if ($lateFeeModeSelectValue !== '' && !in_array($lateFeeModeSelectValue, ['Full-Time', 'Part-Time', 'Others'], true)) {
                                    $lateFeeModeCustomValue = $lateFeeModeCustomValue !== '' ? $lateFeeModeCustomValue : $lateFeeModeSelectValue;
                                    $lateFeeModeSelectValue = 'Others';
                                }
                            ?>
                            <select class="form-select" name="late_fee_rule_mode_of_study" id="late_fee_rule_mode_of_study">
                                <option value="">All Modes of Study</option>
                                <option value="Full-Time" <?php echo ($lateFeeModeSelectValue === 'Full-Time') ? 'selected' : ''; ?>>Full-Time</option>
                                <option value="Part-Time" <?php echo ($lateFeeModeSelectValue === 'Part-Time') ? 'selected' : ''; ?>>Part-Time</option>
                                <option value="Others" <?php echo ($lateFeeModeSelectValue === 'Others') ? 'selected' : ''; ?>>Others</option>
                            </select>
                        </div>

                        <div class="col-md-3" id="late_fee_rule_mode_custom_wrap" <?php echo ($lateFeeModeSelectValue === 'Others') ? '' : 'style="display:none;"'; ?>>
                            <label class="form-label">Custom Mode of Study</label>
                            <input type="text" class="form-control" name="late_fee_rule_mode_of_study_custom" id="late_fee_rule_mode_of_study_custom" value="<?php echo e($lateFeeModeCustomValue); ?>" placeholder="Enter custom Mode of Study">
                        </div>

                        <div class="col-md-3">
                            <label class="form-label">Course of Study</label>
                            <select class="form-select" name="late_fee_rule_course_of_study_id" id="late_fee_rule_course_of_study_id">
                                <option value="0">All Course of Study</option>
                                <?php foreach ($lateFeeCourseOptions as $courseOpt): ?>
                                    <option value="<?php echo (int)($courseOpt['id'] ?? 0); ?>"
                                            data-faculty-id="<?php echo (int)($courseOpt['faculty_id'] ?? 0); ?>"
                                            data-department-id="<?php echo (int)($courseOpt['id'] ?? 0); ?>"
                                            <?php echo ((int)($lateFeeEditRow['course_of_study_id'] ?? 0) === (int)($courseOpt['id'] ?? 0)) ? 'selected' : ''; ?>>
                                        <?php echo e((string)($courseOpt['name'] ?? '')); ?>
                                    </option>
                                <?php endforeach; ?>
                                <?php if ((int)($lateFeeEditRow['course_of_study_id'] ?? 0) <= 0 && trim((string)($lateFeeEditRow['course_of_study'] ?? '')) !== ''): ?>
                                    <option value="0" selected><?php echo e('Legacy stored value: ' . (string)($lateFeeEditRow['course_of_study'] ?? '') . ' (reselect if needed)'); ?></option>
                                <?php endif; ?>
                            </select>
                            <div class="form-text">Controlled selector linked to Faculty / Department / Programme.</div>
                        </div>

                        <div class="col-md-3">
                            <label class="form-label">Level</label>
                            <select class="form-select" name="late_fee_rule_level">
                                <option value="">All Levels</option>
                                <?php foreach ($levels as $levelOpt): ?>
                                    <option value="<?php echo e((string)$levelOpt); ?>" <?php echo (strtoupper((string)($lateFeeEditRow['level'] ?? '')) === strtoupper((string)$levelOpt)) ? 'selected' : ''; ?>><?php echo e((string)$levelOpt); ?></option>
                                <?php endforeach; ?>
                            </select>
                        </div>

                        <div class="col-md-2">
                            <label class="form-label">Late Fee Type</label>
                            <select class="form-select" name="late_fee_rule_type">
                                <option value="fixed" <?php echo (($lateFeeEditRow['late_fee_type'] ?? 'fixed') === 'fixed') ? 'selected' : ''; ?>>Fixed Amount</option>
                                <option value="percentage" <?php echo (($lateFeeEditRow['late_fee_type'] ?? 'fixed') === 'percentage') ? 'selected' : ''; ?>>Percentage</option>
                            </select>
                        </div>

                        <div class="col-md-2">
                            <label class="form-label">Late Fee Amount</label>
                            <input type="number" min="0" step="0.01" class="form-control" name="late_fee_rule_value" value="<?php echo e((string)($lateFeeEditRow['late_fee_value'] ?? '0')); ?>">
                        </div>

                        <div class="col-md-2">
                            <label class="form-label">Trigger</label>
                            <select class="form-select" name="late_fee_rule_trigger_condition" id="late_fee_rule_trigger_condition">
                                <option value="session_closure" <?php echo (($lateFeeEditRow['trigger_condition'] ?? 'session_closure') === 'session_closure') ? 'selected' : ''; ?>>After Session Closes</option>
                                <option value="semester_end" <?php echo (($lateFeeEditRow['trigger_condition'] ?? 'session_closure') === 'semester_end') ? 'selected' : ''; ?>>After Semester Closes</option>
                                <option value="due_date" <?php echo (($lateFeeEditRow['trigger_condition'] ?? 'session_closure') === 'due_date') ? 'selected' : ''; ?>>After Due Date</option>
                            </select>
                        </div>

                        <div class="col-md-2" id="late_fee_rule_due_date_wrap" <?php echo (($lateFeeEditRow['trigger_condition'] ?? 'session_closure') === 'due_date') ? '' : 'style="display:none;"'; ?>>
                            <label class="form-label">Due Date</label>
                            <input type="date" class="form-control" name="late_fee_rule_due_date" value="<?php echo e((string)($lateFeeEditRow['due_date'] ?? '')); ?>">
                        </div>

                        <div class="col-md-2">
                            <label class="form-label">Status</label>
                            <div class="form-check form-switch mt-2">
                                <input class="form-check-input" type="checkbox" name="late_fee_rule_is_enabled" <?php echo !empty($lateFeeEditRow['is_enabled']) ? 'checked' : ''; ?>>
                                <label class="form-check-label">Enabled</label>
                            </div>
                        </div>

                        <div class="col-md-10">
                            <label class="form-label">Description / Reason / Note</label>
                            <input type="text" class="form-control" name="late_fee_rule_reason" value="<?php echo e((string)($lateFeeEditRow['reason'] ?? '')); ?>" placeholder="Explain why/when this late fee applies">
                        </div>

                        <div class="col-md-2 d-flex align-items-end gap-2">
                            <button type="submit" class="btn btn-primary"><?php echo !empty($lateFeeEditRow['id']) ? 'Update Rule' : 'Add Rule'; ?></button>
                            <?php if (!empty($lateFeeEditRow['id'])): ?><a href="?page=admin_fees_manager&fees_tab=late_fee" class="btn btn-outline-secondary">Clear</a><?php endif; ?>
                        </div>
                    </form>

                    <div class="table-responsive border rounded">
                        <table class="table table-sm table-striped align-middle mb-0">
                            <thead class="table-light">
                                <tr>
                                    <th>Session</th>
                                    <th>Semester</th>
                                    <th>Faculty</th>
                                    <th>Department</th>
                                    <th>Programme</th>
                                    <th>Mode of Study</th>
                                    <th>Course of Study</th>
                                    <th>Level</th>
                                    <th>Scope Summary</th>
                                    <th>Late Fee</th>
                                    <th>Trigger</th>
                                    <th>Status</th>
                                    <th>Reason</th>
                                    <th>Action</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php if (!$lateFeeRuleRows): ?>
                                    <tr><td colspan="14" class="text-center text-muted py-4">No late fee rules added yet. The existing global late fee settings will still act as the fallback.</td></tr>
                                <?php else: ?>
                                    <?php foreach ($lateFeeRuleRows as $ruleRow): ?>
                                        <tr>
                                            <td><?php echo e((string)($ruleRow['session_name'] ?: 'All Sessions')); ?></td>
                                            <td><?php echo e((string)($ruleRow['semester'] ?: 'All Semesters')); ?></td>
                                            <td><?php echo e((string)($ruleRow['faculty_name'] ?: 'All Faculties')); ?></td>
                                            <td><?php echo e((string)($ruleRow['department_name'] ?: 'All Departments')); ?></td>
                                            <td><?php echo e((string)($ruleRow['programme_name'] ?: 'All Programmes')); ?></td>
                                            <td><?php echo e((string)($ruleRow['mode_of_study_display'] ?: 'All Modes of Study')); ?></td>
                                            <td><?php echo e((string)(trim((string)($ruleRow['course_of_study_name'] ?? '')) !== '' ? $ruleRow['course_of_study_name'] : 'All Course of Study')); ?></td>
                                            <td><?php echo e((string)($ruleRow['level'] ?: 'All Levels')); ?></td>
                                            <td><span class="small text-muted"><?php echo e(portalLateFeeRuleScopeSummary($pdo, (array)$ruleRow)); ?></span></td>
                                            <td><?php echo (($ruleRow['late_fee_type'] ?? 'fixed') === 'percentage') ? e(number_format((float)($ruleRow['late_fee_value'] ?? 0), 2) . '%') : ('₦' . e(number_format((float)($ruleRow['late_fee_value'] ?? 0), 2))); ?></td>
                                            <td><?php echo e(portalLateFeeRuleTriggerLabel((array)$ruleRow)); ?></td>
                                            <td><span class="badge <?php echo !empty($ruleRow['is_enabled']) ? 'bg-success' : 'bg-secondary'; ?>"><?php echo !empty($ruleRow['is_enabled']) ? 'Enabled' : 'Disabled'; ?></span></td>
                                            <td><?php echo e((string)($ruleRow['reason'] ?? '')); ?></td>
                                            <td class="text-nowrap">
                                                <a class="btn btn-sm btn-outline-primary mb-1" href="?page=admin_fees_manager&fees_tab=late_fee&edit_late_fee_rule_id=<?php echo (int)($ruleRow['id'] ?? 0); ?>">Edit</a>
                                                <button type="button" class="btn btn-sm <?php echo !empty($ruleRow['is_enabled']) ? 'btn-outline-warning' : 'btn-outline-success'; ?> mb-1" onclick="(function(){var f=document.createElement('form');f.method='post';var a=document.createElement('input');a.type='hidden';a.name='action';a.value='toggle_late_fee_rule';f.appendChild(a);var i=document.createElement('input');i.type='hidden';i.name='late_fee_rule_id';i.value='<?php echo (int)($ruleRow['id'] ?? 0); ?>';f.appendChild(i);var s=document.createElement('input');s.type='hidden';s.name='next_state';s.value='<?php echo !empty($ruleRow['is_enabled']) ? 'disable' : 'enable'; ?>';f.appendChild(s);document.body.appendChild(f);f.submit();})();"><?php echo !empty($ruleRow['is_enabled']) ? 'Disable' : 'Enable'; ?></button>
                                                <button type="button" class="btn btn-sm btn-outline-danger mb-1" onclick="if(!confirm('Delete this late fee rule?')) return; (function(){var f=document.createElement('form');f.method='post';var a=document.createElement('input');a.type='hidden';a.name='action';a.value='delete_late_fee_rule';f.appendChild(a);var i=document.createElement('input');i.type='hidden';i.name='late_fee_rule_id';i.value='<?php echo (int)($ruleRow['id'] ?? 0); ?>';f.appendChild(i);document.body.appendChild(f);f.submit();})();">Delete</button>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                <?php endif; ?>
                            </tbody>
                        </table>
                    </div>
                    <div class="small text-muted mt-2">Late fee applies only to School Fees. Exact scope rules now respect Faculty → Department → Programme → Mode of Study → Course of Study → Level specificity, while broader defaults remain fallback rules.</div>
                </div>
            </div>
            <script>
            (function(){
                var faculty = document.getElementById('late_fee_rule_faculty_id');
                var department = document.getElementById('late_fee_rule_department_id');
                var programme = document.getElementById('late_fee_rule_programme_id');
                var mode = document.getElementById('late_fee_rule_mode_of_study');
                var modeCustomWrap = document.getElementById('late_fee_rule_mode_custom_wrap');
                var modeCustomInput = document.getElementById('late_fee_rule_mode_of_study_custom');
                var course = document.getElementById('late_fee_rule_course_of_study_id');
                var courseLabelHidden = document.querySelector('input[name="late_fee_rule_course_of_study_label"]');
                var trigger = document.getElementById('late_fee_rule_trigger_condition');
                var dueWrap = document.getElementById('late_fee_rule_due_date_wrap');

                var deptOptions = department ? Array.from(department.querySelectorAll('option[data-faculty-id]')) : [];
                var programmeOptions = programme ? Array.from(programme.querySelectorAll('option[data-department-id]')) : [];
                var courseOptions = course ? Array.from(course.querySelectorAll('option[data-faculty-id]')) : [];

                function syncDueDate(){
                    if (!trigger || !dueWrap) return;
                    dueWrap.style.display = trigger.value === 'due_date' ? '' : 'none';
                }

                function syncModeCustom(){
                    if (!mode || !modeCustomWrap || !modeCustomInput) return;
                    var show = String(mode.value || '') === 'Others';
                    modeCustomWrap.style.display = show ? '' : 'none';
                    modeCustomInput.required = show;
                    if (!show) {
                        modeCustomInput.value = '';
                    }
                }

                function syncDepartments(){
                    if (!faculty || !department) return;
                    var facultyId = String(faculty.value || '0');
                    deptOptions.forEach(function(opt){
                        var show = facultyId === '0' || String(opt.getAttribute('data-faculty-id') || '0') === facultyId;
                        opt.hidden = !show;
                        opt.disabled = !show;
                    });
                    var selected = department.options[department.selectedIndex];
                    if (selected && (selected.hidden || selected.disabled)) {
                        department.value = '0';
                    }
                }

                function syncProgrammes(){
                    if (!faculty || !department || !programme) return;
                    var facultyId = String(faculty.value || '0');
                    var departmentId = String(department.value || '0');
                    programmeOptions.forEach(function(opt){
                        var optFaculty = String(opt.getAttribute('data-faculty-id') || '0');
                        var optDepartment = String(opt.getAttribute('data-department-id') || '0');
                        var showFaculty = facultyId === '0' || optFaculty === facultyId;
                        var showDepartment = departmentId === '0' || optDepartment === departmentId;
                        var show = showFaculty && showDepartment;
                        opt.hidden = !show;
                        opt.disabled = !show;
                    });
                    var selected = programme.options[programme.selectedIndex];
                    if (selected && (selected.hidden || selected.disabled)) {
                        programme.value = '0';
                    }
                }

                function syncCourses(){
                    if (!faculty || !department || !programme || !course) return;
                    var facultyId = String(faculty.value || '0');
                    var departmentId = String(department.value || '0');
                    var selectedProgramme = programme.options[programme.selectedIndex] || null;
                    var programmeDepartmentId = selectedProgramme ? String(selectedProgramme.getAttribute('data-department-id') || '0') : '0';
                    var effectiveDepartmentId = departmentId !== '0' ? departmentId : programmeDepartmentId;

                    courseOptions.forEach(function(opt){
                        var optFaculty = String(opt.getAttribute('data-faculty-id') || '0');
                        var optDepartment = String(opt.getAttribute('data-department-id') || '0');
                        var showFaculty = facultyId === '0' || optFaculty === facultyId;
                        var showDepartment = effectiveDepartmentId === '0' || optDepartment === effectiveDepartmentId;
                        var show = showFaculty && showDepartment;
                        opt.hidden = !show;
                        opt.disabled = !show;
                    });

                    var selected = course.options[course.selectedIndex];
                    if (selected && (selected.hidden || selected.disabled)) {
                        course.value = '0';
                    }

                    if (courseLabelHidden) {
                        var courseSelected = course.options[course.selectedIndex] || null;
                        courseLabelHidden.value = courseSelected && String(course.value || '0') !== '0'
                            ? String(courseSelected.textContent || '').trim()
                            : '';
                    }
                }

                function syncProgrammeSuggestedMode(){
                    if (!programme || !mode || !modeCustomInput) return;
                    var selectedProgramme = programme.options[programme.selectedIndex] || null;
                    var suggestedMode = selectedProgramme ? String(selectedProgramme.getAttribute('data-mode-of-study') || '').trim() : '';
                    if (String(mode.value || '') !== '') return;
                    if (suggestedMode === 'Full-Time' || suggestedMode === 'Part-Time') {
                        mode.value = suggestedMode;
                    } else if (suggestedMode !== '') {
                        mode.value = 'Others';
                        if (!String(modeCustomInput.value || '').trim()) {
                            modeCustomInput.value = suggestedMode;
                        }
                    }
                    syncModeCustom();
                }

                if (faculty) {
                    faculty.addEventListener('change', function(){
                        syncDepartments();
                        syncProgrammes();
                        syncCourses();
                    });
                }
                if (department) {
                    department.addEventListener('change', function(){
                        syncProgrammes();
                        syncCourses();
                    });
                }
                if (programme) {
                    programme.addEventListener('change', function(){
                        var selectedProgramme = programme.options[programme.selectedIndex] || null;
                        var programmeDepartmentId = selectedProgramme ? String(selectedProgramme.getAttribute('data-department-id') || '0') : '0';
                        if (programmeDepartmentId !== '0' && department && String(department.value || '0') !== programmeDepartmentId) {
                            department.value = programmeDepartmentId;
                            syncDepartments();
                            syncProgrammes();
                        }
                        syncProgrammeSuggestedMode();
                        syncCourses();
                    });
                }
                if (mode) {
                    mode.addEventListener('change', syncModeCustom);
                }
                if (course) {
                    course.addEventListener('change', syncCourses);
                }
                if (trigger) {
                    trigger.addEventListener('change', syncDueDate);
                }

                syncDepartments();
                syncProgrammes();
                syncProgrammeSuggestedMode();
                syncCourses();
                syncModeCustom();
                syncDueDate();
            })();
            </script>

<?php elseif ($feesTab === 'wallet_settings'): ?>
            <div class="card mb-3">
                <div class="card-header">Wallet Settings</div>
                <div class="card-body">
                    <form method="post" class="row g-3">
                        <input type="hidden" name="action" value="update_wallet_settings">
                        <input type="hidden" name="return_to" value="<?php echo e('?page=admin_fees_manager&fees_tab=wallet_settings'); ?>">
                        <div class="col-md-4"><div class="form-check form-switch mt-4"><input class="form-check-input" type="checkbox" name="wallet_enabled" <?php echo (($settings['wallet_enabled'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>><label class="form-check-label">Enable Wallet Feature</label></div></div>
                        <div class="col-md-4"><div class="form-check form-switch mt-4"><input class="form-check-input" type="checkbox" name="wallet_funding_enabled" <?php echo (($settings['wallet_funding_enabled'] ?? 'enabled') === 'enabled') ? 'checked' : ''; ?>><label class="form-check-label">Allow Wallet Funding Online</label></div></div>
                        <div class="col-md-4"><label class="form-label">Minimum Wallet Funding (₦)</label><input type="number" class="form-control" name="wallet_min_fund" min="0" step="50" value="<?php echo e((string)($settings['wallet_min_fund'] ?? '1000')); ?>"></div>
                        <div class="col-12"><button class="btn btn-primary" type="submit">Save Wallet Settings</button></div>
                    </form>
                </div>
            </div>
        <?php elseif ($feesTab === 'receipts_notifications'): ?>
            <div class="card mb-3">
                <div class="card-header">Receipts / Payment Notifications</div>
                <div class="card-body">
                    <form method="post" class="row g-3">
                        <input type="hidden" name="action" value="update_settings">
                        <input type="hidden" name="return_to" value="<?php echo e('?page=admin_fees_manager&fees_tab=receipts_notifications'); ?>"><?php portalRenderUpdateSettingsHiddenInputs($settings, ['portal_title','smtp_from_email','smtp_from_name','smtp_reply_to','smtp_host','smtp_port','smtp_username','smtp_password']); ?>
                        <div class="col-md-4"><label class="form-label">Portal Title</label><input class="form-control" name="portal_title" value="<?php echo e((string)($settings['portal_title'] ?? 'HontTech System')); ?>"></div>
                        <div class="col-md-4"><label class="form-label">SMTP From Email</label><input class="form-control" name="smtp_from_email" value="<?php echo e((string)($settings['smtp_from_email'] ?? '')); ?>"></div>
                        <div class="col-md-4"><label class="form-label">SMTP From Name</label><input class="form-control" name="smtp_from_name" value="<?php echo e((string)($settings['smtp_from_name'] ?? '')); ?>"></div>
                        <div class="col-md-4"><label class="form-label">SMTP Reply-To</label><input class="form-control" name="smtp_reply_to" value="<?php echo e((string)($settings['smtp_reply_to'] ?? '')); ?>"></div>
                        <div class="col-md-4"><label class="form-label">SMTP Host</label><input class="form-control" name="smtp_host" value="<?php echo e((string)($settings['smtp_host'] ?? '')); ?>"></div>
                        <div class="col-md-4"><label class="form-label">SMTP Port</label><input class="form-control" name="smtp_port" value="<?php echo e((string)($settings['smtp_port'] ?? '')); ?>"></div>
                        <div class="col-md-6"><label class="form-label">SMTP Username</label><input class="form-control" name="smtp_username" value="<?php echo e((string)($settings['smtp_username'] ?? '')); ?>"></div>
                        <div class="col-md-6"><label class="form-label">SMTP Password</label><input type="password" class="form-control" name="smtp_password" value="<?php echo e((string)($settings['smtp_password'] ?? '')); ?>"></div>
                        <div class="col-12"><div class="alert alert-light border mb-0">Receipt links sent by email now use secure public receipt tokens, so students can open the receipt even when logged out. Invalid or expired links fail safely.</div></div>
                        <div class="col-12"><button class="btn btn-primary" type="submit">Save Receipt / Notification Settings</button></div>
                    </form>
                </div>
            </div>
        <?php elseif ($feesTab === 'fee_scope_filters'): ?>
            <div class="alert alert-info border mb-3">
                Scope filters support global fees, faculty-only, department-only, programme-only, session-only, semester-only, level-only, and combined academic scope rules. Exact scope matches continue to take priority over generic rules.
            </div>
        <?php endif; ?>

        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card">
                    <div class="card-header"><?php echo $editId > 0 ? 'Edit Fee Record' : 'Add Fee Record'; ?></div>
                    <div class="card-body">
                        <form method="post" class="row g-3" id="schoolFeeScopeForm">
                            <input type="hidden" name="action" value="save_school_fee_scope">
                            <input type="hidden" name="edit_id" value="<?php echo (int)($editRow['id'] ?? 0); ?>">
                            <input type="hidden" name="return_to" value="<?php echo e($currentFeesManagerUrl); ?>">
                            <div class="col-12"><label class="form-label">Fee Type</label><select class="form-select" name="fee_type" id="school_fee_type" required><?php foreach (portalManagedFeeTypeOptions() as $feeTypeKey => $feeTypeLabel): ?><option value="<?php echo e($feeTypeKey); ?>" <?php echo (portalNormalizeManagedFeeType((string)($editRow['fee_type'] ?? 'school')) === $feeTypeKey) ? 'selected' : ''; ?>><?php echo e($feeTypeLabel); ?></option><?php endforeach; ?></select></div>
                            <div class="col-12" id="school_fee_custom_title_wrap" style="<?php echo (portalNormalizeManagedFeeType((string)($editRow['fee_type'] ?? 'school')) === 'custom') ? '' : 'display:none;'; ?>">
                                <label class="form-label">Custom Fee Title</label>
                                <input class="form-control" name="custom_title" id="school_fee_custom_title" value="<?php echo e((string)($editRow['custom_title'] ?? '')); ?>" placeholder="e.g. Departmental Dues">
                            </div>
                            <div class="col-12">
                                <label class="form-label">Session</label>
                                <select class="form-select" name="session_name" id="school_fee_session_name">
                                    <option value="">All Sessions</option>
                                    <?php foreach ($sessions as $s): ?><option value="<?php echo e($s); ?>" <?php echo (strcasecmp((string)($editRow['session_name'] ?? ''), (string)$s) === 0) ? 'selected' : ''; ?>><?php echo e($s); ?></option><?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-12">
                                <label class="form-label">Semester</label>
                                <select class="form-select" name="semester">
                                    <option value="">All Semesters</option>
                                    <?php foreach (portalAvailableSemesterOptions() as $opt): ?>
                                        <option value="<?php echo e($opt); ?>" <?php echo (strcasecmp((string)($editRow['semester'] ?? ''), $opt) === 0) ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-12">
                                <label class="form-label">Faculty</label>
                                <select class="form-select" name="faculty_id" id="school_fee_faculty_id">
                                    <option value="0">All Faculties</option>
                                    <?php foreach ($faculties as $f): ?>
                                        <option value="<?php echo (int)$f['id']; ?>" <?php echo ((int)($editRow['faculty_id'] ?? 0) === (int)$f['id']) ? 'selected' : ''; ?>><?php echo e($f['name']); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-12">
                                <label class="form-label">Department</label>
                                <select class="form-select" name="department_id" id="school_fee_department_id">
                                    <option value="0">All Departments</option>
                                    <?php foreach ($departments as $d): ?>
                                        <option value="<?php echo (int)$d['id']; ?>" data-faculty-id="<?php echo (int)($d['faculty_id'] ?? 0); ?>" <?php echo ((int)($editRow['department_id'] ?? 0) === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e($d['name']); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-12">
                                <label class="form-label">Programme</label>
                                <select class="form-select" name="programme_id" id="school_fee_programme_id">
                                    <option value="0">All Programmes</option>
                                    <?php foreach ($programmes as $p): ?>
                                        <option value="<?php echo (int)$p['id']; ?>" data-faculty-id="<?php echo (int)($p['faculty_id'] ?? 0); ?>" data-department-id="<?php echo (int)($p['department_id'] ?? 0); ?>" <?php echo ((int)($editRow['programme_id'] ?? 0) === (int)$p['id']) ? 'selected' : ''; ?>><?php echo e($p['name']); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-12">
                                <label class="form-label">Level</label>
                                <input class="form-control" name="level" value="<?php echo e((string)($editRow['level'] ?? '')); ?>" list="school_fee_level_list" placeholder="Leave blank for all levels">
                                <datalist id="school_fee_level_list">
                                    <?php foreach ($levels as $lv): ?><option value="<?php echo e($lv); ?>"></option><?php endforeach; ?>
                                </datalist>
                            </div>
                            <div class="col-12">
                                <label class="form-label">Fee Amount (₦)</label>
                                <input type="number" min="0" step="0.01" class="form-control" name="amount" value="<?php echo e((string)($editRow['amount'] ?? '0')); ?>" required>
                            </div>
                            <div class="col-12">
                                <label class="form-label">Notes</label>
                                <textarea class="form-control" name="notes" rows="2" placeholder="Optional internal note"><?php echo e((string)($editRow['notes'] ?? '')); ?></textarea>
                            </div>
                            <div class="col-12">
                                <div class="form-check">
                                    <input class="form-check-input" type="checkbox" name="is_active" id="school_fee_active" <?php echo !empty($editRow['is_active']) ? 'checked' : ''; ?>>
                                    <label class="form-check-label" for="school_fee_active">Active</label>
                                </div>
                            </div>
                            <div class="col-12 d-flex gap-2">
                                <button class="btn btn-primary" type="submit"><i class="bi bi-save"></i> <?php echo $editId > 0 ? 'Update Fee Record' : 'Save Fee Record'; ?></button>
                                <a class="btn btn-outline-secondary" href="?page=admin_fees_manager&fees_tab=<?php echo urlencode($feesTab); ?>"><i class="bi bi-arrow-counterclockwise"></i> Reset</a>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
            <div class="col-lg-8">
                <div class="card">
                    <div class="card-header">Configured Fees</div>
                    <div class="card-body">
                        <form method="get" class="row g-2 align-items-end mb-3">
                            <input type="hidden" name="page" value="admin_fees_manager">
                            <input type="hidden" name="fees_tab" value="<?php echo e($feesTab); ?>">
                            <div class="col-md-2">
                                <label class="form-label">Fee Type</label>
                                <select class="form-select" name="fee_type"><option value="" <?php echo ($filterFeeType === '') ? 'selected' : ''; ?>>All Fee Types</option><?php foreach (portalManagedFeeTypeOptions() as $feeTypeKey => $feeTypeLabel): ?><option value="<?php echo e($feeTypeKey); ?>" <?php echo ($filterFeeType === $feeTypeKey) ? 'selected' : ''; ?>><?php echo e($feeTypeLabel); ?></option><?php endforeach; ?></select>
                            </div>
                            <div class="col-md-2">
                                <label class="form-label">Search</label>
                                <input class="form-control" name="q" value="<?php echo e($feeSearch); ?>" placeholder="Title, note, faculty...">
                            </div>
                            <div class="col-md-2">
                                <label class="form-label">Session</label>
                                <input class="form-control" name="session_name" value="<?php echo e($filterSession); ?>" list="school_fee_session_list" placeholder="All sessions">
                            </div>
                            <div class="col-md-2">
                                <label class="form-label">Semester</label>
                                <select class="form-select" name="semester">
                                    <option value="">All</option>
                                    <?php foreach (portalAvailableSemesterOptions() as $opt): ?>
                                        <option value="<?php echo e($opt); ?>" <?php echo (strcasecmp($filterSemester, $opt) === 0) ? 'selected' : ''; ?>><?php echo e($opt); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-md-2">
                                <label class="form-label">Faculty</label>
                                <select class="form-select" name="faculty_id" id="school_fee_filter_faculty_id">
                                    <option value="0">All</option>
                                    <?php foreach ($faculties as $f): ?>
                                        <option value="<?php echo (int)$f['id']; ?>" <?php echo ($filterFacultyId === (int)$f['id']) ? 'selected' : ''; ?>><?php echo e($f['name']); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-md-2">
                                <label class="form-label">Department</label>
                                <select class="form-select" name="department_id" id="school_fee_filter_department_id">
                                    <option value="0">All</option>
                                    <?php foreach ($departments as $d): ?>
                                        <option value="<?php echo (int)$d['id']; ?>" data-faculty-id="<?php echo (int)($d['faculty_id'] ?? 0); ?>" <?php echo ($filterDepartmentId === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e($d['name']); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-md-2">
                                <label class="form-label">Programme</label>
                                <select class="form-select" name="programme_id" id="school_fee_filter_programme_id">
                                    <option value="0">All</option>
                                    <?php foreach ($programmes as $p): ?>
                                        <option value="<?php echo (int)$p['id']; ?>" data-faculty-id="<?php echo (int)($p['faculty_id'] ?? 0); ?>" data-department-id="<?php echo (int)($p['department_id'] ?? 0); ?>" <?php echo ($filterProgrammeId === (int)$p['id']) ? 'selected' : ''; ?>><?php echo e($p['name']); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-md-1">
                                <label class="form-label">Level</label>
                                <select class="form-select" name="level" id="school_fee_filter_level">
                                    <option value="">All Levels</option>
                                    <?php foreach ($levels as $lvl): ?><option value="<?php echo e($lvl); ?>" <?php echo (strcasecmp($filterLevel, (string)$lvl) === 0) ? 'selected' : ''; ?>><?php echo e($lvl); ?></option><?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-md-2">
                                <label class="form-label">Sort</label>
                                <select class="form-select" name="sort">
                                    <option value="specificity_desc" <?php echo $sortBy==='specificity_desc'?'selected':''; ?>>Most Specific</option>
                                    <option value="amount_asc" <?php echo $sortBy==='amount_asc'?'selected':''; ?>>Amount ↑</option>
                                    <option value="amount_desc" <?php echo $sortBy==='amount_desc'?'selected':''; ?>>Amount ↓</option>
                                    <option value="session_desc" <?php echo $sortBy==='session_desc'?'selected':''; ?>>Session ↓</option>
                                    <option value="newest" <?php echo $sortBy==='newest'?'selected':''; ?>>Newest</option>
                                </select>
                            </div>
                            <div class="col-md-12 d-flex gap-2">
                                <button class="btn btn-outline-primary" type="submit"><i class="bi bi-funnel"></i> Filter</button>
                                <a class="btn btn-outline-secondary" href="?page=admin_fees_manager&fees_tab=<?php echo urlencode($feesTab); ?>"><i class="bi bi-arrow-counterclockwise"></i> Clear</a>
                            </div>
                        </form>

                        <div class="table-responsive">
                            <table class="table table-bordered table-striped align-middle">
                                <thead>
                                    <tr>
                                        <th>Fee Type</th>
                                        <th>Scope Label</th>
                                        <th>Session</th>
                                        <th>Semester</th>
                                        <th>Faculty</th>
                                        <th>Department</th>
                                        <th>Programme</th>
                                        <th>Mode of Study</th>
                                        <th>Level</th>
                                        <th>Amount</th>
                                        <th>Status</th>
                                        <th>Priority</th>
                                        <th style="width: 220px;">Actions</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <?php if (!$rows): ?>
                                        <tr><td colspan="13" class="text-center text-muted">No fee records found for the selected filters.</td></tr>
                                    <?php else: foreach ($rows as $row): ?>
                                        <tr>
                                            <td><?php echo e(portalManagedFeeDisplayLabel((array)$row)); ?></td>
                                            <td><span class="badge bg-light text-dark border"><?php echo e(portalManagedFeeScopeLabel((array)$row)); ?></span></td>
                                            <td><?php echo e(portalFeesDisplayOrFallback((string)($row['session_name'] ?? ''), 'All Sessions')); ?></td>
                                            <td><?php echo e(portalFeesDisplayOrFallback((string)($row['semester'] ?? ''), 'All Semesters')); ?></td>
                                            <td><?php echo e(portalFeesDisplayOrFallback((string)($row['faculty_name'] ?? ''), 'All Faculties')); ?></td>
                                            <td><?php echo e(portalFeesDisplayOrFallback((string)($row['department_name'] ?? ''), 'All Departments')); ?></td>
                                            <td><?php echo e(portalFeesDisplayOrFallback((string)($row['programme_name'] ?? ''), 'All Programmes')); ?></td>
                                            <td><?php echo e((int)($row['programme_id'] ?? 0) > 0 ? portalInferModeOfStudy((array)$row) : 'All Modes'); ?></td>
                                            <td><?php echo e(portalFeesDisplayOrFallback((string)($row['level'] ?? ''), 'All Levels')); ?></td>
                                            <td>₦<?php echo number_format((float)($row['amount'] ?? 0), 2); ?></td>
                                            <td><?php echo !empty($row['is_active']) ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Inactive</span>'; ?></td>
                                            <td><?php echo (int)portalSchoolFeeRuleSpecificity((array)$row); ?></td>
                                            <td>
                                                <div class="d-flex flex-wrap gap-2">
                                                    <a class="btn btn-sm btn-outline-primary" href="?page=admin_fees_manager&fees_tab=<?php echo urlencode($feesTab); ?>&edit_fee_id=<?php echo (int)$row['id']; ?>&fee_type=<?php echo urlencode((string)($row['fee_type'] ?? $filterFeeType)); ?>&q=<?php echo urlencode($feeSearch); ?>&session_name=<?php echo urlencode($filterSession); ?>&semester=<?php echo urlencode($filterSemester); ?>&faculty_id=<?php echo (int)$filterFacultyId; ?>&department_id=<?php echo (int)$filterDepartmentId; ?>&programme_id=<?php echo (int)$filterProgrammeId; ?>&level=<?php echo urlencode($filterLevel); ?>&sort=<?php echo urlencode($sortBy); ?>">Edit</a>
                                                    <form method="post" class="d-inline">
                                                        <input type="hidden" name="action" value="toggle_school_fee_scope">
                                                        <input type="hidden" name="id" value="<?php echo (int)$row['id']; ?>">
                                                        <input type="hidden" name="is_active" value="<?php echo empty($row['is_active']) ? '1' : ''; ?>">
                                                        <input type="hidden" name="return_to" value="<?php echo e($currentFeesManagerUrl); ?>">
                                                        <button class="btn btn-sm btn-outline-<?php echo !empty($row['is_active']) ? 'warning' : 'success'; ?>" type="submit"><?php echo !empty($row['is_active']) ? 'Deactivate' : 'Activate'; ?></button>
                                                    </form>
                                                    <form method="post" class="d-inline" onsubmit="return confirm('Delete this fee record?');">
                                                        <input type="hidden" name="action" value="delete_school_fee_scope">
                                                        <input type="hidden" name="id" value="<?php echo (int)$row['id']; ?>">
                                                        <input type="hidden" name="return_to" value="<?php echo e($currentFeesManagerUrl); ?>">
                                                        <button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
                                                    </form>
                                                </div>
                                            </td>
                                        </tr>
                                    <?php endforeach; endif; ?>
                                </tbody>
                            </table>
                        </div>
                        <div class="alert alert-light border mb-0">
                            Exact scope matching takes priority over generic rules. Registration Fee, Acceptance Fee, Hostel Fee, School Fee, and Custom Fee now use Fees Manager as the central management source where applicable, while legacy fee settings remain synchronized for backward compatibility.
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script>
    (function(){
        const faculty = document.getElementById('school_fee_faculty_id');
        const department = document.getElementById('school_fee_department_id');
        const programme = document.getElementById('school_fee_programme_id');
        const feeType = document.getElementById('school_fee_type');
        const customWrap = document.getElementById('school_fee_custom_title_wrap');
        const customInput = document.getElementById('school_fee_custom_title');
        const filterFaculty = document.getElementById('school_fee_filter_faculty_id');
        const filterDepartment = document.getElementById('school_fee_filter_department_id');
        const filterProgramme = document.getElementById('school_fee_filter_programme_id');

        function syncCustomFeeTitle(){
            if (!feeType || !customWrap) return;
            const isCustom = String(feeType.value || '') === 'custom';
            customWrap.style.display = isCustom ? '' : 'none';
            if (customInput) customInput.required = isCustom;
        }
        function makeScopedSync(facultySelect, departmentSelect, programmeSelect){
            if (!facultySelect || !departmentSelect || !programmeSelect) return function(){};
            const deptOptions = Array.from(departmentSelect.querySelectorAll('option[data-faculty-id]'));
            const progOptions = Array.from(programmeSelect.querySelectorAll('option[data-department-id]'));
            function syncDepartments(){
                const facultyId = String(facultySelect.value || '0');
                deptOptions.forEach(function(opt){
                    const show = facultyId === '0' || String(opt.getAttribute('data-faculty-id') || '0') === facultyId;
                    opt.hidden = !show;
                    opt.disabled = !show;
                });
                const selected = departmentSelect.options[departmentSelect.selectedIndex];
                if (selected && (selected.hidden || selected.disabled)) departmentSelect.value = '0';
            }
            function syncProgrammes(){
                const facultyId = String(facultySelect.value || '0');
                const deptId = String(departmentSelect.value || '0');
                progOptions.forEach(function(opt){
                    const optFaculty = String(opt.getAttribute('data-faculty-id') || '0');
                    const optDept = String(opt.getAttribute('data-department-id') || '0');
                    const showFaculty = facultyId === '0' || optFaculty === facultyId;
                    const showDept = deptId === '0' || optDept === deptId;
                    const show = showFaculty && showDept;
                    opt.hidden = !show;
                    opt.disabled = !show;
                });
                const selected = programmeSelect.options[programmeSelect.selectedIndex];
                if (selected && (selected.hidden || selected.disabled)) programmeSelect.value = '0';
            }
            facultySelect.addEventListener('change', function(){ syncDepartments(); syncProgrammes(); });
            departmentSelect.addEventListener('change', syncProgrammes);
            return function(){ syncDepartments(); syncProgrammes(); };
        }
        const syncMain = makeScopedSync(faculty, department, programme);
        const syncFilters = makeScopedSync(filterFaculty, filterDepartment, filterProgramme);
        if (feeType) feeType.addEventListener('change', syncCustomFeeTitle);
        syncMain();
        syncFilters();
        syncCustomFeeTitle();
    })();
    </script>
    <?php
    break;

case 'admin_registration_settings':
    if (!isAdmin()) { redirect('?page=admin_login'); }

    $sessionName = trim((string)($_GET['session_name'] ?? ''));
    if ($sessionName === '') $sessionName = (string)setting('academic_session','');

    $semester = (string)($_GET['semester'] ?? 'First');
    if (!in_array($semester, ['First','Second'], true)) $semester = 'First';

    // Departmental rule selectors
    $facultyId = (int)($_GET['faculty_id'] ?? 0);
    $departmentId = (int)($_GET['department_id'] ?? 0);
    $level = trim((string)($_GET['level'] ?? ''));
    if ($level === '') $level = null;

    // Dropdown data
    $faculties = [];
    $departments = [];
    try { $faculties = $pdo->query("SELECT id, name FROM faculties WHERE is_active=1 ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try {
        if ($facultyId > 0) {
            $st = $pdo->prepare("SELECT id, name FROM departments WHERE is_active=1 AND faculty_id=? ORDER BY name");
            $st->execute([$facultyId]);
            $departments = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } else {
            $departments = $pdo->query("SELECT id, name FROM departments WHERE is_active=1 ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        }
    } catch (Throwable $e) {}

    portalRegistrationSettingsSupport($pdo);
    $programmeId = (int)($_GET['programme_id'] ?? 0);
    $current = portalRegistrationRuleForContext($pdo, [
        'session_name' => $sessionName,
        'semester' => $semester,
        'faculty_id' => $facultyId,
        'department_id' => $departmentId,
        'programme_id' => $programmeId,
        'level' => (string)($level ?? ''),
    ]);

    // Detect schema variants: session_id FK installs
    $useSessionId = false;
    $sessionId = null;
    try {
        $stc = $pdo->prepare("SHOW COLUMNS FROM registration_settings LIKE 'session_id'");
        $stc->execute();
        $useSessionId = ($stc->rowCount() > 0);
    } catch (Throwable $e) {}

    if ($useSessionId) {
        try {
            $st = $pdo->prepare("SELECT id FROM academic_sessions WHERE name=? LIMIT 1");
            $st->execute([(string)$sessionName]);
            $sessionId = (int)($st->fetchColumn() ?? 0);
        } catch (Throwable $e) { $sessionId = null; }
    }

// sessions list (optional)
    $sessions = [];
    try { $sessions = $pdo->query("SELECT name FROM academic_sessions ORDER BY name DESC")->fetchAll(PDO::FETCH_COLUMN) ?: []; } catch (Throwable $e) {}
    if (!$sessions) { $sessions = array_values(array_unique(array_filter([(string)setting('academic_session','')]))); }

    // Load saved rules for this session/semester (for edit/delete listing)
    $rules = [];
    try {
        if ($useSessionId && (int)$sessionId > 0) {
            $st = $pdo->prepare("
                SELECT rs.*, f.name AS faculty_name, d.name AS department_name, p.name AS programme_name
                FROM registration_settings rs
                LEFT JOIN faculties f ON f.id = rs.faculty_id
                LEFT JOIN departments d ON d.id = rs.department_id
                LEFT JOIN programmes p ON p.id = rs.programme_id
                WHERE rs.session_id=? AND rs.semester=?
                ORDER BY
                    (rs.department_id IS NULL) ASC,
                    rs.department_id ASC,
                    rs.level ASC
            ");
            $st->execute([(int)$sessionId, (string)$semester]);
            $rules = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } else {
            $st = $pdo->prepare("
                SELECT rs.*, f.name AS faculty_name, d.name AS department_name, p.name AS programme_name
                FROM registration_settings rs
                LEFT JOIN faculties f ON f.id = rs.faculty_id
                LEFT JOIN departments d ON d.id = rs.department_id
                LEFT JOIN programmes p ON p.id = rs.programme_id
                WHERE COALESCE(rs.session_name,'')=? AND rs.semester=?
                ORDER BY
                    (rs.department_id IS NULL) ASC,
                    rs.department_id ASC,
                    rs.level ASC
            ");
            $st->execute([(string)$sessionName, (string)$semester]);
            $rules = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        }
    } catch (Throwable $e) {}



    ?>
    <div class="container-fluid">
        <div class="d-flex align-items-center justify-content-between mb-3">
            <div>
                <h4 class="mb-0">Registration Settings</h4>
                <div class="text-muted small">Open/Close registration by session/semester and configure minimum/maximum credit load.</div>
            </div>
            <div class="d-flex gap-2">
                <a class="btn btn-outline-secondary btn-sm" href="?page=admin_registrations_manager">Registrations</a>
                <a class="btn btn-outline-primary btn-sm" href="?page=admin_yet_to_register">Yet To Register</a>
            </div>
        </div>

        <div class="card">
            <div class="card-body">
                <form method="post" class="row g-3">
                    <input type="hidden" name="action" value="save_registration_settings">
                    <div class="col-lg-4">
                        <label class="form-label">Session</label>
                        <select class="form-select" name="session_name">
                            <option value="">Select Session</option>
                            <?php foreach ($sessions as $s): ?>
                                <option value="<?php echo e($s); ?>" <?php echo ((string)$sessionName === (string)$s) ? 'selected' : ''; ?>><?php echo e($s); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-lg-3">
                        <label class="form-label">Semester</label>
                        <select class="form-select" name="semester">
                            <option value="First" <?php echo $semester==='First'?'selected':''; ?>>First</option>
                            <option value="Second" <?php echo $semester==='Second'?'selected':''; ?>>Second</option>
                        </select>
                    </div>
                    <div class="col-lg-3">
                        <label class="form-label">Registration</label>
                        <select class="form-select" name="is_open">
                            <option value="Yes" <?php echo (portalRegistrationIsOpen($current['is_open'] ?? 'Yes'))?'selected':''; ?>>Open</option>
                            <option value="No" <?php echo (!portalRegistrationIsOpen($current['is_open'] ?? 'Yes'))?'selected':''; ?>>Closed</option>
                        </select>
                    </div>
                    <div class="col-lg-2"></div>

                    <div class="col-lg-3">
                        <label class="form-label">Faculty</label>
                        <select class="form-select" name="faculty_id">
                            <option value="0">All Faculties</option>
                            <?php foreach ($faculties as $f): ?>
                                <option value="<?php echo (int)$f['id']; ?>" <?php echo ((int)$facultyId === (int)$f['id']) ? 'selected' : ''; ?>>
                                    <?php echo e($f['name']); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-lg-4">
                        <label class="form-label">Department</label>
                        <select class="form-select" name="department_id">
                            <option value="0">All Departments</option>
                            <?php foreach ($departments as $d): ?>
                                <option value="<?php echo (int)$d['id']; ?>" <?php echo ((int)$departmentId === (int)$d['id']) ? 'selected' : ''; ?>>
                                    <?php echo e($d['name']); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                        <div class="form-text">Set min/max per department (and optionally level).</div>
                    </div>
                    <div class="col-lg-3">
                        <label class="form-label">Programme</label>
                        <select class="form-select" name="programme_id">
                            <option value="0">All Programmes</option>
                            <?php $regProgrammes = []; try { $regProgrammes = portalProgrammeSelectorOptions($pdo, (int)($facultyId ?? 0), (int)($departmentId ?? 0)); } catch (Throwable $e) {} foreach ($regProgrammes as $p): ?>
                                <option value="<?php echo (int)$p['id']; ?>" <?php echo portalProgrammeOptionSelected($pdo, (int)$programmeId, (int)$p['id']) ? 'selected' : ''; ?>><?php echo e((string)($p['name'] ?? '')); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-lg-2">
                        <label class="form-label">Level</label>
                        <select class="form-select" name="level">
                            <option value="">All Levels</option>
                            <?php foreach (portalAcademicLevelOptions($pdo) as $lv): ?>
                                <option value="<?php echo e($lv); ?>" <?php echo ((string)($level ?? '') === (string)$lv) ? 'selected' : ''; ?>><?php echo e($lv); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="col-lg-1">
                        <label class="form-label">Min Units</label>
                        <input type="number" min="0" class="form-control" name="min_units" value="<?php echo (int)($current['min_units'] ?? 0); ?>">
                    </div>
                    <div class="col-lg-2">
                        <label class="form-label">Max Units</label>
                        <input type="number" min="0" class="form-control" name="max_units" value="<?php echo (int)($current['max_units'] ?? 30); ?>">
                        <div class="form-text">0 = no max</div>
                    </div>
                    <div class="col-12 d-flex gap-2">
                        <button class="btn btn-primary" type="submit"><i class="bi bi-save"></i> Save Settings</button>
                        <a class="btn btn-outline-secondary" href="?page=admin_registration_settings"><i class="bi bi-arrow-counterclockwise"></i> Reset</a>
                        <a class="btn btn-outline-dark ms-auto" href="?page=admin_yet_to_register&session_name=<?php echo urlencode($sessionName); ?>&semester=<?php echo urlencode($semester); ?>&faculty_id=<?php echo urlencode((string)$facultyId); ?>&department_id=<?php echo urlencode((string)$departmentId); ?>&programme_id=<?php echo urlencode((string)$programmeId); ?>&level=<?php echo urlencode((string)($level ?? '')); ?>"><i class="bi bi-person-x"></i> View Yet To Register</a>
                    </div>
                </form>

                <div class="alert alert-light border mt-4">
                    Registration rules save immediately and are applied when students load or submit their course registration form. Use the filters above to manage a rule for a specific session, semester, faculty, department, programme, and level.
                </div>

                <div class="table-responsive">
                    <table class="table table-bordered align-middle">
                        <thead>
                            <tr>
                                <th>Session</th>
                                <th>Semester</th>
                                <th>Faculty</th>
                                <th>Department</th>
                                <th>Programme</th>
                                <th>Mode of Study</th>
                                <th>Level</th>
                                <th>Status</th>
                                <th>Min</th>
                                <th>Max</th>
                                <th style="width:180px;">Action</th>
                            </tr>
                        </thead>
                        <tbody>
                        <?php if (!empty($rules)): foreach ($rules as $rule): ?>
                            <tr>
                                <td><?php echo e((string)($rule['session_name'] ?? $sessionName)); ?></td>
                                <td><?php echo e((string)($rule['semester'] ?? '')); ?></td>
                                <td><?php echo e((string)($rule['faculty_name'] ?? 'All Faculties')); ?></td>
                                <td><?php echo e((string)($rule['department_name'] ?? 'All Departments')); ?></td>
                                <td><?php echo e((string)($rule['programme_name'] ?? 'All Programmes')); ?></td>
                                <td><?php echo e((int)($rule['programme_id'] ?? 0) > 0 ? portalInferModeOfStudy((array)$rule) : 'All Modes'); ?></td>
                                <td><?php echo e((string)($rule['level'] ?? 'All Levels')); ?></td>
                                <td><span class="badge bg-<?php echo portalRegistrationIsOpen($rule['is_open'] ?? 'Yes') ? 'success' : 'danger'; ?>"><?php echo portalRegistrationIsOpen($rule['is_open'] ?? 'Yes') ? 'Open' : 'Closed'; ?></span></td>
                                <td><?php echo (int)($rule['min_units'] ?? 0); ?></td>
                                <td><?php echo (int)($rule['max_units'] ?? 30); ?></td>
                                <td>
                                    <a class="btn btn-sm btn-outline-primary" href="?page=admin_registration_settings&session_name=<?php echo urlencode((string)($rule['session_name'] ?? $sessionName)); ?>&semester=<?php echo urlencode((string)($rule['semester'] ?? 'First')); ?>&faculty_id=<?php echo urlencode((string)($rule['faculty_id'] ?? 0)); ?>&department_id=<?php echo urlencode((string)($rule['department_id'] ?? 0)); ?>&programme_id=<?php echo urlencode((string)($rule['programme_id'] ?? 0)); ?>&level=<?php echo urlencode((string)($rule['level'] ?? '')); ?>">Edit Scope</a>
                                    <form method="post" class="d-inline" onsubmit="return confirm('Delete this registration scope rule?');">
                                        <input type="hidden" name="action" value="delete_registration_setting">
                                        <input type="hidden" name="id" value="<?php echo (int)($rule['id'] ?? 0); ?>">
                                        <button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
                                    </form>
                                </td>
                            </tr>
                        <?php endforeach; else: ?>
                            <tr><td colspan="10" class="text-center text-muted">No registration scope rules found for the selected session and semester.</td></tr>
                        <?php endif; ?>
                        </tbody>
                    </table>
                </div>

</div>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;







case 'staff_users':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'staff_users_manage');

    if (isset($_GET['action']) && $_GET['action'] === 'access' && isset($_GET['id'])) {
        if (!isSuperadmin($pdo)) {
            flash('danger', 'Only Superadmin can access another admin/staff account.');
            redirect('?page=staff_users');
        }
        portalStartSuperadminImpersonation($pdo, 'staff', (int)$_GET['id']);
    }

    // Handle create/update/delete staff users
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $action = $_POST['action'] ?? '';
        if ($action === 'save_staff_user') {
            $id = (int)($_POST['id'] ?? 0);
            $username = trim((string)($_POST['username'] ?? ''));
            $email = trim((string)($_POST['email'] ?? ''));
            $fullName = trim((string)($_POST['full_name'] ?? ''));
            $role = portalNormalizeStaffRoleName((string)($_POST['role'] ?? 'Admin'));
            $facultyId = (int)($_POST['faculty_id'] ?? 0);
            $departmentId = (int)($_POST['department_id'] ?? 0);
            $password = (string)($_POST['password'] ?? '');

            // Dean restriction: can only add HOD, Lecturer, Course Adviser
            $myRole = strtolower(currentStaffRole($pdo));
            if ($myRole === 'dean') {
                $allowed = ['hod','lecturer','course adviser','course_adviser','course-adviser'];
                if (!in_array(strtolower($role), $allowed, true)) {
                    flash('danger', 'Dean can only create: HOD, Lecturer, Course Adviser.');
                    redirect('?page=staff_users');
                }
            }

            if ($username === '' || $email === '' || $fullName === '') {
                flash('danger', 'Username, email and full name are required.');
                redirect('?page=staff_users');
            }

            try {
                portalEnsureStaffRoleExists($pdo, $role);
                $isSuperRole = portalIsSuperadminRoleName($role) ? 1 : 0;
                $hasSuperCol = function_exists('portalColumnExistsSafe') && portalColumnExistsSafe($pdo, 'admins', 'is_superadmin');
                if ($id > 0) {
                    if ($password !== '') {
                        $hash = password_hash($password, PASSWORD_DEFAULT);
                        if ($hasSuperCol) {
                            $st = $pdo->prepare("UPDATE admins SET username=?, email=?, full_name=?, role=?, is_superadmin=?, faculty_id=?, department_id=?, password=? WHERE id=?");
                            $st->execute([$username,$email,$fullName,$role,$isSuperRole,($facultyId?:null),($departmentId?:null),$hash,$id]);
                        } else {
                            $st = $pdo->prepare("UPDATE admins SET username=?, email=?, full_name=?, role=?, faculty_id=?, department_id=?, password=? WHERE id=?");
                            $st->execute([$username,$email,$fullName,$role,($facultyId?:null),($departmentId?:null),$hash,$id]);
                        }
                    } else {
                        if ($hasSuperCol) {
                            $st = $pdo->prepare("UPDATE admins SET username=?, email=?, full_name=?, role=?, is_superadmin=?, faculty_id=?, department_id=? WHERE id=?");
                            $st->execute([$username,$email,$fullName,$role,$isSuperRole,($facultyId?:null),($departmentId?:null),$id]);
                        } else {
                            $st = $pdo->prepare("UPDATE admins SET username=?, email=?, full_name=?, role=?, faculty_id=?, department_id=? WHERE id=?");
                            $st->execute([$username,$email,$fullName,$role,($facultyId?:null),($departmentId?:null),$id]);
                        }
                    }
                    try { portalSyncAdminSuperadminAssignments($pdo); } catch (Throwable $e) {}
                    flash('success', 'Staff user updated.');
                } else {
                    $plainPassword = ($password !== '' ? $password : honttechGenerateTemporaryPassword(14));
                    $hash = password_hash($plainPassword, PASSWORD_DEFAULT);
                    if ($hasSuperCol) {
                        $st = $pdo->prepare("INSERT INTO admins (username,password,email,full_name,role,is_superadmin,faculty_id,department_id) VALUES (?,?,?,?,?,?,?,?)");
                        $st->execute([$username,$hash,$email,$fullName,$role,$isSuperRole,($facultyId?:null),($departmentId?:null)]);
                    } else {
                        $st = $pdo->prepare("INSERT INTO admins (username,password,email,full_name,role,faculty_id,department_id) VALUES (?,?,?,?,?,?,?)");
                        $st->execute([$username,$hash,$email,$fullName,$role,($facultyId?:null),($departmentId?:null)]);
                    }
                    $newAdminId = (int)$pdo->lastInsertId();
                    try { portalSyncAdminSuperadminAssignments($pdo); } catch (Throwable $e) {}

                    $savedStaff = [
                        'id' => $newAdminId,
                        'username' => $username,
                        'email' => $email,
                        'full_name' => $fullName,
                        'role' => $role,
                        'faculty_name' => '',
                        'department_name' => '',
                    ];
                    try {
                        $mailStaffStmt = $pdo->prepare("SELECT a.id, a.username, a.email, a.full_name, a.role, f.name AS faculty_name, d.name AS department_name FROM admins a LEFT JOIN faculties f ON f.id = a.faculty_id LEFT JOIN departments d ON d.id = a.department_id WHERE a.id = ? LIMIT 1");
                        $mailStaffStmt->execute([$newAdminId]);
                        $mailStaffRow = $mailStaffStmt->fetch(PDO::FETCH_ASSOC);
                        if ($mailStaffRow) {
                            $savedStaff = array_merge($savedStaff, $mailStaffRow);
                        }
                    } catch (Throwable $e) {}

                    $emailSent = false;
                    if (filter_var((string)$email, FILTER_VALIDATE_EMAIL)) {
                        try {
                            $emailSent = portalSendStaffAccountCreatedEmail($pdo, $savedStaff, $plainPassword);
                        } catch (Throwable $e) {
                            $emailSent = false;
                            $GLOBALS['portal_last_email_error'] = trim((string)$e->getMessage());
                        }
                    }

                    if ($emailSent) {
                        flash('success', 'Staff user created and account details emailed successfully.');
                    } else {
                        $mailErr = trim((string)($GLOBALS['portal_last_email_error'] ?? ''));
                        flash('warning', 'Staff user created, but the account details email could not be sent.' . ($mailErr !== '' ? ' ' . $mailErr : ''));
                    }
                }
            } catch (Throwable $e) {
                flash('danger', 'Failed to save staff user: ' . $e->getMessage());
            }
            redirect('?page=staff_users');
        }

        if ($action === 'delete_staff_user') {
            $id = (int)($_POST['id'] ?? 0);
            if ($id > 0) {
                try {
                    // prevent deleting self
                    if ($id === (int)($_SESSION['user_id'] ?? 0)) {
                        flash('danger', 'You cannot delete your own account.');
                    } else {
                        $st = $pdo->prepare("DELETE FROM admins WHERE id=?");
                        $st->execute([$id]);
                        flash('success', 'Staff user deleted.');
                    }
                } catch (Throwable $e) {
                    flash('danger', 'Failed to delete staff user: ' . $e->getMessage());
                }
            }
            redirect('?page=staff_users');
        }
    }

    // Data
    $roles = [];
    try { $roles = $pdo->query("SELECT role_name FROM staff_roles ORDER BY role_name ASC")->fetchAll(PDO::FETCH_COLUMN); } catch (Throwable $e) { $roles = ['Superadmin','Admin','Dean','HOD','Lecturer','Course Adviser','Account Department','Librarian']; }
    $faculties = [];
    try { $faculties = $pdo->query("SELECT id,name FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { $faculties = []; }
    $departments = [];
    try { $departments = $pdo->query("SELECT id,name,faculty_id FROM departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { $departments = []; }

    $editId = (int)($_GET['edit'] ?? 0);
    $editRow = null;
    if ($editId > 0) {
        try {
            $st = $pdo->prepare("SELECT * FROM admins WHERE id=? LIMIT 1");
            $st->execute([$editId]);
            $editRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        } catch (Throwable $e) { $editRow = null; }
    }

    $staffRows = [];
    try {
        $staffRows = $pdo->query("SELECT id,username,email,full_name,role,faculty_id,department_id,created_at FROM admins ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable $e) { $staffRows = []; }
    ?>
    <div class="container-fluid">
        <div class="d-flex align-items-center justify-content-between mb-3">
            <h4 class="mb-0">Staff Users</h4>
            <a class="btn btn-sm btn-outline-secondary" href="?page=staff_users">Refresh</a>
        </div>

        <div class="row g-3">
            <div class="col-lg-5">
                <div class="card">
                    <div class="card-header"><strong><?php echo $editRow ? 'Edit Staff User' : 'Add Staff User'; ?></strong></div>
                    <div class="card-body">
                        <form method="post">
                            <input type="hidden" name="action" value="save_staff_user">
                            <input type="hidden" name="id" value="<?php echo (int)($editRow['id'] ?? 0); ?>">
                            <div class="mb-2">
                                <label class="form-label">Username</label>
                                <input class="form-control" name="username" value="<?php echo e($editRow['username'] ?? ''); ?>" required>
                            </div>
                            <div class="mb-2">
                                <label class="form-label">Full name</label>
                                <input class="form-control" name="full_name" value="<?php echo e($editRow['full_name'] ?? ''); ?>" required>
                            </div>
                            <div class="mb-2">
                                <label class="form-label">Email</label>
                                <input class="form-control" type="email" name="email" value="<?php echo e($editRow['email'] ?? ''); ?>" required>
                            </div>
                            <div class="mb-2">
                                <label class="form-label">Role</label>
                                <select class="form-select" name="role">
                                    <?php foreach ($roles as $r): ?>
                                        <option value="<?php echo e($r); ?>" <?php echo (($editRow['role'] ?? 'Admin') === $r) ? 'selected' : ''; ?>>
                                            <?php echo e($r); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                            </div>

                            <div class="row g-2">
                                <div class="col-md-6">
                                    <label class="form-label">Faculty (optional)</label>
                                    <select class="form-select" name="faculty_id">
                                        <option value="0">-- none --</option>
                                        <?php foreach ($faculties as $f): ?>
                                            <option value="<?php echo (int)$f['id']; ?>" <?php echo ((int)($editRow['faculty_id'] ?? 0) === (int)$f['id']) ? 'selected' : ''; ?>>
                                                <?php echo e($f['name']); ?>
                                            </option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                                <div class="col-md-6">
                                    <label class="form-label">Department (optional)</label>
                                    <select class="form-select" name="department_id">
                                        <option value="0">-- none --</option>
                                        <?php foreach ($departments as $d): ?>
                                            <option value="<?php echo (int)$d['id']; ?>" <?php echo ((int)($editRow['department_id'] ?? 0) === (int)$d['id']) ? 'selected' : ''; ?>>
                                                <?php echo e($d['name']); ?>
                                            </option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                            </div>

                            <div class="mt-2">
                                <label class="form-label"><?php echo $editRow ? 'New password (leave blank to keep)' : 'Password (leave blank to auto-generate a secure temporary password)'; ?></label>
                                <input class="form-control" type="password" name="password" value="">
                            </div>

                            <button class="btn btn-primary mt-3" type="submit"><?php echo $editRow ? 'Update' : 'Create'; ?></button>
                            <?php if ($editRow): ?>
                                <a class="btn btn-outline-secondary mt-3" href="?page=staff_users">Cancel</a>
                            <?php endif; ?>
                        </form>
                    </div>
                </div>
            </div>

            <div class="col-lg-7">
                <div class="card">
                    <div class="card-header"><strong>All Staff</strong></div>
                    <div class="card-body table-responsive">
                        <table class="table table-sm align-middle">
                            <thead>
                                <tr>
                                    <th>#</th>
                                    <th>Username</th>
                                    <th>Name</th>
                                    <th>Role</th>
                                    <th>Email</th>
                                    <th style="width:150px">Actions</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php foreach ($staffRows as $r): ?>
                                    <tr>
                                        <td><?php echo (int)$r['id']; ?></td>
                                        <td><?php echo e($r['username']); ?></td>
                                        <td><?php echo e($r['full_name']); ?></td>
                                        <td><?php echo e($r['role'] ?? 'Admin'); ?></td>
                                        <td><?php echo e($r['email']); ?></td>
                                        <td>
                                            <a class="btn btn-sm btn-outline-primary" href="?page=staff_users&edit=<?php echo (int)$r['id']; ?>">Edit</a>
                                            <?php if (isSuperadmin($pdo) && (int)$r['id'] !== (int)(($_SESSION['superadmin_impersonation_backup']['user_id'] ?? ($_SESSION['user_id'] ?? 0)))): ?>
                                                <a class="btn btn-sm btn-dark" href="?page=staff_users&action=access&id=<?php echo (int)$r['id']; ?>">Access Account</a>
                                            <?php endif; ?>
                                            <form method="post" style="display:inline" onsubmit="return confirm('Delete this staff user?');">
                                                <input type="hidden" name="action" value="delete_staff_user">
                                                <input type="hidden" name="id" value="<?php echo (int)$r['id']; ?>">
                                                <button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
                                            </form>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                                <?php if (!$staffRows): ?>
                                    <tr><td colspan="6" class="text-muted">No staff users found.</td></tr>
                                <?php endif; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;


case 'staff_roles':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'staff_roles_manage');

    $roleName = portalNormalizeStaffRoleName((string)($_GET['role'] ?? 'Admin'));
    if ($roleName === '') $roleName = 'Admin';
    portalEnsureStaffRoleExists($pdo, $roleName);

    $permCatalog = [
        'staff_users_manage' => 'Manage staff users',
        'staff_roles_manage' => 'Manage roles & permissions',
        'academics_manage' => 'Academics managers (structure/sessions/courses)',
        'registrations_manage' => 'Registrations & reports',
        'results_compute' => 'Compute/submit results (Lecturer)',
        'results_approve' => 'Approve/reject results (Admin)',
        'results_publish' => 'Publish results (Admin)',
        'finance_manage' => 'Finance & billing',
        'clearance_manage' => 'Admin clearance management',
        'library_manage' => 'Library module',
        'cbt_manage' => 'CBT module',
        'lms_manage' => 'LMS module',
        'hrm_manage' => 'Human Resource Management module',
        'attendance_manage' => 'Student Attendance management',
    ];

    $menuCatalog = [
    // Admin core
    'menu_admin_dashboard' => 'Dashboard',
    'menu_admin_students' => 'Students',
    'menu_admin_applications' => 'Applications',
    'menu_admin_add_application' => 'Add Application',
    'menu_admin_payments' => 'Payments',
    'menu_admin_wallets' => 'Wallets',
    'menu_admin_settings' => 'Admin Settings',
    'menu_reports' => 'Reports / Export',
    'menu_messages' => 'Messages',
    'menu_notifications' => 'Notifications',

    // Faculty/Department/Programme + Sessions + Courses
    'menu_structure_manager' => 'Faculty / Department / Programme Manager',
    'menu_sessions_manager' => 'Promotion / Academic Session Manager',
    'menu_registrable_courses' => 'Registrable Courses Manager',
    'menu_numbering_settings' => 'Admission / Matric Numbering',

    // Registrations
    'menu_registrations_manager' => 'Registered Students',
    'menu_yet_to_register' => 'Yet To Register Students',
    'menu_registration_settings' => 'Registration Settings',

    // Results
    'menu_grading_result_settings' => 'Grading & Result Settings',
    'menu_results_manager' => 'Manage Results',
    'menu_results_approvals' => 'Result Approvals',
    'menu_download_scoresheet' => 'Download Scoresheet',
    'menu_transcript_admin' => 'Transcript',

    // Staff/Roles
    'menu_staff_users' => 'Staff Users',
    'menu_staff_roles' => 'Roles & Permissions',

    // Modules
    'menu_lms_admin' => 'LMS',
    'menu_library_admin' => 'Library',
    'menu_cbt_admin' => 'CBT',
    'menu_hrm' => 'HRM',
    'menu_student_attendance_admin' => 'Student Attendance',
    'menu_previous_admission_record' => 'Previous Admission Record',
    'menu_admin_clearance' => 'Admin Clearance',
    'menu_graduated' => 'Graduated',

    // Infrastructure
    'menu_notification_settings' => 'Notification Settings',
    'menu_feature_toggles' => 'Feature Toggles',
            'menu_pages' => 'Pages',
];

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'create_staff_role') {
        $newRoleName = portalNormalizeStaffRoleName((string)($_POST['new_role_name'] ?? ''));
        if ($newRoleName === '') {
            flash('danger', 'Role name is required.');
        } else {
            try {
                portalEnsureStaffRoleExists($pdo, $newRoleName, trim((string)($_POST['new_role_description'] ?? '')));
                if (function_exists('portalAuthLogActivity')) {
                    try {
                        portalAuthLogActivity($pdo, 'admin', (int)($_SESSION['user_id'] ?? 0), 'staff_role_created', [
                            'role_name' => $newRoleName,
                            'description' => trim((string)($_POST['new_role_description'] ?? '')),
                        ]);
                    } catch (Throwable $e) { /* ignore */ }
                }
                flash('success', 'Role created successfully: ' . $newRoleName);
                redirect('?page=staff_roles&role=' . urlencode($newRoleName));
            } catch (Throwable $e) {
                flash('danger', 'Unable to create role right now.');
            }
        }
        redirect('?page=staff_roles');
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete_staff_role') {
        $rn = portalNormalizeStaffRoleName((string)($_POST['role_name'] ?? ''));
        if ($rn === '' || in_array($rn, ['Superadmin','Admin'], true)) {
            flash('danger', 'This system role cannot be deleted.');
            redirect('?page=staff_roles&role=' . urlencode($roleName));
        }
        try {
            $st = $pdo->prepare("SELECT COUNT(*) FROM admins WHERE role = ?");
            $st->execute([$rn]);
            $assignedCount = (int)$st->fetchColumn();
            if ($assignedCount > 0) {
                flash('danger', 'Reassign or update all users with this role before deleting it.');
                redirect('?page=staff_roles&role=' . urlencode($rn));
            }
            $pdo->prepare("DELETE FROM staff_permissions WHERE role_name=?")->execute([$rn]);
            $pdo->prepare("DELETE FROM staff_menu_toggles WHERE role_name=?")->execute([$rn]);
            $pdo->prepare("DELETE FROM staff_roles WHERE role_name=?")->execute([$rn]);
            if (function_exists('portalAuthLogActivity')) {
                try {
                    portalAuthLogActivity($pdo, 'admin', (int)($_SESSION['user_id'] ?? 0), 'staff_role_deleted', [
                        'role_name' => $rn,
                    ]);
                } catch (Throwable $e) { /* ignore */ }
            }
            flash('success', 'Role deleted successfully: ' . $rn);
            redirect('?page=staff_roles');
        } catch (Throwable $e) {
            flash('danger', 'Unable to delete that role right now.');
            redirect('?page=staff_roles&role=' . urlencode($rn));
        }
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_role_settings') {
        $rn = portalNormalizeStaffRoleName((string)($_POST['role_name'] ?? ''));
        if ($rn === '') $rn = $roleName;
        portalEnsureStaffRoleExists($pdo, $rn);

        $oldPermState = [];
        foreach ($permCatalog as $k => $_label) { $oldPermState[$k] = 0; }
        try {
            $stOldPerm = $pdo->prepare("SELECT perm_key, allowed FROM staff_permissions WHERE role_name=?");
            $stOldPerm->execute([$rn]);
            while ($oldRow = $stOldPerm->fetch(PDO::FETCH_ASSOC)) {
                $oldPermState[(string)($oldRow['perm_key'] ?? '')] = (int)($oldRow['allowed'] ?? 0);
            }
        } catch (Throwable $e) { /* ignore */ }

        $oldMenuState = [];
        foreach ($menuCatalog as $k => $_label) { $oldMenuState[$k] = 1; }
        try {
            $stOldMenu = $pdo->prepare("SELECT menu_key, enabled FROM staff_menu_toggles WHERE role_name=?");
            $stOldMenu->execute([$rn]);
            while ($oldRow = $stOldMenu->fetch(PDO::FETCH_ASSOC)) {
                $oldMenuState[(string)($oldRow['menu_key'] ?? '')] = (int)($oldRow['enabled'] ?? 0);
            }
        } catch (Throwable $e) { /* ignore */ }

        $changedPerms = [];
        $changedMenus = [];

        // Save perms
        try {
            $pdo->prepare("DELETE FROM staff_permissions WHERE role_name=?")->execute([$rn]);
            foreach ($permCatalog as $k => $_label) {
                $allowed = isset($_POST['perm'][$k]) ? 1 : 0;
                if ((int)($oldPermState[$k] ?? 0) !== $allowed) {
                    $changedPerms[] = $k;
                }
                $pdo->prepare("INSERT INTO staff_permissions (role_name, perm_key, allowed) VALUES (?,?,?)")->execute([$rn,$k,$allowed]);
            }
        } catch (Throwable $e) { /* ignore */ }

        // Save menus
        try {
            $pdo->prepare("DELETE FROM staff_menu_toggles WHERE role_name=?")->execute([$rn]);
            foreach ($menuCatalog as $k => $_label) {
                $en = isset($_POST['menu'][$k]) ? 1 : 0;
                if ((int)($oldMenuState[$k] ?? 1) !== $en) {
                    $changedMenus[] = $k;
                }
                $pdo->prepare("INSERT INTO staff_menu_toggles (role_name, menu_key, enabled) VALUES (?,?,?)")->execute([$rn,$k,$en]);
            }
        } catch (Throwable $e) { /* ignore */ }

        if (function_exists('portalAuthLogActivity')) {
            try {
                portalAuthLogActivity($pdo, 'admin', (int)($_SESSION['user_id'] ?? 0), 'staff_role_settings_updated', [
                    'role_name' => $rn,
                    'perm_change_count' => count($changedPerms),
                    'menu_change_count' => count($changedMenus),
                    'changed_permissions' => array_slice(array_values($changedPerms), 0, 50),
                    'changed_menus' => array_slice(array_values($changedMenus), 0, 50),
                ]);
            } catch (Throwable $e) { /* ignore */ }
        }

        flash('success', 'Role settings saved for: ' . $rn);
        redirect('?page=staff_roles&role=' . urlencode($rn));
    }

    // Roles list
    $rolesList = [];
    try { $rolesList = $pdo->query("SELECT role_name FROM staff_roles ORDER BY role_name ASC")->fetchAll(PDO::FETCH_COLUMN); } catch (Throwable $e) { $rolesList = array_keys(['Superadmin'=>1,'Admin'=>1]); }
    if (!in_array($roleName, $rolesList, true)) {
        $roleName = !empty($rolesList) ? (string)$rolesList[0] : 'Admin';
    }

    // Current settings
    $permState = [];
    foreach ($permCatalog as $k => $_label) $permState[$k] = 0;
    try {
        $st = $pdo->prepare("SELECT perm_key, allowed FROM staff_permissions WHERE role_name=?");
        $st->execute([$roleName]);
        while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
            $permState[$row['perm_key']] = (int)$row['allowed'];
        }
    } catch (Throwable $e) { /* ignore */ }

    $menuState = [];
    foreach ($menuCatalog as $k => $_label) $menuState[$k] = 1;
    try {
        $st = $pdo->prepare("SELECT menu_key, enabled FROM staff_menu_toggles WHERE role_name=?");
        $st->execute([$roleName]);
        while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
            $menuState[$row['menu_key']] = (int)$row['enabled'];
        }
    } catch (Throwable $e) { /* ignore */ }


    $permGroups = [
        'System Administration' => ['staff_users_manage', 'staff_roles_manage'],
        'Academic Structure' => ['academics_manage', 'registrations_manage'],
        'Results & Approvals' => ['results_compute', 'results_approve', 'results_publish'],
        'Finance / Billing' => ['finance_manage', 'clearance_manage'],
        'Library / LMS / CBT' => ['library_manage', 'cbt_manage', 'lms_manage'],
        'HRM / Attendance' => ['hrm_manage', 'attendance_manage'],
    ];

    $menuGroups = [
        'Dashboard / General' => ['menu_admin_dashboard', 'menu_reports', 'menu_messages', 'menu_notifications', 'menu_admin_settings'],
        'Students' => ['menu_admin_students'],
        'Applications / Admissions' => ['menu_admin_applications', 'menu_admin_add_application', 'menu_previous_admission_record', 'menu_numbering_settings', 'menu_pages'],
        'Finance / Billing' => ['menu_admin_payments', 'menu_admin_wallets'],
        'Academic Structure' => ['menu_structure_manager', 'menu_sessions_manager', 'menu_registrable_courses'],
        'Registrations' => ['menu_registrations_manager', 'menu_yet_to_register', 'menu_registration_settings'],
        'Results' => ['menu_grading_result_settings', 'menu_results_manager', 'menu_results_approvals', 'menu_download_scoresheet', 'menu_transcript_admin'],
        'Library / LMS / CBT' => ['menu_lms_admin', 'menu_library_admin', 'menu_cbt_admin'],
        'System Administration' => ['menu_staff_users', 'menu_staff_roles', 'menu_feature_toggles', 'menu_hrm', 'menu_student_attendance_admin', 'menu_admin_clearance', 'menu_graduated', 'menu_notification_settings'],
    ];

    $permRiskKeys = ['staff_roles_manage', 'results_approve', 'results_publish', 'finance_manage', 'clearance_manage'];
    $menuRiskKeys = ['menu_reports', 'menu_results_approvals', 'menu_download_scoresheet', 'menu_staff_roles', 'menu_feature_toggles', 'menu_admin_clearance', 'menu_notification_settings'];

    $permMenuMap = [
        'staff_users_manage' => ['menu_staff_users'],
        'staff_roles_manage' => ['menu_staff_roles'],
        'academics_manage' => ['menu_structure_manager', 'menu_sessions_manager', 'menu_registrable_courses', 'menu_numbering_settings'],
        'registrations_manage' => ['menu_registrations_manager', 'menu_yet_to_register', 'menu_registration_settings'],
        'results_compute' => ['menu_results_manager', 'menu_download_scoresheet'],
        'results_approve' => ['menu_results_approvals', 'menu_results_manager', 'menu_download_scoresheet'],
        'results_publish' => ['menu_results_manager', 'menu_transcript_admin'],
        'finance_manage' => ['menu_admin_payments', 'menu_admin_wallets', 'menu_reports'],
        'clearance_manage' => ['menu_admin_clearance'],
        'library_manage' => ['menu_library_admin'],
        'cbt_manage' => ['menu_cbt_admin'],
        'lms_manage' => ['menu_lms_admin'],
        'hrm_manage' => ['menu_hrm'],
        'attendance_manage' => ['menu_student_attendance_admin'],
    ];

    $menuPermMap = [];
    foreach ($permMenuMap as $permKey => $linkedMenus) {
        foreach ($linkedMenus as $linkedMenuKey) {
            if (!isset($menuPermMap[$linkedMenuKey])) {
                $menuPermMap[$linkedMenuKey] = [];
            }
            $menuPermMap[$linkedMenuKey][] = $permKey;
        }
    }

    $groupCatalog = static function (array $catalog, array $groups): array {
        $grouped = [];
        $seen = [];
        foreach ($groups as $groupLabel => $keys) {
            $items = [];
            foreach ($keys as $key) {
                if (isset($catalog[$key])) {
                    $items[$key] = $catalog[$key];
                    $seen[$key] = true;
                }
            }
            if ($items) {
                $grouped[$groupLabel] = $items;
            }
        }
        $remaining = [];
        foreach ($catalog as $key => $label) {
            if (!isset($seen[$key])) {
                $remaining[$key] = $label;
            }
        }
        if ($remaining) {
            $grouped['Other'] = $remaining;
        }
        return $grouped;
    };

    $permGrouped = $groupCatalog($permCatalog, $permGroups);
    $menuGrouped = $groupCatalog($menuCatalog, $menuGroups);
    $permEnabledCount = array_sum(array_map('intval', $permState));
    $menuEnabledCount = array_sum(array_map('intval', $menuState));
    ?>
    <style>
        .staff-role-page .staff-role-toolbar {
            position: sticky;
            top: .5rem;
            z-index: 25;
            background: linear-gradient(180deg, rgba(255,255,255,.98), rgba(248,250,252,.98));
            border: 1px solid #d8e2ef;
            border-radius: 1rem;
            padding: 1rem;
            box-shadow: 0 10px 24px rgba(15, 23, 42, .08);
            backdrop-filter: blur(6px);
        }
        .staff-role-page .staff-role-overview {
            border: 1px solid #dbe5f1;
            border-radius: 1rem;
            background: #f8fbff;
            padding: .9rem 1rem;
        }
        .staff-role-page .staff-role-panel {
            border: 1px solid #dbe5f1;
            border-radius: 1rem;
            background: #fff;
            overflow: hidden;
            box-shadow: 0 8px 24px rgba(15, 23, 42, .05);
        }
        .staff-role-page .staff-role-panel-head {
            padding: 1rem 1rem .85rem;
            border-bottom: 1px solid #e8eef5;
            background: linear-gradient(180deg, #ffffff, #f9fbfd);
        }
        .staff-role-page .staff-role-panel-head .staff-role-panel-note {
            color: #64748b;
            font-size: .85rem;
        }
        .staff-role-page .staff-role-panel-body {
            max-height: 68vh;
            overflow: auto;
            background: #fff;
        }
        .staff-role-page .staff-role-group {
            border-top: 1px solid #edf2f7;
        }
        .staff-role-page .staff-role-group:first-child {
            border-top: 0;
        }
        .staff-role-page .staff-role-group-header {
            position: sticky;
            top: 0;
            z-index: 3;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: .75rem;
            padding: .8rem 1rem;
            background: #f8fafc;
            border-bottom: 1px solid #e8eef5;
        }
        .staff-role-page .staff-role-group-title {
            margin: 0;
            font-size: .95rem;
            font-weight: 700;
            color: #0f172a;
        }
        .staff-role-page .staff-role-group-meta {
            font-size: .75rem;
            color: #64748b;
        }
        .staff-role-page .staff-role-item {
            padding: .72rem .95rem;
            border-bottom: 1px solid #edf2f7;
            transition: background-color .18s ease, box-shadow .18s ease, outline-color .18s ease;
        }
        .staff-role-page .staff-role-item:nth-child(odd) {
            background: #fcfdff;
        }
        .staff-role-page .staff-role-item:hover {
            background: #f7fbff;
        }
        .staff-role-page .staff-role-item:last-child {
            border-bottom: 0;
        }
        .staff-role-page .staff-role-item.is-dirty {
            background: #eff6ff !important;
            outline: 1px solid #93c5fd;
            outline-offset: -1px;
        }
        .staff-role-page .staff-role-item.role-risk {
            background: #fff8f8;
        }
        .staff-role-page .staff-role-item.role-risk:hover {
            background: #fff1f2;
        }
        .staff-role-page .staff-role-label {
            font-weight: 600;
            color: #111827;
        }
        .staff-role-page .staff-role-subtext {
            margin-top: .2rem;
            font-size: .77rem;
            color: #64748b;
            line-height: 1.35;
        }
        .staff-role-page .staff-role-subtext code {
            font-size: .72rem;
            color: #0f172a;
            background: #eef2ff;
            border-radius: .4rem;
            padding: .08rem .35rem;
        }
        .staff-role-page .staff-role-savebar {
            margin-top: 1rem;
            padding: .9rem 1rem;
            border: 1px solid #dbe5f1;
            border-radius: 1rem;
            background: #fff;
            box-shadow: 0 8px 24px rgba(15, 23, 42, .04);
        }
        .staff-role-page .staff-role-chip {
            display: inline-flex;
            align-items: center;
            gap: .35rem;
            padding: .2rem .55rem;
            border-radius: 999px;
            font-size: .78rem;
            background: #eef6ff;
            color: #0f3d7a;
            border: 1px solid #d8e8ff;
        }
        .staff-role-page .staff-role-chip.risk {
            background: #fff1f2;
            color: #b42318;
            border-color: #fecdd3;
        }
        .staff-role-page .staff-role-empty {
            padding: 1rem;
            color: #64748b;
            font-size: .9rem;
        }
        .staff-role-page .staff-role-panel-body::-webkit-scrollbar {
            width: 10px;
            height: 10px;
        }
        .staff-role-page .staff-role-panel-body::-webkit-scrollbar-thumb {
            background: rgba(148, 163, 184, .45);
            border-radius: 999px;
            border: 2px solid #fff;
        }
        .staff-role-page .staff-role-panel-body::-webkit-scrollbar-track {
            background: #f8fafc;
        }
        @media (max-width: 1199.98px) {
            .staff-role-page .staff-role-panel-body {
                max-height: none;
            }
        }
        @media (max-width: 767.98px) {
            .staff-role-page .staff-role-toolbar {
                position: static;
            }
            .staff-role-page .staff-role-group-header {
                position: static;
            }
        }
    </style>
    <div class="container-fluid staff-role-page">
        <?php renderFlashes(); ?>
        <div class="d-flex align-items-center justify-content-between mb-3 flex-wrap gap-2">
            <h4 class="mb-0">Roles & Permissions</h4>
            <div class="d-flex gap-2 flex-wrap align-items-start">
                <form method="get" class="d-flex gap-2">
                    <input type="hidden" name="page" value="staff_roles">
                    <select class="form-select form-select-sm" name="role" onchange="this.form.submit()">
                        <?php foreach ($rolesList as $rn): ?>
                            <option value="<?php echo e($rn); ?>" <?php echo $rn === $roleName ? 'selected' : ''; ?>><?php echo e($rn); ?></option>
                        <?php endforeach; ?>
                    </select>
                </form>
                <form method="post" class="d-flex gap-2 flex-wrap">
                    <input type="hidden" name="action" value="create_staff_role">
                    <input type="text" class="form-control form-control-sm" name="new_role_name" placeholder="New role name" required>
                    <input type="text" class="form-control form-control-sm" name="new_role_description" placeholder="Description (optional)">
                    <button class="btn btn-sm btn-primary" type="submit">Create Role</button>
                </form>
            </div>
        </div>

        <div class="staff-role-toolbar mb-3">
            <div class="row g-3 align-items-end">
                <div class="col-lg-6">
                    <label for="staffRoleMatrixFilter" class="form-label mb-1 fw-semibold">Search permissions or menus</label>
                    <input type="search" id="staffRoleMatrixFilter" class="form-control" placeholder="Type a permission key, menu name, module, or workflow area...">
                </div>
                <div class="col-lg-6">
                    <div class="d-flex flex-wrap gap-2 justify-content-lg-end">
                        <button type="button" class="btn btn-outline-secondary btn-sm" data-bulk-target="#staffRolePermissionsPanel" data-bulk-action="check">Select visible permissions</button>
                        <button type="button" class="btn btn-outline-secondary btn-sm" data-bulk-target="#staffRolePermissionsPanel" data-bulk-action="clear">Clear visible permissions</button>
                        <button type="button" class="btn btn-outline-secondary btn-sm" data-bulk-target="#staffRoleMenusPanel" data-bulk-action="check">Select visible menus</button>
                        <button type="button" class="btn btn-outline-secondary btn-sm" data-bulk-target="#staffRoleMenusPanel" data-bulk-action="clear">Clear visible menus</button>
                    </div>
                </div>
            </div>
        </div>

        <div class="staff-role-overview mb-3">
            <div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
                <div>
                    <div class="fw-semibold">Configure: <?php echo e($roleName); ?></div>
                    <div class="small text-muted">Capabilities control what the role can do. Menus control what the role can see in navigation. Superadmin still bypasses these settings.</div>
                </div>
                <div class="d-flex flex-wrap gap-2">
                    <span class="staff-role-chip"><strong><?php echo (int)$permEnabledCount; ?></strong> capabilities enabled</span>
                    <span class="staff-role-chip"><strong><?php echo (int)$menuEnabledCount; ?></strong> menus visible</span>
                    <span class="staff-role-chip risk"><i class="bi bi-shield-exclamation"></i> Sensitive items highlighted</span>
                </div>
            </div>
        </div>

        <div class="card border-0 shadow-sm">
            <div class="card-body">
                <form method="post" id="staffRoleSettingsForm">
                    <input type="hidden" name="role_name" value="<?php echo e($roleName); ?>">

                    <div class="row g-4 align-items-start">
                        <div class="col-xl-5">
                            <div class="staff-role-panel" id="staffRolePermissionsPanel">
                                <div class="staff-role-panel-head">
                                    <div class="d-flex flex-wrap justify-content-between align-items-start gap-2">
                                        <div>
                                            <h5 class="mb-1">Capability Permissions</h5>
                                            <div class="staff-role-panel-note">These grant functional authority such as managing records, approving results, or publishing actions.</div>
                                        </div>
                                        <span class="badge rounded-pill text-bg-primary-subtle border">Permissions Engine</span>
                                    </div>
                                </div>
                                <div class="staff-role-panel-body">
                                    <?php foreach ($permGrouped as $groupLabel => $groupItems): ?>
                                        <section class="staff-role-group" data-group-name="<?php echo e(strtolower($groupLabel)); ?>">
                                            <div class="staff-role-group-header">
                                                <div>
                                                    <h6 class="staff-role-group-title"><?php echo e($groupLabel); ?></h6>
                                                    <div class="staff-role-group-meta"><?php echo count($groupItems); ?> items in this group</div>
                                                </div>
                                                <div class="btn-group btn-group-sm" role="group" aria-label="<?php echo e($groupLabel); ?> bulk actions">
                                                    <button type="button" class="btn btn-outline-secondary" data-bulk-target="#perm_group_<?php echo md5((string)$groupLabel); ?>" data-bulk-action="check">All</button>
                                                    <button type="button" class="btn btn-outline-secondary" data-bulk-target="#perm_group_<?php echo md5((string)$groupLabel); ?>" data-bulk-action="clear">Clear</button>
                                                </div>
                                            </div>
                                            <div id="perm_group_<?php echo md5((string)$groupLabel); ?>">
                                                <?php foreach ($groupItems as $k => $label): ?>
                                                    <?php
                                                        $isRisk = in_array($k, $permRiskKeys, true);
                                                        $relatedMenus = $permMenuMap[$k] ?? [];
                                                        $relatedMenuLabels = [];
                                                        foreach ($relatedMenus as $relatedKey) {
                                                            $relatedMenuLabels[] = $menuCatalog[$relatedKey] ?? $relatedKey;
                                                        }
                                                        $filterText = strtolower($groupLabel . ' ' . $k . ' ' . $label . ' ' . implode(' ', $relatedMenuLabels));
                                                    ?>
                                                    <div class="staff-role-item <?php echo $isRisk ? 'role-risk' : ''; ?>" data-filter-text="<?php echo e($filterText); ?>">
                                                        <div class="d-flex justify-content-between align-items-start gap-3">
                                                            <div class="form-check m-0 flex-grow-1">
                                                                <input class="form-check-input staff-role-checkbox permission-checkbox" type="checkbox" id="sr_perm_<?php echo e($k); ?>" data-perm-key="<?php echo e($k); ?>" data-risk="<?php echo $isRisk ? '1' : '0'; ?>" data-related-menus="<?php echo e(implode(',', $relatedMenus)); ?>" name="perm[<?php echo e($k); ?>]" <?php echo !empty($permState[$k]) ? 'checked' : ''; ?>>
                                                                <label class="form-check-label staff-role-label" for="sr_perm_<?php echo e($k); ?>"><?php echo e($label); ?></label>
                                                                <div class="staff-role-subtext">
                                                                    <code><?php echo e($k); ?></code>
                                                                    <?php if ($relatedMenuLabels): ?>
                                                                        <span class="text-muted"> • Auto-enables related menu visibility: <?php echo e(implode(', ', $relatedMenuLabels)); ?></span>
                                                                    <?php endif; ?>
                                                                </div>
                                                            </div>
                                                            <div class="d-flex flex-wrap gap-1 justify-content-end">
                                                                <?php if ($isRisk): ?><span class="badge rounded-pill text-bg-danger-subtle border">Sensitive</span><?php endif; ?>
                                                                <?php if (str_contains($k, 'manage')): ?><span class="badge rounded-pill text-bg-secondary-subtle border">Manage</span><?php endif; ?>
                                                            </div>
                                                        </div>
                                                    </div>
                                                <?php endforeach; ?>
                                            </div>
                                        </section>
                                    <?php endforeach; ?>
                                </div>
                            </div>
                        </div>

                        <div class="col-xl-7">
                            <div class="staff-role-panel" id="staffRoleMenusPanel">
                                <div class="staff-role-panel-head">
                                    <div class="d-flex flex-wrap justify-content-between align-items-start gap-2">
                                        <div>
                                            <h5 class="mb-1">Menu / Module Visibility</h5>
                                            <div class="staff-role-panel-note">These control what appears in navigation. They do not replace permission checks on protected pages.</div>
                                        </div>
                                        <span class="badge rounded-pill text-bg-info-subtle border">Navigation Visibility</span>
                                    </div>
                                </div>
                                <div class="staff-role-panel-body">
                                    <?php foreach ($menuGrouped as $groupLabel => $groupItems): ?>
                                        <section class="staff-role-group" data-group-name="<?php echo e(strtolower($groupLabel)); ?>">
                                            <div class="staff-role-group-header">
                                                <div>
                                                    <h6 class="staff-role-group-title"><?php echo e($groupLabel); ?></h6>
                                                    <div class="staff-role-group-meta"><?php echo count($groupItems); ?> rows in this group</div>
                                                </div>
                                                <div class="btn-group btn-group-sm" role="group" aria-label="<?php echo e($groupLabel); ?> bulk actions">
                                                    <button type="button" class="btn btn-outline-secondary" data-bulk-target="#menu_group_<?php echo md5((string)$groupLabel); ?>" data-bulk-action="check">All</button>
                                                    <button type="button" class="btn btn-outline-secondary" data-bulk-target="#menu_group_<?php echo md5((string)$groupLabel); ?>" data-bulk-action="clear">Clear</button>
                                                </div>
                                            </div>
                                            <div id="menu_group_<?php echo md5((string)$groupLabel); ?>">
                                                <?php foreach ($groupItems as $k => $label): ?>
                                                    <?php
                                                        $isRisk = in_array($k, $menuRiskKeys, true);
                                                        $relatedPerms = $menuPermMap[$k] ?? [];
                                                        $relatedPermLabels = [];
                                                        foreach ($relatedPerms as $relatedPermKey) {
                                                            $relatedPermLabels[] = $permCatalog[$relatedPermKey] ?? $relatedPermKey;
                                                        }
                                                        $filterText = strtolower($groupLabel . ' ' . $k . ' ' . $label . ' ' . implode(' ', $relatedPermLabels));
                                                    ?>
                                                    <div class="staff-role-item <?php echo $isRisk ? 'role-risk' : ''; ?>" data-filter-text="<?php echo e($filterText); ?>">
                                                        <div class="d-flex justify-content-between align-items-start gap-3">
                                                            <div class="form-check m-0 flex-grow-1">
                                                                <input class="form-check-input staff-role-checkbox menu-checkbox" type="checkbox" id="sr_menu_<?php echo e($k); ?>" data-menu-key="<?php echo e($k); ?>" data-risk="<?php echo $isRisk ? '1' : '0'; ?>" name="menu[<?php echo e($k); ?>]" <?php echo !empty($menuState[$k]) ? 'checked' : ''; ?>>
                                                                <label class="form-check-label staff-role-label" for="sr_menu_<?php echo e($k); ?>"><?php echo e($label); ?></label>
                                                                <div class="staff-role-subtext">
                                                                    <code><?php echo e($k); ?></code>
                                                                    <?php if ($relatedPermLabels): ?>
                                                                        <span class="text-muted"> • Related capability check<?php echo count($relatedPermLabels) > 1 ? 's' : ''; ?>: <?php echo e(implode(', ', $relatedPermLabels)); ?></span>
                                                                    <?php else: ?>
                                                                        <span class="text-muted"> • Navigation visibility only</span>
                                                                    <?php endif; ?>
                                                                </div>
                                                            </div>
                                                            <div class="d-flex flex-wrap gap-1 justify-content-end">
                                                                <?php if ($isRisk): ?><span class="badge rounded-pill text-bg-danger-subtle border">Sensitive</span><?php endif; ?>
                                                                <?php if (str_contains($k, 'menu_')): ?><span class="badge rounded-pill text-bg-light border">Menu</span><?php endif; ?>
                                                            </div>
                                                        </div>
                                                    </div>
                                                <?php endforeach; ?>
                                            </div>
                                        </section>
                                    <?php endforeach; ?>
                                    <div class="staff-role-empty border-top">
                                        Tip: Superadmin always has full access, regardless of these settings.
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="staff-role-savebar d-flex flex-wrap justify-content-between align-items-center gap-3">
                        <div>
                            <div class="fw-semibold"><span id="staffRoleChangedCount">0</span> pending changes</div>
                            <div class="small text-muted">Changed rows are highlighted. Sensitive changes will ask for confirmation before saving.</div>
                        </div>
                        <div class="d-flex gap-2 flex-wrap">
                            <button class="btn btn-primary" type="submit" name="action" value="save_role_settings">Save Settings</button>
                            <?php if (!in_array($roleName, ['Superadmin','Admin'], true)): ?>
                                <button class="btn btn-outline-danger" type="submit" name="action" value="delete_staff_role" formnovalidate onclick="return confirm('Delete this role? This will remove its permission mappings.');">Delete Role</button>
                            <?php endif; ?>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>

    <script>
    (function(){
        const form = document.getElementById('staffRoleSettingsForm');
        if (!form) return;

        const filterInput = document.getElementById('staffRoleMatrixFilter');
        const countEl = document.getElementById('staffRoleChangedCount');
        const allCheckboxes = Array.from(form.querySelectorAll('.staff-role-checkbox'));
        const normalize = (value) => String(value || '').toLowerCase().trim();

        function updateDirtyState(input) {
            const item = input.closest('.staff-role-item');
            if (!item) return;
            const initial = input.dataset.initialChecked === '1';
            const dirty = input.checked !== initial;
            item.classList.toggle('is-dirty', dirty);
            if (dirty) item.dataset.dirty = '1';
            else delete item.dataset.dirty;
        }

        function updateDirtyCount() {
            if (!countEl) return;
            const dirtyCount = form.querySelectorAll('.staff-role-item[data-dirty="1"]').length;
            countEl.textContent = String(dirtyCount);
        }

        function applyFilter() {
            const query = normalize(filterInput ? filterInput.value : '');
            form.querySelectorAll('.staff-role-group').forEach(group => {
                let visible = 0;
                group.querySelectorAll('.staff-role-item').forEach(item => {
                    const haystack = normalize(item.dataset.filterText || '');
                    const matched = !query || haystack.includes(query);
                    item.classList.toggle('d-none', !matched);
                    if (matched) visible += 1;
                });
                group.classList.toggle('d-none', visible === 0);
            });
        }

        allCheckboxes.forEach(input => {
            input.dataset.initialChecked = input.checked ? '1' : '0';
            updateDirtyState(input);
            input.addEventListener('change', function(){
                if (input.classList.contains('permission-checkbox') && input.checked) {
                    const relatedMenus = String(input.dataset.relatedMenus || '').split(',').map(v => v.trim()).filter(Boolean);
                    relatedMenus.forEach(menuKey => {
                        const relatedMenuInput = form.querySelector('.menu-checkbox[data-menu-key="' + menuKey.replace(/"/g, '\"') + '"]');
                        if (relatedMenuInput && !relatedMenuInput.checked) {
                            relatedMenuInput.checked = true;
                            updateDirtyState(relatedMenuInput);
                        }
                    });
                }
                updateDirtyState(input);
                updateDirtyCount();
            });
        });
        updateDirtyCount();

        if (filterInput) {
            filterInput.addEventListener('input', applyFilter);
            applyFilter();
        }

        form.querySelectorAll('[data-bulk-target]').forEach(btn => {
            btn.addEventListener('click', function(){
                const target = document.querySelector(btn.getAttribute('data-bulk-target'));
                if (!target) return;
                const mode = btn.getAttribute('data-bulk-action');
                target.querySelectorAll('.staff-role-item').forEach(item => {
                    if (item.classList.contains('d-none')) return;
                    const input = item.querySelector('.staff-role-checkbox');
                    if (!input || input.disabled) return;
                    input.checked = mode === 'check';
                    if (input.classList.contains('permission-checkbox') && input.checked) {
                        const relatedMenus = String(input.dataset.relatedMenus || '').split(',').map(v => v.trim()).filter(Boolean);
                        relatedMenus.forEach(menuKey => {
                            const relatedMenuInput = form.querySelector('.menu-checkbox[data-menu-key="' + menuKey.replace(/"/g, '\"') + '"]');
                            if (relatedMenuInput && !relatedMenuInput.checked) {
                                relatedMenuInput.checked = true;
                                updateDirtyState(relatedMenuInput);
                            }
                        });
                    }
                    updateDirtyState(input);
                });
                updateDirtyCount();
            });
        });

        form.addEventListener('submit', function(e){
            const saveButton = e.submitter;
            if (!saveButton || saveButton.value !== 'save_role_settings') return;
            const riskyChanges = Array.from(form.querySelectorAll('.staff-role-checkbox[data-risk="1"]')).filter(input => input.checked !== (input.dataset.initialChecked === '1'));
            if (!riskyChanges.length) return;
            const labels = riskyChanges.slice(0, 6).map(input => {
                const label = form.querySelector('label[for="' + input.id + '"]');
                return label ? label.textContent.trim() : input.id;
            }).filter(Boolean);
            const more = riskyChanges.length > labels.length ? '
+' + (riskyChanges.length - labels.length) + ' more sensitive change(s).' : '';
            const message = 'You changed sensitive role settings for <?php echo addslashes($roleName); ?>:

- ' + labels.join('
- ') + more + '

Continue saving?';
            if (!window.confirm(message)) {
                e.preventDefault();
            }
        });
    })();
    </script>

    <?php
    break;


case 'hrm_dashboard':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}

    $hrmStats = [
        'staff' => 0, 'departments' => 0, 'units' => 0, 'designations' => 0,
        'attendance_today' => 0, 'leave_pending' => 0, 'payroll_pending' => 0,
        'recruitment_open' => 0, 'reviews' => 0, 'documents' => 0
    ];
    try { $hrmStats['staff'] = (int)$pdo->query("SELECT COUNT(*) FROM hr_staff")->fetchColumn(); } catch (Throwable $e) {}
    try { $hrmStats['departments'] = (int)$pdo->query("SELECT COUNT(*) FROM hr_departments")->fetchColumn(); } catch (Throwable $e) {}
    try { $hrmStats['units'] = (int)$pdo->query("SELECT COUNT(*) FROM hr_units")->fetchColumn(); } catch (Throwable $e) {}
    try { $hrmStats['designations'] = (int)$pdo->query("SELECT COUNT(*) FROM hr_designations")->fetchColumn(); } catch (Throwable $e) {}
    try { $st=$pdo->prepare("SELECT COUNT(*) FROM hr_attendance WHERE attendance_date = ?"); $st->execute([date('Y-m-d')]); $hrmStats['attendance_today'] = (int)$st->fetchColumn(); } catch (Throwable $e) {}
    try { $st=$pdo->prepare("SELECT COUNT(*) FROM hr_leave_requests WHERE status = 'Pending'"); $st->execute(); $hrmStats['leave_pending'] = (int)$st->fetchColumn(); } catch (Throwable $e) {}
    try { $st=$pdo->prepare("SELECT COUNT(*) FROM hr_payroll WHERE payment_status = 'Pending'"); $st->execute(); $hrmStats['payroll_pending'] = (int)$st->fetchColumn(); } catch (Throwable $e) {}
    try { $st=$pdo->prepare("SELECT COUNT(*) FROM hr_recruitment WHERE status IN ('Open','Shortlisted','Interview')"); $st->execute(); $hrmStats['recruitment_open'] = (int)$st->fetchColumn(); } catch (Throwable $e) {}
    try { $hrmStats['reviews'] = (int)$pdo->query("SELECT COUNT(*) FROM hr_performance_reviews")->fetchColumn(); } catch (Throwable $e) {}
    try { $hrmStats['documents'] = (int)$pdo->query("SELECT COUNT(*) FROM hr_documents")->fetchColumn(); } catch (Throwable $e) {}
    ?>
    <div class="container-fluid">
        <div class="d-flex flex-wrap align-items-center justify-content-between mb-3">
            <h4 class="mb-0">HRM Dashboard</h4>
            <div class="d-flex gap-2">
                <a class="btn btn-outline-secondary btn-sm" href="?page=hrm_reports">View Reports</a>
                <button class="btn btn-outline-primary btn-sm" onclick="window.print()">Print</button>
            </div>
        </div>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <?php
            $cards = [
                ['Total Staff', $hrmStats['staff'], 'bi-people'],
                ['Departments', $hrmStats['departments'], 'bi-diagram-3'],
                ['Units', $hrmStats['units'], 'bi-diagram-2'],
                ['Designations', $hrmStats['designations'], 'bi-award'],
                ['Attendance Today', $hrmStats['attendance_today'], 'bi-calendar-check'],
                ['Pending Leave', $hrmStats['leave_pending'], 'bi-airplane'],
                ['Pending Payroll', $hrmStats['payroll_pending'], 'bi-cash-stack'],
                ['Open Recruitment', $hrmStats['recruitment_open'], 'bi-person-plus'],
                ['Performance Reviews', $hrmStats['reviews'], 'bi-graph-up-arrow'],
                ['Staff Documents', $hrmStats['documents'], 'bi-folder2-open'],
            ];
            foreach ($cards as $c): ?>
                <div class="col-md-6 col-xl-3">
                    <div class="card h-100">
                        <div class="card-body d-flex justify-content-between align-items-center">
                            <div>
                                <div class="text-muted small"><?php echo e($c[0]); ?></div>
                                <div class="fs-3 fw-bold"><?php echo (int)$c[1]; ?></div>
                            </div>
                            <i class="bi <?php echo e($c[2]); ?>" style="font-size:2rem;"></i>
                        </div>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>

        <div class="row g-3 mt-1">
            <div class="col-lg-8">
                <div class="card">
                    <div class="card-header"><strong>Quick Access</strong></div>
                    <div class="card-body d-flex flex-wrap gap-2">
                        <?php foreach (hrmMenuPages() as $slug => $label): ?>
                            <a class="btn btn-outline-primary btn-sm" href="?page=<?php echo e($slug); ?>"><?php echo e($label); ?></a>
                        <?php endforeach; ?>
                    </div>
                </div>
            </div>
            <div class="col-lg-4">
                <div class="card">
                    <div class="card-header"><strong>Staff Portal Access</strong></div>
                    <div class="card-body">
                        <p class="mb-2 small text-muted">Staff records can be linked to existing admin/staff accounts. Portal-enabled staff remain synchronized through the linked <code>admin_id</code> and the <code>portal_access</code> flag.</p>
                        <a class="btn btn-primary btn-sm" href="?page=staff_users">Manage Staff Portal Users</a>
                    </div>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_staff_management':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $action = trim((string)($_POST['action'] ?? ''));
            if ($action === 'save_hr_staff') {
                $id = (int)($_POST['id'] ?? 0);
                $staffNo = trim((string)($_POST['staff_no'] ?? ''));
                $firstName = trim((string)($_POST['first_name'] ?? ''));
                $lastName = trim((string)($_POST['last_name'] ?? ''));
                if ($firstName === '' || $lastName === '') throw new RuntimeException('First name and last name are required.');
                $params = [
                    $staffNo !== '' ? $staffNo : null,
                    $firstName,
                    $lastName,
                    trim((string)($_POST['other_name'] ?? '')) ?: null,
                    trim((string)($_POST['email'] ?? '')) ?: null,
                    trim((string)($_POST['phone'] ?? '')) ?: null,
                    trim((string)($_POST['gender'] ?? '')) ?: null,
                    (int)($_POST['department_id'] ?? 0) ?: null,
                    (int)($_POST['unit_id'] ?? 0) ?: null,
                    (int)($_POST['designation_id'] ?? 0) ?: null,
                    trim((string)($_POST['employment_type'] ?? 'Full-Time')),
                    trim((string)($_POST['employment_status'] ?? 'Active')),
                    isset($_POST['portal_access']) ? 1 : 0,
                    trim((string)($_POST['date_hired'] ?? '')) ?: null,
                    (float)($_POST['basic_salary'] ?? 0),
                    trim((string)($_POST['address'] ?? '')) ?: null,
                    trim((string)($_POST['notes'] ?? '')) ?: null,
                    (int)($_POST['admin_id'] ?? 0) ?: null,
                ];
                if ($id > 0) {
                    $sql = "UPDATE hr_staff SET staff_no=?, first_name=?, last_name=?, other_name=?, email=?, phone=?, gender=?, department_id=?, unit_id=?, designation_id=?, employment_type=?, employment_status=?, portal_access=?, date_hired=?, basic_salary=?, address=?, notes=?, admin_id=? WHERE id=?";
                    $st = $pdo->prepare($sql);
                    $params[] = $id;
                    $st->execute($params);
                    flash('success', 'HR staff record updated.');
                } else {
                    $sql = "INSERT INTO hr_staff (staff_no, first_name, last_name, other_name, email, phone, gender, department_id, unit_id, designation_id, employment_type, employment_status, portal_access, date_hired, basic_salary, address, notes, admin_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
                    $st = $pdo->prepare($sql);
                    $st->execute($params);
                    flash('success', 'HR staff record created.');
                }
            } elseif ($action === 'delete_hr_staff') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id > 0) {
                    $st = $pdo->prepare("DELETE FROM hr_staff WHERE id = ?");
                    $st->execute([$id]);
                    flash('success', 'HR staff record deleted.');
                }
            }
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Unable to save HR staff record.');
        }
        redirect('?page=hrm_staff_management');
    }

    $q = trim((string)($_GET['q'] ?? ''));
    $editId = (int)($_GET['edit'] ?? 0);
    $editRow = null;
    try {
        if ($editId > 0) {
            $st = $pdo->prepare("SELECT * FROM hr_staff WHERE id = ? LIMIT 1");
            $st->execute([$editId]);
            $editRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        }
    } catch (Throwable $e) {}
    $departments = $pdo->query("SELECT id, name FROM hr_departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $units = $pdo->query("SELECT id, name FROM hr_units ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $designations = $pdo->query("SELECT id, name FROM hr_designations ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $admins = $pdo->query("SELECT id, full_name, username FROM admins ORDER BY full_name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $sql = "SELECT s.*, d.name AS department_name, u.name AS unit_name, z.name AS designation_name, a.full_name AS portal_name
            FROM hr_staff s
            LEFT JOIN hr_departments d ON d.id = s.department_id
            LEFT JOIN hr_units u ON u.id = s.unit_id
            LEFT JOIN hr_designations z ON z.id = s.designation_id
            LEFT JOIN admins a ON a.id = s.admin_id";
    $params = [];
    if ($q !== '') {
        $sql .= " WHERE s.staff_no LIKE ? OR s.first_name LIKE ? OR s.last_name LIKE ? OR s.email LIKE ?";
        $like = '%' . $q . '%';
        $params = [$like, $like, $like, $like];
    }
    $sql .= " ORDER BY s.id DESC";
    $st = $pdo->prepare($sql);
    $st->execute($params);
    $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];

    if (($_GET['export'] ?? '') === 'csv') {
        $csvRows = [];
        foreach ($rows as $r) {
            $csvRows[] = [$r['staff_no'], trim(($r['first_name'] ?? '') . ' ' . ($r['last_name'] ?? '')), $r['email'], $r['phone'], $r['department_name'], $r['unit_name'], $r['designation_name'], $r['employment_type'], $r['employment_status'], (int)($r['portal_access'] ?? 0) === 1 ? 'Enabled' : 'Disabled'];
        }
        hrmCsvDownload('hrm_staff.csv', ['Staff No','Name','Email','Phone','Department','Unit','Designation','Employment Type','Status','Portal Access'], $csvRows);
    }
    ?>
    <div class="container-fluid">
        <div class="d-flex flex-wrap align-items-center justify-content-between mb-3">
            <h4 class="mb-0">HRM - Staff Management</h4>
            <div class="d-flex gap-2">
                <a class="btn btn-outline-success btn-sm" href="?page=hrm_staff_management&export=csv">Export CSV</a>
                <button class="btn btn-outline-secondary btn-sm" onclick="window.print()">Print</button>
            </div>
        </div>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card">
                    <div class="card-header"><strong><?php echo $editRow ? 'Edit Staff' : 'Add Staff'; ?></strong></div>
                    <div class="card-body">
                        <form method="post">
                            <input type="hidden" name="action" value="save_hr_staff">
                            <input type="hidden" name="id" value="<?php echo (int)($editRow['id'] ?? 0); ?>">
                            <div class="row g-2">
                                <div class="col-6"><input class="form-control" name="staff_no" placeholder="Staff No" value="<?php echo e($editRow['staff_no'] ?? ''); ?>"></div>
                                <div class="col-6">
                                    <select class="form-select" name="admin_id">
                                        <option value="">Portal User (optional)</option>
                                        <?php foreach ($admins as $a): ?>
                                            <option value="<?php echo (int)$a['id']; ?>" <?php echo ((int)($editRow['admin_id'] ?? 0) === (int)$a['id']) ? 'selected' : ''; ?>><?php echo e($a['full_name'] . ' (' . $a['username'] . ')'); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                                <div class="col-6"><input class="form-control" name="first_name" placeholder="First Name" required value="<?php echo e($editRow['first_name'] ?? ''); ?>"></div>
                                <div class="col-6"><input class="form-control" name="last_name" placeholder="Last Name" required value="<?php echo e($editRow['last_name'] ?? ''); ?>"></div>
                                <div class="col-12"><input class="form-control" name="other_name" placeholder="Other Name" value="<?php echo e($editRow['other_name'] ?? ''); ?>"></div>
                                <div class="col-6"><input class="form-control" name="email" type="email" placeholder="Email" value="<?php echo e($editRow['email'] ?? ''); ?>"></div>
                                <div class="col-6"><input class="form-control" name="phone" placeholder="Phone" value="<?php echo e($editRow['phone'] ?? ''); ?>"></div>
                                <div class="col-6"><input class="form-control" name="gender" placeholder="Gender" value="<?php echo e($editRow['gender'] ?? ''); ?>"></div>
                                <div class="col-6"><input class="form-control" name="date_hired" type="date" value="<?php echo e($editRow['date_hired'] ?? ''); ?>"></div>
                                <div class="col-6">
                                    <select class="form-select" name="department_id">
                                        <option value="">Department</option>
                                        <?php foreach ($departments as $d): ?>
                                            <option value="<?php echo (int)$d['id']; ?>" <?php echo ((int)($editRow['department_id'] ?? 0) === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e($d['name']); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                                <div class="col-6">
                                    <select class="form-select" name="unit_id">
                                        <option value="">Unit</option>
                                        <?php foreach ($units as $u): ?>
                                            <option value="<?php echo (int)$u['id']; ?>" <?php echo ((int)($editRow['unit_id'] ?? 0) === (int)$u['id']) ? 'selected' : ''; ?>><?php echo e($u['name']); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                                <div class="col-6">
                                    <select class="form-select" name="designation_id">
                                        <option value="">Designation</option>
                                        <?php foreach ($designations as $z): ?>
                                            <option value="<?php echo (int)$z['id']; ?>" <?php echo ((int)($editRow['designation_id'] ?? 0) === (int)$z['id']) ? 'selected' : ''; ?>><?php echo e($z['name']); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                                <div class="col-6"><input class="form-control" name="employment_type" placeholder="Employment Type" value="<?php echo e($editRow['employment_type'] ?? 'Full-Time'); ?>"></div>
                                <div class="col-6"><input class="form-control" name="employment_status" placeholder="Status" value="<?php echo e($editRow['employment_status'] ?? 'Active'); ?>"></div>
                                <div class="col-6"><input class="form-control" name="basic_salary" type="number" step="0.01" placeholder="Basic Salary" value="<?php echo e($editRow['basic_salary'] ?? '0'); ?>"></div>
                                <div class="col-12"><textarea class="form-control" name="address" placeholder="Address"><?php echo e($editRow['address'] ?? ''); ?></textarea></div>
                                <div class="col-12"><textarea class="form-control" name="notes" placeholder="Notes"><?php echo e($editRow['notes'] ?? ''); ?></textarea></div>
                                <div class="col-12 form-check mt-2 ms-2">
                                    <input class="form-check-input" type="checkbox" name="portal_access" id="portal_access" <?php echo ((int)($editRow['portal_access'] ?? 1) === 1) ? 'checked' : ''; ?>>
                                    <label class="form-check-label" for="portal_access">Enable Staff Portal Access</label>
                                </div>
                            </div>
                            <button class="btn btn-primary mt-3" type="submit"><?php echo $editRow ? 'Update Staff' : 'Save Staff'; ?></button>
                            <?php if ($editRow): ?><a class="btn btn-outline-secondary mt-3" href="?page=hrm_staff_management">Cancel</a><?php endif; ?>
                        </form>
                    </div>
                </div>
            </div>

            <div class="col-lg-8">
                <div class="card">
                    <div class="card-header">
                        <div class="d-flex flex-wrap gap-2 align-items-center justify-content-between">
                            <strong>Staff Records</strong>
                            <form method="get" class="d-flex gap-2">
                                <input type="hidden" name="page" value="hrm_staff_management">
                                <input class="form-control form-control-sm" type="text" name="q" placeholder="Search staff..." value="<?php echo e($q); ?>">
                                <button class="btn btn-outline-primary btn-sm">Search</button>
                            </form>
                        </div>
                    </div>
                    <div class="card-body table-responsive">
                        <table class="table table-sm align-middle">
                            <thead><tr><th>#</th><th>Staff</th><th>Department</th><th>Unit</th><th>Designation</th><th>Status</th><th>Portal</th><th>Action</th></tr></thead>
                            <tbody>
                                <?php foreach ($rows as $r): ?>
                                    <tr>
                                        <td><?php echo (int)$r['id']; ?></td>
                                        <td>
                                            <div class="fw-semibold"><?php echo e(trim(($r['first_name'] ?? '') . ' ' . ($r['last_name'] ?? ''))); ?></div>
                                            <div class="small text-muted"><?php echo e($r['staff_no'] ?: 'No Staff No'); ?> | <?php echo e($r['email'] ?: 'No Email'); ?></div>
                                        </td>
                                        <td><?php echo e($r['department_name'] ?: '-'); ?></td>
                                        <td><?php echo e($r['unit_name'] ?: '-'); ?></td>
                                        <td><?php echo e($r['designation_name'] ?: '-'); ?></td>
                                        <td><?php echo e($r['employment_status']); ?></td>
                                        <td><?php echo (int)($r['portal_access'] ?? 0) === 1 ? 'Enabled' : 'Disabled'; ?></td>
                                        <td>
                                            <a class="btn btn-sm btn-outline-primary" href="?page=hrm_staff_management&edit=<?php echo (int)$r['id']; ?>">Edit</a>
                                            <form method="post" style="display:inline" onsubmit="return confirm('Delete this staff record?');">
                                                <input type="hidden" name="action" value="delete_hr_staff">
                                                <input type="hidden" name="id" value="<?php echo (int)$r['id']; ?>">
                                                <button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
                                            </form>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                                <?php if (!$rows): ?><tr><td colspan="8" class="text-muted">No staff records found.</td></tr><?php endif; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_departments':
case 'hrm_units':
case 'hrm_designations':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}

    $config = [
        'hrm_departments' => ['table' => 'hr_departments', 'title' => 'Departments', 'name_col' => 'name'],
        'hrm_units' => ['table' => 'hr_units', 'title' => 'Units', 'name_col' => 'name'],
        'hrm_designations' => ['table' => 'hr_designations', 'title' => 'Designations', 'name_col' => 'name'],
    ][$page];

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $action = trim((string)($_POST['action'] ?? ''));
            if ($action === 'save_lookup') {
                $id = (int)($_POST['id'] ?? 0);
                $name = trim((string)($_POST['name'] ?? ''));
                $description = trim((string)($_POST['description'] ?? ''));
                $isActive = isset($_POST['is_active']) ? 1 : 0;
                if ($name === '') throw new RuntimeException($config['title'] . ' name is required.');
                if ($page === 'hrm_units') {
                    $departmentId = (int)($_POST['department_id'] ?? 0) ?: null;
                    if ($id > 0) {
                        $st = $pdo->prepare("UPDATE hr_units SET department_id=?, name=?, description=?, is_active=? WHERE id=?");
                        $st->execute([$departmentId, $name, $description !== '' ? $description : null, $isActive, $id]);
                    } else {
                        $st = $pdo->prepare("INSERT INTO hr_units (department_id, name, description, is_active) VALUES (?,?,?,?)");
                        $st->execute([$departmentId, $name, $description !== '' ? $description : null, $isActive]);
                    }
                } else {
                    $sql = $id > 0
                        ? "UPDATE {$config['table']} SET name=?, description=?, is_active=? WHERE id=?"
                        : "INSERT INTO {$config['table']} (name, description, is_active) VALUES (?,?,?)";
                    $st = $pdo->prepare($sql);
                    if ($id > 0) $st->execute([$name, $description !== '' ? $description : null, $isActive, $id]);
                    else $st->execute([$name, $description !== '' ? $description : null, $isActive]);
                }
                flash('success', $config['title'] . ' saved successfully.');
            } elseif ($action === 'delete_lookup') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id > 0) {
                    $st = $pdo->prepare("DELETE FROM {$config['table']} WHERE id=?");
                    $st->execute([$id]);
                    flash('success', $config['title'] . ' deleted.');
                }
            }
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Operation failed.');
        }
        redirect('?page=' . $page);
    }

    $editId = (int)($_GET['edit'] ?? 0);
    $editRow = null;
    if ($editId > 0) {
        $st = $pdo->prepare("SELECT * FROM {$config['table']} WHERE id=? LIMIT 1");
        $st->execute([$editId]);
        $editRow = $st->fetch(PDO::FETCH_ASSOC) ?: null;
    }
    $listSql = $page === 'hrm_units'
        ? "SELECT u.*, d.name AS department_name FROM hr_units u LEFT JOIN hr_departments d ON d.id = u.department_id ORDER BY u.name ASC"
        : "SELECT * FROM {$config['table']} ORDER BY name ASC";
    $rows = $pdo->query($listSql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $departments = $pdo->query("SELECT id, name FROM hr_departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    ?>
    <div class="container-fluid">
        <div class="d-flex align-items-center justify-content-between mb-3">
            <h4 class="mb-0">HRM - <?php echo e($config['title']); ?></h4>
            <button class="btn btn-outline-secondary btn-sm" onclick="window.print()">Print</button>
        </div>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card">
                    <div class="card-header"><strong><?php echo $editRow ? 'Edit' : 'Add'; ?> <?php echo e(rtrim($config['title'], 's')); ?></strong></div>
                    <div class="card-body">
                        <form method="post">
                            <input type="hidden" name="action" value="save_lookup">
                            <input type="hidden" name="id" value="<?php echo (int)($editRow['id'] ?? 0); ?>">
                            <?php if ($page === 'hrm_units'): ?>
                                <div class="mb-2">
                                    <select class="form-select" name="department_id">
                                        <option value="">Department</option>
                                        <?php foreach ($departments as $d): ?>
                                            <option value="<?php echo (int)$d['id']; ?>" <?php echo ((int)($editRow['department_id'] ?? 0) === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e($d['name']); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </div>
                            <?php endif; ?>
                            <input class="form-control mb-2" name="name" required placeholder="Name" value="<?php echo e($editRow['name'] ?? ''); ?>">
                            <textarea class="form-control mb-2" name="description" placeholder="Description"><?php echo e($editRow['description'] ?? ''); ?></textarea>
                            <div class="form-check mb-2">
                                <input class="form-check-input" type="checkbox" name="is_active" id="is_active_lookup" <?php echo ((int)($editRow['is_active'] ?? 1) === 1) ? 'checked' : ''; ?>>
                                <label class="form-check-label" for="is_active_lookup">Active</label>
                            </div>
                            <button class="btn btn-primary" type="submit">Save</button>
                            <?php if ($editRow): ?><a class="btn btn-outline-secondary" href="?page=<?php echo e($page); ?>">Cancel</a><?php endif; ?>
                        </form>
                    </div>
                </div>
            </div>
            <div class="col-lg-8">
                <div class="card">
                    <div class="card-header"><strong>All <?php echo e($config['title']); ?></strong></div>
                    <div class="card-body table-responsive">
                        <table class="table table-sm align-middle">
                            <thead><tr><th>#</th><th>Name</th><?php if ($page === 'hrm_units'): ?><th>Department</th><?php endif; ?><th>Status</th><th>Action</th></tr></thead>
                            <tbody>
                                <?php foreach ($rows as $r): ?>
                                    <tr>
                                        <td><?php echo (int)$r['id']; ?></td>
                                        <td><?php echo e($r['name']); ?></td>
                                        <?php if ($page === 'hrm_units'): ?><td><?php echo e($r['department_name'] ?? '-'); ?></td><?php endif; ?>
                                        <td><?php echo ((int)($r['is_active'] ?? 0) === 1) ? 'Active' : 'Inactive'; ?></td>
                                        <td>
                                            <a class="btn btn-sm btn-outline-primary" href="?page=<?php echo e($page); ?>&edit=<?php echo (int)$r['id']; ?>">Edit</a>
                                            <form method="post" style="display:inline" onsubmit="return confirm('Delete this record?');">
                                                <input type="hidden" name="action" value="delete_lookup">
                                                <input type="hidden" name="id" value="<?php echo (int)$r['id']; ?>">
                                                <button class="btn btn-sm btn-outline-danger">Delete</button>
                                            </form>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                                <?php if (!$rows): ?><tr><td colspan="<?php echo $page === 'hrm_units' ? 5 : 4; ?>" class="text-muted">No records found.</td></tr><?php endif; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_attendance':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $staffId = (int)($_POST['staff_id'] ?? 0);
            $attendanceDate = trim((string)($_POST['attendance_date'] ?? date('Y-m-d')));
            $status = trim((string)($_POST['status'] ?? 'Present'));
            if ($staffId <= 0) throw new RuntimeException('Select a staff member.');
            $st = $pdo->prepare("INSERT INTO hr_attendance (staff_id, attendance_date, status, check_in_time, check_out_time, remark, recorded_by) VALUES (?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE status=VALUES(status), check_in_time=VALUES(check_in_time), check_out_time=VALUES(check_out_time), remark=VALUES(remark), recorded_by=VALUES(recorded_by)");
            $st->execute([$staffId, $attendanceDate, $status, trim((string)($_POST['check_in_time'] ?? '')) ?: null, trim((string)($_POST['check_out_time'] ?? '')) ?: null, trim((string)($_POST['remark'] ?? '')) ?: null, (int)($_SESSION['user_id'] ?? 0) ?: null]);
            flash('success', 'Attendance saved.');
        } catch (Throwable $e) {
            flash('danger', $e->getMessage() ?: 'Unable to save attendance.');
        }
        redirect('?page=hrm_attendance');
    }
    $staffOptions = $pdo->query("SELECT id, CONCAT(first_name, ' ', last_name, ' - ', COALESCE(staff_no, 'No Staff No')) AS label FROM hr_staff ORDER BY first_name ASC, last_name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $attendanceRows = $pdo->query("SELECT a.*, CONCAT(s.first_name, ' ', s.last_name) AS staff_name, s.staff_no FROM hr_attendance a JOIN hr_staff s ON s.id = a.staff_id ORDER BY a.attendance_date DESC, s.first_name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    if (($_GET['export'] ?? '') === 'csv') {
        $csvRows = [];
        foreach ($attendanceRows as $r) $csvRows[] = [$r['attendance_date'], $r['staff_no'], $r['staff_name'], $r['status'], $r['check_in_time'], $r['check_out_time'], $r['remark']];
        hrmCsvDownload('hrm_attendance.csv', ['Date','Staff No','Staff','Status','Check In','Check Out','Remark'], $csvRows);
    }
    ?>
    <div class="container-fluid">
        <div class="d-flex align-items-center justify-content-between mb-3">
            <h4 class="mb-0">HRM - Attendance</h4>
            <a class="btn btn-outline-success btn-sm" href="?page=hrm_attendance&export=csv">Export CSV</a>
        </div>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card">
                    <div class="card-header"><strong>Record Attendance</strong></div>
                    <div class="card-body">
                        <form method="post">
                            <select class="form-select mb-2" name="staff_id" required>
                                <option value="">Select Staff</option>
                                <?php foreach ($staffOptions as $s): ?><option value="<?php echo (int)$s['id']; ?>"><?php echo e($s['label']); ?></option><?php endforeach; ?>
                            </select>
                            <input class="form-control mb-2" type="date" name="attendance_date" value="<?php echo e(date('Y-m-d')); ?>" required>
                            <select class="form-select mb-2" name="status">
                                <?php foreach (['Present','Absent','Late','Excused'] as $st): ?><option value="<?php echo e($st); ?>"><?php echo e($st); ?></option><?php endforeach; ?>
                            </select>
                            <input class="form-control mb-2" type="time" name="check_in_time" placeholder="Check In">
                            <input class="form-control mb-2" type="time" name="check_out_time" placeholder="Check Out">
                            <textarea class="form-control mb-2" name="remark" placeholder="Remark"></textarea>
                            <button class="btn btn-primary">Save Attendance</button>
                        </form>
                    </div>
                </div>
            </div>
            <div class="col-lg-8">
                <div class="card">
                    <div class="card-header"><strong>Attendance Records</strong></div>
                    <div class="card-body table-responsive">
                        <table class="table table-sm align-middle">
                            <thead><tr><th>Date</th><th>Staff</th><th>Status</th><th>Check In</th><th>Check Out</th><th>Remark</th></tr></thead>
                            <tbody>
                                <?php foreach ($attendanceRows as $r): ?>
                                    <tr>
                                        <td><?php echo e($r['attendance_date']); ?></td>
                                        <td><?php echo e(($r['staff_name'] ?? '') . ' (' . ($r['staff_no'] ?? 'N/A') . ')'); ?></td>
                                        <td><?php echo e($r['status']); ?></td>
                                        <td><?php echo e($r['check_in_time'] ?: '-'); ?></td>
                                        <td><?php echo e($r['check_out_time'] ?: '-'); ?></td>
                                        <td><?php echo e($r['remark'] ?: '-'); ?></td>
                                    </tr>
                                <?php endforeach; ?>
                                <?php if (!$attendanceRows): ?><tr><td colspan="6" class="text-muted">No attendance records found.</td></tr><?php endif; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_leave_management':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $action = trim((string)($_POST['action'] ?? ''));
            if ($action === 'save_leave') {
                $staffId = (int)($_POST['staff_id'] ?? 0);
                $startDate = trim((string)($_POST['start_date'] ?? ''));
                $endDate = trim((string)($_POST['end_date'] ?? ''));
                if ($staffId <= 0 || $startDate === '' || $endDate === '') throw new RuntimeException('Staff, start date and end date are required.');
                $days = (int)((strtotime($endDate) - strtotime($startDate)) / 86400) + 1;
                $st = $pdo->prepare("INSERT INTO hr_leave_requests (staff_id, leave_type, start_date, end_date, days_requested, reason, status, approved_by, approval_note) VALUES (?,?,?,?,?,?,?,?,?)");
                $st->execute([$staffId, trim((string)($_POST['leave_type'] ?? 'Annual Leave')), $startDate, $endDate, max(1, $days), trim((string)($_POST['reason'] ?? '')) ?: null, trim((string)($_POST['status'] ?? 'Pending')), (int)($_SESSION['user_id'] ?? 0) ?: null, trim((string)($_POST['approval_note'] ?? '')) ?: null]);
                flash('success', 'Leave request saved.');
            } elseif ($action === 'delete_leave') {
                $id = (int)($_POST['id'] ?? 0);
                if ($id > 0) { $pdo->prepare("DELETE FROM hr_leave_requests WHERE id=?")->execute([$id]); flash('success','Leave request deleted.'); }
            }
        } catch (Throwable $e) { flash('danger', $e->getMessage() ?: 'Unable to save leave request.'); }
        redirect('?page=hrm_leave_management');
    }
    $staffOptions = $pdo->query("SELECT id, CONCAT(first_name, ' ', last_name) AS label FROM hr_staff ORDER BY first_name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $leaveRows = $pdo->query("SELECT l.*, CONCAT(s.first_name, ' ', s.last_name) AS staff_name FROM hr_leave_requests l JOIN hr_staff s ON s.id = l.staff_id ORDER BY l.created_at DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    ?>
    <div class="container-fluid">
        <h4 class="mb-3">HRM - Leave Management</h4>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card">
                    <div class="card-header"><strong>Create Leave Request</strong></div>
                    <div class="card-body">
                        <form method="post">
                            <input type="hidden" name="action" value="save_leave">
                            <select class="form-select mb-2" name="staff_id" required><option value="">Select Staff</option><?php foreach ($staffOptions as $s): ?><option value="<?php echo (int)$s['id']; ?>"><?php echo e($s['label']); ?></option><?php endforeach; ?></select>
                            <input class="form-control mb-2" name="leave_type" placeholder="Leave Type" value="Annual Leave">
                            <input class="form-control mb-2" type="date" name="start_date" required>
                            <input class="form-control mb-2" type="date" name="end_date" required>
                            <select class="form-select mb-2" name="status"><?php foreach (['Pending','Approved','Rejected'] as $st): ?><option value="<?php echo e($st); ?>"><?php echo e($st); ?></option><?php endforeach; ?></select>
                            <textarea class="form-control mb-2" name="reason" placeholder="Reason"></textarea>
                            <textarea class="form-control mb-2" name="approval_note" placeholder="Approval Note"></textarea>
                            <button class="btn btn-primary">Save Leave Request</button>
                        </form>
                    </div>
                </div>
            </div>
            <div class="col-lg-8">
                <div class="card">
                    <div class="card-header"><strong>Leave Requests</strong></div>
                    <div class="card-body table-responsive">
                        <table class="table table-sm align-middle">
                            <thead><tr><th>Staff</th><th>Type</th><th>Period</th><th>Days</th><th>Status</th><th>Action</th></tr></thead>
                            <tbody>
                            <?php foreach ($leaveRows as $r): ?>
                                <tr>
                                    <td><?php echo e($r['staff_name']); ?></td>
                                    <td><?php echo e($r['leave_type']); ?></td>
                                    <td><?php echo e($r['start_date'] . ' to ' . $r['end_date']); ?></td>
                                    <td><?php echo (int)$r['days_requested']; ?></td>
                                    <td><?php echo e($r['status']); ?></td>
                                    <td>
                                        <form method="post" style="display:inline" onsubmit="return confirm('Delete this leave request?');">
                                            <input type="hidden" name="action" value="delete_leave">
                                            <input type="hidden" name="id" value="<?php echo (int)$r['id']; ?>">
                                            <button class="btn btn-sm btn-outline-danger">Delete</button>
                                        </form>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                            <?php if (!$leaveRows): ?><tr><td colspan="6" class="text-muted">No leave requests found.</td></tr><?php endif; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_payroll':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $staffId = (int)($_POST['staff_id'] ?? 0);
            $month = (int)($_POST['payroll_month'] ?? date('n'));
            $year = (int)($_POST['payroll_year'] ?? date('Y'));
            $basic = (float)($_POST['basic_salary'] ?? 0);
            $allowance = (float)($_POST['allowance'] ?? 0);
            $deduction = (float)($_POST['deduction'] ?? 0);
            $net = $basic + $allowance - $deduction;
            if ($staffId <= 0) throw new RuntimeException('Select a staff member.');
            $st = $pdo->prepare("INSERT INTO hr_payroll (staff_id, payroll_month, payroll_year, basic_salary, allowance, deduction, net_salary, payment_status, note) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE basic_salary=VALUES(basic_salary), allowance=VALUES(allowance), deduction=VALUES(deduction), net_salary=VALUES(net_salary), payment_status=VALUES(payment_status), note=VALUES(note)");
            $st->execute([$staffId, $month, $year, $basic, $allowance, $deduction, $net, trim((string)($_POST['payment_status'] ?? 'Pending')), trim((string)($_POST['note'] ?? '')) ?: null]);
            flash('success', 'Payroll saved.');
        } catch (Throwable $e) { flash('danger', $e->getMessage() ?: 'Unable to save payroll.'); }
        redirect('?page=hrm_payroll');
    }
    $staffOptions = $pdo->query("SELECT id, CONCAT(first_name, ' ', last_name) AS label, basic_salary FROM hr_staff ORDER BY first_name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $payrollRows = $pdo->query("SELECT p.*, CONCAT(s.first_name, ' ', s.last_name) AS staff_name FROM hr_payroll p JOIN hr_staff s ON s.id = p.staff_id ORDER BY p.payroll_year DESC, p.payroll_month DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    if (($_GET['export'] ?? '') === 'csv') {
        $csvRows = [];
        foreach ($payrollRows as $r) $csvRows[] = [$r['payroll_year'], $r['payroll_month'], $r['staff_name'], $r['basic_salary'], $r['allowance'], $r['deduction'], $r['net_salary'], $r['payment_status']];
        hrmCsvDownload('hrm_payroll.csv', ['Year','Month','Staff','Basic','Allowance','Deduction','Net','Status'], $csvRows);
    }
    ?>
    <div class="container-fluid">
        <div class="d-flex align-items-center justify-content-between mb-3">
            <h4 class="mb-0">HRM - Payroll</h4>
            <a class="btn btn-outline-success btn-sm" href="?page=hrm_payroll&export=csv">Export CSV</a>
        </div>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card">
                    <div class="card-header"><strong>Generate Payroll</strong></div>
                    <div class="card-body">
                        <form method="post">
                            <select class="form-select mb-2" name="staff_id" required><option value="">Select Staff</option><?php foreach ($staffOptions as $s): ?><option value="<?php echo (int)$s['id']; ?>"><?php echo e($s['label']); ?></option><?php endforeach; ?></select>
                            <div class="row g-2">
                                <div class="col-6"><input class="form-control" type="number" name="payroll_month" min="1" max="12" value="<?php echo (int)date('n'); ?>" required></div>
                                <div class="col-6"><input class="form-control" type="number" name="payroll_year" value="<?php echo (int)date('Y'); ?>" required></div>
                            </div>
                            <input class="form-control my-2" type="number" step="0.01" name="basic_salary" placeholder="Basic Salary" required>
                            <input class="form-control mb-2" type="number" step="0.01" name="allowance" placeholder="Allowance" value="0">
                            <input class="form-control mb-2" type="number" step="0.01" name="deduction" placeholder="Deduction" value="0">
                            <select class="form-select mb-2" name="payment_status"><?php foreach (['Pending','Paid'] as $st): ?><option value="<?php echo e($st); ?>"><?php echo e($st); ?></option><?php endforeach; ?></select>
                            <textarea class="form-control mb-2" name="note" placeholder="Note"></textarea>
                            <button class="btn btn-primary">Save Payroll</button>
                        </form>
                    </div>
                </div>
            </div>
            <div class="col-lg-8">
                <div class="card"><div class="card-header"><strong>Payroll Records</strong></div><div class="card-body table-responsive">
                    <table class="table table-sm align-middle">
                        <thead><tr><th>Period</th><th>Staff</th><th>Basic</th><th>Allowance</th><th>Deduction</th><th>Net</th><th>Status</th></tr></thead>
                        <tbody>
                        <?php foreach ($payrollRows as $r): ?>
                            <tr>
                                <td><?php echo e($r['payroll_year'] . '-' . str_pad((string)$r['payroll_month'], 2, '0', STR_PAD_LEFT)); ?></td>
                                <td><?php echo e($r['staff_name']); ?></td>
                                <td><?php echo number_format((float)$r['basic_salary'], 2); ?></td>
                                <td><?php echo number_format((float)$r['allowance'], 2); ?></td>
                                <td><?php echo number_format((float)$r['deduction'], 2); ?></td>
                                <td><?php echo number_format((float)$r['net_salary'], 2); ?></td>
                                <td><?php echo e($r['payment_status']); ?></td>
                            </tr>
                        <?php endforeach; ?>
                        <?php if (!$payrollRows): ?><tr><td colspan="7" class="text-muted">No payroll records found.</td></tr><?php endif; ?>
                        </tbody>
                    </table>
                </div></div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_recruitment':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $action = trim((string)($_POST['action'] ?? 'save_recruitment'));
            if ($action === 'save_recruitment') {
                $st = $pdo->prepare("INSERT INTO hr_recruitment (job_title, department_id, employment_type, vacancy_count, applicant_name, applicant_email, stage, status, applied_at, note) VALUES (?,?,?,?,?,?,?,?,?,?)");
                $st->execute([
                    trim((string)($_POST['job_title'] ?? '')),
                    (int)($_POST['department_id'] ?? 0) ?: null,
                    trim((string)($_POST['employment_type'] ?? 'Full-Time')),
                    max(1, (int)($_POST['vacancy_count'] ?? 1)),
                    trim((string)($_POST['applicant_name'] ?? '')) ?: null,
                    trim((string)($_POST['applicant_email'] ?? '')) ?: null,
                    trim((string)($_POST['stage'] ?? 'Applied')) ?: null,
                    trim((string)($_POST['status'] ?? 'Open')),
                    trim((string)($_POST['applied_at'] ?? '')) ?: null,
                    trim((string)($_POST['note'] ?? '')) ?: null
                ]);
                flash('success', 'Recruitment record saved.');
            }
        } catch (Throwable $e) { flash('danger', $e->getMessage() ?: 'Unable to save recruitment record.'); }
        redirect('?page=hrm_recruitment');
    }
    $departments = $pdo->query("SELECT id, name FROM hr_departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $rows = $pdo->query("SELECT r.*, d.name AS department_name FROM hr_recruitment r LEFT JOIN hr_departments d ON d.id = r.department_id ORDER BY r.created_at DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    ?>
    <div class="container-fluid">
        <h4 class="mb-3">HRM - Recruitment</h4>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card"><div class="card-header"><strong>Add Recruitment Record</strong></div><div class="card-body">
                    <form method="post">
                        <input type="hidden" name="action" value="save_recruitment">
                        <input class="form-control mb-2" name="job_title" placeholder="Job Title" required>
                        <select class="form-select mb-2" name="department_id"><option value="">Department</option><?php foreach ($departments as $d): ?><option value="<?php echo (int)$d['id']; ?>"><?php echo e($d['name']); ?></option><?php endforeach; ?></select>
                        <input class="form-control mb-2" name="employment_type" placeholder="Employment Type" value="Full-Time">
                        <input class="form-control mb-2" type="number" min="1" name="vacancy_count" value="1">
                        <input class="form-control mb-2" name="applicant_name" placeholder="Applicant Name">
                        <input class="form-control mb-2" type="email" name="applicant_email" placeholder="Applicant Email">
                        <input class="form-control mb-2" name="stage" placeholder="Stage" value="Applied">
                        <select class="form-select mb-2" name="status"><?php foreach (['Open','Shortlisted','Interview','Offered','Closed','Rejected'] as $st): ?><option value="<?php echo e($st); ?>"><?php echo e($st); ?></option><?php endforeach; ?></select>
                        <input class="form-control mb-2" type="date" name="applied_at">
                        <textarea class="form-control mb-2" name="note" placeholder="Notes"></textarea>
                        <button class="btn btn-primary">Save Record</button>
                    </form>
                </div></div>
            </div>
            <div class="col-lg-8">
                <div class="card"><div class="card-header"><strong>Recruitment Tracker</strong></div><div class="card-body table-responsive">
                    <table class="table table-sm align-middle">
                        <thead><tr><th>Job Title</th><th>Department</th><th>Applicant</th><th>Stage</th><th>Status</th><th>Date</th></tr></thead>
                        <tbody>
                            <?php foreach ($rows as $r): ?>
                                <tr>
                                    <td><?php echo e($r['job_title']); ?></td>
                                    <td><?php echo e($r['department_name'] ?: '-'); ?></td>
                                    <td><?php echo e($r['applicant_name'] ?: '-'); ?></td>
                                    <td><?php echo e($r['stage'] ?: '-'); ?></td>
                                    <td><?php echo e($r['status']); ?></td>
                                    <td><?php echo e($r['applied_at'] ?: '-'); ?></td>
                                </tr>
                            <?php endforeach; ?>
                            <?php if (!$rows): ?><tr><td colspan="6" class="text-muted">No recruitment records found.</td></tr><?php endif; ?>
                        </tbody>
                    </table>
                </div></div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_performance_review':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $st = $pdo->prepare("INSERT INTO hr_performance_reviews (staff_id, review_period, reviewer_name, rating, strengths, weaknesses, recommendations) VALUES (?,?,?,?,?,?,?)");
            $st->execute([
                (int)($_POST['staff_id'] ?? 0),
                trim((string)($_POST['review_period'] ?? '')),
                trim((string)($_POST['reviewer_name'] ?? '')) ?: null,
                (float)($_POST['rating'] ?? 0),
                trim((string)($_POST['strengths'] ?? '')) ?: null,
                trim((string)($_POST['weaknesses'] ?? '')) ?: null,
                trim((string)($_POST['recommendations'] ?? '')) ?: null
            ]);
            flash('success', 'Performance review saved.');
        } catch (Throwable $e) { flash('danger', $e->getMessage() ?: 'Unable to save performance review.'); }
        redirect('?page=hrm_performance_review');
    }
    $staffOptions = $pdo->query("SELECT id, CONCAT(first_name, ' ', last_name) AS label FROM hr_staff ORDER BY first_name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $rows = $pdo->query("SELECT p.*, CONCAT(s.first_name, ' ', s.last_name) AS staff_name FROM hr_performance_reviews p JOIN hr_staff s ON s.id = p.staff_id ORDER BY p.created_at DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    ?>
    <div class="container-fluid">
        <h4 class="mb-3">HRM - Performance Review</h4>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card"><div class="card-header"><strong>Create Review</strong></div><div class="card-body">
                    <form method="post">
                        <select class="form-select mb-2" name="staff_id" required><option value="">Select Staff</option><?php foreach ($staffOptions as $s): ?><option value="<?php echo (int)$s['id']; ?>"><?php echo e($s['label']); ?></option><?php endforeach; ?></select>
                        <input class="form-control mb-2" name="review_period" placeholder="Review Period e.g. 2026 Q1" required>
                        <input class="form-control mb-2" name="reviewer_name" placeholder="Reviewer Name">
                        <input class="form-control mb-2" type="number" step="0.01" min="0" max="5" name="rating" placeholder="Rating /5">
                        <textarea class="form-control mb-2" name="strengths" placeholder="Strengths"></textarea>
                        <textarea class="form-control mb-2" name="weaknesses" placeholder="Weaknesses"></textarea>
                        <textarea class="form-control mb-2" name="recommendations" placeholder="Recommendations"></textarea>
                        <button class="btn btn-primary">Save Review</button>
                    </form>
                </div></div>
            </div>
            <div class="col-lg-8">
                <div class="card"><div class="card-header"><strong>Performance Reviews</strong></div><div class="card-body table-responsive">
                    <table class="table table-sm align-middle">
                        <thead><tr><th>Staff</th><th>Period</th><th>Reviewer</th><th>Rating</th><th>Recommendations</th></tr></thead>
                        <tbody>
                            <?php foreach ($rows as $r): ?>
                                <tr>
                                    <td><?php echo e($r['staff_name']); ?></td>
                                    <td><?php echo e($r['review_period']); ?></td>
                                    <td><?php echo e($r['reviewer_name'] ?: '-'); ?></td>
                                    <td><?php echo e($r['rating']); ?></td>
                                    <td><?php echo e($r['recommendations'] ?: '-'); ?></td>
                                </tr>
                            <?php endforeach; ?>
                            <?php if (!$rows): ?><tr><td colspan="5" class="text-muted">No performance reviews found.</td></tr><?php endif; ?>
                        </tbody>
                    </table>
                </div></div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_staff_documents':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $st = $pdo->prepare("INSERT INTO hr_documents (staff_id, document_title, document_type, file_path, note) VALUES (?,?,?,?,?)");
            $st->execute([
                (int)($_POST['staff_id'] ?? 0),
                trim((string)($_POST['document_title'] ?? '')),
                trim((string)($_POST['document_type'] ?? '')) ?: null,
                trim((string)($_POST['file_path'] ?? '')) ?: null,
                trim((string)($_POST['note'] ?? '')) ?: null
            ]);
            flash('success', 'Staff document saved.');
        } catch (Throwable $e) { flash('danger', $e->getMessage() ?: 'Unable to save staff document.'); }
        redirect('?page=hrm_staff_documents');
    }
    $staffOptions = $pdo->query("SELECT id, CONCAT(first_name, ' ', last_name) AS label FROM hr_staff ORDER BY first_name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $rows = $pdo->query("SELECT d.*, CONCAT(s.first_name, ' ', s.last_name) AS staff_name FROM hr_documents d JOIN hr_staff s ON s.id = d.staff_id ORDER BY d.created_at DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    ?>
    <div class="container-fluid">
        <h4 class="mb-3">HRM - Staff Documents</h4>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card"><div class="card-header"><strong>Add Document</strong></div><div class="card-body">
                    <form method="post">
                        <select class="form-select mb-2" name="staff_id" required><option value="">Select Staff</option><?php foreach ($staffOptions as $s): ?><option value="<?php echo (int)$s['id']; ?>"><?php echo e($s['label']); ?></option><?php endforeach; ?></select>
                        <input class="form-control mb-2" name="document_title" placeholder="Document Title" required>
                        <input class="form-control mb-2" name="document_type" placeholder="Document Type">
                        <input class="form-control mb-2" name="file_path" placeholder="File Path / URL">
                        <textarea class="form-control mb-2" name="note" placeholder="Note"></textarea>
                        <button class="btn btn-primary">Save Document</button>
                    </form>
                </div></div>
            </div>
            <div class="col-lg-8">
                <div class="card"><div class="card-header"><strong>Document Register</strong></div><div class="card-body table-responsive">
                    <table class="table table-sm align-middle">
                        <thead><tr><th>Staff</th><th>Title</th><th>Type</th><th>Path / URL</th><th>Note</th></tr></thead>
                        <tbody>
                            <?php foreach ($rows as $r): ?>
                                <tr>
                                    <td><?php echo e($r['staff_name']); ?></td>
                                    <td><?php echo e($r['document_title']); ?></td>
                                    <td><?php echo e($r['document_type'] ?: '-'); ?></td>
                                    <td><?php echo e($r['file_path'] ?: '-'); ?></td>
                                    <td><?php echo e($r['note'] ?: '-'); ?></td>
                                </tr>
                            <?php endforeach; ?>
                            <?php if (!$rows): ?><tr><td colspan="5" class="text-muted">No staff documents found.</td></tr><?php endif; ?>
                        </tbody>
                    </table>
                </div></div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_reports':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}
    $byDepartment = $pdo->query("SELECT COALESCE(d.name,'Unassigned') AS label, COUNT(*) AS total FROM hr_staff s LEFT JOIN hr_departments d ON d.id=s.department_id GROUP BY COALESCE(d.name,'Unassigned') ORDER BY total DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $byDesignation = $pdo->query("SELECT COALESCE(z.name,'Unassigned') AS label, COUNT(*) AS total FROM hr_staff s LEFT JOIN hr_designations z ON z.id=s.designation_id GROUP BY COALESCE(z.name,'Unassigned') ORDER BY total DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $attendanceSummary = $pdo->query("SELECT attendance_date, COUNT(*) AS total FROM hr_attendance GROUP BY attendance_date ORDER BY attendance_date DESC LIMIT 15")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    if (($_GET['export'] ?? '') === 'csv') {
        $csvRows = [];
        foreach ($byDepartment as $r) $csvRows[] = ['Department', $r['label'], $r['total']];
        foreach ($byDesignation as $r) $csvRows[] = ['Designation', $r['label'], $r['total']];
        foreach ($attendanceSummary as $r) $csvRows[] = ['Attendance Date', $r['attendance_date'], $r['total']];
        hrmCsvDownload('hrm_reports.csv', ['Report Type','Label','Total'], $csvRows);
    }
    ?>
    <div class="container-fluid">
        <div class="d-flex align-items-center justify-content-between mb-3">
            <h4 class="mb-0">HRM - Reports</h4>
            <div class="d-flex gap-2">
                <a class="btn btn-outline-success btn-sm" href="?page=hrm_reports&export=csv">Export CSV</a>
                <button class="btn btn-outline-secondary btn-sm" onclick="window.print()">Print</button>
            </div>
        </div>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-6">
                <div class="card"><div class="card-header"><strong>Staff by Department</strong></div><div class="card-body table-responsive">
                    <table class="table table-sm"><thead><tr><th>Department</th><th>Total Staff</th></tr></thead><tbody><?php foreach ($byDepartment as $r): ?><tr><td><?php echo e($r['label']); ?></td><td><?php echo (int)$r['total']; ?></td></tr><?php endforeach; ?></tbody></table>
                </div></div>
            </div>
            <div class="col-lg-6">
                <div class="card"><div class="card-header"><strong>Staff by Designation</strong></div><div class="card-body table-responsive">
                    <table class="table table-sm"><thead><tr><th>Designation</th><th>Total Staff</th></tr></thead><tbody><?php foreach ($byDesignation as $r): ?><tr><td><?php echo e($r['label']); ?></td><td><?php echo (int)$r['total']; ?></td></tr><?php endforeach; ?></tbody></table>
                </div></div>
            </div>
            <div class="col-12">
                <div class="card"><div class="card-header"><strong>Recent Attendance Summary</strong></div><div class="card-body table-responsive">
                    <table class="table table-sm"><thead><tr><th>Date</th><th>Total Marked</th></tr></thead><tbody><?php foreach ($attendanceSummary as $r): ?><tr><td><?php echo e($r['attendance_date']); ?></td><td><?php echo (int)$r['total']; ?></td></tr><?php endforeach; ?></tbody></table>
                </div></div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_settings':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            foreach (['company_name','default_currency','attendance_default_status'] as $k) {
                $val = trim((string)($_POST[$k] ?? ''));
                $st = $pdo->prepare("INSERT INTO hr_settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value)");
                $st->execute([$k, $val]);
            }
            flash('success', 'HR settings saved.');
        } catch (Throwable $e) { flash('danger', $e->getMessage() ?: 'Unable to save HR settings.'); }
        redirect('?page=hrm_settings');
    }
    $settings = [];
    foreach ($pdo->query("SELECT setting_key, setting_value FROM hr_settings")->fetchAll(PDO::FETCH_ASSOC) ?: [] as $r) $settings[$r['setting_key']] = $r['setting_value'];
    ?>
    <div class="container-fluid">
        <h4 class="mb-3">HRM - Settings</h4>
        <?php renderHrmSubmenu($page); ?>
        <div class="card">
            <div class="card-header"><strong>HR Settings</strong></div>
            <div class="card-body">
                <form method="post" class="row g-3">
                    <div class="col-md-4"><label class="form-label">Company Name</label><input class="form-control" name="company_name" value="<?php echo e($settings['company_name'] ?? APP_NAME); ?>"></div>
                    <div class="col-md-4"><label class="form-label">Default Currency</label><input class="form-control" name="default_currency" value="<?php echo e($settings['default_currency'] ?? 'NGN'); ?>"></div>
                    <div class="col-md-4"><label class="form-label">Attendance Default Status</label><input class="form-control" name="attendance_default_status" value="<?php echo e($settings['attendance_default_status'] ?? 'Present'); ?>"></div>
                    <div class="col-12"><button class="btn btn-primary">Save Settings</button></div>
                </form>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'hrm_notifications':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'hrm_manage');
    try { ensureHrmModule($pdo); } catch (Throwable $e) {}
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $st = $pdo->prepare("INSERT INTO hr_notifications (target_role, title, message, is_read) VALUES (?,?,?,0)");
            $st->execute([trim((string)($_POST['target_role'] ?? '')) ?: null, trim((string)($_POST['title'] ?? '')), trim((string)($_POST['message'] ?? ''))]);
            flash('success', 'HR notification saved.');
        } catch (Throwable $e) { flash('danger', $e->getMessage() ?: 'Unable to save HR notification.'); }
        redirect('?page=hrm_notifications');
    }
    $rows = $pdo->query("SELECT * FROM hr_notifications ORDER BY created_at DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    ?>
    <div class="container-fluid">
        <h4 class="mb-3">HRM - Notifications</h4>
        <?php renderHrmSubmenu($page); ?>
        <div class="row g-3">
            <div class="col-lg-4">
                <div class="card"><div class="card-header"><strong>Create Notification</strong></div><div class="card-body">
                    <form method="post">
                        <input class="form-control mb-2" name="target_role" placeholder="Target Role (optional)">
                        <input class="form-control mb-2" name="title" placeholder="Title" required>
                        <textarea class="form-control mb-2" name="message" placeholder="Message" required></textarea>
                        <button class="btn btn-primary">Save Notification</button>
                    </form>
                </div></div>
            </div>
            <div class="col-lg-8">
                <div class="card"><div class="card-header"><strong>Notification Log</strong></div><div class="card-body table-responsive">
                    <table class="table table-sm align-middle">
                        <thead><tr><th>Role</th><th>Title</th><th>Message</th><th>Date</th></tr></thead>
                        <tbody>
                            <?php foreach ($rows as $r): ?>
                                <tr>
                                    <td><?php echo e($r['target_role'] ?: 'All'); ?></td>
                                    <td><?php echo e($r['title']); ?></td>
                                    <td><?php echo e($r['message']); ?></td>
                                    <td><?php echo e($r['created_at']); ?></td>
                                </tr>
                            <?php endforeach; ?>
                            <?php if (!$rows): ?><tr><td colspan="4" class="text-muted">No notifications found.</td></tr><?php endif; ?>
                        </tbody>
                    </table>
                </div></div>
            </div>
        </div>
    </div>

    

    <?php
    break;




case 'download_scoresheet':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'scoresheet_manage');
    $gradingSettings = portalGradingResultSettings($pdo);
    $caLabel = trim((string)($gradingSettings['result_ca_label'] ?? 'CA')) ?: 'CA';
    $examLabel = trim((string)($gradingSettings['result_exam_label'] ?? 'EXAM')) ?: 'EXAM';
    $caMaxScore = (float)($gradingSettings['result_ca_max_score'] ?? 40);
    if ($caMaxScore <= 0) $caMaxScore = 40;
    $examMaxScore = (float)($gradingSettings['result_exam_max_score'] ?? 60);
    if ($examMaxScore <= 0) $examMaxScore = 60;
    $passMarkScore = (float)($gradingSettings['result_pass_mark'] ?? 40);
    $minimumCaScore = portalResultMinimumCaScore($pdo);

    // Self-heal: ensure minimal indexes for filtering speed
    try { $pdo->exec("CREATE INDEX IF NOT EXISTS idx_cr_session_sem ON course_registrations(session, semester)"); } catch (Throwable $e) {}
    try { $pdo->exec("CREATE INDEX IF NOT EXISTS idx_cr_fac_dept_level ON course_registrations(faculty_id, department_id, programme_id, level)"); } catch (Throwable $e) {}
    try { $pdo->exec("CREATE INDEX IF NOT EXISTS idx_cri_course ON course_registration_items(course_id)"); } catch (Throwable $e) {}
    try { $pdo->exec("CREATE INDEX IF NOT EXISTS idx_results_course_sess_sem ON results(course_id, session, semester)"); } catch (Throwable $e) {}

    $facultyId   = (int)($_GET['faculty_id'] ?? 0);
    $departmentId= (int)($_GET['department_id'] ?? 0);
    $programmeId = (int)($_GET['programme_id'] ?? 0);
    $level       = trim((string)($_GET['level'] ?? ''));
    $session     = trim((string)($_GET['session'] ?? ''));
    $semester    = trim((string)($_GET['semester'] ?? ''));
    $courseId    = (int)($_GET['course_id'] ?? 0);
    $export      = trim((string)($_GET['export'] ?? ''));

    // Fetch dropdown data
    $faculties = [];
    $departments = [];
    $programmes = [];
    $sessions = [];
    $courses = [];

    try { $faculties = $pdo->query("SELECT id, name FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try {
        if ($facultyId > 0) {
            $st = $pdo->prepare("SELECT id, name FROM departments WHERE faculty_id=? ORDER BY name ASC");
            $st->execute([$facultyId]);
            $departments = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } else {
            $departments = $pdo->query("SELECT id, name FROM departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        }
    } catch (Throwable $e) {}
    try {
        $programmes = portalProgrammeSelectorOptions($pdo, (int)$facultyId, (int)$departmentId);
    } catch (Throwable $e) {}
    try {
        foreach (portalAcademicSessionOptions($pdo) as $sessionOpt) {
            $sessionOpt = trim((string)$sessionOpt);
            if ($sessionOpt === '') continue;
            $sessions[] = ['name' => $sessionOpt, 'is_current' => ($sessionOpt === portalCurrentAcademicSessionName($pdo) ? 1 : 0)];
        }
    } catch (Throwable $e) {}
    $crSessionCol = portalCourseRegistrationSessionColumn($pdo);
    $rcSessionCol = portalRegistrableCourseSessionColumn($pdo);
    $courses = portalFetchRegisteredCourseOptions($pdo, [
        'faculty_id' => $facultyId,
        'department_id' => $departmentId,
        'programme_id' => $programmeId,
        'level' => $level,
        'session' => $session,
        'semester' => $semester,
    ]);

    // Helper: get course meta
    $courseMeta = null;
    if ($courseId > 0) {
        try {
            $st = $pdo->prepare("SELECT * FROM registrable_courses WHERE id=?");
            $st->execute([$courseId]);
            $courseMeta = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        } catch (Throwable $e) { $courseMeta = null; }
    }
    if ($courseMeta && !array_filter($courses, static function(array $row) use ($courseId): bool { return (int)($row['id'] ?? 0) === (int)$courseId; })) {
        $courseMeta['student_count'] = (int)($courseMeta['student_count'] ?? 0);
        $courses[] = $courseMeta;
    }

    // Build scoresheet rows only when we have enough filters
    $rows = [];
    if ($courseId > 0 && $session !== '' && $semester !== '') {
        $rows = portalFetchRegisteredStudentsForCourseContext($pdo, $courseId, $session, $semester, [
            'faculty_id' => $facultyId,
            'department_id' => $departmentId,
            'programme_id' => $programmeId,
            'level' => $level,
        ]);
    }

    $selectedFacultyName = '';
    foreach ($faculties as $f) {
        if ((int)($f['id'] ?? 0) === $facultyId) { $selectedFacultyName = trim((string)($f['name'] ?? '')); break; }
    }
    $selectedDepartmentName = '';
    foreach ($departments as $d) {
        if ((int)($d['id'] ?? 0) === $departmentId) { $selectedDepartmentName = trim((string)($d['name'] ?? '')); break; }
    }
    $selectedProgrammeName = '';
    foreach ($programmes as $p) {
        if ((int)($p['id'] ?? 0) === $programmeId) { $selectedProgrammeName = trim((string)($p['name'] ?? '')); break; }
    }

    $scoresheetInstitutionName = trim((string)portalSettingValueSafe($pdo, 'institution_name', portalSettingValueSafe($pdo, 'portal_title', APP_NAME)));
    if ($scoresheetInstitutionName === '') $scoresheetInstitutionName = trim((string)portalNotificationMailSubjectPrefix($pdo));
    $scoresheetInstitutionLine = function_exists('mb_strtoupper') ? mb_strtoupper($scoresheetInstitutionName, 'UTF-8') : strtoupper($scoresheetInstitutionName);
    $scoresheetLeftLogo = trim((string)(function_exists('getSystemAsset') ? getSystemAsset('left_logo') : ''));
    $scoresheetRightLogo = trim((string)(function_exists('getSystemAsset') ? getSystemAsset('right_logo') : ''));
    if ($scoresheetRightLogo === '') $scoresheetRightLogo = $scoresheetLeftLogo;
    $contextSchoolName = trim((string)($rows[0]['faculty_name'] ?? $selectedFacultyName ?? ''));
    $contextDepartmentName = trim((string)($rows[0]['department_name'] ?? $selectedDepartmentName ?? ''));
    $contextProgrammeName = trim((string)($rows[0]['programme_name'] ?? $selectedProgrammeName ?? ''));
    $contextCourseOfStudy = $contextProgrammeName !== '' && $contextProgrammeName !== '—' ? $contextProgrammeName : ($contextDepartmentName !== '' ? $contextDepartmentName : '—');
    $contextAcademicLevel = trim((string)($level !== '' ? $level : ($courseMeta['level'] ?? ($rows[0]['academic_level'] ?? ''))));
    $contextCourseCode = trim((string)($courseMeta['course_code'] ?? ''));
    $contextCourseTitle = trim((string)($courseMeta['course_title'] ?? ''));
    $contextCreditUnit = trim((string)($courseMeta['credit_unit'] ?? ''));

    $scoresheetFilePart = static function($value, string $fallback = ''): string {
        $value = trim((string)$value);
        if ($value === '') {
            $value = $fallback;
        }
        $value = trim((string)$value);
        $value = str_replace(["\r", "\n", "\t"], ' ', $value);
        $value = str_replace(['/', '\\'], '-', $value);
        $value = str_replace([':', '*', '?', '"', '<', '>', '|'], ' ', $value);
        $value = preg_replace('/[^A-Za-z0-9._\- ]+/', ' ', $value) ?: $value;
        $value = preg_replace('/\s+/', '_', $value) ?: $value;
        $value = preg_replace('/_+/', '_', $value) ?: $value;
        $value = preg_replace('/-+/', '-', $value) ?: $value;
        $value = trim($value, '._- ');
        return $value !== '' ? $value : trim((string)$fallback, '._- ');
    };

    $buildScoresheetFileBase = static function(bool $isEmpty = false) use (
        $scoresheetFilePart,
        $contextCourseTitle,
        $contextCourseCode,
        $contextAcademicLevel,
        $contextDepartmentName,
        $contextProgrammeName,
        $semester,
        $session,
        $courseId
    ): string {
        $departmentPart = $scoresheetFilePart($contextDepartmentName, $contextProgrammeName !== '' ? $contextProgrammeName : 'Department');
        $parts = [
            $scoresheetFilePart($contextCourseTitle, 'Course_' . (int)$courseId),
            $scoresheetFilePart($contextCourseCode, 'CourseCode'),
            $scoresheetFilePart($contextAcademicLevel, 'Level'),
            $departmentPart !== '' ? $departmentPart : 'Department',
            $scoresheetFilePart($semester, 'Semester'),
            $scoresheetFilePart($session, 'Session'),
            $isEmpty ? 'Empty_Scoresheet' : 'Scoresheet',
        ];
        $parts = array_values(array_filter($parts, static fn($item): bool => trim((string)$item) !== ''));
        $base = implode('_', $parts);
        $base = preg_replace('/_+/', '_', $base) ?: $base;
        $base = trim((string)$base, '._- ');
        return $base !== '' ? $base : ($isEmpty ? 'Empty_Scoresheet' : 'Scoresheet');
    };

    $filledScoresheetFileBase = $buildScoresheetFileBase(false);
    $emptyScoresheetFileBase = $buildScoresheetFileBase(true);

    $emptyScoresheetRows = [];
    $emptySn = 1;
    foreach ($rows as $r) {
        $emptyScoresheetRows[] = [
            'sn' => $emptySn++,
            'student_name' => (string)($r['full_name'] ?? ''),
            'matric_no' => (string)($r['matric_no'] ?? ''),
            'ca_score' => '',
            'exam_score' => '',
            'total_score' => '',
            'grade' => '',
            'remarks' => '',
        ];
    }

    // Export CSV (server-side)
    if ($export === 'csv' && $courseId > 0 && $session !== '' && $semester !== '') {
        $fn = $filledScoresheetFileBase . ".csv";
        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="'.$fn.'"');

        $out = fopen('php://output', 'w');
        fputcsv($out, ['S/N','Student Name','Matric Number','CA Score','Exam Score','Total Score','Grade','Remarks','Status']);
        $sn = 1;
        foreach ($rows as $r) {
            fputcsv($out, [
                $sn++,
                $r['full_name'] ?? '',
                $r['matric_no'] ?? '',
                $r['ca_score'] ?? '',
                $r['exam_score'] ?? '',
                $r['total_score'] ?? '',
                $r['grade'] ?? '',
                (portalResultAssessment($pdo, (float)($r['ca_score'] ?? 0), (float)($r['exam_score'] ?? 0), (float)($r['total_score'] ?? 0))['remark'] ?? 'FAIL'),
                $r['status'] ?? '',
            ]);
        }
        fclose($out);
        exit;
    }

    // Save results from the "Input Results" tab
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $action = $_POST['action'] ?? '';
        if ($action === 'save_scoresheet_results') {
            $courseIdP = (int)($_POST['course_id'] ?? 0);
            $sessionP  = trim((string)($_POST['session'] ?? ''));
            $semesterP = trim((string)($_POST['semester'] ?? ''));
            if ($courseIdP <= 0 || $sessionP === '' || $semesterP === '') {
                flash('Invalid request (missing course/session/semester).', 'danger');
                redirect('?page=download_scoresheet');
            }

            // Lock if published
            $isPublished = false;
            try {
                $st = $pdo->prepare("SELECT COUNT(*) FROM result_submissions WHERE course_id=? AND session=? AND semester=? AND status='published'");
                $st->execute([$courseIdP, $sessionP, $semesterP]);
                $isPublished = ((int)$st->fetchColumn() > 0);
            } catch (Throwable $e) {}
            if (!$isPublished) {
                try {
                    $st = $pdo->prepare("SELECT COUNT(*) FROM results WHERE course_id=? AND session=? AND semester=? AND status='published'");
                    $st->execute([$courseIdP, $sessionP, $semesterP]);
                    $isPublished = ((int)$st->fetchColumn() > 0);
                } catch (Throwable $e) {}
            }

            if ($isPublished) {
                flash('This result has already been published. Editing is locked.', 'warning');
                redirect('?page=download_scoresheet&course_id='.$courseIdP.'&session='.urlencode($sessionP).'&semester='.urlencode($semesterP));
            }


            $ca = $_POST['ca'] ?? [];
            $exam = $_POST['exam'] ?? [];

            $saved = 0;
            foreach ($rows as $r) {
                $sid = (int)($r['student_id'] ?? 0);
                if ($sid <= 0) continue;

                $caScore = isset($ca[$sid]) ? (float)$ca[$sid] : 0.0;
                $examScore = isset($exam[$sid]) ? (float)$exam[$sid] : 0.0;

                // basic validation
                if ($caScore < 0 || $examScore < 0 || $caScore > $caMaxScore || $examScore > $examMaxScore) {
                    continue;
                }

                $assessment = portalResultAssessment($pdo, $caScore, $examScore);
                $total = (float)($assessment['total_score'] ?? ($caScore + $examScore));
                $grade = (string)($assessment['grade'] ?? 'F');
                $gp = (float)($assessment['grade_point'] ?? 0);

                try {
                    $st = $pdo->prepare("
                        INSERT INTO results (submission_id, course_id, student_id, session, semester, ca_score, exam_score, total_score, grade, grade_point, status)
                        VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'draft')
                        ON DUPLICATE KEY UPDATE
                            ca_score=VALUES(ca_score),
                            exam_score=VALUES(exam_score),
                            total_score=VALUES(total_score),
                            grade=VALUES(grade),
                            grade_point=VALUES(grade_point),
                            status=IF(status='published', status, 'draft')
                    ");
                    $st->execute([$courseIdP, $sid, $sessionP, $semesterP, $caScore, $examScore, $total, $grade, $gp]);
                    $saved++;
                } catch (Throwable $e) { /* ignore */ }
            }

            flash("Saved $saved score(s) as draft.", 'success');
            redirect('?page=download_scoresheet&faculty_id='.$facultyId.'&department_id='.$departmentId.'&programme_id='.$programmeId.'&level='.urlencode($level).'&session='.urlencode($sessionP).'&semester='.urlencode($semesterP).'&course_id='.$courseIdP);
        }
    }

    ?>
    <div class="container-fluid py-3">
        <div class="d-flex align-items-center justify-content-between mb-3">
            <div>
                <h4 class="mb-0">Download Scoresheet</h4>
                <div class="text-muted small">Shows only students who registered the selected course for the chosen session/semester and context.</div>
            </div>
            <div class="d-flex gap-2">
                <?php if ($courseId > 0 && $session !== '' && $semester !== ''): ?>
                    <a class="btn btn-outline-secondary btn-sm" href="?page=download_scoresheet&faculty_id=<?php echo (int)$facultyId; ?>&department_id=<?php echo (int)$departmentId; ?>&programme_id=<?php echo (int)$programmeId; ?>&level=<?php echo htmlspecialchars(urlencode($level)); ?>&session=<?php echo htmlspecialchars(urlencode($session)); ?>&semester=<?php echo htmlspecialchars(urlencode($semester)); ?>&course_id=<?php echo (int)$courseId; ?>&export=csv">
                        <i class="bi bi-filetype-csv"></i> Download CSV
                    </a>
                <?php endif; ?>
            </div>
        </div>

        <div class="card mb-3">
            <div class="card-body">
                <form method="get" class="row g-2 align-items-end">
                    <input type="hidden" name="page" value="download_scoresheet">

                    <div class="col-12 col-md-3">
                        <label class="form-label">Faculty</label>
                        <select name="faculty_id" class="form-select">
                            <option value="0">All Faculties</option>
                            <?php foreach ($faculties as $f): ?>
                                <option value="<?php echo (int)$f['id']; ?>" <?php echo ((int)$f['id']===$facultyId)?'selected':''; ?>>
                                    <?php echo htmlspecialchars($f['name']); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="col-12 col-md-3">
                        <label class="form-label">Department</label>
                        <select name="department_id" class="form-select">
                            <option value="0">All Departments</option>
                            <?php foreach ($departments as $d): ?>
                                <option value="<?php echo (int)$d['id']; ?>" <?php echo ((int)$d['id']===$departmentId)?'selected':''; ?>>
                                    <?php echo htmlspecialchars($d['name']); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="col-12 col-md-3">
                        <label class="form-label">Programme</label>
                        <select name="programme_id" class="form-select">
                            <option value="0">All Programmes</option>
                            <?php foreach ($programmes as $p): ?>
                                <option value="<?php echo (int)$p['id']; ?>" <?php echo ((int)$p['id']===$programmeId)?'selected':''; ?>>
                                    <?php echo htmlspecialchars($p['name']); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="col-6 col-md-1">
                        <label class="form-label">Level</label>
                        <select name="level" class="form-select">
                            <option value="">All</option>
                            <?php foreach (['100','200','300','400','500','600'] as $lv): ?>
                                <option value="<?php echo $lv; ?>" <?php echo ($level===$lv)?'selected':''; ?>><?php echo $lv; ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="col-6 col-md-2">
                        <label class="form-label">Session</label>
                        <select name="session" class="form-select">
                            <option value="">Select Session</option>
                            <?php foreach ($sessions as $s): ?>
                                <option value="<?php echo htmlspecialchars($s['name']); ?>" <?php echo ($session===$s['name'])?'selected':''; ?>>
                                    <?php echo htmlspecialchars($s['name']); ?><?php echo ((int)($s['is_current']??0)===1)?' (Current)':''; ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="col-6 col-md-2">
                        <label class="form-label">Semester</label>
                        <select name="semester" class="form-select">
                            <option value="">Select</option>
                            <?php foreach (['First','Second'] as $sem): ?>
                                <option value="<?php echo $sem; ?>" <?php echo ($semester===$sem)?'selected':''; ?>><?php echo $sem; ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="col-12 col-md-3">
                        <label class="form-label">Course</label>
                        <select name="course_id" class="form-select">
                            <option value="0">Select Course</option>
                            <?php foreach ($courses as $c): ?>
                                <option value="<?php echo (int)$c['id']; ?>" <?php echo ((int)$c['id']===$courseId)?'selected':''; ?>>
                                    <?php echo htmlspecialchars(($c['course_code'] ?? '').' - '.($c['course_title'] ?? '')); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                        <div class="small text-muted mt-1">Tip: choose Faculty/Department/Programme/Level first to narrow courses.</div>
                    </div>

                    <div class="col-12 col-md-2">
                        <button class="btn btn-primary w-100" type="submit">
                            <i class="bi bi-search"></i> Load
                        </button>
                    </div>
                </form>
            </div>
        </div>

        <ul class="nav nav-tabs" role="tablist">
            <li class="nav-item" role="presentation">
                <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tabScoresheet" type="button" role="tab">Scoresheet</button>
            </li>
            <li class="nav-item" role="presentation">
                <button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabInput" type="button" role="tab">Input Results</button>
            </li>
            <li class="nav-item" role="presentation">
                <button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabEmptyScoresheet" type="button" role="tab">Download Empty Scoresheet</button>
            </li>
        </ul>

        <style>
            .scoresheet-export-wrap{background:#fff;}
            .scoresheet-export-head{
                margin-bottom:14px;
                padding:14px 18px 12px;
                background:#f8fafc;
                border:1px solid #d9e2ec;
                border-radius:12px;
            }
            .scoresheet-export-title{
                margin:0 0 10px;
                text-align:center;
                font-weight:700;
                font-size:1.2rem;
                line-height:1.3;
                color:#1f2937;
            }
            .scoresheet-export-meta{margin-bottom:12px; gap:10px 14px;}
            .scoresheet-export-brand{display:grid; grid-template-columns:92px minmax(0,1fr) 92px; align-items:center; gap:14px; margin-bottom:8px;}
            .scoresheet-export-brand-center{min-width:0;}
            .scoresheet-export-logo-slot{display:flex; align-items:center; justify-content:center; min-height:74px;}
            .scoresheet-export-logo-slot img{max-width:78px; max-height:78px; width:auto; height:auto; object-fit:contain;}
            .scoresheet-export-logo-placeholder{display:block; width:78px; height:78px;}
            .empty-scoresheet-head .scoresheet-export-title{margin-bottom:6px;}
            .empty-scoresheet-signatures .signature-date-line{margin-top:10px; display:flex; align-items:center; justify-content:center; gap:8px; font-weight:600;}
            .empty-scoresheet-signatures .signature-date-line .date-fill{display:inline-block; min-width:140px; border-bottom:1px solid #475569; height:1em;}
            .empty-scoresheet-actions{margin-bottom:14px; padding:12px 14px; border:1px solid #d9e2ec; border-radius:12px; background:#f8fafc;}
            .empty-scoresheet-actions .btn{min-width:170px;}
            .empty-scoresheet-preview-note{margin-bottom:10px;}
            #emptyScoresheetPreviewTable{width:100%; table-layout:fixed;}
            #emptyScoresheetPreviewTable thead th{vertical-align:middle; text-align:left; white-space:normal;}
            #emptyScoresheetPreviewTable tbody td{vertical-align:top;}
            #emptyScoresheetPreviewTable .col-sn{width:60px;}
            #emptyScoresheetPreviewTable .col-student{width:26%; min-width:220px;}
            #emptyScoresheetPreviewTable .col-matric{width:19%; min-width:160px;}
            #emptyScoresheetPreviewTable .col-score{width:12%; min-width:92px; text-align:center; white-space:nowrap;}
            #emptyScoresheetPreviewTable .col-grade{width:9%; min-width:70px; text-align:center; white-space:nowrap;}
            #emptyScoresheetPreviewTable .col-remarks{width:16%; min-width:130px;}
            .empty-scoresheet-signatures{margin-top:18px; padding-top:16px; border-top:1px dashed #cbd5e1;}
            .empty-scoresheet-signatures .signature-line{padding-top:14px; border-top:1px solid #475569; font-weight:600; letter-spacing:.02em; text-align:center;}
            #scoresheetTable{width:100% !important; table-layout:fixed;}
            #scoresheetTable thead th{
                vertical-align:middle;
                text-align:left;
                white-space:normal;
            }
            #scoresheetTable tbody td{vertical-align:top;}
            #scoresheetTable .col-sn{width:60px;}
            #scoresheetTable .col-student{width:22%; min-width:220px;}
            #scoresheetTable .col-matric{width:16%; min-width:155px;}
            #scoresheetTable .col-score{width:11%; min-width:92px; text-align:center; white-space:nowrap;}
            #scoresheetTable .col-grade{width:8%; min-width:70px; text-align:center; white-space:nowrap;}
            #scoresheetTable .col-remarks{width:14%; min-width:130px;}
            #scoresheetTable .col-status{width:10%; min-width:95px; text-align:center;}
            #scoresheetTable .student-name-cell{font-weight:600; line-height:1.35; word-break:normal; overflow-wrap:anywhere;}
            #scoresheetTable .matric-cell{white-space:nowrap; word-break:normal;}
            #scoresheetTable .remark-cell{line-height:1.3; word-break:normal; overflow-wrap:break-word;}
            #scoresheetTable .status-cell{white-space:nowrap; text-align:center;}
            #scoresheetTable .badge{display:inline-block; min-width:72px; text-transform:capitalize;}
            @media print{
                .scoresheet-export-head{margin-bottom:12px; padding:10px 12px; border-color:#cbd5e1; background:#fff !important;}
                .scoresheet-export-brand{grid-template-columns:74px minmax(0,1fr) 74px; gap:10px; margin-bottom:6px;}
                .scoresheet-export-logo-slot{min-height:62px;}
                .scoresheet-export-logo-slot img{max-width:62px; max-height:62px;}
                .scoresheet-export-logo-placeholder{width:62px; height:62px;}
                .scoresheet-export-title{font-size:18px; margin-bottom:8px;}
                .scoresheet-export-meta{margin-bottom:10px;}
                #scoresheetTable{table-layout:fixed;}
                #scoresheetTable thead th,
                #scoresheetTable tbody td{padding:8px 10px !important; font-size:13px !important;}
                #scoresheetTable .col-student{width:24% !important;}
                #scoresheetTable .col-matric{width:17% !important;}
                #scoresheetTable .col-score{width:10% !important;}
                #scoresheetTable .col-grade{width:7% !important;}
                #scoresheetTable .col-remarks{width:14% !important;}
                #scoresheetTable .col-status{width:10% !important;}
                #scoresheetTable .badge{
                    color:#111 !important;
                    background:#fff !important;
                    border:1px solid #777 !important;
                    box-shadow:none !important;
                    min-width:0;
                    padding:2px 8px;
                }
                .dt-buttons,.dataTables_filter,.dataTables_length,.dataTables_info,.dataTables_paginate{display:none !important;}
            }
        </style>

        <div class="tab-content border border-top-0 p-3 bg-white">
            <div class="tab-pane fade show active" id="tabScoresheet" role="tabpanel">
                <?php if (!$courseId || $session==='' || $semester===''): ?>
                    <div class="alert alert-info mb-0">Select Faculty/Department/Session/Semester and a Course, then click <b>Load</b>.</div>
                <?php else: ?>
                    
                    <div class="scoresheet-export-head">
                        <div class="scoresheet-export-brand">
                            <div class="scoresheet-export-logo-slot">
                                <?php if ($scoresheetLeftLogo !== ''): ?><img src="<?php echo e($scoresheetLeftLogo); ?>" alt="Left Logo"><?php else: ?><span class="scoresheet-export-logo-placeholder" aria-hidden="true"></span><?php endif; ?>
                            </div>
                            <div class="scoresheet-export-brand-center">
                                <div class="scoresheet-export-title">EXAMINATION SCORE ENTRY SHEET</div>
                            </div>
                            <div class="scoresheet-export-logo-slot">
                                <?php if ($scoresheetRightLogo !== ''): ?><img src="<?php echo e($scoresheetRightLogo); ?>" alt="Right Logo"><?php else: ?><span class="scoresheet-export-logo-placeholder" aria-hidden="true"></span><?php endif; ?>
                            </div>
                        </div>
                        <div class="row small mt-2">
                            <div class="col-md-6">
                                <div><b>Course Code:</b> <?php echo htmlspecialchars($courseMeta['course_code'] ?? ''); ?></div>
                                <div><b>Course Title:</b> <?php echo htmlspecialchars($courseMeta['course_title'] ?? ''); ?></div>
                                <div><b>Credit Unit:</b> <?php echo htmlspecialchars($courseMeta['credit_unit'] ?? ''); ?></div>
                            </div>
                            <div class="col-md-6">
                                <div><b>Academic Level:</b> <?php echo htmlspecialchars($level !== '' ? $level : ($courseMeta['level'] ?? '')); ?></div>
                                <div><b>Semester:</b> <?php echo htmlspecialchars($semester); ?></div>
                                <div><b>Session:</b> <?php echo htmlspecialchars($session); ?></div>
                            </div>
                        </div>
                    </div>

<div class="d-flex flex-wrap justify-content-between align-items-center scoresheet-export-meta">
                        <div class="small text-muted">
                            Course: <b><?php echo htmlspecialchars(($courseMeta['course_code'] ?? '').' - '.($courseMeta['course_title'] ?? '')); ?></b>
                            | Session: <b><?php echo htmlspecialchars($session); ?></b>
                            | Semester: <b><?php echo htmlspecialchars($semester); ?></b>
                        </div>
                        <div class="small text-muted">
                            Students: <b><?php echo (int)count($rows); ?></b> (alphabetical)
                        </div>
                    </div>

                    <div class="table-responsive scoresheet-export-wrap">
                        <table id="scoresheetTable" class="table table-bordered table-sm align-middle mb-0">
                            <thead class="table-light">
                                <tr>
                                    <th class="col-sn">S/N</th>
                                    <th class="col-student">Student Name</th>
                                    <th class="col-matric">Matric Number</th>
                                    <th class="col-score">CA Score</th>
                                    <th class="col-score">Exam Score</th>
                                    <th class="col-score">Total Score</th>
                                    <th class="col-grade">Grade</th>
                                    <th class="col-remarks">Remarks</th>
                                    <th class="col-status">Status</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php $sn=1; foreach ($rows as $r): ?>
                                    <tr>
                                        <td class="col-sn text-center"><?php echo $sn++; ?></td>
                                        <td class="student-name-cell"><?php echo htmlspecialchars((string)($r['full_name'] ?? '')); ?></td>
                                        <td class="matric-cell"><?php echo htmlspecialchars((string)($r['matric_no'] ?? '')); ?></td>
                                        <td class="col-score"><?php echo htmlspecialchars((string)($r['ca_score'] ?? '')); ?></td>
                                        <td class="col-score"><?php echo htmlspecialchars((string)($r['exam_score'] ?? '')); ?></td>
                                        <td class="col-score"><?php echo htmlspecialchars((string)($r['total_score'] ?? '')); ?></td>
                                        <td class="col-grade"><?php echo htmlspecialchars((string)($r['grade'] ?? '')); ?></td>
                                        <td class="remark-cell"><?php $resultMeta = portalResultAssessment($pdo, (float)($r['ca_score'] ?? 0), (float)($r['exam_score'] ?? 0), (float)($r['total_score'] ?? 0)); echo e((string)($resultMeta['remark'] ?? 'FAIL')); ?></td>
                                        <td class="status-cell">
                                            <?php
                                                $st = (string)($r['status'] ?? 'draft');
                                                $badge = ($st==='published')?'success':(($st==='submitted')?'warning':'secondary');
                                            ?>
                                            <span class="badge bg-<?php echo $badge; ?>"><?php echo htmlspecialchars($st); ?></span>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>

                    <div class="alert alert-secondary small mb-0">
                        Use the table controls to <b>sort</b>, <b>search</b>, and <b>export</b> (CSV/PDF/Print) using the buttons.
                    </div>
                <?php endif; ?>
            </div>

            <div class="tab-pane fade" id="tabInput" role="tabpanel">
                <?php if (!$courseId || $session==='' || $semester===''): ?>
                    <div class="alert alert-info mb-0">Load a course first, then you can input results for only the students who registered.</div>
                <?php else: ?>
                    <form method="post">
                        <input type="hidden" name="action" value="save_scoresheet_results">
                        <input type="hidden" name="course_id" value="<?php echo (int)$courseId; ?>">
                        <input type="hidden" name="session" value="<?php echo htmlspecialchars($session); ?>">
                        <input type="hidden" name="semester" value="<?php echo htmlspecialchars($semester); ?>">

                        <div class="d-flex align-items-center justify-content-between mb-2">
                            <div class="small text-muted">
                                Enter <b><?php echo e($caLabel); ?> (0–<?php echo e((string)$caMaxScore); ?>)</b> and <b><?php echo e($examLabel); ?> (0–<?php echo e((string)$examMaxScore); ?>)</b>. Minimum <?php echo e($caLabel); ?> required to pass: <b><?php echo e(rtrim(rtrim(number_format((float)$minimumCaScore, 2, '.', ''), '0'), '.')); ?></b>. Saved as <b>draft</b>. Publishing is handled in Result Approvals.
                            </div>
                            <button class="btn btn-primary btn-sm" type="submit">
                                <i class="bi bi-save"></i> Save Draft Scores
                            </button>
                        </div>

                        <div class="table-responsive">
                            <table class="table table-bordered table-sm align-middle">
                                <thead class="table-light">
                                    <tr>
                                        <th style="width:60px">S/N</th>
                                        <th style="min-width:220px;">Student Name</th>
                                        <th style="min-width:150px;">Matric Number</th>
                                        <th style="width:120px"><?php echo e($caLabel); ?> Score</th>
                                        <th style="width:120px"><?php echo e($examLabel); ?> Score</th>
                                        <th style="width:120px">Current Total</th>
                                        <th style="width:120px">Current Grade</th>
                                        <th style="width:110px">Remarks</th>
                                        <th style="width:110px">Status</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <?php $sn=1; foreach ($rows as $r): $sid=(int)($r['student_id']??0); ?>
                                        <tr>
                                            <td><?php echo $sn++; ?></td>
                                            <td class="fw-semibold"><?php echo htmlspecialchars((string)($r['full_name'] ?? '')); ?></td>
                                            <td class="text-nowrap"><?php echo htmlspecialchars((string)($r['matric_no'] ?? '')); ?></td>
                                            <td>
                                                <input class="form-control form-control-sm" type="number" step="0.01" min="0" max="<?php echo e((string)$caMaxScore); ?>"
                                                       name="ca[<?php echo $sid; ?>]" value="<?php echo htmlspecialchars((string)($r['ca_score'] ?? '')); ?>">
                                            </td>
                                            <td>
                                                <input class="form-control form-control-sm" type="number" step="0.01" min="0" max="<?php echo e((string)$examMaxScore); ?>"
                                                       name="exam[<?php echo $sid; ?>]" value="<?php echo htmlspecialchars((string)($r['exam_score'] ?? '')); ?>">
                                            </td>
                                            <td><?php echo htmlspecialchars((string)($r['total_score'] ?? '')); ?></td>
                                            <td><?php echo htmlspecialchars((string)($r['grade'] ?? '')); ?></td>
                                            <td><?php $resultMeta = portalResultAssessment($pdo, (float)($r['ca_score'] ?? 0), (float)($r['exam_score'] ?? 0), (float)($r['total_score'] ?? 0)); echo e((string)($resultMeta['remark'] ?? 'FAIL')); ?></td>
                                            <td>
                                                <?php
                                                    $st = (string)($r['status'] ?? 'draft');
                                                    $badge = ($st==='published')?'success':(($st==='submitted')?'warning':'secondary');
                                                ?>
                                                <span class="badge bg-<?php echo $badge; ?>"><?php echo htmlspecialchars($st); ?></span>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                </tbody>
                            </table>
                        </div>

                        <div class="small text-muted">
                            Note: If this course/session/semester is already <b>published</b>, saving will be blocked automatically.
                        </div>
                    </form>
                <?php endif; ?>
            </div>

            <div class="tab-pane fade" id="tabEmptyScoresheet" role="tabpanel">
                <?php if (!$courseId || $session==='' || $semester===''): ?>
                    <div class="alert alert-info mb-0">Select Faculty/Department/Session/Semester and a Course, then click <b>Load</b> to prepare the empty score entry sheet template.</div>
                <?php else: ?>
                    <div class="scoresheet-export-head empty-scoresheet-head">
                        <div class="scoresheet-export-brand">
                            <div class="scoresheet-export-logo-slot">
                                <?php if ($scoresheetLeftLogo !== ''): ?><img src="<?php echo e($scoresheetLeftLogo); ?>" alt="Left Logo"><?php else: ?><span class="scoresheet-export-logo-placeholder" aria-hidden="true"></span><?php endif; ?>
                            </div>
                            <div class="scoresheet-export-brand-center">
                                <div class="scoresheet-export-title">EXAMINATION SCORE ENTRY SHEET</div>
                                <div class="text-center fw-semibold"><?php echo htmlspecialchars($scoresheetInstitutionLine); ?></div>
                            </div>
                            <div class="scoresheet-export-logo-slot">
                                <?php if ($scoresheetRightLogo !== ''): ?><img src="<?php echo e($scoresheetRightLogo); ?>" alt="Right Logo"><?php else: ?><span class="scoresheet-export-logo-placeholder" aria-hidden="true"></span><?php endif; ?>
                            </div>
                        </div>
                        <div class="text-center small text-muted mt-1">Blank score entry template generated from the current registered-student context.</div>
                        <div class="row small mt-3 g-2">
                            <div class="col-md-6">
                                <div><b>Course Code:</b> <?php echo htmlspecialchars($contextCourseCode); ?></div>
                                <div><b>Course Title:</b> <?php echo htmlspecialchars($contextCourseTitle); ?></div>
                                <div><b>Credit Unit:</b> <?php echo htmlspecialchars($contextCreditUnit); ?></div>
                                <div><b>Course of Study:</b> <?php echo htmlspecialchars($contextCourseOfStudy); ?></div>
                            </div>
                            <div class="col-md-6">
                                <div><b>School:</b> <?php echo htmlspecialchars($contextSchoolName !== '' ? $contextSchoolName : '—'); ?></div>
                                <div><b>Department:</b> <?php echo htmlspecialchars($contextDepartmentName !== '' ? $contextDepartmentName : '—'); ?></div>
                                <div><b>Programme:</b> <?php echo htmlspecialchars($contextProgrammeName !== '' ? $contextProgrammeName : '—'); ?></div>
                                <div><b>Academic Level:</b> <?php echo htmlspecialchars($contextAcademicLevel !== '' ? $contextAcademicLevel : '—'); ?> &nbsp; <b>Semester:</b> <?php echo htmlspecialchars($semester); ?> &nbsp; <b>Session:</b> <?php echo htmlspecialchars($session); ?></div>
                            </div>
                        </div>
                    </div>

                    <div class="empty-scoresheet-actions d-flex flex-wrap justify-content-between align-items-center gap-2">
                        <div>
                            <div class="fw-semibold">Download Empty Scoresheet</div>
                            <div class="small text-muted">Uses the same registered-student synchronization already powering the current Scoresheet tab.</div>
                        </div>
                        <div class="d-flex flex-wrap gap-2">
                            <button type="button" class="btn btn-outline-danger js-empty-scoresheet-download" data-format="pdf"><i class="bi bi-filetype-pdf"></i> Download PDF</button>
                            <button type="button" class="btn btn-outline-secondary js-empty-scoresheet-download" data-format="csv"><i class="bi bi-filetype-csv"></i> Download CSV</button>
                            <button type="button" class="btn btn-outline-success js-empty-scoresheet-download" data-format="xlsx"><i class="bi bi-file-earmark-excel"></i> Download Excel</button>
                        </div>
                    </div>

                    <?php if (!$rows): ?>
                        <div class="alert alert-warning empty-scoresheet-preview-note">No registered students were found for the selected course context. You can still download the empty template with the course information and blank result columns.</div>
                    <?php else: ?>
                        <div class="small text-muted empty-scoresheet-preview-note">Students included: <b><?php echo (int)count($rows); ?></b> (alphabetical). Score columns are intentionally blank and ready for entry.</div>
                    <?php endif; ?>

                    <div class="table-responsive">
                        <table id="emptyScoresheetPreviewTable" class="table table-bordered table-sm align-middle mb-0">
                            <thead class="table-light">
                                <tr>
                                    <th class="col-sn">S/N</th>
                                    <th class="col-matric">Matric Number</th>
                                    <th class="col-student">Student Name</th>
                                    <th class="col-score">CA Score</th>
                                    <th class="col-score">Exam Score</th>
                                    <th class="col-score">Total Score</th>
                                    <th class="col-grade">Grade</th>
                                    <th class="col-remarks">Remarks</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php if ($emptyScoresheetRows): ?>
                                    <?php foreach ($emptyScoresheetRows as $entry): ?>
                                        <tr>
                                            <td><?php echo (int)($entry['sn'] ?? 0); ?></td>
                                            <td class="text-nowrap"><?php echo htmlspecialchars((string)($entry['matric_no'] ?? '')); ?></td>
                                            <td class="fw-semibold"><?php echo htmlspecialchars((string)($entry['student_name'] ?? '')); ?></td>
                                            <td></td>
                                            <td></td>
                                            <td></td>
                                            <td></td>
                                            <td></td>
                                        </tr>
                                    <?php endforeach; ?>
                                <?php else: ?>
                                    <tr>
                                        <td colspan="8" class="text-center text-muted py-4">No registered students found for the selected context. Download buttons remain available for a blank template.</td>
                                    </tr>
                                <?php endif; ?>
                            </tbody>
                        </table>
                    </div>

                    <div class="row g-3 empty-scoresheet-signatures">
                        <div class="col-md-6">
                            <div class="signature-line">LECTURER NAME &amp; SIGNATURE</div>
                            <div class="signature-date-line"><span>DATE:</span><span class="date-fill"></span></div>
                        </div>
                        <div class="col-md-6">
                            <div class="signature-line">MODERATOR NAME &amp; SIGNATURE</div>
                            <div class="signature-date-line"><span>DATE:</span><span class="date-fill"></span></div>
                        </div>
                    </div>
                <?php endif; ?>
            </div>
        </div>
    </div>

    <!-- DataTables + Buttons (for sorting/exporting/downloading CSV/PDF) -->
    <link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/jquery.dataTables.min.css">
    <link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.2/css/buttons.dataTables.min.css">
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script>
    <script src="https://cdn.datatables.net/buttons/2.4.2/js/dataTables.buttons.min.js"></script>
    <script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.html5.min.js"></script>
    <script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.print.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
    <script>
        (function(){
            var filledScoresheetFileBase = <?php echo json_encode($filledScoresheetFileBase, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
            var emptyScoresheetFileBase = <?php echo json_encode($emptyScoresheetFileBase, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
            var hasScoreTable = !!document.getElementById('scoresheetTable');
            if (window.jQuery && hasScoreTable) {
                try{
                    var exportColumns = [0,1,2,3,4,5,6,7,8];
                    $('#scoresheetTable').DataTable({
                        pageLength: 50,
                        dom: 'Bfrtip',
                        autoWidth: false,
                        columnDefs: [
                            { targets: 0, width: '60px', className: 'text-center' },
                            { targets: 1, width: '22%' },
                            { targets: 2, width: '16%', className: 'text-nowrap' },
                            { targets: [3,4,5,6], className: 'text-center text-nowrap' },
                            { targets: 7, width: '14%' },
                            { targets: 8, width: '10%', className: 'text-center text-nowrap' }
                        ],
                        buttons: [
                            { extend: 'copy', className: 'btn btn-sm btn-outline-secondary', exportOptions: { columns: exportColumns }, title: filledScoresheetFileBase },
                            { extend: 'csv', className: 'btn btn-sm btn-outline-secondary', exportOptions: { columns: exportColumns }, filename: filledScoresheetFileBase, title: filledScoresheetFileBase },
                            { extend: 'excel', className: 'btn btn-sm btn-outline-secondary', exportOptions: { columns: exportColumns }, filename: filledScoresheetFileBase, title: filledScoresheetFileBase, sheetName: 'Scoresheet' },
                            {
                                extend: 'pdf',
                                className: 'btn btn-sm btn-outline-secondary',
                                exportOptions: { columns: exportColumns },
                                filename: filledScoresheetFileBase,
                                title: filledScoresheetFileBase,
                                orientation: 'landscape',
                                pageSize: 'A4',
                                customize: function(doc){
                                    doc.styles.tableHeader.alignment = 'left';
                                    doc.styles.tableHeader.fontSize = 10;
                                    doc.defaultStyle.fontSize = 9;
                                    doc.pageMargins = [18, 24, 18, 24];
                                    if (doc.content && doc.content[1] && doc.content[1].table) {
                                        doc.content[1].table.widths = ['5%','22%','16%','10%','10%','10%','7%','12%','8%'];
                                    }
                                }
                            },
                            {
                                extend: 'print',
                                className: 'btn btn-sm btn-outline-secondary',
                                exportOptions: { columns: exportColumns },
                                title: filledScoresheetFileBase,
                                customize: function(win){
                                    var css = '\
                                        body{font-size:13px;padding:18px;}\
                                        h1,h2,h3,h4,h5{margin-bottom:10px !important;}\
                                        table{width:100% !important;border-collapse:collapse !important;table-layout:fixed !important;}\
                                        th,td{border:1px solid #9aa4b2 !important;padding:8px 10px !important;vertical-align:top !important;}\
                                        th{text-align:left !important;background:#eef2f7 !important;color:#111 !important;}\
                                        th:nth-child(1),td:nth-child(1){width:5%;text-align:center !important;}\
                                        th:nth-child(2),td:nth-child(2){width:22%;}\
                                        th:nth-child(3),td:nth-child(3){width:16%;white-space:nowrap !important;}\
                                        th:nth-child(4),td:nth-child(4),th:nth-child(5),td:nth-child(5),th:nth-child(6),td:nth-child(6),th:nth-child(7),td:nth-child(7){width:10%;text-align:center !important;white-space:nowrap !important;}\
                                        th:nth-child(8),td:nth-child(8){width:12%;}\
                                        th:nth-child(9),td:nth-child(9){width:8%;text-align:center !important;white-space:nowrap !important;}\
                                        .badge{display:inline-block;border:1px solid #666 !important;background:#fff !important;color:#111 !important;padding:2px 8px !important;}';
                                    var style = win.document.createElement('style');
                                    style.type = 'text/css';
                                    style.appendChild(win.document.createTextNode(css));
                                    win.document.head.appendChild(style);
                                }
                            }
                        ]
                    });
                }catch(e){}
            }

            var emptyScoresheetContext = <?php echo json_encode([
                'institution_name' => $scoresheetInstitutionName,
                'institution_line' => $scoresheetInstitutionLine,
                'course_code' => $contextCourseCode,
                'course_title' => $contextCourseTitle,
                'credit_unit' => $contextCreditUnit,
                'school' => $contextSchoolName,
                'department' => $contextDepartmentName,
                'programme' => $contextProgrammeName,
                'course_of_study' => $contextCourseOfStudy,
                'academic_level' => $contextAcademicLevel,
                'semester' => $semester,
                'session' => $session,
                'left_logo' => $scoresheetLeftLogo,
                'right_logo' => $scoresheetRightLogo,
                'rows' => $emptyScoresheetRows,
            ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;

            function emptyScoresheetHasContext(ctx){
                return !!(ctx && ctx.course_code && ctx.session && ctx.semester);
            }

            function xmlEscape(value){
                return String(value == null ? '' : value)
                    .replace(/&/g, '&amp;')
                    .replace(/</g, '&lt;')
                    .replace(/>/g, '&gt;')
                    .replace(/"/g, '&quot;')
                    .replace(/'/g, '&apos;');
            }

            function csvEscape(value){
                var str = String(value == null ? '' : value);
                if (/[",\n]/.test(str)) return '"' + str.replace(/"/g, '""') + '"';
                return str;
            }

            function sanitizeFileName(value){
                return String(value == null ? '' : value)
                    .replace(/\s+/g, '_')
                    .replace(/[^A-Za-z0-9._-]+/g, '_')
                    .replace(/_+/g, '_')
                    .replace(/^_+|_+$/g, '') || 'Empty_Scoresheet';
            }

            function buildEmptyScoresheetFileBase(ctx){
                if (emptyScoresheetFileBase) return sanitizeFileName(emptyScoresheetFileBase);
                return sanitizeFileName([
                    ctx.course_title || 'Course',
                    ctx.course_code || 'CourseCode',
                    ctx.academic_level || 'Level',
                    ctx.department || ctx.programme || 'Department',
                    ctx.semester || 'Semester',
                    ctx.session || 'Session',
                    'Empty_Scoresheet'
                ].join('_'));
            }

            function downloadBlob(blob, filename){
                var link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                link.download = filename;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                setTimeout(function(){ URL.revokeObjectURL(link.href); }, 1000);
            }

            var emptyScoresheetLogoCache = {};

            function imageBlobToDataUrl(blob){
                return new Promise(function(resolve){
                    try {
                        var reader = new FileReader();
                        reader.onload = function(){ resolve(String(reader.result || '')); };
                        reader.onerror = function(){ resolve(''); };
                        reader.readAsDataURL(blob);
                    } catch (err) {
                        resolve('');
                    }
                });
            }

            function resolveEmptyScoresheetLogo(url){
                if (!url) return Promise.resolve('');
                if (/^data:image\//i.test(url)) return Promise.resolve(String(url));
                if (emptyScoresheetLogoCache[url]) return Promise.resolve(emptyScoresheetLogoCache[url]);
                return fetch(url, { credentials: 'same-origin' })
                    .then(function(response){ return response && response.ok ? response.blob() : Promise.reject(new Error('logo')); })
                    .then(function(blob){ return imageBlobToDataUrl(blob); })
                    .then(function(dataUrl){ emptyScoresheetLogoCache[url] = String(dataUrl || ''); return emptyScoresheetLogoCache[url]; })
                    .catch(function(){ return ''; });
            }

            function buildEmptyScoresheetLogoCell(dataUrl, alignment){
                if (dataUrl) {
                    return { image: dataUrl, fit: [62, 62], alignment: alignment || 'center', margin: [0, 0, 0, 0], border: [false, false, false, false] };
                }
                return { text: '', border: [false, false, false, false] };
            }

            function buildPdfTableBody(ctx){
                var body = [[
                    { text: 'S/N', style: 'tableHeader' },
                    { text: 'Matric Number', style: 'tableHeader' },
                    { text: 'Student Name', style: 'tableHeader' },
                    { text: 'CA Score', style: 'tableHeader' },
                    { text: 'Exam Score', style: 'tableHeader' },
                    { text: 'Total Score', style: 'tableHeader' },
                    { text: 'Grade', style: 'tableHeader' },
                    { text: 'Remarks', style: 'tableHeader' }
                ]];
                var rows = Array.isArray(ctx.rows) ? ctx.rows : [];
                if (!rows.length) {
                    body.push([
                        { text: '', alignment: 'center' },
                        { text: '', alignment: 'center' },
                        { text: 'No registered students found for the selected context.', colSpan: 6, italics: true, color: '#6b7280' },
                        {},{},{},{},{}
                    ]);
                    return body;
                }
                rows.forEach(function(row){
                    body.push([
                        { text: String(row.sn || ''), alignment: 'center' },
                        { text: String(row.matric_no || ''), noWrap: true },
                        { text: String(row.student_name || '') },
                        { text: '' },
                        { text: '' },
                        { text: '' },
                        { text: '' },
                        { text: '' }
                    ]);
                });
                return body;
            }

            function downloadEmptyScoresheetPdf(ctx){
                if (!window.pdfMake) { alert('PDF generator is not available on this page.'); return; }
                Promise.all([
                    resolveEmptyScoresheetLogo(ctx.left_logo || ''),
                    resolveEmptyScoresheetLogo(ctx.right_logo || ctx.left_logo || '')
                ]).then(function(logoData){
                    var docDefinition = {
                        pageSize: 'A4',
                        pageOrientation: 'portrait',
                        pageMargins: [22, 24, 22, 28],
                        defaultStyle: { fontSize: 9.2, color: '#111827' },
                        styles: {
                            title: { fontSize: 16, bold: true, alignment: 'center', margin: [0, 0, 0, 4] },
                            institution: { fontSize: 11.5, bold: true, alignment: 'center', margin: [0, 0, 0, 2] },
                            subhead: { fontSize: 9.5, alignment: 'center', margin: [0, 0, 0, 10] },
                            tableHeader: { bold: true, fillColor: '#eef2f7', fontSize: 9.2 }
                        },
                        content: [
                            {
                                table: {
                                    widths: [72, '*', 72],
                                    body: [[
                                        buildEmptyScoresheetLogoCell(logoData[0] || '', 'left'),
                                        {
                                            stack: [
                                                { text: 'EXAMINATION SCORE ENTRY SHEET', style: 'title' },
                                                { text: String(ctx.institution_line || ctx.institution_name || ''), style: 'institution' },
                                                { text: 'Course of Study: ' + String(ctx.course_of_study || ctx.programme || ctx.department || '—'), style: 'subhead' }
                                            ],
                                            border: [false, false, false, false]
                                        },
                                        buildEmptyScoresheetLogoCell(logoData[1] || '', 'right')
                                    ]]
                                },
                                layout: 'noBorders',
                                margin: [0, 0, 0, 6]
                            },
                            {
                                table: {
                                    widths: ['33%', '17%', '16%', '34%'],
                                    body: [
                                        [
                                            { text: 'Course Code:\n' + String(ctx.course_code || ''), margin: [0, 2, 0, 2] },
                                            { text: 'Academic Level:\n' + String(ctx.academic_level || ''), margin: [0, 2, 0, 2] },
                                            { text: 'Semester:\n' + String(ctx.semester || ''), margin: [0, 2, 0, 2] },
                                            { text: 'Session:\n' + String(ctx.session || ''), margin: [0, 2, 0, 2] }
                                        ],
                                        [
                                            { text: 'Course Title:\n' + String(ctx.course_title || ''), colSpan: 2, margin: [0, 2, 0, 2] }, {},
                                            { text: 'Credit Unit:\n' + String(ctx.credit_unit || ''), margin: [0, 2, 0, 2] },
                                            { text: 'School / Department / Programme:\n' + [ctx.school || '—', ctx.department || '—', ctx.programme || '—'].join(' | '), margin: [0, 2, 0, 2] }
                                        ]
                                    ]
                                },
                                layout: 'lightHorizontalLines',
                                margin: [0, 0, 0, 10]
                            },
                            {
                                table: {
                                    headerRows: 1,
                                    widths: [24, 84, '*', 52, 60, 58, 42, 58],
                                    body: buildPdfTableBody(ctx)
                                },
                                layout: {
                                    fillColor: function(rowIndex){ return rowIndex === 0 ? '#eef2f7' : null; }
                                }
                            },
                            {
                                columns: [
                                    {
                                        width: '*',
                                        stack: [
                                            { text: 'LECTURER NAME & SIGNATURE', bold: true, alignment: 'center', margin: [0, 18, 12, 0], decoration: 'underline' },
                                            { text: 'DATE: __________________', bold: true, alignment: 'center', margin: [0, 10, 12, 0] }
                                        ]
                                    },
                                    {
                                        width: '*',
                                        stack: [
                                            { text: 'MODERATOR NAME & SIGNATURE', bold: true, alignment: 'center', margin: [12, 18, 0, 0], decoration: 'underline' },
                                            { text: 'DATE: __________________', bold: true, alignment: 'center', margin: [12, 10, 0, 0] }
                                        ]
                                    }
                                ],
                                columnGap: 16
                            }
                        ]
                    };
                    pdfMake.createPdf(docDefinition).download(buildEmptyScoresheetFileBase(ctx) + '.pdf');
                }).catch(function(){
                    alert('Unable to prepare the scoresheet logos for PDF export right now.');
                });
            }

            function downloadEmptyScoresheetCsv(ctx){
                var lines = [];
                lines.push([ 'EXAMINATION SCORE ENTRY SHEET' ]);
                lines.push([ 'Institution', ctx.institution_name || '' ]);
                lines.push([ 'Course Code', ctx.course_code || '', 'Course Title', ctx.course_title || '' ]);
                lines.push([ 'Academic Level', ctx.academic_level || '', 'Semester', ctx.semester || '', 'Session', ctx.session || '' ]);
                lines.push([ 'Credit Unit', ctx.credit_unit || '', 'School', ctx.school || '', 'Department', ctx.department || '', 'Programme', ctx.programme || '' ]);
                lines.push([]);
                lines.push([ 'S/N', 'Matric Number', 'Student Name', 'CA Score', 'Exam Score', 'Total Score', 'Grade', 'Remarks' ]);
                (Array.isArray(ctx.rows) ? ctx.rows : []).forEach(function(row){
                    lines.push([ row.sn || '', row.matric_no || '', row.student_name || '', '', '', '', '', '' ]);
                });
                var csv = lines.map(function(row){ return row.map(csvEscape).join(','); }).join('\r\n');
                downloadBlob(new Blob([csv], { type: 'text/csv;charset=utf-8;' }), buildEmptyScoresheetFileBase(ctx) + '.csv');
            }

            function sheetCell(ref, value){
                if (value === null || typeof value === 'undefined') return '';
                var stringValue = String(value);
                if (stringValue === '') return '<c r="' + ref + '" t="inlineStr"><is><t></t></is></c>';
                if (/^-?\d+(?:\.\d+)?$/.test(stringValue) && !/^0\d+/.test(stringValue)) {
                    return '<c r="' + ref + '"><v>' + stringValue + '</v></c>';
                }
                return '<c r="' + ref + '" t="inlineStr"><is><t xml:space="preserve">' + xmlEscape(stringValue) + '</t></is></c>';
            }

            function buildSheetXml(rows){
                var xmlRows = [];
                rows.forEach(function(cols, rowIndex){
                    var cells = [];
                    for (var i = 0; i < cols.length; i++) {
                        var ref = String.fromCharCode(65 + i) + (rowIndex + 1);
                        cells.push(sheetCell(ref, cols[i]));
                    }
                    xmlRows.push('<row r="' + (rowIndex + 1) + '">' + cells.join('') + '</row>');
                });
                return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
                    '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' +
                    '<sheetViews><sheetView workbookViewId="0"/></sheetViews>' +
                    '<sheetFormatPr defaultRowHeight="15"/>' +
                    '<cols><col min="1" max="1" width="8" customWidth="1"/><col min="2" max="2" width="20" customWidth="1"/><col min="3" max="3" width="34" customWidth="1"/><col min="4" max="8" width="15" customWidth="1"/></cols>' +
                    '<sheetData>' + xmlRows.join('') + '</sheetData>' +
                    '</worksheet>';
            }

            function downloadEmptyScoresheetXlsx(ctx){
                if (!window.JSZip) { alert('Excel generator is not available on this page.'); return; }
                var workbookRows = [
                    ['EXAMINATION SCORE ENTRY SHEET'],
                    [ctx.institution_name || ''],
                    ['Course of Study', ctx.course_of_study || ctx.programme || ctx.department || ''],
                    ['Course Code', ctx.course_code || '', 'Course Title', ctx.course_title || ''],
                    ['Academic Level', ctx.academic_level || '', 'Semester', ctx.semester || '', 'Session', ctx.session || ''],
                    ['Credit Unit', ctx.credit_unit || '', 'School', ctx.school || '', 'Department', ctx.department || '', 'Programme', ctx.programme || ''],
                    [],
                    ['S/N', 'Matric Number', 'Student Name', 'CA Score', 'Exam Score', 'Total Score', 'Grade', 'Remarks']
                ];
                (Array.isArray(ctx.rows) ? ctx.rows : []).forEach(function(row){
                    workbookRows.push([row.sn || '', row.matric_no || '', row.student_name || '', '', '', '', '', '']);
                });
                if (!ctx.rows || !ctx.rows.length) {
                    workbookRows.push(['', '', 'No registered students found for the selected context.', '', '', '', '', '']);
                }
                workbookRows.push([]);
                workbookRows.push(['LECTURER NAME & SIGNATURE', 'DATE:', '', '', 'MODERATOR NAME & SIGNATURE', 'DATE:']);

                var zip = new JSZip();
                zip.file('[Content_Types].xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
                    '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
                    '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
                    '<Default Extension="xml" ContentType="application/xml"/>' +
                    '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>' +
                    '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>' +
                    '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>' +
                    '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>' +
                    '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>' +
                    '</Types>');
                zip.folder('_rels').file('.rels', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
                    '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
                    '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' +
                    '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>' +
                    '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>' +
                    '</Relationships>');
                zip.folder('docProps').file('app.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
                    '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">' +
                    '<Application>ChatGPT</Application><HeadingPairs><vt:vector size="2" baseType="variant"><vt:variant><vt:lpstr>Worksheets</vt:lpstr></vt:variant><vt:variant><vt:i4>1</vt:i4></vt:variant></vt:vector></HeadingPairs><TitlesOfParts><vt:vector size="1" baseType="lpstr"><vt:lpstr>Empty Scoresheet</vt:lpstr></vt:vector></TitlesOfParts></Properties>');
                zip.folder('docProps').file('core.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
                    '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
                    '<dc:title>' + xmlEscape(buildEmptyScoresheetFileBase(ctx)) + '</dc:title><dc:creator>' + xmlEscape(ctx.institution_name || 'Portal') + '</dc:creator></cp:coreProperties>');
                var xl = zip.folder('xl');
                xl.file('workbook.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
                    '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' +
                    '<sheets><sheet name="Empty Scoresheet" sheetId="1" r:id="rId1"/></sheets></workbook>');
                xl.folder('_rels').file('workbook.xml.rels', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
                    '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
                    '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>' +
                    '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>' +
                    '</Relationships>');
                xl.file('styles.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
                    '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts><fills count="1"><fill><patternFill patternType="none"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs></styleSheet>');
                xl.folder('worksheets').file('sheet1.xml', buildSheetXml(workbookRows));
                zip.generateAsync({type:'blob'}).then(function(blob){
                    downloadBlob(blob, buildEmptyScoresheetFileBase(ctx) + '.xlsx');
                });
            }

            document.querySelectorAll('.js-empty-scoresheet-download').forEach(function(btn){
                btn.addEventListener('click', function(){
                    if (!emptyScoresheetHasContext(emptyScoresheetContext)) {
                        alert('Select Faculty/Department/Session/Semester and a Course, then click Load before downloading the empty scoresheet.');
                        return;
                    }
                    var format = btn.getAttribute('data-format');
                    if (format === 'pdf') downloadEmptyScoresheetPdf(emptyScoresheetContext);
                    else if (format === 'csv') downloadEmptyScoresheetCsv(emptyScoresheetContext);
                    else if (format === 'xlsx') downloadEmptyScoresheetXlsx(emptyScoresheetContext);
                });
            });
        })();
    </script>
    <?php
    break;



case 'admin_student_attendance':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'attendance_manage');
    try { ensureStudentAttendanceModule($pdo); } catch (Throwable $e) {}

    $tab = trim((string)($_GET['tab'] ?? 'take'));
    if (!in_array($tab, ['take','view','edit','reports','settings'], true)) $tab = 'take';

    $currentSession = trim((string)portalCurrentAcademicSessionName($pdo));
    $sessionName = trim((string)($_REQUEST['session_name'] ?? $currentSession));
    $semester = trim((string)($_REQUEST['semester'] ?? 'First'));
    if (!in_array($semester, ['First','Second'], true)) $semester = 'First';
    $facultyId = (int)($_REQUEST['faculty_id'] ?? 0);
    $departmentId = (int)($_REQUEST['department_id'] ?? 0);
    $programmeId = (int)($_REQUEST['programme_id'] ?? 0);
    $level = trim((string)($_REQUEST['level'] ?? ''));
    $courseId = (int)($_REQUEST['course_id'] ?? 0);
    $attendanceDate = trim((string)($_REQUEST['attendance_date'] ?? date('Y-m-d')));
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $attendanceDate)) $attendanceDate = date('Y-m-d');
    $studentIdFilter = (int)($_REQUEST['student_id'] ?? 0);
    $statusFilter = trim((string)($_REQUEST['status'] ?? ''));
    $dateFrom = trim((string)($_REQUEST['date_from'] ?? ''));
    $dateTo = trim((string)($_REQUEST['date_to'] ?? ''));
    $search = trim((string)($_REQUEST['search'] ?? ''));

    $currentAdminId = (int)($_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? 0);
    $currentRole = currentStaffRole($pdo);
    $scopeFacultyId = 0;
    $scopeDepartmentId = 0;
    try {
        $st = $pdo->prepare("SELECT faculty_id, department_id FROM admins WHERE id = ? LIMIT 1");
        $st->execute([$currentAdminId]);
        if ($r = $st->fetch(PDO::FETCH_ASSOC)) {
            $scopeFacultyId = (int)($r['faculty_id'] ?? 0);
            $scopeDepartmentId = (int)($r['department_id'] ?? 0);
        }
    } catch (Throwable $e) {}
    if (!isSuperadmin($pdo)) {
        if (in_array($currentRole, ['Dean'], true) && $scopeFacultyId > 0) $facultyId = $scopeFacultyId;
        if (in_array($currentRole, ['HOD'], true) && $scopeDepartmentId > 0) $departmentId = $scopeDepartmentId;
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $action = trim((string)($_POST['action'] ?? ''));

        if ($action === 'save_attendance_settings') {
            $pairs = [
                'attendance_default_status' => trim((string)($_POST['attendance_default_status'] ?? 'Present')),
                'attendance_allow_future_date' => isset($_POST['attendance_allow_future_date']) ? '1' : '0',
                'attendance_show_student_portal' => isset($_POST['attendance_show_student_portal']) ? '1' : '0',
            ];
            foreach ($pairs as $k => $v) {
                try {
                    $pdo->prepare("INSERT INTO student_attendance_settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)")->execute([$k, $v]);
                } catch (Throwable $e) {}
            }
            portalAuthLogActivity($pdo, 'admin', $currentAdminId, 'attendance_settings_saved', $pairs);
            flash('success', 'Attendance settings saved successfully.');
            redirect('?page=admin_student_attendance&tab=settings');
        }

        if ($action === 'delete_attendance') {
            $id = (int)($_POST['id'] ?? 0);
            if ($id > 0) {
                try {
                    $st = $pdo->prepare("SELECT * FROM student_attendance WHERE id=? LIMIT 1");
                    $st->execute([$id]);
                    $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                    $pdo->prepare("DELETE FROM student_attendance WHERE id=?")->execute([$id]);
                    portalAuthLogActivity($pdo, 'admin', $currentAdminId, 'attendance_deleted', ['attendance_id'=>$id, 'course_id'=>(int)($row['course_id'] ?? 0), 'student_id'=>(int)($row['student_id'] ?? 0)]);
                    flash('success', 'Attendance record deleted successfully.');
                } catch (Throwable $e) {
                    flash('danger', 'Unable to delete attendance record: ' . $e->getMessage());
                }
            }
            redirect('?page=admin_student_attendance&tab=view');
        }

        if ($action === 'update_attendance_record') {
            $id = (int)($_POST['id'] ?? 0);
            $status = trim((string)($_POST['status'] ?? 'Present'));
            if (!in_array($status, ['Present','Absent','Late','Excused'], true)) $status = 'Present';
            $remark = trim((string)($_POST['remark'] ?? ''));
            $venue = trim((string)($_POST['venue'] ?? ''));
            if ($id > 0) {
                try {
                    $pdo->prepare("UPDATE student_attendance SET status=?, remark=?, venue=?, recorded_by=?, updated_at=NOW() WHERE id=?")->execute([$status, $remark, $venue, $currentAdminId, $id]);
                    portalAuthLogActivity($pdo, 'admin', $currentAdminId, 'attendance_updated', ['attendance_id'=>$id, 'status'=>$status]);
                    flash('success', 'Attendance record updated successfully.');
                } catch (Throwable $e) {
                    flash('danger', 'Unable to update attendance record: ' . $e->getMessage());
                }
            }
            redirect('?page=admin_student_attendance&tab=edit');
        }

        if ($action === 'save_bulk_attendance') {
            $sessionName = trim((string)($_POST['session_name'] ?? $sessionName));
            $semester = trim((string)($_POST['semester'] ?? $semester));
            $facultyId = (int)($_POST['faculty_id'] ?? $facultyId);
            $departmentId = (int)($_POST['department_id'] ?? $departmentId);
            $programmeId = (int)($_POST['programme_id'] ?? $programmeId);
            $level = trim((string)($_POST['level'] ?? $level));
            $courseId = (int)($_POST['course_id'] ?? $courseId);
            $attendanceDate = trim((string)($_POST['attendance_date'] ?? $attendanceDate));
            $remarkMap = $_POST['remark'] ?? [];
            $statusMap = $_POST['status'] ?? [];
            $venue = trim((string)($_POST['venue'] ?? ''));
            $lecturerStaffId = (int)($_POST['lecturer_staff_id'] ?? $currentAdminId);
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $attendanceDate)) $attendanceDate = date('Y-m-d');
            if (studentAttendanceSetting($pdo, 'attendance_allow_future_date', '0') !== '1' && $attendanceDate > date('Y-m-d')) {
                flash('danger', 'Future attendance dates are not allowed.');
                redirect('?page=admin_student_attendance&tab=take');
            }
            if ($courseId <= 0 || $sessionName === '' || $level === '') {
                flash('danger', 'Session, semester, level, and course are required.');
                redirect('?page=admin_student_attendance&tab=take');
            }

            $studentsForSave = [];
            try {
                $sql = "SELECT s.id AS student_id, cr.id AS registration_id, cr.faculty_id, cr.department_id, cr.programme_id, cr.level
                        FROM course_registration_items cri
                        INNER JOIN course_registrations cr ON cr.id = cri.registration_id
                        INNER JOIN students s ON s.id = cr.student_id
                        WHERE cri.course_id = ?
                          AND COALESCE(cr.session_name,'') = ?
                          AND cr.semester = ?
                          AND COALESCE(cr.level,'') = ?
                          AND s.application_status = 'Active'";
                $params = [$courseId, $sessionName, $semester, $level];
                if ($facultyId > 0) { $sql .= " AND cr.faculty_id = ?"; $params[] = $facultyId; }
                if ($departmentId > 0) { $sql .= " AND cr.department_id = ?"; $params[] = $departmentId; }
                if ($programmeId > 0) { $sql .= " AND cr.programme_id = ?"; $params[] = $programmeId; }
                $sql .= " ORDER BY s.surname ASC, s.first_name ASC";
                $st = $pdo->prepare($sql);
                $st->execute($params);
                $studentsForSave = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) { $studentsForSave = []; }

            if (!$studentsForSave) {
                flash('danger', 'No registered students found for the selected filters.');
                redirect('?page=admin_student_attendance&tab=take');
            }

            try {
                $pdo->beginTransaction();
                $pdo->prepare("INSERT INTO student_attendance_classes (course_id, attendance_date, session_name, semester, faculty_id, department_id, programme_id, level, lecturer_staff_id, venue, created_by)
                    VALUES (?,?,?,?,?,?,?,?,?,?,?)
                    ON DUPLICATE KEY UPDATE faculty_id=VALUES(faculty_id), department_id=VALUES(department_id), programme_id=VALUES(programme_id), level=VALUES(level), lecturer_staff_id=VALUES(lecturer_staff_id), venue=VALUES(venue), created_by=VALUES(created_by)")
                    ->execute([$courseId, $attendanceDate, $sessionName, $semester, $facultyId ?: null, $departmentId ?: null, $programmeId ?: null, $level, $lecturerStaffId ?: null, $venue ?: null, $currentAdminId ?: null]);

                $saved = 0;
                foreach ($studentsForSave as $row) {
                    $sid = (int)$row['student_id'];
                    $status = trim((string)($statusMap[$sid] ?? studentAttendanceSetting($pdo, 'attendance_default_status', 'Present')));
                    if (!in_array($status, ['Present','Absent','Late','Excused'], true)) $status = 'Present';
                    $remark = trim((string)($remarkMap[$sid] ?? ''));
                    $pdo->prepare("INSERT INTO student_attendance (student_id, registration_id, course_id, attendance_date, session_name, semester, faculty_id, department_id, programme_id, level, status, remark, lecturer_staff_id, recorded_by, venue)
                        VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
                        ON DUPLICATE KEY UPDATE registration_id=VALUES(registration_id), faculty_id=VALUES(faculty_id), department_id=VALUES(department_id), programme_id=VALUES(programme_id), level=VALUES(level), status=VALUES(status), remark=VALUES(remark), lecturer_staff_id=VALUES(lecturer_staff_id), recorded_by=VALUES(recorded_by), venue=VALUES(venue)")
                        ->execute([$sid, (int)($row['registration_id'] ?? 0) ?: null, $courseId, $attendanceDate, $sessionName, $semester, (int)($row['faculty_id'] ?? 0) ?: null, (int)($row['department_id'] ?? 0) ?: null, (int)($row['programme_id'] ?? 0) ?: null, (string)($row['level'] ?? $level), $status, $remark, $lecturerStaffId ?: null, $currentAdminId ?: null, $venue ?: null]);
                    $saved++;
                }
                $pdo->commit();
                portalAuthLogActivity($pdo, 'admin', $currentAdminId, 'attendance_saved_bulk', ['course_id'=>$courseId, 'attendance_date'=>$attendanceDate, 'count'=>$saved, 'session_name'=>$sessionName, 'semester'=>$semester]);
                flash('success', 'Attendance saved successfully for ' . $saved . ' registered students.');
            } catch (Throwable $e) {
                if ($pdo->inTransaction()) $pdo->rollBack();
                flash('danger', 'Failed to save attendance: ' . $e->getMessage());
            }
            redirect('?page=admin_student_attendance&tab=take&session_name=' . urlencode($sessionName) . '&semester=' . urlencode($semester) . '&faculty_id=' . (int)$facultyId . '&department_id=' . (int)$departmentId . '&programme_id=' . (int)$programmeId . '&level=' . urlencode($level) . '&course_id=' . (int)$courseId . '&attendance_date=' . urlencode($attendanceDate));
        }
    }

    $faculties = []; $departments = []; $programmes = []; $courses = []; $students = []; $sessions = [];
    try { $faculties = $pdo->query("SELECT id, name FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $departments = $pdo->query("SELECT id, name, faculty_id FROM departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $programmes = $pdo->query("SELECT id, name, department_id FROM programmes ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $sessions = $pdo->query("SELECT name FROM academic_sessions ORDER BY name DESC")->fetchAll(PDO::FETCH_COLUMN) ?: []; } catch (Throwable $e) {}
    if (!$sessions && $currentSession !== '') $sessions = [$currentSession];

    try {
        $sql = "SELECT id, course_code, course_title, session_name, semester, level, faculty_id, department_id, programme_id
                FROM registrable_courses
                WHERE registrable='Yes'";
        $params = [];
        if ($sessionName !== '') { $sql .= " AND (COALESCE(session_name,'')='' OR session_name=?)"; $params[] = $sessionName; }
        if ($semester !== '') { $sql .= " AND (COALESCE(semester,'')='' OR semester=?)"; $params[] = $semester; }
        if ($level !== '') { $sql .= " AND (COALESCE(level,'')='' OR level=?)"; $params[] = $level; }
        if ($facultyId > 0) { $sql .= " AND (faculty_id IS NULL OR faculty_id=?)"; $params[] = $facultyId; }
        if ($departmentId > 0) { $sql .= " AND (department_id IS NULL OR department_id=?)"; $params[] = $departmentId; }
        if ($programmeId > 0) { $sql .= " AND (programme_id IS NULL OR programme_id=?)"; $params[] = $programmeId; }
        $sql .= " ORDER BY course_code ASC";
        $st = $pdo->prepare($sql);
        $st->execute($params);
        $courses = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) { $courses = []; }

    if ($courseId > 0 && $tab === 'take') {
        try {
            $sql = "SELECT s.id AS student_id, cr.id AS registration_id, s.matric_no, s.registration_no,
                        CONCAT(TRIM(COALESCE(s.surname,'')), CASE WHEN COALESCE(s.first_name,'')<>'' THEN CONCAT(', ', TRIM(s.first_name)) ELSE '' END, CASE WHEN COALESCE(s.other_name,'')<>'' THEN CONCAT(' ', TRIM(s.other_name)) ELSE '' END) AS student_name,
                        COALESCE(sa.status, 'Present') AS current_status,
                        COALESCE(sa.remark, '') AS current_remark
                    FROM course_registration_items cri
                    INNER JOIN course_registrations cr ON cr.id = cri.registration_id
                    INNER JOIN students s ON s.id = cr.student_id
                    LEFT JOIN student_attendance sa ON sa.student_id = s.id AND sa.course_id = cri.course_id AND sa.attendance_date = ? AND sa.session_name = ? AND sa.semester = ?
                    WHERE cri.course_id = ?
                      AND COALESCE(cr.session_name,'') = ?
                      AND cr.semester = ?
                      AND COALESCE(cr.level,'') = ?
                      AND s.application_status = 'Active'";
            $params = [$attendanceDate, $sessionName, $semester, $courseId, $sessionName, $semester, $level];
            if ($facultyId > 0) { $sql .= " AND cr.faculty_id = ?"; $params[] = $facultyId; }
            if ($departmentId > 0) { $sql .= " AND cr.department_id = ?"; $params[] = $departmentId; }
            if ($programmeId > 0) { $sql .= " AND cr.programme_id = ?"; $params[] = $programmeId; }
            $sql .= " ORDER BY s.surname ASC, s.first_name ASC";
            $st = $pdo->prepare($sql);
            $st->execute($params);
            $students = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) { $students = []; }
    }

    $joinedFilters = [
        'faculty_id' => $facultyId,
        'department_id' => $departmentId,
        'programme_id' => $programmeId,
        'level' => $level,
        'semester' => $semester,
        'session_name' => $sessionName,
        'course_id' => $courseId,
        'student_id' => $studentIdFilter,
        'date_from' => $dateFrom,
        'date_to' => $dateTo,
        'search' => $search,
        'status' => $statusFilter,
    ];
    $attendanceRows = in_array($tab, ['view','edit','reports'], true) ? studentAttendanceJoinedRows($pdo, $joinedFilters) : [];

    if (isset($_GET['export']) && in_array($tab, ['view','reports'], true)) {
        $export = trim((string)$_GET['export']);
        $headers = ['Date','Session','Semester','Faculty','Department','Programme','Level','Course Code','Course Title','Matric/Application No','Student','Status','Remark','Venue'];
        $rows = studentAttendanceExportRows($attendanceRows);
        if ($export === 'csv') hrmCsvDownload('student_attendance_export.csv', $headers, $rows);
        if ($export === 'excel') studentAttendanceExcelDownload('student_attendance_export.xls', $headers, $rows);
        if ($export === 'pdf') {
            ?>
            <html><head><title>Student Attendance Report</title><style>body{font-family:Arial,sans-serif;font-size:12px;}table{width:100%;border-collapse:collapse}th,td{border:1px solid #999;padding:6px;text-align:left}th{background:#f2f2f2}.no-print{margin:12px 0}</style></head><body>
            <div class="no-print"><button onclick="window.print()">Print / Save as PDF</button></div>
            <h3>Student Attendance Report</h3>
            <table><thead><tr><?php foreach ($headers as $h): ?><th><?php echo e($h); ?></th><?php endforeach; ?></tr></thead><tbody>
            <?php foreach ($rows as $r): ?><tr><?php foreach ($r as $c): ?><td><?php echo e((string)$c); ?></td><?php endforeach; ?></tr><?php endforeach; ?>
            </tbody></table></body></html>
            <?php
            exit;
        }
    }

    $summaryByStudent = [];
    $summaryByCourse = [];
    $summaryByDepartment = [];
    $summaryByFaculty = [];
    if ($tab === 'reports' && $attendanceRows) {
        foreach ($attendanceRows as $r) {
            $sid = (int)($r['student_id'] ?? 0);
            $cid = (int)($r['course_id'] ?? 0);
            $did = (int)($r['department_id'] ?? 0);
            $fid = (int)($r['faculty_id'] ?? 0);
            if (!isset($summaryByStudent[$sid])) {
                $summaryByStudent[$sid] = ['student_name'=>(string)($r['student_name'] ?? ''),'matric_no'=>(string)($r['matric_no'] ?? $r['registration_no'] ?? ''),'total'=>0,'present'=>0,'absent'=>0,'late'=>0,'excused'=>0];
            }
            if (!isset($summaryByCourse[$cid])) {
                $summaryByCourse[$cid] = ['label'=>(string)($r['course_code'] ?? '') . ' - ' . (string)($r['course_title'] ?? ''),'total'=>0,'present'=>0,'absent'=>0,'late'=>0,'excused'=>0];
            }
            if (!isset($summaryByDepartment[$did])) {
                $summaryByDepartment[$did] = ['label'=>(string)($r['department_name'] ?? 'Unknown'),'total'=>0,'present'=>0,'absent'=>0,'late'=>0,'excused'=>0];
            }
            if (!isset($summaryByFaculty[$fid])) {
                $summaryByFaculty[$fid] = ['label'=>(string)($r['faculty_name'] ?? 'Unknown'),'total'=>0,'present'=>0,'absent'=>0,'late'=>0,'excused'=>0];
            }
            foreach ([&$summaryByStudent[$sid], &$summaryByCourse[$cid], &$summaryByDepartment[$did], &$summaryByFaculty[$fid]] as &$bucket) {
                $bucket['total']++;
                $k = strtolower((string)($r['status'] ?? 'present'));
                if (!isset($bucket[$k])) $bucket[$k] = 0;
                $bucket[$k]++;
            }
            unset($bucket);
        }
    }
    ?>
    <div class="container-fluid">
        <div class="d-flex align-items-center justify-content-between mb-3">
            <h4 class="mb-0">Student Attendance</h4>
        </div>
        <?php renderStudentAttendanceSubmenu($tab); ?>

        <div class="card mb-3"><div class="card-body">
            <form method="get" class="row g-2 align-items-end">
                <input type="hidden" name="page" value="admin_student_attendance">
                <input type="hidden" name="tab" value="<?php echo e($tab); ?>">
                <div class="col-md-2"><label class="form-label">Session</label><select class="form-select" name="session_name"><?php foreach ($sessions as $sn): ?><option value="<?php echo e($sn); ?>" <?php echo $sessionName===$sn?'selected':''; ?>><?php echo e($sn); ?></option><?php endforeach; ?></select></div>
                <div class="col-md-2"><label class="form-label">Semester</label><select class="form-select" name="semester"><option value="First" <?php echo $semester==='First'?'selected':''; ?>>First</option><option value="Second" <?php echo $semester==='Second'?'selected':''; ?>>Second</option></select></div>
                <div class="col-md-2"><label class="form-label">Faculty</label><select class="form-select" name="faculty_id"><option value="0">All</option><?php foreach ($faculties as $f): ?><option value="<?php echo (int)$f['id']; ?>" <?php echo $facultyId===(int)$f['id']?'selected':''; ?>><?php echo e($f['name']); ?></option><?php endforeach; ?></select></div>
                <div class="col-md-2"><label class="form-label">Department</label><select class="form-select" name="department_id"><option value="0">All</option><?php foreach ($departments as $d): ?><option value="<?php echo (int)$d['id']; ?>" <?php echo $departmentId===(int)$d['id']?'selected':''; ?>><?php echo e($d['name']); ?></option><?php endforeach; ?></select></div>
                <div class="col-md-2"><label class="form-label">Programme</label><select class="form-select" name="programme_id"><option value="0">All</option><?php foreach ($programmes as $p): ?><option value="<?php echo (int)$p['id']; ?>" <?php echo $programmeId===(int)$p['id']?'selected':''; ?>><?php echo e($p['name']); ?></option><?php endforeach; ?></select></div>
                <div class="col-md-2"><label class="form-label">Level</label><select class="form-select" name="level"><option value="">All</option><?php foreach (portalAcademicLevelOptions($pdo) as $lv): ?><option value="<?php echo e($lv); ?>" <?php echo ((string)$level===(string)$lv)?'selected':''; ?>><?php echo e($lv); ?></option><?php endforeach; ?></select></div>
                <div class="col-md-3"><label class="form-label">Course</label><select class="form-select" name="course_id"><option value="0">All / Select</option><?php foreach ($courses as $c): ?><option value="<?php echo (int)$c['id']; ?>" <?php echo $courseId===(int)$c['id']?'selected':''; ?>><?php echo e(($c['course_code'] ?? '') . ' - ' . ($c['course_title'] ?? '')); ?></option><?php endforeach; ?></select></div>
                <div class="col-md-2"><label class="form-label">Attendance Date</label><input type="date" class="form-control" name="attendance_date" value="<?php echo e($attendanceDate); ?>"></div>
                <div class="col-md-2"><label class="form-label">Date From</label><input type="date" class="form-control" name="date_from" value="<?php echo e($dateFrom); ?>"></div>
                <div class="col-md-2"><label class="form-label">Date To</label><input type="date" class="form-control" name="date_to" value="<?php echo e($dateTo); ?>"></div>
                <div class="col-md-2"><label class="form-label">Status</label><select class="form-select" name="status"><option value="">All</option><?php foreach (['Present','Absent','Late','Excused'] as $stt): ?><option value="<?php echo e($stt); ?>" <?php echo $statusFilter===$stt?'selected':''; ?>><?php echo e($stt); ?></option><?php endforeach; ?></select></div>
                <div class="col-md-3"><label class="form-label">Search</label><input class="form-control" name="search" value="<?php echo e($search); ?>" placeholder="Student or course"></div>
                <div class="col-md-2"><button class="btn btn-primary w-100" type="submit">Load</button></div>
            </form>
        </div></div>

        <?php if ($tab === 'take'): ?>
            <div class="card">
                <div class="card-header d-flex justify-content-between align-items-center"><strong>Take Attendance</strong><span class="text-muted small">Only students who registered the selected course are loaded.</span></div>
                <div class="card-body">
                    <?php if ($courseId <= 0 || $level === ''): ?>
                        <div class="alert alert-info mb-0">Select session, semester, level, and course to load registered students.</div>
                    <?php elseif (!$students): ?>
                        <div class="alert alert-warning mb-0">No active registered students found for the selected course and academic filters.</div>
                    <?php else: ?>
                        <form method="post">
                            <input type="hidden" name="action" value="save_bulk_attendance">
                            <input type="hidden" name="session_name" value="<?php echo e($sessionName); ?>">
                            <input type="hidden" name="semester" value="<?php echo e($semester); ?>">
                            <input type="hidden" name="faculty_id" value="<?php echo (int)$facultyId; ?>">
                            <input type="hidden" name="department_id" value="<?php echo (int)$departmentId; ?>">
                            <input type="hidden" name="programme_id" value="<?php echo (int)$programmeId; ?>">
                            <input type="hidden" name="level" value="<?php echo e($level); ?>">
                            <input type="hidden" name="course_id" value="<?php echo (int)$courseId; ?>">
                            <input type="hidden" name="attendance_date" value="<?php echo e($attendanceDate); ?>">
                            <div class="row g-2 mb-3">
                                <div class="col-md-4"><label class="form-label">Class Venue</label><input class="form-control" name="venue" placeholder="Optional venue"></div>
                                <div class="col-md-3"><label class="form-label">Lecturer / Staff In Charge</label><input class="form-control" name="lecturer_staff_id" value="<?php echo (int)$currentAdminId; ?>"></div>
                                <div class="col-md-5 d-flex align-items-end gap-2 flex-wrap">
                                    <button class="btn btn-outline-success btn-sm" type="button" onclick="setAttendanceBulk('Present')">Mark All Present</button>
                                    <button class="btn btn-outline-danger btn-sm" type="button" onclick="setAttendanceBulk('Absent')">Mark All Absent</button>
                                    <button class="btn btn-outline-warning btn-sm" type="button" onclick="setAttendanceBulk('Late')">Mark All Late</button>
                                    <button class="btn btn-outline-secondary btn-sm" type="button" onclick="setAttendanceBulk('Excused')">Mark All Excused</button>
                                    <button class="btn btn-outline-dark btn-sm" type="button" onclick="resetAttendanceBulk()">Reset Attendance</button>
                                </div>
                            </div>
                            <div class="table-responsive">
                                <table class="table table-bordered table-striped align-middle">
                                    <thead><tr><th>#</th><th>Student</th><th>Matric / App No.</th><th>Status</th><th>Remark</th></tr></thead>
                                    <tbody>
                                        <?php foreach ($students as $i => $st): $sid = (int)$st['student_id']; $currentStatus = trim((string)($st['current_status'] ?? studentAttendanceSetting($pdo, 'attendance_default_status', 'Present'))); if (!in_array($currentStatus,['Present','Absent','Late','Excused'],true)) $currentStatus='Present'; ?>
                                        <tr>
                                            <td><?php echo (int)$i + 1; ?></td>
                                            <td><?php echo e($st['student_name'] ?? ''); ?></td>
                                            <td><?php echo e(($st['matric_no'] ?? '') !== '' ? ($st['matric_no'] ?? '') : ($st['registration_no'] ?? '')); ?></td>
                                            <td>
                                                <select class="form-select attendance-status" name="status[<?php echo $sid; ?>]">
                                                    <?php foreach (['Present','Absent','Late','Excused'] as $opt): ?><option value="<?php echo e($opt); ?>" <?php echo $currentStatus===$opt?'selected':''; ?>><?php echo e($opt); ?></option><?php endforeach; ?>
                                                </select>
                                            </td>
                                            <td><input class="form-control" name="remark[<?php echo $sid; ?>]" value="<?php echo e($st['current_remark'] ?? ''); ?>" placeholder="Optional comment"></td>
                                        </tr>
                                        <?php endforeach; ?>
                                    </tbody>
                                </table>
                            </div>
                            <div class="mt-3"><button class="btn btn-primary" type="submit">Save Attendance</button></div>
                        </form>
                    <?php endif; ?>
                </div>
            </div>
            <script>
                function setAttendanceBulk(v){ document.querySelectorAll('.attendance-status').forEach(function(el){ el.value=v; }); }
                function resetAttendanceBulk(){ document.querySelectorAll('.attendance-status').forEach(function(el){ el.value='Present'; }); }
            </script>
        <?php elseif ($tab === 'view' || $tab === 'edit'): ?>
            <div class="card">
                <div class="card-header d-flex justify-content-between align-items-center">
                    <strong><?php echo $tab === 'edit' ? 'Edit Attendance' : 'View Attendance'; ?></strong>
                    <div class="d-flex gap-2 flex-wrap">
                        <a class="btn btn-outline-secondary btn-sm" target="_blank" href="?page=admin_student_attendance&tab=<?php echo e($tab); ?>&export=pdf&session_name=<?php echo urlencode($sessionName); ?>&semester=<?php echo urlencode($semester); ?>&faculty_id=<?php echo (int)$facultyId; ?>&department_id=<?php echo (int)$departmentId; ?>&programme_id=<?php echo (int)$programmeId; ?>&level=<?php echo urlencode($level); ?>&course_id=<?php echo (int)$courseId; ?>&date_from=<?php echo urlencode($dateFrom); ?>&date_to=<?php echo urlencode($dateTo); ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($statusFilter); ?>">PDF</a>
                        <a class="btn btn-outline-success btn-sm" href="?page=admin_student_attendance&tab=<?php echo e($tab); ?>&export=csv&session_name=<?php echo urlencode($sessionName); ?>&semester=<?php echo urlencode($semester); ?>&faculty_id=<?php echo (int)$facultyId; ?>&department_id=<?php echo (int)$departmentId; ?>&programme_id=<?php echo (int)$programmeId; ?>&level=<?php echo urlencode($level); ?>&course_id=<?php echo (int)$courseId; ?>&date_from=<?php echo urlencode($dateFrom); ?>&date_to=<?php echo urlencode($dateTo); ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($statusFilter); ?>">CSV</a>
                        <a class="btn btn-outline-primary btn-sm" href="?page=admin_student_attendance&tab=<?php echo e($tab); ?>&export=excel&session_name=<?php echo urlencode($sessionName); ?>&semester=<?php echo urlencode($semester); ?>&faculty_id=<?php echo (int)$facultyId; ?>&department_id=<?php echo (int)$departmentId; ?>&programme_id=<?php echo (int)$programmeId; ?>&level=<?php echo urlencode($level); ?>&course_id=<?php echo (int)$courseId; ?>&date_from=<?php echo urlencode($dateFrom); ?>&date_to=<?php echo urlencode($dateTo); ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($statusFilter); ?>">Excel</a>
                        <button class="btn btn-outline-dark btn-sm" type="button" onclick="window.print()">Print</button>
                    </div>
                </div>
                <div class="card-body">
                    <div class="table-responsive">
                        <table class="table table-striped table-bordered align-middle">
                            <thead><tr><th>Date</th><th>Student</th><th>Matric / App No.</th><th>Course</th><th>Status</th><th>Remark</th><th>Venue</th><?php if ($tab === 'edit'): ?><th>Action</th><?php endif; ?></tr></thead>
                            <tbody>
                            <?php if (!$attendanceRows): ?>
                                <tr><td colspan="<?php echo $tab === 'edit' ? 8 : 7; ?>" class="text-center text-muted">No attendance records found.</td></tr>
                            <?php else: foreach ($attendanceRows as $r): ?>
                                <tr>
                                    <td><?php echo e($r['attendance_date'] ?? ''); ?></td>
                                    <td><?php echo e($r['student_name'] ?? ''); ?></td>
                                    <td><?php echo e(($r['matric_no'] ?? '') !== '' ? ($r['matric_no'] ?? '') : ($r['registration_no'] ?? '')); ?></td>
                                    <td><?php echo e(($r['course_code'] ?? '') . ' - ' . ($r['course_title'] ?? '')); ?></td>
                                    <?php if ($tab === 'edit'): ?>
                                        <td colspan="3">
                                            <form method="post" class="row g-2 align-items-center">
                                                <input type="hidden" name="action" value="update_attendance_record">
                                                <input type="hidden" name="id" value="<?php echo (int)$r['id']; ?>">
                                                <div class="col-md-3"><select class="form-select form-select-sm" name="status"><?php foreach (['Present','Absent','Late','Excused'] as $opt): ?><option value="<?php echo e($opt); ?>" <?php echo ($r['status'] ?? '')===$opt?'selected':''; ?>><?php echo e($opt); ?></option><?php endforeach; ?></select></div>
                                                <div class="col-md-4"><input class="form-control form-control-sm" name="remark" value="<?php echo e($r['remark'] ?? ''); ?>"></div>
                                                <div class="col-md-3"><input class="form-control form-control-sm" name="venue" value="<?php echo e($r['venue'] ?? ''); ?>"></div>
                                                <div class="col-md-2 d-flex gap-1"><button class="btn btn-primary btn-sm" type="submit">Save</button><button class="btn btn-outline-danger btn-sm" type="submit" name="action" value="delete_attendance" onclick="return confirm('Delete this attendance record?');">Delete</button></div>
                                            </form>
                                        </td>
                                        <td></td>
                                    <?php else: ?>
                                        <td><?php echo e($r['status'] ?? ''); ?></td>
                                        <td><?php echo e($r['remark'] ?? ''); ?></td>
                                        <td><?php echo e($r['venue'] ?? ''); ?></td>
                                    <?php endif; ?>
                                </tr>
                            <?php endforeach; endif; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        <?php elseif ($tab === 'reports'): ?>
            <div class="row g-3 mb-3">
                <div class="col-md-2"><div class="card"><div class="card-body"><div class="text-muted small">Total Classes Held</div><div class="h4 mb-0"><?php echo count(array_unique(array_map(fn($r) => ($r['course_id'] ?? 0) . '|' . ($r['attendance_date'] ?? ''), $attendanceRows))); ?></div></div></div></div>
                <div class="col-md-2"><div class="card"><div class="card-body"><div class="text-muted small">Present</div><div class="h4 mb-0"><?php echo array_sum(array_map(fn($x)=>(int)($x['present'] ?? 0), $summaryByStudent)); ?></div></div></div></div>
                <div class="col-md-2"><div class="card"><div class="card-body"><div class="text-muted small">Absent</div><div class="h4 mb-0"><?php echo array_sum(array_map(fn($x)=>(int)($x['absent'] ?? 0), $summaryByStudent)); ?></div></div></div></div>
                <div class="col-md-2"><div class="card"><div class="card-body"><div class="text-muted small">Late</div><div class="h4 mb-0"><?php echo array_sum(array_map(fn($x)=>(int)($x['late'] ?? 0), $summaryByStudent)); ?></div></div></div></div>
                <div class="col-md-2"><div class="card"><div class="card-body"><div class="text-muted small">Excused</div><div class="h4 mb-0"><?php echo array_sum(array_map(fn($x)=>(int)($x['excused'] ?? 0), $summaryByStudent)); ?></div></div></div></div>
            </div>
            <div class="card mb-3"><div class="card-header d-flex justify-content-between align-items-center"><strong>Per Student Summary</strong><div class="d-flex gap-2"><a class="btn btn-outline-success btn-sm" href="?page=admin_student_attendance&tab=reports&export=csv&session_name=<?php echo urlencode($sessionName); ?>&semester=<?php echo urlencode($semester); ?>&faculty_id=<?php echo (int)$facultyId; ?>&department_id=<?php echo (int)$departmentId; ?>&programme_id=<?php echo (int)$programmeId; ?>&level=<?php echo urlencode($level); ?>&course_id=<?php echo (int)$courseId; ?>&date_from=<?php echo urlencode($dateFrom); ?>&date_to=<?php echo urlencode($dateTo); ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($statusFilter); ?>">CSV</a><a class="btn btn-outline-primary btn-sm" href="?page=admin_student_attendance&tab=reports&export=excel&session_name=<?php echo urlencode($sessionName); ?>&semester=<?php echo urlencode($semester); ?>&faculty_id=<?php echo (int)$facultyId; ?>&department_id=<?php echo (int)$departmentId; ?>&programme_id=<?php echo (int)$programmeId; ?>&level=<?php echo urlencode($level); ?>&course_id=<?php echo (int)$courseId; ?>&date_from=<?php echo urlencode($dateFrom); ?>&date_to=<?php echo urlencode($dateTo); ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($statusFilter); ?>">Excel</a><a class="btn btn-outline-secondary btn-sm" target="_blank" href="?page=admin_student_attendance&tab=reports&export=pdf&session_name=<?php echo urlencode($sessionName); ?>&semester=<?php echo urlencode($semester); ?>&faculty_id=<?php echo (int)$facultyId; ?>&department_id=<?php echo (int)$departmentId; ?>&programme_id=<?php echo (int)$programmeId; ?>&level=<?php echo urlencode($level); ?>&course_id=<?php echo (int)$courseId; ?>&date_from=<?php echo urlencode($dateFrom); ?>&date_to=<?php echo urlencode($dateTo); ?>&search=<?php echo urlencode($search); ?>&status=<?php echo urlencode($statusFilter); ?>">PDF</a></div></div><div class="card-body"><div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr><th>Student</th><th>Matric / App No.</th><th>Total Classes</th><th>Present</th><th>Absent</th><th>Late</th><th>Excused</th><th>Attendance %</th></tr></thead><tbody><?php if (!$summaryByStudent): ?><tr><td colspan="8" class="text-center text-muted">No records found.</td></tr><?php else: foreach ($summaryByStudent as $row): $pct = ((int)$row['total'] > 0) ? round((((int)$row['present'] + (int)$row['late'] + (int)$row['excused']) / (int)$row['total']) * 100, 2) : 0; ?><tr><td><?php echo e($row['student_name']); ?></td><td><?php echo e($row['matric_no']); ?></td><td><?php echo (int)$row['total']; ?></td><td><?php echo (int)$row['present']; ?></td><td><?php echo (int)$row['absent']; ?></td><td><?php echo (int)$row['late']; ?></td><td><?php echo (int)$row['excused']; ?></td><td><?php echo e(number_format($pct, 2)); ?>%</td></tr><?php endforeach; endif; ?></tbody></table></div></div></div>
            <div class="row g-3">
                <div class="col-md-6"><div class="card"><div class="card-header"><strong>Course Summary</strong></div><div class="card-body"><div class="table-responsive"><table class="table table-sm table-bordered"><thead><tr><th>Course</th><th>Total</th><th>Present</th><th>Absent</th><th>Late</th><th>Excused</th></tr></thead><tbody><?php if (!$summaryByCourse): ?><tr><td colspan="6" class="text-center text-muted">No data</td></tr><?php else: foreach ($summaryByCourse as $row): ?><tr><td><?php echo e($row['label']); ?></td><td><?php echo (int)$row['total']; ?></td><td><?php echo (int)$row['present']; ?></td><td><?php echo (int)$row['absent']; ?></td><td><?php echo (int)$row['late']; ?></td><td><?php echo (int)$row['excused']; ?></td></tr><?php endforeach; endif; ?></tbody></table></div></div></div></div>
                <div class="col-md-6"><div class="card"><div class="card-header"><strong>Departmental Summary</strong></div><div class="card-body"><div class="table-responsive"><table class="table table-sm table-bordered"><thead><tr><th>Department</th><th>Total</th><th>Present</th><th>Absent</th><th>Late</th><th>Excused</th></tr></thead><tbody><?php if (!$summaryByDepartment): ?><tr><td colspan="6" class="text-center text-muted">No data</td></tr><?php else: foreach ($summaryByDepartment as $row): ?><tr><td><?php echo e($row['label']); ?></td><td><?php echo (int)$row['total']; ?></td><td><?php echo (int)$row['present']; ?></td><td><?php echo (int)$row['absent']; ?></td><td><?php echo (int)$row['late']; ?></td><td><?php echo (int)$row['excused']; ?></td></tr><?php endforeach; endif; ?></tbody></table></div></div></div></div>
                <div class="col-md-6"><div class="card"><div class="card-header"><strong>Faculty Summary</strong></div><div class="card-body"><div class="table-responsive"><table class="table table-sm table-bordered"><thead><tr><th>Faculty</th><th>Total</th><th>Present</th><th>Absent</th><th>Late</th><th>Excused</th></tr></thead><tbody><?php if (!$summaryByFaculty): ?><tr><td colspan="6" class="text-center text-muted">No data</td></tr><?php else: foreach ($summaryByFaculty as $row): ?><tr><td><?php echo e($row['label']); ?></td><td><?php echo (int)$row['total']; ?></td><td><?php echo (int)$row['present']; ?></td><td><?php echo (int)$row['absent']; ?></td><td><?php echo (int)$row['late']; ?></td><td><?php echo (int)$row['excused']; ?></td></tr><?php endforeach; endif; ?></tbody></table></div></div></div></div>
            </div>
        <?php else: ?>
            <div class="card"><div class="card-header"><strong>Attendance Settings</strong></div><div class="card-body"><form method="post" class="row g-3"><input type="hidden" name="action" value="save_attendance_settings"><div class="col-md-4"><label class="form-label">Default Status</label><select class="form-select" name="attendance_default_status"><?php foreach (['Present','Absent','Late','Excused'] as $opt): ?><option value="<?php echo e($opt); ?>" <?php echo studentAttendanceSetting($pdo, 'attendance_default_status', 'Present')===$opt?'selected':''; ?>><?php echo e($opt); ?></option><?php endforeach; ?></select></div><div class="col-md-4 d-flex align-items-end"><div class="form-check"><input class="form-check-input" type="checkbox" name="attendance_allow_future_date" value="1" id="attFutureDate" <?php echo studentAttendanceSetting($pdo, 'attendance_allow_future_date', '0')==='1'?'checked':''; ?>><label class="form-check-label" for="attFutureDate">Allow future attendance dates</label></div></div><div class="col-md-4 d-flex align-items-end"><div class="form-check"><input class="form-check-input" type="checkbox" name="attendance_show_student_portal" value="1" id="attPortal" <?php echo studentAttendanceSetting($pdo, 'attendance_show_student_portal', '1')==='1'?'checked':''; ?>><label class="form-check-label" for="attPortal">Show attendance in student portal</label></div></div><div class="col-12"><button class="btn btn-primary" type="submit">Save Settings</button></div></form></div></div>
        <?php endif; ?>
    </div>

    

    <?php
    break;


case 'student_results':
    if (!isLoggedIn() || !isStudent()) { redirect('?page=login'); }
    if (!studentMenuEnabled($pdo, 'menu_student_results')) {
        flash('danger', 'Results / CGPA is currently unavailable.');
        redirect('?page=student_dashboard');
    }

    try { portalEnsureStudentResultsPageSupport($pdo); } catch (Throwable $e) {}

    $studentId = (int)($_SESSION['student_id'] ?? ($_SESSION['user_id'] ?? 0));
    if ($studentId <= 0) {
        flash('danger', 'Student session not found. Please log in again.');
        redirect('?page=login');
    }

    $student = [];
    try {
        $st = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
        $st->execute([$studentId]);
        $student = $st->fetch(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) { $student = []; }

    if (!$student) {
        flash('danger', 'Student record not found.');
        redirect('?page=student_dashboard');
    }

    $gradingSettings = portalGradingResultSettings($pdo);
    $gpaDecimals = max(0, (int)($gradingSettings['result_cgp_decimal_places'] ?? 2));
    $cgpaDecimals = max(0, (int)($gradingSettings['result_cgpa_decimal_places'] ?? 2));
    $currentSessionName = portalCurrentAcademicSessionName($pdo);
    $currentSemesterName = portalCurrentAcademicSemesterName($pdo);
    $requestedSession = trim((string)($_GET['session_name'] ?? $currentSessionName));
    $requestedSemester = trim((string)($_GET['semester'] ?? $currentSemesterName));
    $requestedLevel = trim((string)($_GET['level'] ?? ''));
    if (!in_array($requestedSemester, ['First','Second',''], true)) {
        $requestedSemester = $currentSemesterName;
    }

    $resultPrintMode = ((int)($_GET['print'] ?? 0) === 1) || (strtolower(trim((string)($_GET['download'] ?? ''))) === 'pdf');

    $academicProfile = portalResolveStudentAcademicProfile($pdo, $student);
    $displayLevel = portalStudentResolvedCurrentLevel($pdo, $student, $requestedSession !== '' ? $requestedSession : $currentSessionName);
    $matricDisplay = portalReceiptMatricNumber($student);
    if ($matricDisplay === '') {
        $matricDisplay = trim((string)($student['registration_no'] ?? $student['reg_no'] ?? $student['application_no'] ?? ''));
    }

    $buildResultFilters = static function(array $filters, string $resultsAlias = 'r', string $courseAlias = 'rc'): array {
        $where = [];
        $params = [];
        $sessionName = trim((string)($filters['session_name'] ?? ''));
        $semester = trim((string)($filters['semester'] ?? ''));
        $level = trim((string)($filters['level'] ?? ''));
        if ($sessionName !== '') {
            $where[] = "COALESCE({$resultsAlias}.session,'') = ?";
            $params[] = $sessionName;
        }
        if ($semester !== '') {
            $where[] = "COALESCE({$resultsAlias}.semester,'') = ?";
            $params[] = $semester;
        }
        if ($level !== '') {
            $where[] = "(COALESCE(NULLIF({$courseAlias}.level,''),'') = ? OR EXISTS (
                SELECT 1
                FROM course_registrations crx
                INNER JOIN course_registration_items crix ON crix.registration_id = crx.id
                WHERE crx.student_id = {$resultsAlias}.student_id
                  AND crix.course_id = {$resultsAlias}.course_id
                  AND COALESCE(crx.session_name,'') = COALESCE({$resultsAlias}.session,'')
                  AND COALESCE(crx.semester,'') = COALESCE({$resultsAlias}.semester,'')
                  AND COALESCE(TRIM(crx.level),'') = ?
            ))";
            $params[] = $level;
            $params[] = $level;
        }
        return [$where, $params];
    };

    $fetchResultRows = static function(PDO $pdo, int $studentId, array $filters = []): array {
        [$where, $params] = $buildResultFilters = (static function(array $filters, string $resultsAlias = 'r', string $courseAlias = 'rc'): array {
            $where = [];
            $params = [];
            $sessionName = trim((string)($filters['session_name'] ?? ''));
            $semester = trim((string)($filters['semester'] ?? ''));
            $level = trim((string)($filters['level'] ?? ''));
            if ($sessionName !== '') {
                $where[] = "COALESCE({$resultsAlias}.session,'') = ?";
                $params[] = $sessionName;
            }
            if ($semester !== '') {
                $where[] = "COALESCE({$resultsAlias}.semester,'') = ?";
                $params[] = $semester;
            }
            if ($level !== '') {
                $where[] = "(COALESCE(NULLIF({$courseAlias}.level,''),'') = ? OR EXISTS (
                    SELECT 1
                    FROM course_registrations crx
                    INNER JOIN course_registration_items crix ON crix.registration_id = crx.id
                    WHERE crx.student_id = {$resultsAlias}.student_id
                      AND crix.course_id = {$resultsAlias}.course_id
                      AND COALESCE(crx.session_name,'') = COALESCE({$resultsAlias}.session,'')
                      AND COALESCE(crx.semester,'') = COALESCE({$resultsAlias}.semester,'')
                      AND COALESCE(TRIM(crx.level),'') = ?
                ))";
                $params[] = $level;
                $params[] = $level;
            }
            return [$where, $params];
        })($filters, 'r', 'rc');

        $sql = "SELECT
                    r.*,
                    COALESCE(rc.course_code, '') AS course_code,
                    COALESCE(rc.course_title, '') AS course_title,
                    COALESCE(rc.credit_unit, 0) AS credit_unit,
                    COALESCE(rc.course_type, '') AS course_type,
                    COALESCE(rc.level, '') AS course_level,
                    COALESCE(NULLIF(rs.status,''), NULLIF(r.status,''), 'draft') AS workflow_status,
                    rs.submitted_at,
                    rs.reviewed_at,
                    rs.approved_at,
                    rs.published_at,
                    rs.rejected_at,
                    rs.returned_at
                FROM results r
                LEFT JOIN registrable_courses rc ON rc.id = r.course_id
                LEFT JOIN result_submissions rs ON rs.id = r.submission_id
                WHERE r.student_id = ?";
        $args = [$studentId];
        if ($where) {
            $sql .= " AND " . implode(' AND ', $where);
            $args = array_merge($args, $params);
        }
        $sql .= " ORDER BY COALESCE(r.session,'') DESC,
                         CASE COALESCE(r.semester,'') WHEN 'First' THEN 1 WHEN 'Second' THEN 2 ELSE 3 END ASC,
                         COALESCE(rc.course_code,'') ASC,
                         r.id ASC";
        try {
            $st = $pdo->prepare($sql);
            $st->execute($args);
            return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return [];
        }
    };

    $aggregateRows = static function(PDO $pdo, array $rows, int $decimalPlaces = 2, string $roundingMode = 'half_up'): array {
        $totalUnits = 0.0;
        $earnedUnits = 0.0;
        $qualityPoints = 0.0;
        $passedCourses = 0;
        $failedCourses = 0;
        $carryOvers = [];
        foreach ($rows as $row) {
            $creditUnit = (float)($row['credit_unit'] ?? 0);
            $ca = (float)($row['ca_score'] ?? 0);
            $exam = (float)($row['exam_score'] ?? 0);
            $assessment = portalResultAssessment($pdo, $ca, $exam, (float)($row['total_score'] ?? ($ca + $exam)));
            $totalUnits += $creditUnit;
            $qualityPoints += $creditUnit * (float)($assessment['grade_point'] ?? 0);
            if (($assessment['status'] ?? '') === 'PASS') {
                $passedCourses++;
                $earnedUnits += $creditUnit;
            } else {
                $failedCourses++;
                $carryOvers[] = $row;
            }
        }
        $gpa = $totalUnits > 0 ? portalApplyRoundedDecimal($qualityPoints / $totalUnits, $decimalPlaces, $roundingMode) : 0.0;
        return [
            'total_units' => $totalUnits,
            'earned_units' => $earnedUnits,
            'quality_points' => $qualityPoints,
            'passed_courses' => $passedCourses,
            'failed_courses' => $failedCourses,
            'carry_overs' => $carryOvers,
            'gpa' => $gpa,
            'course_count' => count($rows),
        ];
    };

    $resultFilterOptions = ['sessions' => [], 'levels' => []];
    try {
        $st = $pdo->prepare("SELECT DISTINCT session FROM results WHERE student_id = ? AND COALESCE(TRIM(session),'') <> '' ORDER BY session DESC");
        $st->execute([$studentId]);
        foreach (($st->fetchAll(PDO::FETCH_COLUMN) ?: []) as $sessionValue) {
            $sessionValue = trim((string)$sessionValue);
            if ($sessionValue !== '') $resultFilterOptions['sessions'][$sessionValue] = $sessionValue;
        }
    } catch (Throwable $e) {}
    try {
        $st = $pdo->prepare("SELECT DISTINCT session_name FROM course_registrations WHERE student_id = ? AND COALESCE(TRIM(session_name),'') <> '' ORDER BY session_name DESC");
        $st->execute([$studentId]);
        foreach (($st->fetchAll(PDO::FETCH_COLUMN) ?: []) as $sessionValue) {
            $sessionValue = trim((string)$sessionValue);
            if ($sessionValue !== '') $resultFilterOptions['sessions'][$sessionValue] = $sessionValue;
        }
    } catch (Throwable $e) {}
    if ($currentSessionName !== '') $resultFilterOptions['sessions'][$currentSessionName] = $currentSessionName;

    try {
        $st = $pdo->prepare("SELECT DISTINCT COALESCE(NULLIF(rc.level,''), '') AS level_name
            FROM results r
            LEFT JOIN registrable_courses rc ON rc.id = r.course_id
            WHERE r.student_id = ? AND COALESCE(TRIM(rc.level),'') <> ''
            ORDER BY rc.level ASC");
        $st->execute([$studentId]);
        foreach (($st->fetchAll(PDO::FETCH_COLUMN) ?: []) as $levelValue) {
            $levelValue = trim((string)$levelValue);
            if ($levelValue !== '') $resultFilterOptions['levels'][$levelValue] = $levelValue;
        }
    } catch (Throwable $e) {}
    try {
        $st = $pdo->prepare("SELECT DISTINCT level FROM course_registrations WHERE student_id = ? AND COALESCE(TRIM(level),'') <> '' ORDER BY level ASC");
        $st->execute([$studentId]);
        foreach (($st->fetchAll(PDO::FETCH_COLUMN) ?: []) as $levelValue) {
            $levelValue = trim((string)$levelValue);
            if ($levelValue !== '') $resultFilterOptions['levels'][$levelValue] = $levelValue;
        }
    } catch (Throwable $e) {}
    if ($displayLevel !== '') $resultFilterOptions['levels'][$displayLevel] = $displayLevel;
    krsort($resultFilterOptions['sessions']);
    ksort($resultFilterOptions['levels'], SORT_NATURAL);

    $filterPayload = ['session_name' => $requestedSession, 'semester' => $requestedSemester, 'level' => $requestedLevel];
    $allScopedRows = $fetchResultRows($pdo, $studentId, $filterPayload);
    $publishedRows = array_values(array_filter($allScopedRows, static function(array $row): bool {
        $status = strtolower(trim((string)($row['workflow_status'] ?? $row['status'] ?? 'draft')));
        return $status === 'published';
    }));

    $allPublishedRows = array_values(array_filter($fetchResultRows($pdo, $studentId, ['session_name' => '', 'semester' => '', 'level' => '']), static function(array $row): bool {
        $status = strtolower(trim((string)($row['workflow_status'] ?? $row['status'] ?? 'draft')));
        return $status === 'published';
    }));

    $semesterSummary = $aggregateRows($pdo, $publishedRows, $gpaDecimals, (string)($gradingSettings['result_cgp_rounding_mode'] ?? 'half_up'));
    $cumulativeSummary = $aggregateRows($pdo, $allPublishedRows, $cgpaDecimals, (string)($gradingSettings['result_cgpa_rounding_mode'] ?? 'half_up'));
    $currentCgpa = (float)($cumulativeSummary['gpa'] ?? 0.0);
    $classificationLabel = portalStudentResultClassification($pdo, $currentCgpa);

    $registrationSummary = ['courses' => 0, 'units' => 0.0];
    try {
        $sql = "SELECT COUNT(cri.id) AS total_courses, COALESCE(SUM(COALESCE(cri.credit_unit,0)),0) AS total_units
                FROM course_registrations cr
                LEFT JOIN course_registration_items cri ON cri.registration_id = cr.id
                WHERE cr.student_id = ?";
        $params = [$studentId];
        if ($requestedSession !== '') { $sql .= " AND COALESCE(cr.session_name,'') = ?"; $params[] = $requestedSession; }
        if ($requestedSemester !== '') { $sql .= " AND COALESCE(cr.semester,'') = ?"; $params[] = $requestedSemester; }
        if ($requestedLevel !== '') { $sql .= " AND COALESCE(TRIM(cr.level),'') = ?"; $params[] = $requestedLevel; }
        $st = $pdo->prepare($sql);
        $st->execute($params);
        $registrationSummary = $st->fetch(PDO::FETCH_ASSOC) ?: $registrationSummary;
    } catch (Throwable $e) {}

    $workflowCounts = [];
    foreach ($allScopedRows as $row) {
        $statusKey = strtolower(trim((string)($row['workflow_status'] ?? $row['status'] ?? 'draft')));
        if ($statusKey === '') $statusKey = 'draft';
        $workflowCounts[$statusKey] = (int)($workflowCounts[$statusKey] ?? 0) + 1;
    }

    $resultStatusLabel = 'No Result Yet';
    $resultStatusClass = 'secondary';
    if ((int)($workflowCounts['published'] ?? 0) > 0) {
        $resultStatusLabel = 'Published';
        $resultStatusClass = 'success';
    } elseif ((int)($workflowCounts['approved'] ?? 0) > 0) {
        $resultStatusLabel = 'Awaiting Publication';
        $resultStatusClass = 'primary';
    } elseif ((int)($workflowCounts['reviewed'] ?? 0) > 0 || (int)($workflowCounts['submitted'] ?? 0) > 0 || (int)($workflowCounts['draft'] ?? 0) > 0 || (int)($workflowCounts['pending_review'] ?? 0) > 0) {
        $resultStatusLabel = 'Pending Approval';
        $resultStatusClass = 'warning text-dark';
    } elseif ((int)($workflowCounts['returned'] ?? 0) > 0 || (int)($workflowCounts['returned_for_correction'] ?? 0) > 0) {
        $resultStatusLabel = 'Returned for Correction';
        $resultStatusClass = 'warning text-dark';
    } elseif ((int)($workflowCounts['rejected'] ?? 0) > 0) {
        $resultStatusLabel = 'Rejected';
        $resultStatusClass = 'danger';
    }

    $publishedCourseIds = [];
    foreach ($publishedRows as $row) {
        $publishedCourseIds[(int)($row['course_id'] ?? 0)] = true;
    }
    $allCourseIds = [];
    foreach ($allScopedRows as $row) {
        $allCourseIds[(int)($row['course_id'] ?? 0)] = true;
    }
    $pendingCourseCount = max(0, count($allCourseIds) - count($publishedCourseIds));
    $missingResultCount = max(0, ((int)($registrationSummary['total_courses'] ?? 0)) - count($allCourseIds));
    $carryOverRows = (array)($semesterSummary['carry_overs'] ?? []);

    $historyGroups = [];
    foreach ($allPublishedRows as $row) {
        $sessionKey = trim((string)($row['session'] ?? ''));
        $semesterKey = trim((string)($row['semester'] ?? ''));
        $groupKey = $sessionKey . '|' . $semesterKey;
        if (!isset($historyGroups[$groupKey])) {
            $historyGroups[$groupKey] = ['session' => $sessionKey, 'semester' => $semesterKey, 'rows' => []];
        }
        $historyGroups[$groupKey]['rows'][] = $row;
    }
    foreach ($historyGroups as $groupKey => $groupData) {
        $historyGroups[$groupKey]['summary'] = $aggregateRows($pdo, (array)$groupData['rows'], $gpaDecimals, (string)($gradingSettings['result_cgp_rounding_mode'] ?? 'half_up'));
    }
    uasort($historyGroups, static function(array $a, array $b): int {
        $left = ($a['session'] ?? '') . '|' . ($a['semester'] ?? '');
        $right = ($b['session'] ?? '') . '|' . ($b['semester'] ?? '');
        return strcmp($right, $left);
    });

    $gradeKeyRows = portalGradeScaleRangeRows($pdo);
    $canPrintResult = !empty($publishedRows);

    ?>
    <div class="container-fluid student-results-page <?php echo $resultPrintMode ? 'student-results-print-mode' : ''; ?>">
        <style>
            .student-results-page .results-hero{background:linear-gradient(135deg,#0b3f7b 0%,#0e4c92 55%,#123e72 100%);border-radius:1.25rem;color:#fff;box-shadow:0 18px 45px rgba(10,55,106,.16);}
            .student-results-page .results-hero .hero-subtle{color:rgba(255,255,255,.82);}
            .student-results-page .results-card{border:1px solid rgba(11,63,123,.10);border-radius:1rem;box-shadow:0 10px 28px rgba(15,23,42,.05);}
            .student-results-page .summary-tile{border:1px solid rgba(11,63,123,.10);border-radius:1rem;background:#fff;height:100%;box-shadow:0 8px 20px rgba(15,23,42,.04);}
            .student-results-page .summary-tile .label{font-size:.78rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#6b7280;}
            .student-results-page .summary-tile .value{font-size:1.45rem;font-weight:800;color:#0b3f7b;line-height:1.15;}
            .student-results-page .summary-grid .meta-label{font-size:.76rem;text-transform:uppercase;letter-spacing:.04em;color:#6b7280;font-weight:700;}
            .student-results-page .summary-grid .meta-value{font-size:.98rem;font-weight:700;color:#111827;}
            .student-results-page .status-chip{display:inline-flex;align-items:center;gap:.45rem;border-radius:999px;padding:.4rem .9rem;font-weight:700;}
            .student-results-page .status-chip.success{background:rgba(25,135,84,.12);color:#198754;}
            .student-results-page .status-chip.primary{background:rgba(13,110,253,.12);color:#0d6efd;}
            .student-results-page .status-chip.warning{background:rgba(255,193,7,.18);color:#8a6200;}
            .student-results-page .status-chip.danger{background:rgba(220,53,69,.12);color:#dc3545;}
            .student-results-page .status-chip.secondary{background:rgba(108,117,125,.12);color:#6c757d;}
            .student-results-page .result-table thead th{background:#eef4fb;color:#0b3f7b;border-color:#d7e3f2;font-size:.85rem;white-space:nowrap;}
            .student-results-page .result-table td{vertical-align:middle;}
            .student-results-page .result-table .course-title{font-weight:700;color:#14213d;}
            .student-results-page .result-table .course-meta{font-size:.8rem;color:#6b7280;}
            .student-results-page .metric-pill{display:inline-flex;align-items:center;padding:.3rem .7rem;border-radius:999px;font-size:.82rem;font-weight:700;background:#f8f9fa;border:1px solid rgba(15,23,42,.08);}
            .student-results-page .history-link{display:block;border:1px solid rgba(11,63,123,.10);border-radius:.9rem;padding:.9rem 1rem;text-decoration:none;color:inherit;background:#fff;transition:.18s ease;}
            .student-results-page .history-link:hover{transform:translateY(-1px);box-shadow:0 10px 18px rgba(15,23,42,.06);}
            .student-results-page .empty-state{border:1px dashed rgba(11,63,123,.22);border-radius:1rem;padding:2rem;background:#f8fbff;text-align:center;color:#4b5563;}
            .student-results-page .print-note{font-size:.85rem;color:#6b7280;}
            .student-results-page .result-toolbar .btn{white-space:nowrap;}
            .student-results-page .results-mini-badge{font-size:.75rem;}
            @media (max-width: 767.98px){
                .student-results-page .results-hero{border-radius:1rem;}
                .student-results-page .summary-tile .value{font-size:1.2rem;}
                .student-results-page .result-toolbar{flex-direction:column;align-items:stretch !important;}
                .student-results-page .result-toolbar .btn{width:100%;}
            }
            @media print{
                .student-results-page .no-print, .sidebar, .topbar, .navbar, .portal-search-wrap { display:none !important; }
                .student-results-page{padding:0 !important;margin:0 !important;}
                .student-results-page .results-card, .student-results-page .summary-tile, .student-results-page .results-hero{box-shadow:none !important;}
                .student-results-page .results-card, .student-results-page .summary-tile{border-color:#b9c7d8 !important;}
                .student-results-page .result-table thead th{background:#eef4fb !important;-webkit-print-color-adjust:exact;print-color-adjust:exact;}
                .student-results-page .page-break-avoid{break-inside:avoid;page-break-inside:avoid;}
            }
        </style>

        <?php if ($resultPrintMode && strtolower(trim((string)($_GET['download'] ?? ''))) === 'pdf' && $canPrintResult): ?>
            <script>window.addEventListener('load', function(){ setTimeout(function(){ window.print(); }, 250); });</script>
        <?php endif; ?>

        <div class="results-hero p-4 p-lg-5 mb-4 page-break-avoid">
            <div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
                <div>
                    <div class="hero-subtle text-uppercase fw-semibold small mb-2">Student Academic Results</div>
                    <h3 class="mb-2"><?php echo e(studentDisplayFullName($student)); ?></h3>
                    <div class="hero-subtle d-flex flex-wrap gap-3">
                        <span><strong>Matric Number:</strong> <?php echo e($matricDisplay !== '' ? $matricDisplay : '—'); ?></span>
                        <span><strong>Department:</strong> <?php echo e((string)($academicProfile['course_of_study'] ?? '—')); ?></span>
                        <span><strong>Programme:</strong> <?php echo e((string)($academicProfile['programme'] ?? '—')); ?></span>
                    </div>
                </div>
                <div class="text-start text-lg-end">
                    <div class="status-chip <?php echo strpos($resultStatusClass, ' ') !== false ? explode(' ', $resultStatusClass)[0] : $resultStatusClass; ?>">
                        <i class="bi bi-bar-chart-line"></i>
                        <span><?php echo e($resultStatusLabel); ?></span>
                    </div>
                    <div class="hero-subtle mt-2">Session: <?php echo e($requestedSession !== '' ? $requestedSession : 'All Sessions'); ?></div>
                    <div class="hero-subtle">Semester: <?php echo e($requestedSemester !== '' ? $requestedSemester : 'All Semesters'); ?></div>
                </div>
            </div>
        </div>

        <?php renderFlashes(); ?>

        <div class="card results-card mb-4 no-print">
            <div class="card-body">
                <form method="get" class="row g-3 align-items-end">
                    <input type="hidden" name="page" value="student_results">
                    <div class="col-md-4">
                        <label class="form-label">Session</label>
                        <select class="form-select" name="session_name">
                            <option value="">All Sessions</option>
                            <?php foreach ($resultFilterOptions['sessions'] as $sessionOption): ?>
                                <option value="<?php echo e($sessionOption); ?>" <?php echo $requestedSession === $sessionOption ? 'selected' : ''; ?>><?php echo e($sessionOption); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-md-3">
                        <label class="form-label">Semester</label>
                        <select class="form-select" name="semester">
                            <option value="">All Semesters</option>
                            <?php foreach (['First','Second'] as $semesterOption): ?>
                                <option value="<?php echo e($semesterOption); ?>" <?php echo $requestedSemester === $semesterOption ? 'selected' : ''; ?>><?php echo e($semesterOption); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-md-3">
                        <label class="form-label">Level</label>
                        <select class="form-select" name="level">
                            <option value="">All Levels</option>
                            <?php foreach ($resultFilterOptions['levels'] as $levelOption): ?>
                                <option value="<?php echo e($levelOption); ?>" <?php echo $requestedLevel === $levelOption ? 'selected' : ''; ?>><?php echo e($levelOption); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-md-2 d-flex gap-2">
                        <button class="btn btn-primary flex-fill" type="submit">View</button>
                        <a class="btn btn-outline-secondary flex-fill" href="?page=student_results">Reset</a>
                    </div>
                </form>
            </div>
        </div>

        <div class="row g-3 mb-4 summary-grid">
            <?php
            $cards = [
                ['label' => 'Current CGPA', 'value' => number_format($currentCgpa, $cgpaDecimals)],
                ['label' => 'Semester GPA', 'value' => number_format((float)($semesterSummary['gpa'] ?? 0), $gpaDecimals)],
                ['label' => 'Registered Courses', 'value' => (int)($registrationSummary['total_courses'] ?? 0)],
                ['label' => 'Registered Units', 'value' => number_format((float)($registrationSummary['total_units'] ?? 0), 0)],
                ['label' => 'Earned Units', 'value' => number_format((float)($semesterSummary['earned_units'] ?? 0), 0)],
                ['label' => 'Passed / Failed', 'value' => (int)($semesterSummary['passed_courses'] ?? 0) . ' / ' . (int)($semesterSummary['failed_courses'] ?? 0)],
            ];
            ?>
            <?php foreach ($cards as $card): ?>
                <div class="col-6 col-lg-4 col-xxl-2">
                    <div class="summary-tile p-3">
                        <div class="label"><?php echo e($card['label']); ?></div>
                        <div class="value mt-2"><?php echo e((string)$card['value']); ?></div>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>

        <div class="row g-3 mb-4">
            <div class="col-xl-8">
                <div class="card results-card h-100">
                    <div class="card-header bg-white border-0 pb-0">
                        <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 result-toolbar">
                            <div>
                                <h5 class="mb-1">Result Summary</h5>
                                <div class="text-muted small">Real-time view of your approved and published semester result records, GPA, and cumulative CGPA.</div>
                            </div>
                            <div class="d-flex flex-wrap gap-2 no-print">
                                <a class="btn btn-outline-dark <?php echo $canPrintResult ? '' : 'disabled'; ?>" href="<?php echo $canPrintResult ? ('?page=student_results&session_name=' . urlencode($requestedSession) . '&semester=' . urlencode($requestedSemester) . '&level=' . urlencode($requestedLevel) . '&print=1') : '#'; ?>" target="_blank"><i class="bi bi-printer"></i> Print Result</a>
                                <a class="btn btn-outline-primary <?php echo $canPrintResult ? '' : 'disabled'; ?>" href="<?php echo $canPrintResult ? ('?page=student_results&session_name=' . urlencode($requestedSession) . '&semester=' . urlencode($requestedSemester) . '&level=' . urlencode($requestedLevel) . '&download=pdf') : '#'; ?>" target="_blank"><i class="bi bi-download"></i> Download Result PDF</a>
                            </div>
                        </div>
                    </div>
                    <div class="card-body">
                        <div class="row g-3 mb-3">
                            <div class="col-md-4">
                                <div class="meta-label">Student Name</div>
                                <div class="meta-value"><?php echo e(studentDisplayFullName($student)); ?></div>
                            </div>
                            <div class="col-md-4">
                                <div class="meta-label">Matric Number</div>
                                <div class="meta-value"><?php echo e($matricDisplay !== '' ? $matricDisplay : '—'); ?></div>
                            </div>
                            <div class="col-md-4">
                                <div class="meta-label">Level</div>
                                <div class="meta-value"><?php echo e($requestedLevel !== '' ? $requestedLevel : $displayLevel); ?></div>
                            </div>
                            <div class="col-md-4">
                                <div class="meta-label">Department</div>
                                <div class="meta-value"><?php echo e((string)($academicProfile['course_of_study'] ?? '—')); ?></div>
                            </div>
                            <div class="col-md-4">
                                <div class="meta-label">Programme</div>
                                <div class="meta-value"><?php echo e((string)($academicProfile['programme'] ?? '—')); ?></div>
                            </div>
                            <div class="col-md-4">
                                <div class="meta-label">Mode of Study</div>
                                <div class="meta-value"><?php echo e((string)($academicProfile['mode_of_study'] ?? 'Full-Time')); ?></div>
                            </div>
                            <div class="col-md-4">
                                <div class="meta-label">Current Session</div>
                                <div class="meta-value"><?php echo e($requestedSession !== '' ? $requestedSession : $currentSessionName); ?></div>
                            </div>
                            <div class="col-md-4">
                                <div class="meta-label">Current Semester</div>
                                <div class="meta-value"><?php echo e($requestedSemester !== '' ? $requestedSemester : $currentSemesterName); ?></div>
                            </div>
                            <div class="col-md-4">
                                <div class="meta-label">Classification</div>
                                <div class="meta-value"><?php echo e($classificationLabel); ?></div>
                            </div>
                        </div>

                        <div class="d-flex flex-wrap gap-2 mb-3">
                            <span class="metric-pill"><i class="bi bi-check2-circle me-1"></i> Published Courses: <?php echo count($publishedRows); ?></span>
                            <span class="metric-pill"><i class="bi bi-hourglass-split me-1"></i> Outstanding: <?php echo $pendingCourseCount; ?></span>
                            <span class="metric-pill"><i class="bi bi-exclamation-circle me-1"></i> Missing Results: <?php echo $missingResultCount; ?></span>
                            <span class="metric-pill"><i class="bi bi-arrow-repeat me-1"></i> Carry-Overs: <?php echo count($carryOverRows); ?></span>
                        </div>

                        <?php if (!$publishedRows): ?>
                            <div class="empty-state">
                                <div class="fs-5 fw-semibold mb-2">No published result available for this session/semester yet.</div>
                                <div>
                                    <?php if (!empty($allScopedRows)): ?>
                                        Your result records exist in the system, but they are currently under the <strong><?php echo e($resultStatusLabel); ?></strong> workflow state and are not yet visible under the current publication rules.
                                    <?php else: ?>
                                        There is currently no result record for the selected session, semester, and level combination.
                                    <?php endif; ?>
                                </div>
                            </div>
                        <?php else: ?>
                            <div class="table-responsive">
                                <table class="table table-bordered table-striped align-middle result-table">
                                    <thead>
                                        <tr>
                                            <th>#</th>
                                            <th>Course Code</th>
                                            <th>Course Title</th>
                                            <th>Credit Unit</th>
                                            <th>CA Score</th>
                                            <th>Exam Score</th>
                                            <th>Total Score</th>
                                            <th>Grade</th>
                                            <th>Grade Point</th>
                                            <th>Remark / Status</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <?php foreach ($publishedRows as $index => $row): ?>
                                            <?php
                                                $caScore = (float)($row['ca_score'] ?? 0);
                                                $examScore = (float)($row['exam_score'] ?? 0);
                                                $assessment = portalResultAssessment($pdo, $caScore, $examScore, (float)($row['total_score'] ?? ($caScore + $examScore)));
                                                $remarkLabel = (string)($assessment['remark'] ?? (($assessment['status'] ?? '') === 'PASS' ? 'PASS' : 'FAIL'));
                                            ?>
                                            <tr>
                                                <td><?php echo (int)$index + 1; ?></td>
                                                <td class="fw-semibold"><?php echo e((string)($row['course_code'] ?? '')); ?></td>
                                                <td>
                                                    <div class="course-title"><?php echo e((string)($row['course_title'] ?? 'Untitled Course')); ?></div>
                                                    <div class="course-meta"><?php echo e((string)($row['session'] ?? '')); ?> • <?php echo e((string)($row['semester'] ?? '')); ?></div>
                                                </td>
                                                <td><?php echo e(number_format((float)($row['credit_unit'] ?? 0), 0)); ?></td>
                                                <td><?php echo e(number_format($caScore, 2)); ?></td>
                                                <td><?php echo e(number_format($examScore, 2)); ?></td>
                                                <td><?php echo e(number_format((float)($assessment['total_score'] ?? 0), 2)); ?></td>
                                                <td><?php echo e((string)($assessment['grade'] ?? ($row['grade'] ?? ''))); ?></td>
                                                <td><?php echo e(number_format((float)($assessment['grade_point'] ?? ($row['grade_point'] ?? 0)), 2)); ?></td>
                                                <td>
                                                    <div class="fw-semibold"><?php echo e($remarkLabel); ?></div>
                                                    <span class="badge bg-success results-mini-badge">Published</span>
                                                </td>
                                            </tr>
                                        <?php endforeach; ?>
                                    </tbody>
                                </table>
                            </div>
                            <?php if ($resultPrintMode): ?>
                                <div class="print-note mt-3">Tip: use your browser print dialog to save this published result view as PDF.</div>
                            <?php endif; ?>
                        <?php endif; ?>
                    </div>
                </div>
            </div>

            <div class="col-xl-4">
                <div class="card results-card mb-3">
                    <div class="card-header bg-white">
                        <strong>Carry-Over / Outstanding Indicators</strong>
                    </div>
                    <div class="card-body">
                        <?php if (!$carryOverRows && $pendingCourseCount <= 0 && $missingResultCount <= 0): ?>
                            <div class="text-muted">No carry-over, missing, or outstanding result issue was detected for the current filter.</div>
                        <?php else: ?>
                            <ul class="list-group list-group-flush">
                                <?php foreach ($carryOverRows as $row): ?>
                                    <li class="list-group-item px-0">
                                        <div class="fw-semibold"><?php echo e((string)($row['course_code'] ?? '')); ?> - <?php echo e((string)($row['course_title'] ?? '')); ?></div>
                                        <div class="small text-muted">Carry-over course based on the current published result.</div>
                                    </li>
                                <?php endforeach; ?>
                                <?php if ($pendingCourseCount > 0): ?>
                                    <li class="list-group-item px-0">
                                        <div class="fw-semibold"><?php echo (int)$pendingCourseCount; ?> course result(s) outstanding</div>
                                        <div class="small text-muted">These courses have result records in the system but are not yet published to the student portal.</div>
                                    </li>
                                <?php endif; ?>
                                <?php if ($missingResultCount > 0): ?>
                                    <li class="list-group-item px-0">
                                        <div class="fw-semibold"><?php echo (int)$missingResultCount; ?> registered course(s) missing result rows</div>
                                        <div class="small text-muted">These registered courses do not yet have result rows under the selected filter scope.</div>
                                    </li>
                                <?php endif; ?>
                            </ul>
                        <?php endif; ?>
                    </div>
                </div>

                <div class="card results-card mb-3">
                    <div class="card-header bg-white">
                        <strong>Grade Interpretation Key</strong>
                    </div>
                    <div class="card-body">
                        <div class="table-responsive">
                            <table class="table table-sm table-bordered align-middle mb-0">
                                <thead>
                                    <tr>
                                        <th>Grade</th>
                                        <th>Score Range</th>
                                        <th>Point</th>
                                        <th>Remark</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <?php foreach ($gradeKeyRows as $gradeRow): ?>
                                        <tr>
                                            <td><?php echo e((string)($gradeRow['grade'] ?? '')); ?></td>
                                            <td><?php echo e((string)($gradeRow['range_label'] ?? '')); ?></td>
                                            <td><?php echo e(number_format((float)($gradeRow['point'] ?? 0), 2)); ?></td>
                                            <td><?php echo e((string)($gradeRow['remark'] ?? '')); ?></td>
                                        </tr>
                                    <?php endforeach; ?>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>

                <div class="card results-card">
                    <div class="card-header bg-white d-flex justify-content-between align-items-center">
                        <strong>Result History</strong>
                        <span class="badge bg-light text-dark border"><?php echo count($historyGroups); ?></span>
                    </div>
                    <div class="card-body">
                        <?php if (!$historyGroups): ?>
                            <div class="text-muted">No published result history is available yet.</div>
                        <?php else: ?>
                            <div class="d-grid gap-2">
                                <?php foreach ($historyGroups as $history): ?>
                                    <?php $historySummary = (array)($history['summary'] ?? []); ?>
                                    <a class="history-link no-print" href="?page=student_results&session_name=<?php echo urlencode((string)($history['session'] ?? '')); ?>&semester=<?php echo urlencode((string)($history['semester'] ?? '')); ?>&level=">
                                        <div class="d-flex justify-content-between align-items-center gap-3">
                                            <div>
                                                <div class="fw-semibold"><?php echo e((string)($history['session'] ?? '')); ?> • <?php echo e((string)($history['semester'] ?? '')); ?> Semester</div>
                                                <div class="small text-muted"><?php echo (int)($historySummary['course_count'] ?? 0); ?> published course(s)</div>
                                            </div>
                                            <div class="text-end">
                                                <div class="small text-muted">GPA</div>
                                                <div class="fw-bold text-primary"><?php echo e(number_format((float)($historySummary['gpa'] ?? 0), $gpaDecimals)); ?></div>
                                            </div>
                                        </div>
                                    </a>
                                <?php endforeach; ?>
                            </div>
                            <?php if ($resultPrintMode): ?>
                                <div class="small text-muted">Use the normal portal view to switch between previous session and semester results.</div>
                            <?php endif; ?>
                        <?php endif; ?>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <?php
    break;

case 'student_attendance':
    if (!isLoggedIn() || isAdmin()) { redirect('?page=login'); }
    try { ensureStudentAttendanceModule($pdo); } catch (Throwable $e) {}
    if (studentAttendanceSetting($pdo, 'attendance_show_student_portal', '1') !== '1') {
        flash('danger', 'Attendance is currently unavailable in the student portal.');
        redirect('?page=student_dashboard');
    }
    $studentId = (int)($_SESSION['user_id'] ?? 0);
    $sessionName = trim((string)($_GET['session_name'] ?? ''));
    $semester = trim((string)($_GET['semester'] ?? ''));
    $courseId = (int)($_GET['course_id'] ?? 0);
    $studentRows = [];
    try {
        $sql = "SELECT sa.*, rc.course_code, rc.course_title
                FROM student_attendance sa
                LEFT JOIN registrable_courses rc ON rc.id = sa.course_id
                WHERE sa.student_id = ?";
        $params = [$studentId];
        if ($sessionName !== '') { $sql .= " AND sa.session_name = ?"; $params[] = $sessionName; }
        if ($semester !== '') { $sql .= " AND sa.semester = ?"; $params[] = $semester; }
        if ($courseId > 0) { $sql .= " AND sa.course_id = ?"; $params[] = $courseId; }
        $sql .= " ORDER BY sa.attendance_date DESC, rc.course_code ASC";
        $st = $pdo->prepare($sql);
        $st->execute($params);
        $studentRows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) { $studentRows = []; }

    $summary = [];
    foreach ($studentRows as $r) {
        $cid = (int)($r['course_id'] ?? 0);
        if (!isset($summary[$cid])) {
            $summary[$cid] = ['course_code'=>(string)($r['course_code'] ?? ''),'course_title'=>(string)($r['course_title'] ?? ''),'total'=>0,'present'=>0,'absent'=>0,'late'=>0,'excused'=>0];
        }
        $summary[$cid]['total']++;
        $k = strtolower((string)($r['status'] ?? 'present'));
        if (!isset($summary[$cid][$k])) $summary[$cid][$k] = 0;
        $summary[$cid][$k]++;
    }

    $courses = [];
    try {
        $sql = "SELECT DISTINCT sa.course_id, rc.course_code, rc.course_title FROM student_attendance sa LEFT JOIN registrable_courses rc ON rc.id=sa.course_id WHERE sa.student_id=? ORDER BY rc.course_code ASC";
        $st = $pdo->prepare($sql); $st->execute([$studentId]); $courses = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {}
    $sessions = [];
    try { $st = $pdo->prepare("SELECT DISTINCT session_name FROM student_attendance WHERE student_id=? ORDER BY session_name DESC"); $st->execute([$studentId]); $sessions = $st->fetchAll(PDO::FETCH_COLUMN) ?: []; } catch (Throwable $e) {}
    ?>
    <div class="container-fluid">
        <div class="d-flex align-items-center justify-content-between mb-3"><h4 class="mb-0">My Attendance</h4></div>
        <div class="card mb-3"><div class="card-body"><form method="get" class="row g-2 align-items-end"><input type="hidden" name="page" value="student_attendance"><div class="col-md-3"><label class="form-label">Session</label><select class="form-select" name="session_name"><option value="">All</option><?php foreach ($sessions as $sn): ?><option value="<?php echo e($sn); ?>" <?php echo $sessionName===$sn?'selected':''; ?>><?php echo e($sn); ?></option><?php endforeach; ?></select></div><div class="col-md-3"><label class="form-label">Semester</label><select class="form-select" name="semester"><option value="">All</option><option value="First" <?php echo $semester==='First'?'selected':''; ?>>First</option><option value="Second" <?php echo $semester==='Second'?'selected':''; ?>>Second</option></select></div><div class="col-md-4"><label class="form-label">Course</label><select class="form-select" name="course_id"><option value="0">All</option><?php foreach ($courses as $c): ?><option value="<?php echo (int)$c['course_id']; ?>" <?php echo $courseId===(int)$c['course_id']?'selected':''; ?>><?php echo e(($c['course_code'] ?? '') . ' - ' . ($c['course_title'] ?? '')); ?></option><?php endforeach; ?></select></div><div class="col-md-2"><button class="btn btn-primary w-100" type="submit">Filter</button></div></form></div></div>
        <div class="card mb-3"><div class="card-header"><strong>Attendance Summary</strong></div><div class="card-body"><div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr><th>Course</th><th>Classes Attended</th><th>Classes Missed</th><th>Late</th><th>Excused</th><th>Total Classes</th><th>Attendance %</th></tr></thead><tbody><?php if (!$summary): ?><tr><td colspan="7" class="text-center text-muted">No attendance records yet.</td></tr><?php else: foreach ($summary as $row): $attended = (int)($row['present'] ?? 0); $missed = (int)($row['absent'] ?? 0); $late = (int)($row['late'] ?? 0); $excused = (int)($row['excused'] ?? 0); $total = (int)($row['total'] ?? 0); $pct = $total > 0 ? round((($attended + $late + $excused) / $total) * 100, 2) : 0; ?><tr><td><?php echo e(($row['course_code'] ?? '') . ' - ' . ($row['course_title'] ?? '')); ?></td><td><?php echo $attended; ?></td><td><?php echo $missed; ?></td><td><?php echo $late; ?></td><td><?php echo $excused; ?></td><td><?php echo $total; ?></td><td><?php echo e(number_format($pct, 2)); ?>%</td></tr><?php endforeach; endif; ?></tbody></table></div></div></div>
        <div class="card"><div class="card-header"><strong>Attendance Records</strong></div><div class="card-body"><div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr><th>Date</th><th>Course</th><th>Status</th><th>Remark</th><th>Venue</th><th>Session</th><th>Semester</th></tr></thead><tbody><?php if (!$studentRows): ?><tr><td colspan="7" class="text-center text-muted">No attendance records found.</td></tr><?php else: foreach ($studentRows as $r): ?><tr><td><?php echo e($r['attendance_date'] ?? ''); ?></td><td><?php echo e(($r['course_code'] ?? '') . ' - ' . ($r['course_title'] ?? '')); ?></td><td><?php echo e($r['status'] ?? ''); ?></td><td><?php echo e($r['remark'] ?? ''); ?></td><td><?php echo e($r['venue'] ?? ''); ?></td><td><?php echo e($r['session_name'] ?? ''); ?></td><td><?php echo e($r['semester'] ?? ''); ?></td></tr><?php endforeach; endif; ?></tbody></table></div></div></div>
    </div>

    

    <?php
    break;

case 'grading_result_settings':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    portalRequireAcademicResultPagesAccess($pdo);
    $gradingSettings = portalGradingResultSettings($pdo);
    $gradeScaleRows = portalGradeScaleRows($pdo);
    $gradeScaleRangeRows = portalBuildGradeScaleRangeRowsFromSettings($gradingSettings);
    $gradingUpdatedMeta = portalGradingResultSettingsUpdatedMeta($pdo);
    $actionOptions = portalResultRuleActionOptions();
    $roundingOptions = portalResultRoundingModeOptions();
    $maxTotalObtainable = (float)($gradingSettings['result_ca_max_score'] ?? 40) + (float)($gradingSettings['result_exam_max_score'] ?? 60);
    $gradingDefaultsJson = json_encode(portalGradingResultSettingsDefaults(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
    $gradingCurrentJson = json_encode($gradingSettings, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
    ?>
    <div class="container-fluid grading-result-settings-page">
        <style>
            .grading-result-settings-page .grading-settings-header{background:linear-gradient(135deg,#0b3f7b 0%, #0a376a 100%) !important;color:#fff;border-radius:.75rem .75rem 0 0;}
            .grading-result-settings-page .grading-settings-header h4,
            .grading-result-settings-page .grading-settings-header .small,
            .grading-result-settings-page .grading-settings-header .grading-meta{color:#fff !important;}
            .grading-result-settings-page .grading-settings-header .small{opacity:.92;}
            .grading-result-settings-page .grading-summary-card{border:1px solid rgba(11,63,123,.12);border-radius:1rem;height:100%;box-shadow:0 .35rem 1rem rgba(15,23,42,.06);}
            .grading-result-settings-page .grading-summary-card .summary-label{font-size:.78rem;letter-spacing:.04em;text-transform:uppercase;color:#6c757d;}
            .grading-result-settings-page .grading-summary-card .summary-value{font-weight:700;color:#0b3f7b;}
            .grading-result-settings-page .grading-section-card{border:1px solid rgba(11,63,123,.12);border-radius:1rem;}
            .grading-result-settings-page .grading-preview-output{background:#f8fafc;border:1px dashed rgba(11,63,123,.22);border-radius:.85rem;padding:.9rem 1rem;height:100%;}
            .grading-result-settings-page .grading-preview-output .value{font-size:1.15rem;font-weight:700;color:#0b3f7b;}
            .grading-result-settings-page .grading-toolbar{gap:.75rem;}
            .grading-result-settings-page .nav-tabs .nav-link{color:#0b57d0;}
            .grading-result-settings-page .nav-tabs .nav-link.active{color:#212529;font-weight:600;}
            .grading-result-settings-page .grading-help-list{margin:0;padding-left:1.1rem;}
            .grading-result-settings-page .grading-help-list li{margin-bottom:.25rem;}
            .grading-result-settings-page .grading-status-badge{display:inline-flex;align-items:center;gap:.35rem;padding:.35rem .65rem;border-radius:999px;background:#e7f1ff;color:#0b3f7b;font-weight:700;}
        </style>
        <div class="card shadow-sm border-0">
            <div class="card-header grading-settings-header d-flex justify-content-between align-items-center flex-wrap gap-3">
                <div>
                    <h4 class="mb-1">Grading &amp; Result Settings</h4>
                    <div class="small">Configure CA, EXAM, GRADE, CGP, and CGPA behavior from one central admin page.</div>
                </div>
                <div class="grading-meta text-md-end small">
                    <div><strong>Scope:</strong> Global portal-wide grading configuration</div>
                    <div><strong>Last Updated By:</strong> <?php echo e((string)($gradingUpdatedMeta['updated_by'] ?? '') ?: 'Not yet recorded'); ?></div>
                    <div><strong>Last Updated On:</strong> <?php echo e((string)($gradingUpdatedMeta['updated_on'] ?? '') ?: 'Not yet recorded'); ?></div>
                </div>
            </div>
            <div class="card-body">
                <?php renderFlashes(); ?>

                <div class="row g-3 mb-3">
                    <div class="col-xl-4 col-md-6">
                        <div class="grading-summary-card p-3">
                            <div class="summary-label mb-2">Active Scoring Formula</div>
                            <div class="summary-value mb-2" id="gradingSummaryFormula">Total Score = <?php echo e((string)($gradingSettings['result_ca_label'] ?? 'CA')); ?> + <?php echo e((string)($gradingSettings['result_exam_label'] ?? 'EXAM')); ?></div>
                            <ul class="grading-help-list small text-muted">
                                <li id="gradingSummaryPassMark">Pass Mark = <?php echo e(rtrim(rtrim(number_format((float)($gradingSettings['result_pass_mark'] ?? 40), 2, '.', ''), '0'), '.')); ?></li>
                                <li id="gradingSummaryMinCa">Minimum <?php echo e((string)($gradingSettings['result_ca_label'] ?? 'CA')); ?> rule = <?php echo e(rtrim(rtrim(number_format((float)($gradingSettings['result_minimum_ca_score'] ?? 0), 2, '.', ''), '0'), '.')); ?> → <?php echo e(portalResultRuleActionLabel((string)($gradingSettings['result_minimum_ca_action'] ?? 'fail'))); ?></li>
                                <li id="gradingSummaryMinExam">Minimum EXAM rule = <?php echo portalNormalizeBooleanSettingValue($gradingSettings['result_exam_minimum_required'] ?? '0') === '1' ? e(rtrim(rtrim(number_format((float)($gradingSettings['result_exam_minimum_score'] ?? 0), 2, '.', ''), '0'), '.') . ' → ' . portalResultRuleActionLabel((string)($gradingSettings['result_exam_below_minimum_action'] ?? 'fail'))) : 'Disabled'; ?></li>
                                <li id="gradingSummaryBelowPass">Below pass mark action = <?php echo e(portalResultRuleActionLabel((string)($gradingSettings['result_below_pass_mark_action'] ?? 'fail'))); ?></li>
                            </ul>
                        </div>
                    </div>
                    <div class="col-xl-4 col-md-6">
                        <div class="grading-summary-card p-3">
                            <div class="summary-label mb-2">Current Grade Scale</div>
                            <div class="small text-muted mb-2">Configured remarks now flow through result-related areas that already read from the grading engine.</div>
                            <div class="summary-value" style="font-size:.98rem; line-height:1.6;" id="gradingSummaryScale"><?php echo e(portalFormatGradeScaleSummaryText($gradeScaleRangeRows)); ?></div>
                        </div>
                    </div>
                    <div class="col-xl-4 col-md-12">
                        <div class="grading-summary-card p-3">
                            <div class="summary-label mb-2">Scope Visibility</div>
                            <div class="summary-value mb-2">Global</div>
                            <div class="small text-muted">This build currently applies grading settings globally across the portal. No scoped override is active here, so all saved rules remain portal-wide unless you later extend scope logic intentionally.</div>
                        </div>
                    </div>
                </div>

                <div class="alert alert-info border d-flex justify-content-between align-items-start flex-wrap gap-2 mb-3">
                    <div>
                        <div class="fw-semibold">Professional safeguards are active on this page.</div>
                        <div class="small mb-0">Server-side and client-side checks validate score ranges, pass mark limits, grade ordering, and rounding configuration before save.</div>
                    </div>
                    <div class="small text-muted">Total Obtainable Score: <strong id="gradingTotalObtainableText"><?php echo e(rtrim(rtrim(number_format($maxTotalObtainable, 2, '.', ''), '0'), '.')); ?></strong></div>
                </div>

                <div id="gradingSettingsValidationAlert" class="alert alert-danger d-none"></div>

                <form method="post" id="gradingResultSettingsForm" novalidate>
                    <input type="hidden" name="action" value="save_grading_result_settings">
                    <input type="hidden" name="grading_settings_active_tab" id="grading_settings_active_tab" value="grading_ca">

                    <ul class="nav nav-tabs mb-3" role="tablist">
                        <li class="nav-item" role="presentation"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#grading_ca" type="button" role="tab">CA Settings</button></li>
                        <li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#grading_exam" type="button" role="tab">EXAM Settings</button></li>
                        <li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#grading_grade" type="button" role="tab">GRADE Settings</button></li>
                        <li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#grading_cgp" type="button" role="tab">CGP Settings</button></li>
                        <li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#grading_cgpa" type="button" role="tab">CGPA Settings</button></li>
                    </ul>

                    <div class="tab-content">
                        <div class="tab-pane fade show active" id="grading_ca" role="tabpanel">
                            <div class="grading-section-card p-3">
                                <div class="row g-3">
                                    <div class="col-lg-3 col-md-6">
                                        <label class="form-label">CA Label</label>
                                        <input type="text" class="form-control" data-grading-tab="grading_ca" name="result_ca_label" value="<?php echo e((string)($gradingSettings['result_ca_label'] ?? 'CA')); ?>">
                                    </div>
                                    <div class="col-lg-3 col-md-6">
                                        <label class="form-label">Maximum CA Score</label>
                                        <input type="number" step="0.01" min="0" class="form-control" data-grading-tab="grading_ca" name="result_ca_max_score" value="<?php echo e((string)($gradingSettings['result_ca_max_score'] ?? '40')); ?>">
                                    </div>
                                    <div class="col-lg-3 col-md-6">
                                        <label class="form-label">Minimum CA Score</label>
                                        <input type="number" step="0.01" min="0" class="form-control" data-grading-tab="grading_ca" name="result_minimum_ca_score" value="<?php echo e((string)($gradingSettings['result_minimum_ca_score'] ?? '0')); ?>">
                                    </div>
                                    <div class="col-lg-3 col-md-6">
                                        <label class="form-label">If CA is Below Minimum</label>
                                        <select class="form-select" data-grading-tab="grading_ca" name="result_minimum_ca_action">
                                            <?php foreach ($actionOptions as $actionValue => $actionLabel): ?>
                                                <option value="<?php echo e($actionValue); ?>" <?php echo (($gradingSettings['result_minimum_ca_action'] ?? 'fail') === $actionValue) ? 'selected' : ''; ?>><?php echo e($actionLabel); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-12">
                                        <div class="form-text">Students below the configured CA minimum cannot pass, even if the EXAM score is full marks. The configured action above will be applied safely without changing your existing saved settings unless you update it.</div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="tab-pane fade" id="grading_exam" role="tabpanel">
                            <div class="grading-section-card p-3">
                                <div class="row g-3">
                                    <div class="col-lg-3 col-md-6">
                                        <label class="form-label">EXAM Label</label>
                                        <input type="text" class="form-control" data-grading-tab="grading_exam" name="result_exam_label" value="<?php echo e((string)($gradingSettings['result_exam_label'] ?? 'EXAM')); ?>">
                                    </div>
                                    <div class="col-lg-3 col-md-6">
                                        <label class="form-label">Maximum EXAM Score</label>
                                        <input type="number" step="0.01" min="0" class="form-control" data-grading-tab="grading_exam" name="result_exam_max_score" value="<?php echo e((string)($gradingSettings['result_exam_max_score'] ?? '60')); ?>">
                                    </div>
                                    <div class="col-lg-3 col-md-6">
                                        <label class="form-label">Minimum EXAM Score</label>
                                        <input type="number" step="0.01" min="0" class="form-control" data-grading-tab="grading_exam" name="result_exam_minimum_score" value="<?php echo e((string)($gradingSettings['result_exam_minimum_score'] ?? '0')); ?>">
                                    </div>
                                    <div class="col-lg-3 col-md-6">
                                        <label class="form-label">Minimum EXAM Required Before Pass?</label>
                                        <select class="form-select" data-grading-tab="grading_exam" name="result_exam_minimum_required">
                                            <option value="0" <?php echo (($gradingSettings['result_exam_minimum_required'] ?? '0') === '0') ? 'selected' : ''; ?>>No</option>
                                            <option value="1" <?php echo (($gradingSettings['result_exam_minimum_required'] ?? '0') === '1') ? 'selected' : ''; ?>>Yes</option>
                                        </select>
                                    </div>
                                    <div class="col-lg-4 col-md-6">
                                        <label class="form-label">If EXAM is Below Minimum</label>
                                        <select class="form-select" data-grading-tab="grading_exam" name="result_exam_below_minimum_action">
                                            <?php foreach ($actionOptions as $actionValue => $actionLabel): ?>
                                                <option value="<?php echo e($actionValue); ?>" <?php echo (($gradingSettings['result_exam_below_minimum_action'] ?? 'fail') === $actionValue) ? 'selected' : ''; ?>><?php echo e($actionLabel); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-lg-8 col-md-6">
                                        <div class="alert alert-light border mb-0 h-100">
                                            <div class="fw-semibold mb-1">EXAM Rule Control</div>
                                            <div class="small text-muted">Use this area to enforce whether a student must reach a minimum EXAM score before a pass can stand. Leave it disabled to preserve the current pass-mark-only behavior.</div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="tab-pane fade" id="grading_grade" role="tabpanel">
                            <div class="grading-section-card p-3">
                                <div class="row g-3">
                                    <div class="col-lg-4 col-md-6">
                                        <label class="form-label">Pass Mark</label>
                                        <input type="number" step="0.01" min="0" class="form-control" data-grading-tab="grading_grade" name="result_pass_mark" value="<?php echo e((string)($gradingSettings['result_pass_mark'] ?? '40')); ?>">
                                    </div>
                                    <div class="col-lg-4 col-md-6">
                                        <label class="form-label">If Total Score is Below Pass Mark</label>
                                        <select class="form-select" data-grading-tab="grading_grade" name="result_below_pass_mark_action">
                                            <?php foreach ($actionOptions as $actionValue => $actionLabel): ?>
                                                <option value="<?php echo e($actionValue); ?>" <?php echo (($gradingSettings['result_below_pass_mark_action'] ?? 'fail') === $actionValue) ? 'selected' : ''; ?>><?php echo e($actionLabel); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-lg-4 col-md-12">
                                        <div class="alert alert-light border mb-0 h-100">
                                            <div class="fw-semibold">Total Obtainable Score</div>
                                            <div class="fs-4 text-primary fw-bold" id="gradingTotalObtainableBox"><?php echo e(rtrim(rtrim(number_format($maxTotalObtainable, 2, '.', ''), '0'), '.')); ?></div>
                                            <div class="small text-muted">Calculated as Maximum CA Score + Maximum EXAM Score.</div>
                                        </div>
                                    </div>

                                    <div class="col-md-12">
                                        <div class="table-responsive">
                                            <table class="table table-bordered align-middle mb-0">
                                                <thead class="table-light">
                                                    <tr>
                                                        <th>Grade</th>
                                                        <th>Minimum Score</th>
                                                        <th>Grade Point</th>
                                                        <th>Remark</th>
                                                    </tr>
                                                </thead>
                                                <tbody>
                                                    <?php foreach (['a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'e' => 'E', 'f' => 'F'] as $suffix => $label): ?>
                                                        <tr>
                                                            <td><?php echo e($label); ?></td>
                                                            <td><input type="number" step="0.01" min="0" class="form-control" data-grading-tab="grading_grade" name="result_grade_<?php echo e($suffix); ?>_min" value="<?php echo e((string)($gradingSettings['result_grade_' . $suffix . '_min'] ?? '0')); ?>"></td>
                                                            <td><input type="number" step="0.01" min="0" class="form-control" data-grading-tab="grading_grade" name="result_grade_<?php echo e($suffix); ?>_point" value="<?php echo e((string)($gradingSettings['result_grade_' . $suffix . '_point'] ?? '0')); ?>"></td>
                                                            <td><input type="text" class="form-control" data-grading-tab="grading_grade" name="result_grade_<?php echo e($suffix); ?>_remark" value="<?php echo e((string)($gradingSettings['result_grade_' . $suffix . '_remark'] ?? '')); ?>" placeholder="Enter remark"></td>
                                                        </tr>
                                                    <?php endforeach; ?>
                                                </tbody>
                                            </table>
                                        </div>
                                    </div>

                                    <div class="col-md-12">
                                        <div class="alert alert-light border mb-0">Current grading scale: <span id="gradingScaleSummaryInline"><?php echo e(portalFormatGradeScaleSummaryText($gradeScaleRangeRows)); ?></span></div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="tab-pane fade" id="grading_cgp" role="tabpanel">
                            <div class="grading-section-card p-3">
                                <div class="row g-3">
                                    <div class="col-lg-4 col-md-6">
                                        <label class="form-label">CGP Label</label>
                                        <input type="text" class="form-control" data-grading-tab="grading_cgp" name="result_cgp_label" value="<?php echo e((string)($gradingSettings['result_cgp_label'] ?? 'CGP')); ?>">
                                    </div>
                                    <div class="col-lg-4 col-md-6">
                                        <label class="form-label">CGP Decimal Places</label>
                                        <input type="number" min="0" max="6" class="form-control" data-grading-tab="grading_cgp" name="result_cgp_decimal_places" value="<?php echo e((string)($gradingSettings['result_cgp_decimal_places'] ?? '2')); ?>">
                                    </div>
                                    <div class="col-lg-4 col-md-12">
                                        <label class="form-label">CGP Rounding Mode</label>
                                        <select class="form-select" data-grading-tab="grading_cgp" name="result_cgp_rounding_mode">
                                            <?php foreach ($roundingOptions as $roundKey => $roundLabel): ?>
                                                <option value="<?php echo e($roundKey); ?>" <?php echo (($gradingSettings['result_cgp_rounding_mode'] ?? 'half_up') === $roundKey) ? 'selected' : ''; ?>><?php echo e($roundLabel); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="tab-pane fade" id="grading_cgpa" role="tabpanel">
                            <div class="grading-section-card p-3">
                                <div class="row g-3">
                                    <div class="col-lg-4 col-md-6">
                                        <label class="form-label">CGPA Label</label>
                                        <input type="text" class="form-control" data-grading-tab="grading_cgpa" name="result_cgpa_label" value="<?php echo e((string)($gradingSettings['result_cgpa_label'] ?? 'CGPA')); ?>">
                                    </div>
                                    <div class="col-lg-4 col-md-6">
                                        <label class="form-label">CGPA Decimal Places</label>
                                        <input type="number" min="0" max="6" class="form-control" data-grading-tab="grading_cgpa" name="result_cgpa_decimal_places" value="<?php echo e((string)($gradingSettings['result_cgpa_decimal_places'] ?? '2')); ?>">
                                    </div>
                                    <div class="col-lg-4 col-md-12">
                                        <label class="form-label">CGPA Rounding Mode</label>
                                        <select class="form-select" data-grading-tab="grading_cgpa" name="result_cgpa_rounding_mode">
                                            <?php foreach ($roundingOptions as $roundKey => $roundLabel): ?>
                                                <option value="<?php echo e($roundKey); ?>" <?php echo (($gradingSettings['result_cgpa_rounding_mode'] ?? 'half_up') === $roundKey) ? 'selected' : ''; ?>><?php echo e($roundLabel); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-12">
                                        <label class="form-label">Classification Rules</label>
                                        <textarea class="form-control" data-grading-tab="grading_cgpa" name="result_classification_rules" rows="8"><?php echo e((string)($gradingSettings['result_classification_rules'] ?? '')); ?></textarea>
                                        <div class="form-text">Use one rule per line. Example: 4.50 - 5.00 : First Class</div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="grading-section-card p-3 mt-3">
                        <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
                            <div>
                                <h5 class="mb-1">Live Grading Preview Tool</h5>
                                <div class="small text-muted">Test sample scores against the current form values. This preview updates from the real configuration on the page, not hardcoded rules.</div>
                            </div>
                            <span class="grading-status-badge" id="previewStatusBadge">PASS</span>
                        </div>
                        <div class="row g-3 align-items-stretch">
                            <div class="col-lg-3 col-md-6">
                                <label class="form-label" id="previewCaLabel">Sample CA Score</label>
                                <input type="number" step="0.01" min="0" class="form-control" id="preview_ca_score" value="<?php echo e((string)($gradingSettings['result_minimum_ca_score'] ?? '0')); ?>">
                            </div>
                            <div class="col-lg-3 col-md-6">
                                <label class="form-label" id="previewExamLabel">Sample EXAM Score</label>
                                <input type="number" step="0.01" min="0" class="form-control" id="preview_exam_score" value="<?php echo e((string)($gradingSettings['result_exam_minimum_score'] ?? '0')); ?>">
                            </div>
                            <div class="col-lg-2 col-md-4">
                                <div class="grading-preview-output">
                                    <div class="small text-muted">Total Score</div>
                                    <div class="value" id="previewTotalScore">0</div>
                                </div>
                            </div>
                            <div class="col-lg-2 col-md-4">
                                <div class="grading-preview-output">
                                    <div class="small text-muted">Grade</div>
                                    <div class="value" id="previewGrade">F</div>
                                </div>
                            </div>
                            <div class="col-lg-2 col-md-4">
                                <div class="grading-preview-output">
                                    <div class="small text-muted">Grade Point</div>
                                    <div class="value" id="previewGradePoint">0</div>
                                </div>
                            </div>
                            <div class="col-lg-4 col-md-6">
                                <div class="grading-preview-output">
                                    <div class="small text-muted">Remark</div>
                                    <div class="value" id="previewRemark">Fail</div>
                                </div>
                            </div>
                            <div class="col-lg-4 col-md-6">
                                <div class="grading-preview-output">
                                    <div class="small text-muted">Applied Rule</div>
                                    <div class="value" id="previewAppliedRule">Grade scale</div>
                                </div>
                            </div>
                            <div class="col-lg-4">
                                <div class="grading-preview-output">
                                    <div class="small text-muted">Result Status</div>
                                    <div class="value" id="previewStatusText">PASS</div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="mt-3 d-flex justify-content-between align-items-center flex-wrap grading-toolbar">
                        <div class="d-flex flex-wrap grading-toolbar">
                            <button type="button" class="btn btn-outline-warning" id="gradingRestoreTabDefaultsBtn"><i class="bi bi-arrow-counterclockwise"></i> Restore Recommended Defaults (Current Tab)</button>
                            <button type="button" class="btn btn-outline-secondary" id="gradingResetUnsavedBtn"><i class="bi bi-x-circle"></i> Reset Unsaved Changes (Current Tab)</button>
                        </div>
                        <button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> Save Grading &amp; Result Settings</button>
                    </div>
                </form>
            </div>
        </div>

        <script>
        (function () {
            const form = document.getElementById('gradingResultSettingsForm');
            if (!form) return;

            const defaults = <?php echo $gradingDefaultsJson ?: '{}'; ?>;
            const originalValues = <?php echo $gradingCurrentJson ?: '{}'; ?>;
            const actionLabels = {fail: 'Fail', carry_over: 'Carry Over'};
            const validationAlert = document.getElementById('gradingSettingsValidationAlert');
            const activeTabInput = document.getElementById('grading_settings_active_tab');
            const previewStatusBadge = document.getElementById('previewStatusBadge');
            let submitted = false;

            function field(name) {
                return form.elements[name] || null;
            }
            function getValue(name, fallback = '') {
                const el = field(name);
                if (!el) return fallback;
                return typeof el.value === 'string' ? el.value : fallback;
            }
            function setValue(name, value) {
                const el = field(name);
                if (!el) return;
                el.value = value == null ? '' : String(value);
            }
            function num(name) {
                const parsed = parseFloat(getValue(name, '0'));
                return Number.isFinite(parsed) ? parsed : 0;
            }
            function text(name, fallback = '') {
                const value = String(getValue(name, fallback)).trim();
                return value !== '' ? value : fallback;
            }
            function formatNumber(value) {
                const number = Number(value);
                if (!Number.isFinite(number)) return '0';
                return number.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 2});
            }
            function actionLabel(value) {
                const key = String(value || 'fail').toLowerCase();
                return actionLabels[key] || 'Fail';
            }
            function buildGradeRows() {
                return ['a','b','c','d','e','f'].map(function (suffix) {
                    return {
                        grade: suffix.toUpperCase(),
                        min: num('result_grade_' + suffix + '_min'),
                        point: num('result_grade_' + suffix + '_point'),
                        remark: text('result_grade_' + suffix + '_remark', suffix === 'f' ? 'Fail' : '')
                    };
                }).sort(function (left, right) {
                    return right.min - left.min;
                });
            }
            function serializeForm() {
                const data = {};
                Array.prototype.forEach.call(form.elements, function (el) {
                    if (!el.name || el.disabled) return;
                    if (el.name === 'grading_settings_active_tab') return;
                    if (['submit', 'button', 'file'].includes(el.type)) return;
                    data[el.name] = el.value;
                });
                return JSON.stringify(data);
            }
            const initialSnapshot = serializeForm();

            function showErrors(errors) {
                if (!validationAlert) return;
                if (!errors.length) {
                    validationAlert.classList.add('d-none');
                    validationAlert.innerHTML = '';
                    return;
                }
                validationAlert.classList.remove('d-none');
                validationAlert.innerHTML = '<div class="fw-semibold mb-1">Please correct the following before saving:</div><ul class="mb-0"><li>' + errors.join('</li><li>') + '</li></ul>';
            }

            function validateForm() {
                const errors = [];
                const maxCa = num('result_ca_max_score');
                const minCa = num('result_minimum_ca_score');
                const maxExam = num('result_exam_max_score');
                const minExam = num('result_exam_minimum_score');
                const totalMax = maxCa + maxExam;
                const passMark = num('result_pass_mark');
                const mins = {
                    a: num('result_grade_a_min'),
                    b: num('result_grade_b_min'),
                    c: num('result_grade_c_min'),
                    d: num('result_grade_d_min'),
                    e: num('result_grade_e_min'),
                    f: num('result_grade_f_min')
                };
                const points = ['a','b','c','d','e','f'].map(function (suffix) {
                    return num('result_grade_' + suffix + '_point');
                });

                if (minCa > maxCa) errors.push('Minimum CA Score cannot exceed Maximum CA Score.');
                if (minExam > maxExam) errors.push('Minimum EXAM Score cannot exceed Maximum EXAM Score.');
                if (passMark > totalMax) errors.push('Pass Mark cannot exceed the total obtainable score.');
                if (!(mins.a >= mins.b && mins.b >= mins.c && mins.c >= mins.d && mins.d >= mins.e && mins.e >= mins.f)) {
                    errors.push('Grade minimums must remain logically ordered from A down to F.');
                }
                Object.keys(mins).forEach(function (key) {
                    if (mins[key] > totalMax) {
                        errors.push('Grade ' + key.toUpperCase() + ' minimum cannot exceed the total obtainable score.');
                    }
                });
                points.forEach(function (point) {
                    if (point < 0) {
                        errors.push('Grade points must be valid numeric values greater than or equal to 0.');
                    }
                });
                return errors;
            }

            function activeTabName() {
                return activeTabInput && activeTabInput.value ? activeTabInput.value : 'grading_ca';
            }

            function updateSummary() {
                const caLabel = text('result_ca_label', 'CA');
                const examLabel = text('result_exam_label', 'EXAM');
                const maxCa = num('result_ca_max_score');
                const maxExam = num('result_exam_max_score');
                const minCa = num('result_minimum_ca_score');
                const minExam = num('result_exam_minimum_score');
                const examRequired = getValue('result_exam_minimum_required', '0') === '1';
                const totalMax = maxCa + maxExam;

                document.getElementById('gradingSummaryFormula').textContent = 'Total Score = ' + caLabel + ' + ' + examLabel + ' (Max ' + formatNumber(totalMax) + ')';
                document.getElementById('gradingSummaryPassMark').textContent = 'Pass Mark = ' + formatNumber(num('result_pass_mark'));
                document.getElementById('gradingSummaryMinCa').textContent = 'Minimum ' + caLabel + ' rule = ' + formatNumber(minCa) + ' → ' + actionLabel(getValue('result_minimum_ca_action', 'fail'));
                document.getElementById('gradingSummaryMinExam').textContent = examRequired
                    ? ('Minimum EXAM rule = ' + formatNumber(minExam) + ' → ' + actionLabel(getValue('result_exam_below_minimum_action', 'fail')))
                    : 'Minimum EXAM rule = Disabled';
                document.getElementById('gradingSummaryBelowPass').textContent = 'Below pass mark action = ' + actionLabel(getValue('result_below_pass_mark_action', 'fail'));
                document.getElementById('gradingTotalObtainableText').textContent = formatNumber(totalMax);
                document.getElementById('gradingTotalObtainableBox').textContent = formatNumber(totalMax);

                const gradeRows = buildGradeRows();
                const hasDecimalRange = [totalMax].concat(gradeRows.map(function (row) { return row.min; })).some(function (value) {
                    return Math.abs(value - Math.round(value)) > 0.00001;
                });
                const rangePrecision = hasDecimalRange ? 2 : 0;
                const rangeStep = hasDecimalRange ? 0.01 : 1;
                const scaleText = gradeRows.map(function (row, index) {
                    const rawUpper = index === 0 ? totalMax : (gradeRows[index - 1].min - rangeStep);
                    const upper = Math.max(row.min, Number(rawUpper.toFixed(rangePrecision)));
                    let base = row.grade + ' = ' + formatNumber(row.min) + '–' + formatNumber(upper) + ' (' + formatNumber(row.point) + ' GP)';
                    if (row.remark) base += ' - ' + row.remark;
                    return base;
                }).join(' | ');
                document.getElementById('gradingSummaryScale').textContent = scaleText;
                document.getElementById('gradingScaleSummaryInline').textContent = scaleText;
                document.getElementById('previewCaLabel').textContent = 'Sample ' + caLabel + ' Score';
                document.getElementById('previewExamLabel').textContent = 'Sample ' + examLabel + ' Score';
            }

            function computePreview() {
                updateSummary();

                const caScore = num('preview_ca_score');
                const examScore = num('preview_exam_score');
                const total = caScore + examScore;
                const gradeRows = buildGradeRows();
                const failureRow = gradeRows.find(function (row) { return row.grade === 'F'; }) || {grade: 'F', point: 0, remark: 'Fail'};
                let gradeRow = gradeRows.find(function (row) { return total >= row.min; }) || failureRow;

                const minCa = num('result_minimum_ca_score');
                const minExam = num('result_exam_minimum_score');
                const examRequired = getValue('result_exam_minimum_required', '0') === '1';
                const passMark = num('result_pass_mark');

                let status = 'PASS';
                let remark = gradeRow.remark || 'Pass';
                let grade = gradeRow.grade;
                let point = gradeRow.point;
                let appliedRule = 'Grade scale';

                if (caScore < minCa) {
                    status = actionLabel(getValue('result_minimum_ca_action', 'fail')).toUpperCase();
                    remark = actionLabel(getValue('result_minimum_ca_action', 'fail'));
                    grade = failureRow.grade;
                    point = failureRow.point;
                    appliedRule = 'Minimum CA rule';
                } else if (examRequired && examScore < minExam) {
                    status = actionLabel(getValue('result_exam_below_minimum_action', 'fail')).toUpperCase();
                    remark = actionLabel(getValue('result_exam_below_minimum_action', 'fail'));
                    grade = failureRow.grade;
                    point = failureRow.point;
                    appliedRule = 'Minimum EXAM rule';
                } else if (total < passMark) {
                    status = actionLabel(getValue('result_below_pass_mark_action', 'fail')).toUpperCase();
                    remark = actionLabel(getValue('result_below_pass_mark_action', 'fail'));
                    appliedRule = 'Pass mark rule';
                }

                document.getElementById('previewTotalScore').textContent = formatNumber(total);
                document.getElementById('previewGrade').textContent = grade;
                document.getElementById('previewGradePoint').textContent = formatNumber(point);
                document.getElementById('previewRemark').textContent = remark;
                document.getElementById('previewAppliedRule').textContent = appliedRule;
                document.getElementById('previewStatusText').textContent = status;
                previewStatusBadge.textContent = status;
            }

            function restoreTabValues(sourceValues, confirmationText) {
                const tab = activeTabName();
                const fields = form.querySelectorAll('[data-grading-tab="' + tab + '"]');
                if (!fields.length) return;
                if (!window.confirm(confirmationText)) return;
                fields.forEach(function (el) {
                    const name = el.getAttribute('name');
                    if (!name || !(name in sourceValues)) return;
                    setValue(name, sourceValues[name]);
                });
                computePreview();
                showErrors(validateForm());
            }

            document.getElementById('gradingRestoreTabDefaultsBtn').addEventListener('click', function () {
                restoreTabValues(defaults, 'Restore recommended defaults for the current tab? This only changes the form fields until you save.');
            });
            document.getElementById('gradingResetUnsavedBtn').addEventListener('click', function () {
                restoreTabValues(originalValues, 'Reset unsaved changes for the current tab back to the last saved values?');
            });

            form.addEventListener('input', function () {
                computePreview();
                showErrors(validateForm());
            });
            form.addEventListener('change', function () {
                computePreview();
                showErrors(validateForm());
            });
            form.addEventListener('submit', function (event) {
                const errors = validateForm();
                if (errors.length) {
                    event.preventDefault();
                    showErrors(errors);
                    return;
                }
                submitted = true;
                showErrors([]);
            });

            window.addEventListener('beforeunload', function (event) {
                if (submitted) return;
                if (serializeForm() === initialSnapshot) return;
                event.preventDefault();
                event.returnValue = '';
            });

            document.querySelectorAll('[data-bs-toggle="tab"]').forEach(function (tabButton) {
                tabButton.addEventListener('shown.bs.tab', function (event) {
                    const target = event.target.getAttribute('data-bs-target') || '#grading_ca';
                    const cleanTarget = target.replace('#', '');
                    activeTabInput.value = cleanTarget;
                    if (history.replaceState) {
                        history.replaceState(null, '', '#'+cleanTarget);
                    }
                });
            });

            const incomingHash = (window.location.hash || '').replace('#', '');
            if (incomingHash) {
                const targetButton = form.querySelector('[data-bs-target="#' + incomingHash + '"]');
                if (targetButton && window.bootstrap && bootstrap.Tab) {
                    new bootstrap.Tab(targetButton).show();
                    activeTabInput.value = incomingHash;
                }
            }

            computePreview();
            showErrors([]);
        }());
        </script>
    </div>
    <?php
    break;

case 'admin_transcript':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    portalRequireAcademicResultPagesAccess($pdo);
    $transcriptSettings = portalTranscriptSettings($pdo);
    $transcriptDoc = portalDocumentContent($pdo, 'transcript');
    $transcriptTab = trim((string)($_GET['tab'] ?? 'transcript'));
    if (!in_array($transcriptTab, ['transcript','rules','display','approval'], true)) $transcriptTab = 'transcript';
    ?>
    <div class="container-fluid">
        <div class="card shadow-sm border-0">
            <div class="card-header bg-white d-flex justify-content-between align-items-center flex-wrap gap-2">
                <div>
                    <h4 class="mb-1">Transcript</h4>
                    <div class="text-muted small">Manage transcript availability, header content, grading source linkage, aggregation rules, print/download setup, and approval presentation.</div>
                </div>
                <a href="?page=admin_pages" class="btn btn-outline-secondary btn-sm"><i class="bi bi-file-earmark-richtext"></i> Open Pages Manager</a>
            </div>
            <div class="card-body">
                <?php renderFlashes(); ?>
                <form method="post">
                    <input type="hidden" name="action" value="save_transcript_settings">
                    <input type="hidden" name="current_tab" value="<?php echo e($transcriptTab); ?>">
                    <ul class="nav nav-tabs mb-3" role="tablist">
                        <li class="nav-item" role="presentation"><a class="nav-link <?php echo $transcriptTab === 'transcript' ? 'active' : ''; ?>" href="?page=admin_transcript&tab=transcript">Transcript</a></li>
                        <li class="nav-item" role="presentation"><a class="nav-link <?php echo $transcriptTab === 'rules' ? 'active' : ''; ?>" href="?page=admin_transcript&tab=rules">Rules &amp; Aggregation</a></li>
                        <li class="nav-item" role="presentation"><a class="nav-link <?php echo $transcriptTab === 'display' ? 'active' : ''; ?>" href="?page=admin_transcript&tab=display">Display Fields</a></li>
                        <li class="nav-item" role="presentation"><a class="nav-link <?php echo $transcriptTab === 'approval' ? 'active' : ''; ?>" href="?page=admin_transcript&tab=approval">Approval / Signature</a></li>
                    </ul>
                    <?php if ($transcriptTab === 'transcript'): ?>
                        <div class="alert alert-light border d-flex justify-content-between align-items-center flex-wrap gap-2">
                            <div>Control transcript availability and keep the transcript page synchronized with <strong>Admin + Pages</strong>.</div>
                            <div class="form-check form-switch fs-5 mb-0">
                                <input type="hidden" name="transcript_page_enabled_present" value="1">
                                <input class="form-check-input" type="checkbox" role="switch" id="transcript_page_enabled_toggle" name="transcript_page_enabled_toggle" value="1" <?php echo strtoupper(trim((string)($transcriptSettings['transcript_page_enabled'] ?? 'Yes'))) !== 'NO' ? 'checked' : ''; ?>>
                                <label class="form-check-label" for="transcript_page_enabled_toggle">Enable Transcript</label>
                            </div>
                        </div>
                        <div class="row g-3">
                            <div class="col-md-6"><label class="form-label">Transcript Title</label><input type="text" class="form-control" name="transcript_title_text" value="<?php echo e((string)($transcriptSettings['transcript_title_text'] ?? '')); ?>"></div>
                            <div class="col-md-6"><label class="form-label">Transcript Header</label><input type="text" class="form-control" name="transcript_header_text" value="<?php echo e((string)($transcriptSettings['transcript_header_text'] ?? '')); ?>"></div>
                            <div class="col-md-6"><label class="form-label">Generation Enabled</label><select class="form-select" name="transcript_generation_enabled"><option value="Yes" <?php echo (($transcriptSettings['transcript_generation_enabled'] ?? 'Yes') === 'Yes') ? 'selected' : ''; ?>>Yes</option><option value="No" <?php echo (($transcriptSettings['transcript_generation_enabled'] ?? 'Yes') === 'No') ? 'selected' : ''; ?>>No</option></select></div>
                            <div class="col-md-6"><label class="form-label">Result Source Linkage</label><select class="form-select" name="transcript_result_source"><option value="published_results" <?php echo (($transcriptSettings['transcript_result_source'] ?? '') === 'published_results') ? 'selected' : ''; ?>>Published results only</option><option value="approved_results" <?php echo (($transcriptSettings['transcript_result_source'] ?? '') === 'approved_results') ? 'selected' : ''; ?>>Approved results</option><option value="all_results" <?php echo (($transcriptSettings['transcript_result_source'] ?? '') === 'all_results') ? 'selected' : ''; ?>>All saved result rows</option></select></div>
                            <div class="col-md-12"><div class="alert alert-info mb-0">The document wording for Transcript is also available under <strong>Admin + Pages → Transcript</strong>. Current heading: <strong><?php echo e((string)($transcriptDoc['heading'] ?? 'ACADEMIC TRANSCRIPT')); ?></strong>.</div></div>
                        </div>
                    <?php elseif ($transcriptTab === 'rules'): ?>
                        <div class="row g-3">
                            <div class="col-md-6"><label class="form-label">Print Setup</label><select class="form-select" name="transcript_print_enabled"><option value="Yes" <?php echo (($transcriptSettings['transcript_print_enabled'] ?? 'Yes') === 'Yes') ? 'selected' : ''; ?>>Enable print</option><option value="No" <?php echo (($transcriptSettings['transcript_print_enabled'] ?? 'Yes') === 'No') ? 'selected' : ''; ?>>Disable print</option></select></div>
                            <div class="col-md-6"><label class="form-label">Download Setup</label><select class="form-select" name="transcript_download_enabled"><option value="Yes" <?php echo (($transcriptSettings['transcript_download_enabled'] ?? 'Yes') === 'Yes') ? 'selected' : ''; ?>>Enable download</option><option value="No" <?php echo (($transcriptSettings['transcript_download_enabled'] ?? 'Yes') === 'No') ? 'selected' : ''; ?>>Disable download</option></select></div>
                            <div class="col-md-6"><label class="form-label">Session Aggregation</label><select class="form-select" name="transcript_session_aggregation"><option value="all_sessions" <?php echo (($transcriptSettings['transcript_session_aggregation'] ?? '') === 'all_sessions') ? 'selected' : ''; ?>>Aggregate all sessions</option><option value="current_session" <?php echo (($transcriptSettings['transcript_session_aggregation'] ?? '') === 'current_session') ? 'selected' : ''; ?>>Current session only</option><option value="selected_session" <?php echo (($transcriptSettings['transcript_session_aggregation'] ?? '') === 'selected_session') ? 'selected' : ''; ?>>Selected session on request</option></select></div>
                            <div class="col-md-6"><label class="form-label">Semester Aggregation</label><select class="form-select" name="transcript_semester_aggregation"><option value="both_semesters" <?php echo (($transcriptSettings['transcript_semester_aggregation'] ?? '') === 'both_semesters') ? 'selected' : ''; ?>>Combine both semesters</option><option value="separate_semesters" <?php echo (($transcriptSettings['transcript_semester_aggregation'] ?? '') === 'separate_semesters') ? 'selected' : ''; ?>>Show semesters separately</option></select></div>
                        </div>
                    <?php elseif ($transcriptTab === 'display'): ?>
                        <div class="row g-3">
                            <div class="col-md-12"><label class="form-label">Transcript Display Fields</label><textarea class="form-control" name="transcript_display_fields" rows="10"><?php echo e((string)($transcriptSettings['transcript_display_fields'] ?? '')); ?></textarea><div class="form-text">Use one field per line for the transcript body, summary, remarks, classification, and academic totals.</div></div>
                            <div class="col-md-6"><label class="form-label">Show Remarks</label><select class="form-select" name="transcript_show_remarks"><option value="Yes" <?php echo (($transcriptSettings['transcript_show_remarks'] ?? 'Yes') === 'Yes') ? 'selected' : ''; ?>>Yes</option><option value="No" <?php echo (($transcriptSettings['transcript_show_remarks'] ?? 'Yes') === 'No') ? 'selected' : ''; ?>>No</option></select></div>
                            <div class="col-md-6"><label class="form-label">Show Classification</label><select class="form-select" name="transcript_show_classification"><option value="Yes" <?php echo (($transcriptSettings['transcript_show_classification'] ?? 'Yes') === 'Yes') ? 'selected' : ''; ?>>Yes</option><option value="No" <?php echo (($transcriptSettings['transcript_show_classification'] ?? 'Yes') === 'No') ? 'selected' : ''; ?>>No</option></select></div>
                        </div>
                    <?php else: ?>
                        <div class="row g-3">
                            <div class="col-md-4"><label class="form-label">Approval Title</label><input type="text" class="form-control" name="transcript_approval_title" value="<?php echo e((string)($transcriptSettings['transcript_approval_title'] ?? '')); ?>"></div>
                            <div class="col-md-4"><label class="form-label">Approval Name</label><input type="text" class="form-control" name="transcript_approval_name" value="<?php echo e((string)($transcriptSettings['transcript_approval_name'] ?? '')); ?>"></div>
                            <div class="col-md-4"><label class="form-label">Signature Label</label><input type="text" class="form-control" name="transcript_signature_label" value="<?php echo e((string)($transcriptSettings['transcript_signature_label'] ?? '')); ?>"></div>
                        </div>
                    <?php endif; ?>
                    <div class="mt-3 d-flex justify-content-end"><button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> Save Transcript Settings</button></div>
                </form>
            </div>
        </div>
    </div>
    <?php
    break;

case 'results_manager':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'results_compute');
    $gradingSettings = portalGradingResultSettings($pdo);
    $caLabel = trim((string)($gradingSettings['result_ca_label'] ?? 'CA')) ?: 'CA';
    $examLabel = trim((string)($gradingSettings['result_exam_label'] ?? 'EXAM')) ?: 'EXAM';
    $caMaxScore = (float)($gradingSettings['result_ca_max_score'] ?? 40);
    if ($caMaxScore <= 0) $caMaxScore = 40;
    $examMaxScore = (float)($gradingSettings['result_exam_max_score'] ?? 60);
    if ($examMaxScore <= 0) $examMaxScore = 60;
    $passMarkScore = (float)($gradingSettings['result_pass_mark'] ?? 40);
    $minimumCaScore = portalResultMinimumCaScore($pdo);

    if (!function_exists('gradeFromTotal')) {
        function gradeFromTotal(float $total): array {
            global $pdo;
            if (isset($pdo) && ($pdo instanceof PDO) && function_exists('portalGradeFromTotal')) {
                return portalGradeFromTotal($pdo, $total);
            }
            if ($total >= 70) return ['A', 5.0];
            if ($total >= 60) return ['B', 4.0];
            if ($total >= 50) return ['C', 3.0];
            if ($total >= 45) return ['D', 2.0];
            if ($total >= 40) return ['E', 1.0];
            return ['F', 0.0];
        }
    }

    $lecturerId = (int)($_SESSION['user_id'] ?? 0);
    $sessionOptions = [];
    try { $sessionOptions = portalAcademicSessionOptions($pdo); } catch (Throwable $e) { $sessionOptions = []; }
    $sessionName = trim((string)($_GET['session_name'] ?? ''));
    if ($sessionName === '') {
        try { $sessionName = trim((string)portalCurrentAcademicSessionName($pdo)); } catch (Throwable $e) { $sessionName = ''; }
    }
    if ($sessionName === '' && !empty($sessionOptions[0])) $sessionName = trim((string)$sessionOptions[0]);
    if ($sessionName === '') $sessionName = '2026/2027';

    $semester = trim((string)($_GET['semester'] ?? 'First'));
    if (!in_array($semester, ['First','Second'], true)) $semester = 'First';
    $courseId = (int)($_GET['course_id'] ?? 0);

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $action = $_POST['action'] ?? '';
        $courseId = (int)($_POST['course_id'] ?? $courseId);
        $sessionName = trim((string)($_POST['session_name'] ?? $sessionName));
        $semester = trim((string)($_POST['semester'] ?? $semester));
        if (!in_array($semester, ['First','Second'], true)) $semester = 'First';

        $subId = 0;
        try {
            $st = $pdo->prepare("SELECT id,status FROM result_submissions WHERE course_id=? AND session=? AND semester=? AND lecturer_id=? LIMIT 1");
            $st->execute([$courseId,$sessionName,$semester,$lecturerId]);
            $row = $st->fetch(PDO::FETCH_ASSOC);
            if ($row) $subId = (int)$row['id'];
            if ($subId <= 0) {
                $pdo->prepare("INSERT INTO result_submissions (course_id,session,semester,lecturer_id,status) VALUES (?,?,?,?, 'draft')")
                    ->execute([$courseId,$sessionName,$semester,$lecturerId]);
                $subId = (int)$pdo->lastInsertId();
            }
        } catch (Throwable $e) { $subId = 0; }

        if ($action === 'save_results' && $courseId > 0) {
            $ca = $_POST['ca'] ?? [];
            $exam = $_POST['exam'] ?? [];
            try {
                $stPub = $pdo->prepare("SELECT COUNT(*) FROM results WHERE course_id=? AND session=? AND semester=? AND status='published'");
                $stPub->execute([$courseId,$sessionName,$semester]);
                $publishedCount = (int)$stPub->fetchColumn();
            } catch (Throwable $e) { $publishedCount = 0; }
            if ($publishedCount > 0) {
                flash('danger', 'Results for this course/session/semester have been published and are locked. Editing is not allowed.');
                redirect('?page=results_manager&session_name='.urlencode($sessionName).'&semester='.urlencode($semester).'&course_id='.(int)$courseId);
            }

            try {
                foreach ($ca as $studentIdStr => $caVal) {
                    $studentId = (int)$studentIdStr;
                    $caScore = (float)$caVal;
                    $examScore = (float)($exam[$studentIdStr] ?? 0);
                    if ($caScore < 0 || $examScore < 0 || $caScore > $caMaxScore || $examScore > $examMaxScore) {
                        throw new RuntimeException($caLabel . ' and ' . $examLabel . ' scores must be within the configured limits.');
                    }
                    $assessment = portalResultAssessment($pdo, $caScore, $examScore);
                    $total = (float)($assessment['total_score'] ?? ($caScore + $examScore));
                    $grade = (string)($assessment['grade'] ?? 'F');
                    $gp = (float)($assessment['grade_point'] ?? 0.0);
                    $st = $pdo->prepare("INSERT INTO results (submission_id,course_id,student_id,session,semester,ca_score,exam_score,total_score,grade,grade_point,status)
                        VALUES (?,?,?,?,?,?,?,?,?,?, 'draft')
                        ON DUPLICATE KEY UPDATE submission_id=IF(status='published',submission_id,VALUES(submission_id)), ca_score=IF(status='published',ca_score,VALUES(ca_score)), exam_score=IF(status='published',exam_score,VALUES(exam_score)), total_score=IF(status='published',total_score,VALUES(total_score)), grade=IF(status='published',grade,VALUES(grade)), grade_point=IF(status='published',grade_point,VALUES(grade_point)), status=IF(status='published',status,'draft')");
                    $st->execute([$subId,$courseId,$studentId,$sessionName,$semester,$caScore,$examScore,$total,$grade,$gp]);
                }
                flash('success', 'Results saved as draft.');
            } catch (Throwable $e) {
                flash('danger', 'Failed to save results: ' . $e->getMessage());
            }
            redirect('?page=results_manager&session_name='.urlencode($sessionName).'&semester='.urlencode($semester).'&course_id='.(int)$courseId);
        }

        if ($action === 'submit_results' && $courseId > 0) {
            try {
                $pdo->prepare("UPDATE result_submissions SET status='submitted', submitted_at=NOW() WHERE id=?")->execute([$subId]);
                $pdo->prepare("UPDATE results SET status='submitted' WHERE submission_id=? AND course_id=? AND session=? AND semester=?")->execute([$subId,$courseId,$sessionName,$semester]);
                flash('success', 'Results submitted for admin approval.');
            } catch (Throwable $e) {
                flash('danger', 'Failed to submit results: ' . $e->getMessage());
            }
            redirect('?page=results_manager&session_name='.urlencode($sessionName).'&semester='.urlencode($semester).'&course_id='.(int)$courseId);
        }
    }

    $courses = portalFetchRegisteredCourseOptions($pdo, [
        'session' => $sessionName,
        'semester' => $semester,
    ]);

    $courseMeta = null;
    foreach ($courses as $courseRow) {
        if ((int)($courseRow['id'] ?? 0) === $courseId) { $courseMeta = $courseRow; break; }
    }
    if ($courseId > 0 && !$courseMeta) {
        try {
            $fallbackCreditExpr = portalColumnExistsSafe($pdo, 'registrable_courses', 'credit_load')
                ? "COALESCE(credit_unit, credit_load, 0)"
                : "COALESCE(credit_unit, 0)";
            $st = $pdo->prepare("SELECT id, course_code, course_title, {$fallbackCreditExpr} AS credit_unit FROM registrable_courses WHERE id=? LIMIT 1");
            $st->execute([$courseId]);
            $courseMeta = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        } catch (Throwable $e) { $courseMeta = null; }
    }
    if ($courseMeta && !array_filter($courses, static function(array $row) use ($courseId): bool { return (int)($row['id'] ?? 0) === (int)$courseId; })) {
        $courseMeta['student_count'] = (int)($courseMeta['student_count'] ?? 0);
        $courses[] = $courseMeta;
    }

    $students = portalFetchRegisteredStudentsForCourseContext($pdo, $courseId, $sessionName, $semester);

    $existing = [];
    if ($courseId > 0) {
        try {
            $st = $pdo->prepare("SELECT student_id, ca_score, exam_score, total_score, status FROM results WHERE course_id=? AND session=? AND semester=?");
            $st->execute([$courseId,$sessionName,$semester]);
            while ($r = $st->fetch(PDO::FETCH_ASSOC)) {
                $existing[(int)$r['student_id']] = $r;
            }
        } catch (Throwable $e) { $existing = []; }
    }

    $registeredCount = count($students);
    $draftCount = 0;
    $submittedCount = 0;
    $publishedCount = 0;
    foreach ($existing as $resultRow) {
        $statusLabel = strtolower(trim((string)($resultRow['status'] ?? 'draft')));
        if ($statusLabel === 'published') $publishedCount++;
        elseif ($statusLabel === 'submitted') $submittedCount++;
        else $draftCount++;
    }
    $contextReady = $courseId > 0 && $sessionName !== '' && $semester !== '';
    $selectedCourseLabel = trim((string)(($courseMeta['course_code'] ?? '') . ' - ' . ($courseMeta['course_title'] ?? '')));
    if ($selectedCourseLabel === '-' || $selectedCourseLabel === '') $selectedCourseLabel = 'Selected Course';
    ?>
    <style>
        .results-manager-shell { max-width: 1480px; margin: 0 auto; }
        .results-filter-card, .results-entry-card { border:1px solid rgba(7,62,124,.10); border-radius:22px; box-shadow:0 12px 30px rgba(12,43,84,.05); }
        .results-filter-card .card-body, .results-entry-card .card-body { padding:1.25rem; }
        .results-filter-card .form-label { font-weight:700; color:#173b69; }
        .results-filter-card .form-select, .results-filter-card .form-control { min-height:48px; border-radius:14px; }
        .results-context-grid { display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:1rem; }
        .results-context-card { border:1px solid rgba(7,62,124,.10); border-radius:18px; background:#fff; padding:1rem 1.1rem; box-shadow:0 8px 18px rgba(8,48,92,.04); }
        .results-context-card .label { font-size:.78rem; font-weight:800; letter-spacing:.12em; text-transform:uppercase; color:#6481a3; }
        .results-context-card .value { font-size:2rem; line-height:1; font-weight:800; color:#073e7c; margin:.45rem 0 .35rem; }
        .results-mini-hint { font-size:.88rem; color:#5f728a; }
        .results-entry-table { border:1px solid rgba(7,62,124,.08); border-radius:16px; overflow:hidden; }
        .results-entry-table table { margin-bottom:0; }
        @media (max-width: 991.98px) { .results-context-grid { grid-template-columns:1fr; } }
    </style>
    <div class="container-fluid py-3 results-manager-shell">
        <div class="card results-filter-card mb-3">
            <div class="card-body">
                <div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3">
                    <div>
                        <h4 class="mb-1">Manage Results</h4>
                        <div class="text-muted">The result manager now reads directly from the real saved course registration forms, so once you load a session, semester, and course, it pulls only the correct registered students.</div>
                    </div>
                    <?php if ($contextReady): ?>
                        <span class="badge rounded-pill text-bg-primary px-3 py-2"><?php echo e($selectedCourseLabel); ?></span>
                    <?php endif; ?>
                </div>

                <form method="get" class="row g-3 align-items-end">
                    <input type="hidden" name="page" value="results_manager">
                    <div class="col-md-3">
                        <label class="form-label">Session</label>
                        <select class="form-select" name="session_name">
                            <option value="">Select Session</option>
                            <?php foreach ($sessionOptions as $sessionOpt): $sessionOpt = trim((string)$sessionOpt); if ($sessionOpt === '') continue; ?>
                                <option value="<?php echo e($sessionOpt); ?>" <?php echo $sessionOpt === $sessionName ? 'selected' : ''; ?>><?php echo e($sessionOpt); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-md-2">
                        <label class="form-label">Semester</label>
                        <select class="form-select" name="semester">
                            <?php foreach (['First','Second'] as $sem): ?>
                                <option value="<?php echo e($sem); ?>" <?php echo $semester === $sem ? 'selected' : ''; ?>><?php echo e($sem); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-md-4">
                        <label class="form-label">Course</label>
                        <select class="form-select" name="course_id" required>
                            <option value="0">-- select course --</option>
                            <?php foreach ($courses as $c): ?>
                                <option value="<?php echo (int)($c['id'] ?? 0); ?>" <?php echo ((int)$courseId === (int)($c['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e(trim((string)($c['course_code'] ?? '') . ' - ' . (string)($c['course_title'] ?? ''))); ?><?php echo !empty($c['student_count']) ? ' (' . (int)$c['student_count'] . ')' : ''; ?></option>
                            <?php endforeach; ?>
                        </select>
                        <div class="results-mini-hint mt-1">This course list is synchronized to actual registered course forms for the selected session and semester.</div>
                    </div>
                    <div class="col-md-3">
                        <button class="btn btn-primary w-100" type="submit">Load</button>
                    </div>
                </form>

                <?php if (!$courses): ?>
                    <div class="alert alert-warning mt-3 mb-0">No registered courses were found for <?php echo e($sessionName !== '' ? $sessionName : 'the selected session'); ?> / <?php echo e($semester); ?> yet. Once students save their course registration forms, the correct courses will appear here automatically.</div>
                <?php endif; ?>
            </div>
        </div>

        <div class="results-context-grid mb-3">
            <div class="results-context-card">
                <div class="label">Registered Students</div>
                <div class="value"><?php echo (int)$registeredCount; ?></div>
                <div class="text-muted small">Students found for this course context.</div>
            </div>
            <div class="results-context-card">
                <div class="label">Draft / Submitted</div>
                <div class="value"><?php echo (int)($draftCount + $submittedCount); ?></div>
                <div class="text-muted small"><?php echo (int)$draftCount; ?> draft and <?php echo (int)$submittedCount; ?> submitted result row(s).</div>
            </div>
            <div class="results-context-card">
                <div class="label">Published Rows</div>
                <div class="value"><?php echo (int)$publishedCount; ?></div>
                <div class="text-muted small">Published rows are locked from editing.</div>
            </div>
        </div>

        <?php if ($contextReady): ?>
            <div class="card results-entry-card">
                <div class="card-header bg-white border-0 pt-3 px-3"><strong>Enter CA / Exam Scores</strong></div>
                <div class="card-body pt-0">
                    <form method="post">
                        <input type="hidden" name="action" value="save_results">
                        <input type="hidden" name="course_id" value="<?php echo (int)$courseId; ?>">
                        <input type="hidden" name="session_name" value="<?php echo e($sessionName); ?>">
                        <input type="hidden" name="semester" value="<?php echo e($semester); ?>">

                        <div class="results-entry-table table-responsive">
                            <table class="table table-sm align-middle">
                                <thead class="table-light">
                                    <tr>
                                        <th>Student</th>
                                        <th style="width:120px"><?php echo e($caLabel); ?></th>
                                        <th style="width:120px"><?php echo e($examLabel); ?></th>
                                        <th style="width:120px">Total</th>
                                        <th style="width:140px">Grade</th>
                                        <th style="width:170px">Remark</th>
                                        <th style="width:140px">Status</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <?php foreach ($students as $s):
                                        $sid = (int)($s['student_id'] ?? 0);
                                        $ex = $existing[$sid] ?? null;
                                        $caScore = $ex ? (float)$ex['ca_score'] : 0;
                                        $examScore = $ex ? (float)$ex['exam_score'] : 0;
                                        $assessment = portalResultAssessment($pdo, $caScore, $examScore, $caScore + $examScore);
                                        $total = (float)($assessment['total_score'] ?? ($caScore + $examScore));
                                        $remarkLabel = (string)($assessment['remark'] ?? 'FAIL');
                                        $gradeLabel = (string)($assessment['grade'] ?? ($ex['grade'] ?? ''));
                                        $stLabel = strtolower(trim((string)($ex['status'] ?? 'draft')));
                                        $badgeClass = $stLabel === 'published' ? 'success' : ($stLabel === 'submitted' ? 'warning text-dark' : 'secondary');
                                        $matricLabel = trim((string)($s['matric_no'] ?? $s['reg_no'] ?? $s['registration_no'] ?? $s['admission_no'] ?? ''));
                                    ?>
                                        <tr>
                                            <td><?php echo e(full_name($s)); ?><div class="small text-muted"><?php echo e($matricLabel); ?></div></td>
                                            <td><input class="form-control form-control-sm" type="number" step="0.01" min="0" max="<?php echo e((string)$caMaxScore); ?>" name="ca[<?php echo $sid; ?>]" value="<?php echo e((string)$caScore); ?>" <?php echo $stLabel === 'published' ? 'readonly' : ''; ?>></td>
                                            <td><input class="form-control form-control-sm" type="number" step="0.01" min="0" max="<?php echo e((string)$examMaxScore); ?>" name="exam[<?php echo $sid; ?>]" value="<?php echo e((string)$examScore); ?>" <?php echo $stLabel === 'published' ? 'readonly' : ''; ?>></td>
                                            <td><?php echo e(number_format((float)$total, 2)); ?></td>
                                            <td><?php echo e($gradeLabel); ?></td>
                                            <td><?php echo e($remarkLabel); ?></td>
                                            <td><span class="badge bg-<?php echo e($badgeClass); ?>"><?php echo e($stLabel); ?></span></td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$students): ?>
                                        <tr><td colspan="7" class="text-muted text-center py-4">No registered students were found for this course/session/semester. Only students with saved course registration forms now appear here.</td></tr>
                                    <?php endif; ?>
                                </tbody>
                            </table>
                        </div>

                        <div class="d-flex flex-wrap gap-2 mt-3">
                            <button class="btn btn-primary" type="submit" <?php echo !$students ? 'disabled' : ''; ?>>Save Draft</button>
                        </div>
                    </form>

                    <hr>

                    <form method="post" onsubmit="return confirm('Submit results for admin approval?');">
                        <input type="hidden" name="action" value="submit_results">
                        <input type="hidden" name="course_id" value="<?php echo (int)$courseId; ?>">
                        <input type="hidden" name="session_name" value="<?php echo e($sessionName); ?>">
                        <input type="hidden" name="semester" value="<?php echo e($semester); ?>">
                        <button class="btn btn-success" <?php echo !$students ? 'disabled' : ''; ?>>Submit for Approval</button>
                    </form>
                </div>
            </div>
        <?php endif; ?>
    </div>

    

    <?php
    break;


case 'results_approvals':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    requireStaffPermission($pdo, 'results_approve');

    $me = (int)($_SESSION['user_id'] ?? 0);
    $reviewerName = trim((string)($_SESSION['full_name'] ?? $_SESSION['name'] ?? $_SESSION['username'] ?? $_SESSION['email'] ?? 'Admin Reviewer'));
    $sessionOptions = [];
    try { $sessionOptions = portalAcademicSessionOptions($pdo); } catch (Throwable $e) { $sessionOptions = []; }
    $statusFilter = portalNormalizeResultSubmissionStatusFilter($_GET['status'] ?? '');
    $sessionFilter = trim((string)($_GET['session_name'] ?? ''));
    $semesterFilter = trim((string)($_GET['semester'] ?? ''));
    $searchFilter = trim((string)($_GET['q'] ?? ''));
    $sortFilter = trim((string)($_GET['sort'] ?? 'newest'));
    if (!in_array($semesterFilter, ['', 'First', 'Second'], true)) $semesterFilter = '';
    if (!in_array($sortFilter, ['newest', 'oldest'], true)) $sortFilter = 'newest';

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $action = trim((string)($_POST['action'] ?? ''));
        $subId = (int)($_POST['submission_id'] ?? 0);
        $note = trim((string)($_POST['remark'] ?? $_POST['review_note'] ?? ''));
        $redirectParams = ['page' => 'results_approvals'];
        if ($searchFilter !== '') { $redirectParams['q'] = $searchFilter; }
        if ($statusFilter !== '') { $redirectParams['status'] = $statusFilter; }
        if ($sessionFilter !== '') { $redirectParams['session_name'] = $sessionFilter; }
        if ($semesterFilter !== '') { $redirectParams['semester'] = $semesterFilter; }
        if ($sortFilter !== '' && $sortFilter !== 'newest') { $redirectParams['sort'] = $sortFilter; }
        if ($subId > 0) { $redirectParams['view'] = $subId; }
        $redirectUrl = '?' . http_build_query($redirectParams);

        portalResultApprovalsDebugLog('Workflow action requested', [
            'submission_id' => $subId,
            'action' => $action,
            'status_filter' => $statusFilter,
            'session_filter' => $sessionFilter,
            'semester_filter' => $semesterFilter,
            'sort_filter' => $sortFilter,
        ]);

        if ($subId > 0) {
            try {
                $cur = $pdo->prepare("SELECT * FROM result_submissions WHERE id=? LIMIT 1");
                $cur->execute([$subId]);
                $currentSubmission = $cur->fetch(PDO::FETCH_ASSOC) ?: null;
                portalResultApprovalsDebugLog('Submission lookup completed', [
                    'submission_id' => $subId,
                    'found' => $currentSubmission ? 1 : 0,
                ]);
            } catch (Throwable $e) {
                $currentSubmission = null;
                portalResultApprovalsDebugLog('Submission lookup failed', [
                    'submission_id' => $subId,
                    'error' => $e->getMessage(),
                ]);
            }
        } else {
            $currentSubmission = null;
        }

        if ($action !== '' && ($subId <= 0 || !$currentSubmission)) {
            portalResultApprovalsDebugLog('Workflow action aborted: submission not found', [
                'submission_id' => $subId,
                'action' => $action,
            ]);
            flash('danger', 'Unable to find the selected submission. Please reopen it from the review queue and try again.');
            redirect($redirectUrl);
        }

        if ($subId > 0 && $currentSubmission) {
            $currentStatus = strtolower(trim((string)($currentSubmission['status'] ?? 'draft')));
            $rowsForValidation = [];
            try {
                $stRows = $pdo->prepare("SELECT * FROM results WHERE submission_id = ? ORDER BY id ASC");
                $stRows->execute([$subId]);
                $rowsForValidation = $stRows->fetchAll(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable $e) {
                $rowsForValidation = [];
                portalResultApprovalsDebugLog('Validation rows load failed', [
                    'submission_id' => $subId,
                    'action' => $action,
                    'error' => $e->getMessage(),
                ]);
            }
            $warnings = portalResultSubmissionWarnings($pdo, $currentSubmission, $rowsForValidation);
            $dangerWarnings = array_values(array_filter($warnings, static fn(array $w): bool => (($w['level'] ?? '') === 'danger')));
            portalResultApprovalsDebugLog('Workflow action validation prepared', [
                'submission_id' => $subId,
                'action' => $action,
                'current_status' => $currentStatus,
                'danger_warning_count' => count($dangerWarnings),
            ]);

            if ($action === 'mark_reviewed') {
                try {
                    $pdo->prepare("UPDATE result_submissions SET status='reviewed', reviewed_by=?, reviewed_at=NOW(), approval_note=? WHERE id=?")
                        ->execute([$me, $note, $subId]);
                    portalResultSubmissionAuditLog($pdo, $subId, 'reviewed', $note, $me, $reviewerName);
                    portalResultApprovalsDebugLog('Workflow action success', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'new_status' => 'reviewed',
                    ]);
                    flash('success', 'Submission reviewed successfully.');
                } catch (Throwable $e) {
                    portalResultApprovalsDebugLog('Workflow action failed', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'error' => $e->getMessage(),
                    ]);
                    flash('danger', 'Unable to mark as reviewed: ' . $e->getMessage());
                }
                redirect($redirectUrl);
            }

            if ($action === 'save_review_note') {
                try {
                    $pdo->prepare("UPDATE result_submissions SET approval_note=? WHERE id=?")->execute([$note, $subId]);
                    portalResultSubmissionAuditLog($pdo, $subId, 'note_saved', $note, $me, $reviewerName);
                    portalResultApprovalsDebugLog('Workflow action success', [
                        'submission_id' => $subId,
                        'action' => $action,
                    ]);
                    flash('success', 'Review note saved successfully.');
                } catch (Throwable $e) {
                    portalResultApprovalsDebugLog('Workflow action failed', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'error' => $e->getMessage(),
                    ]);
                    flash('danger', 'Unable to save review note: ' . $e->getMessage());
                }
                redirect($redirectUrl);
            }

            if ($currentStatus === 'published' && in_array($action, ['approve_submission', 'reject_submission', 'return_submission', 'reopen_submission', 'move_to_pending_review'], true)) {
                portalResultApprovalsDebugLog('Workflow action blocked: published submission is locked', [
                    'submission_id' => $subId,
                    'action' => $action,
                ]);
                flash('danger', 'This submission is already published and locked.');
                redirect($redirectUrl);
            }

            if ($action === 'approve_submission') {
                if ($dangerWarnings) {
                    portalResultApprovalsDebugLog('Workflow action blocked by validation warnings', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'danger_warning_count' => count($dangerWarnings),
                    ]);
                    flash('danger', 'This submission has blocking validation issues. Fix them before approval.');
                    redirect($redirectUrl);
                }
                try {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    $pdo->beginTransaction();
                    $pdo->prepare("UPDATE result_submissions SET status='approved', reviewed_by=COALESCE(reviewed_by, ?), reviewed_at=COALESCE(reviewed_at, NOW()), approved_by=?, approved_at=NOW(), approval_note=?, rejection_reason=NULL, correction_note=NULL, remark=? WHERE id=?")
                        ->execute([$me, $me, $note, $note, $subId]);
                    $pdo->prepare("UPDATE results SET status='approved' WHERE submission_id=?")->execute([$subId]);
                    portalResultSubmissionAuditLog($pdo, $subId, 'approved', $note, $me, $reviewerName);
                    $pdo->commit();
                    portalResultApprovalsDebugLog('Workflow action success', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'new_status' => 'approved',
                    ]);
                    flash('success', 'Result approved successfully.');
                } catch (Throwable $e) {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    portalResultApprovalsDebugLog('Workflow action failed', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'error' => $e->getMessage(),
                    ]);
                    flash('danger', 'Approve failed: ' . $e->getMessage());
                }
                redirect($redirectUrl);
            }

            if ($action === 'reject_submission') {
                try {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    $pdo->beginTransaction();
                    $pdo->prepare("UPDATE result_submissions SET status='rejected', rejected_by=?, rejected_at=NOW(), rejection_reason=?, correction_note=NULL, remark=?, approval_note=? WHERE id=?")
                        ->execute([$me, $note, $note, $note, $subId]);
                    $pdo->prepare("UPDATE results SET status='draft' WHERE submission_id=?")->execute([$subId]);
                    portalResultSubmissionAuditLog($pdo, $subId, 'rejected', $note, $me, $reviewerName);
                    $pdo->commit();
                    portalResultApprovalsDebugLog('Workflow action success', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'new_status' => 'rejected',
                    ]);
                    flash('success', 'Result rejected successfully.');
                } catch (Throwable $e) {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    portalResultApprovalsDebugLog('Workflow action failed', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'error' => $e->getMessage(),
                    ]);
                    flash('danger', 'Reject failed: ' . $e->getMessage());
                }
                redirect($redirectUrl);
            }

            if ($action === 'return_submission') {
                try {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    $pdo->beginTransaction();
                    $pdo->prepare("UPDATE result_submissions SET status='returned', returned_by=?, returned_at=NOW(), correction_note=?, rejection_reason=NULL, remark=?, approval_note=? WHERE id=?")
                        ->execute([$me, $note, $note, $note, $subId]);
                    $pdo->prepare("UPDATE results SET status='draft' WHERE submission_id=?")->execute([$subId]);
                    portalResultSubmissionAuditLog($pdo, $subId, 'returned_for_correction', $note, $me, $reviewerName);
                    $pdo->commit();
                    portalResultApprovalsDebugLog('Workflow action success', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'new_status' => 'returned',
                    ]);
                    flash('success', 'Submission returned for correction successfully.');
                } catch (Throwable $e) {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    portalResultApprovalsDebugLog('Workflow action failed', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'error' => $e->getMessage(),
                    ]);
                    flash('danger', 'Return for correction failed: ' . $e->getMessage());
                }
                redirect($redirectUrl);
            }

            if ($action === 'reopen_submission' || $action === 'move_to_pending_review') {
                try {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    $pdo->beginTransaction();
                    $pdo->prepare("UPDATE result_submissions SET status='pending_review', approval_note=?, remark=? WHERE id=?")
                        ->execute([$note, $note, $subId]);
                    $pdo->prepare("UPDATE results SET status='submitted' WHERE submission_id=?")->execute([$subId]);
                    portalResultSubmissionAuditLog($pdo, $subId, 'pending_review', $note, $me, $reviewerName);
                    $pdo->commit();
                    portalResultApprovalsDebugLog('Workflow action success', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'new_status' => 'pending_review',
                    ]);
                    flash('success', 'Submission moved to pending review successfully.');
                } catch (Throwable $e) {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    portalResultApprovalsDebugLog('Workflow action failed', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'error' => $e->getMessage(),
                    ]);
                    flash('danger', 'Pending review update failed: ' . $e->getMessage());
                }
                redirect($redirectUrl);
            }

            if ($action === 'publish_submission') {
                requireStaffPermission($pdo, 'results_publish');
                if ($dangerWarnings) {
                    portalResultApprovalsDebugLog('Publish blocked by validation warnings', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'danger_warning_count' => count($dangerWarnings),
                    ]);
                    flash('danger', 'Blocking validation issues were found. Resolve them before publishing.');
                    redirect($redirectUrl);
                }
                try {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    $pdo->beginTransaction();
                    $pdo->prepare("UPDATE result_submissions SET status='published', reviewed_by=COALESCE(reviewed_by, ?), reviewed_at=COALESCE(reviewed_at, NOW()), approved_by=COALESCE(approved_by, ?), approved_at=COALESCE(approved_at, NOW()), published_by=?, published_at=NOW(), approval_note=?, remark=? WHERE id=?")
                        ->execute([$me, $me, $me, $note, $note, $subId]);
                    $pdo->prepare("UPDATE results SET status='published' WHERE submission_id=?")->execute([$subId]);
                    portalResultSubmissionAuditLog($pdo, $subId, 'published', $note, $me, $reviewerName);
                    $pdo->commit();
                    portalResultApprovalsDebugLog('Workflow action success', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'new_status' => 'published',
                    ]);
                    flash('success', 'Result published successfully.');
                } catch (Throwable $e) {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    portalResultApprovalsDebugLog('Workflow action failed', [
                        'submission_id' => $subId,
                        'action' => $action,
                        'error' => $e->getMessage(),
                    ]);
                    flash('danger', 'Publish failed: ' . $e->getMessage());
                }
                redirect($redirectUrl);
            }
        }
    }

    $summaryCounts = [
        'total' => 0,
        'pending_review' => 0,
        'approved' => 0,
        'rejected' => 0,
        'returned' => 0,
        'reviewed_today' => 0,
    ];
    try {
        $summaryCounts['total'] = (int)($pdo->query("SELECT COUNT(*) FROM result_submissions")->fetchColumn() ?: 0);
        $summaryCounts['pending_review'] = (int)($pdo->query("SELECT COUNT(*) FROM result_submissions WHERE status IN ('submitted','pending_review')")->fetchColumn() ?: 0);
        $summaryCounts['approved'] = (int)($pdo->query("SELECT COUNT(*) FROM result_submissions WHERE status IN ('approved','published')")->fetchColumn() ?: 0);
        $summaryCounts['rejected'] = (int)($pdo->query("SELECT COUNT(*) FROM result_submissions WHERE status='rejected'")->fetchColumn() ?: 0);
        $summaryCounts['returned'] = (int)($pdo->query("SELECT COUNT(*) FROM result_submissions WHERE status IN ('returned','returned_for_correction')")->fetchColumn() ?: 0);
        $summaryCounts['reviewed_today'] = (int)($pdo->query("SELECT COUNT(*) FROM result_submissions WHERE DATE(COALESCE(reviewed_at, approved_at, rejected_at, returned_at)) = CURDATE()")->fetchColumn() ?: 0);
    } catch (Throwable $e) {}

    $subs = [];
    try {
        $where = [];
        $params = [];
        $statusSql = portalResultSubmissionStatusFilterSql($statusFilter, 'rs.status');
        $statusFilter = (string)($statusSql['status'] ?? $statusFilter);
        if (!empty($statusSql['sql'])) {
            $where[] = (string)$statusSql['sql'];
            array_push($params, ...((array)($statusSql['params'] ?? [])));
        }
        if ($sessionFilter !== '') { $where[] = "rs.session = ?"; $params[] = $sessionFilter; }
        if ($semesterFilter !== '') { $where[] = "rs.semester = ?"; $params[] = $semesterFilter; }
        if ($searchFilter !== '') {
            $where[] = "(COALESCE(rc.course_code,'') LIKE ? OR COALESCE(rc.course_title,'') LIKE ? OR COALESCE(a.full_name,'') LIKE ? OR COALESCE(rs.session,'') LIKE ? OR COALESCE(rs.semester,'') LIKE ? OR COALESCE(d.name,'') LIKE ? OR COALESCE(p.name,'') LIKE ? OR COALESCE(rc.level,'') LIKE ?)";
            $like = '%' . $searchFilter . '%';
            array_push($params, $like, $like, $like, $like, $like, $like, $like, $like);
        }
        portalResultApprovalsDebugLog('Applying queue filters', [
            'status' => $statusFilter,
            'session' => $sessionFilter,
            'semester' => $semesterFilter,
            'search' => $searchFilter !== '' ? 'set' : '',
            'sort' => $sortFilter,
        ]);
        $sql = "SELECT rs.*, rc.course_code, rc.course_title, rc.level, d.name AS department_name, p.name AS programme_name, a.full_name AS lecturer_name
            FROM result_submissions rs
            LEFT JOIN registrable_courses rc ON rc.id = rs.course_id
            LEFT JOIN departments d ON d.id = rc.department_id
            LEFT JOIN programmes p ON p.id = rc.programme_id
            LEFT JOIN admins a ON a.id = rs.lecturer_id";
        if ($where) $sql .= " WHERE " . implode(' AND ', $where);
        $sql .= $sortFilter === 'oldest' ? " ORDER BY COALESCE(rs.submitted_at, rs.updated_at) ASC, rs.id ASC" : " ORDER BY COALESCE(rs.submitted_at, rs.updated_at) DESC, rs.id DESC";
        $st = $pdo->prepare($sql);
        $st->execute($params);
        $subs = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        portalResultApprovalsDebugLog('Queue filter results loaded', [
            'status' => $statusFilter,
            'count' => count($subs),
        ]);
    } catch (Throwable $e) {
        portalResultApprovalsDebugLog('Queue filter query failed', [
            'status' => $statusFilter,
            'error' => $e->getMessage(),
        ]);
        $subs = [];
    }

    $viewId = (int)($_GET['view'] ?? 0);
    $availableSubmissionIds = array_values(array_filter(array_map(static fn(array $row): int => (int)($row['id'] ?? 0), $subs)));
    if ($viewId > 0 && !in_array($viewId, $availableSubmissionIds, true)) {
        $viewId = !empty($availableSubmissionIds) ? (int)$availableSubmissionIds[0] : 0;
    }
    if ($viewId <= 0 && !empty($subs[0]['id'])) $viewId = (int)$subs[0]['id'];
    $viewRows = [];
    $viewSub = null;
    $viewMetrics = [];
    $viewWarnings = [];
    $viewHistory = [];
    if ($viewId > 0) {
        $studentMatricExpr = portalStudentSelectableColumnSql($pdo, 'matric_no', 's');
        $studentLegacyRegExpr = portalStudentSelectableColumnSql($pdo, 'reg_no', 's');
        $studentRegistrationExpr = portalStudentSelectableColumnSql($pdo, 'registration_no', 's', $studentLegacyRegExpr);
        $studentSurnameExpr = portalStudentSelectableColumnSql($pdo, 'surname', 's', portalStudentSelectableColumnSql($pdo, 'last_name', 's'));
        $studentFirstNameExpr = portalStudentFirstNameSql($pdo, 's');
        $studentOtherNameExpr = portalStudentOtherNameSql($pdo, 's');

        try {
            $st = $pdo->prepare("SELECT rs.*, rc.course_code, rc.course_title, rc.level, d.name AS department_name, p.name AS programme_name, a.full_name AS lecturer_name
                FROM result_submissions rs
                LEFT JOIN registrable_courses rc ON rc.id = rs.course_id
                LEFT JOIN departments d ON d.id = rc.department_id
                LEFT JOIN programmes p ON p.id = rc.programme_id
                LEFT JOIN admins a ON a.id = rs.lecturer_id
                WHERE rs.id=? LIMIT 1");
            $st->execute([$viewId]);
            $viewSub = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        } catch (Throwable $e) {
            $viewSub = null;
            portalResultApprovalsDebugLog('Detail panel header load failed', [
                'submission_id' => $viewId,
                'error' => $e->getMessage(),
            ]);
        }

        if ($viewSub) {
            portalResultApprovalsDebugLog('Submission detail opened', [
                'submission_id' => $viewId,
                'status' => (string)($viewSub['status'] ?? ''),
            ]);

            try {
                $sqlRows = "SELECT r.*,
                        {$studentMatricExpr} AS matric_no,
                        {$studentLegacyRegExpr} AS reg_no,
                        {$studentRegistrationExpr} AS registration_no,
                        {$studentSurnameExpr} AS surname,
                        {$studentFirstNameExpr} AS first_name,
                        {$studentOtherNameExpr} AS other_name
                    FROM results r
                    INNER JOIN students s ON s.id = r.student_id
                    WHERE r.submission_id = ?
                    ORDER BY COALESCE(NULLIF(TRIM({$studentSurnameExpr}), ''), NULLIF(TRIM({$studentRegistrationExpr}), ''), CAST(s.id AS CHAR)) ASC,
                             COALESCE(NULLIF(TRIM({$studentFirstNameExpr}), ''), '') ASC,
                             s.id ASC";
                $st = $pdo->prepare($sqlRows);
                $st->execute([$viewId]);
                $viewRows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
                portalResultApprovalsDebugLog('Detail panel result rows loaded', [
                    'submission_id' => $viewId,
                    'row_count' => count($viewRows),
                ]);
            } catch (Throwable $e) {
                $viewRows = [];
                portalResultApprovalsDebugLog('Detail panel result rows failed', [
                    'submission_id' => $viewId,
                    'error' => $e->getMessage(),
                ]);
                $viewWarnings[] = [
                    'level' => 'warning',
                    'text' => 'The submission opened, but some result rows could not be loaded. Check the approval debug log for the underlying query details.',
                ];
            }

            try {
                $viewMetrics = portalResultSubmissionMetrics($pdo, $viewSub, $viewRows);
            } catch (Throwable $e) {
                $viewMetrics = [];
                portalResultApprovalsDebugLog('Detail panel metrics failed', [
                    'submission_id' => $viewId,
                    'error' => $e->getMessage(),
                ]);
            }

            try {
                $computedWarnings = portalResultSubmissionWarnings($pdo, $viewSub, $viewRows);
                if ($computedWarnings) {
                    $viewWarnings = array_merge($viewWarnings, $computedWarnings);
                }
            } catch (Throwable $e) {
                portalResultApprovalsDebugLog('Detail panel warnings failed', [
                    'submission_id' => $viewId,
                    'error' => $e->getMessage(),
                ]);
                $viewWarnings[] = [
                    'level' => 'warning',
                    'text' => 'Some validation details could not be loaded completely. Check the approval debug log for more information.',
                ];
            }

            try {
                $viewHistory = portalResultSubmissionHistory($pdo, $viewId);
            } catch (Throwable $e) {
                $viewHistory = [];
                portalResultApprovalsDebugLog('Detail panel history failed', [
                    'submission_id' => $viewId,
                    'error' => $e->getMessage(),
                ]);
            }
        } else {
            portalResultApprovalsDebugLog('Detail panel submission not found', [
                'submission_id' => $viewId,
            ]);
        }
    }

    ?>
    <style>
        .result-approval-shell{max-width:1580px;margin:0 auto;}
        .result-approval-summary{border:1px solid rgba(7,62,124,.10);border-radius:18px;box-shadow:0 10px 24px rgba(15,23,42,.05);background:#fff;height:100%;}
        .result-approval-summary .label{font-size:.78rem;font-weight:700;letter-spacing:.05em;color:#6b7280;text-transform:uppercase;}
        .result-approval-summary .value{font-size:1.7rem;font-weight:800;color:#0b3e7c;line-height:1.1;margin-top:.35rem;}
        .result-approval-card{border:1px solid rgba(7,62,124,.10);border-radius:22px;box-shadow:0 14px 32px rgba(15,23,42,.06);overflow:hidden;}
        .result-approval-card .card-header{background:#fff;border-bottom:1px solid rgba(7,62,124,.08);}
        .result-approval-list .approval-item{display:block;border:1px solid rgba(7,62,124,.08);border-radius:16px;padding:.9rem 1rem;text-decoration:none;color:inherit;background:#fff;transition:.2s ease;}
        .result-approval-list .approval-item:hover,.result-approval-list .approval-item.active{border-color:#0b3e7c;background:rgba(11,62,124,.05);box-shadow:0 8px 18px rgba(11,62,124,.08);}
        .result-approval-list .meta{font-size:.78rem;color:#6b7280;}
        .result-approval-stat-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.85rem;}
        .result-approval-stat{border:1px solid rgba(7,62,124,.08);border-radius:16px;background:#f8fbff;padding:.9rem 1rem;}
        .result-approval-stat .label{font-size:.76rem;color:#64748b;font-weight:700;text-transform:uppercase;letter-spacing:.04em;}
        .result-approval-stat .value{font-size:1.35rem;font-weight:800;color:#0b3e7c;}
        .result-warning{border-radius:14px;padding:.75rem .9rem;border:1px solid transparent;}
        .result-warning.warning{background:#fff8e6;border-color:#facc15;color:#8a5d00;}
        .result-warning.danger{background:#fff1f2;border-color:#f87171;color:#9f1239;}
        .result-approval-history{max-height:340px;overflow:auto;}
        .result-approval-preview-table td,.result-approval-preview-table th{font-size:.88rem;}
        .result-approval-empty{border:1px dashed rgba(7,62,124,.18);border-radius:20px;padding:2.2rem 1.2rem;background:#fbfdff;text-align:center;color:#64748b;}
        @media (max-width: 1199.98px){.result-approval-stat-grid{grid-template-columns:repeat(2,minmax(0,1fr));}}
        @media (max-width: 767.98px){.result-approval-stat-grid{grid-template-columns:1fr;}}
    </style>
    <div class="container-fluid py-3 result-approval-shell">
        <div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
            <div>
                <h4 class="mb-1">Result Approvals</h4>
                <div class="text-muted">Review submitted scoresheets, validate the result data, add workflow notes, and complete approval actions from one central page.</div>
            </div>
        </div>

        <div class="row g-3 mb-3">
            <?php $cards = [
                ['label' => 'Total Submissions', 'value' => (int)$summaryCounts['total']],
                ['label' => 'Pending Review', 'value' => (int)$summaryCounts['pending_review']],
                ['label' => 'Approved', 'value' => (int)$summaryCounts['approved']],
                ['label' => 'Rejected', 'value' => (int)$summaryCounts['rejected']],
                ['label' => 'Returned for Correction', 'value' => (int)$summaryCounts['returned']],
                ['label' => 'Reviewed Today', 'value' => (int)$summaryCounts['reviewed_today']],
            ]; ?>
            <?php foreach ($cards as $card): ?>
                <div class="col-12 col-md-4 col-xl-2">
                    <div class="result-approval-summary p-3">
                        <div class="label"><?php echo e($card['label']); ?></div>
                        <div class="value"><?php echo (int)$card['value']; ?></div>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>

        <div class="row g-3">
            <div class="col-xl-4 col-xxl-4">
                <div class="card result-approval-card h-100">
                    <div class="card-header">
                        <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
                            <div>
                                <strong>Submission Queue</strong>
                                <div class="small text-muted">Search, filter, and open any submission for full workflow review.</div>
                            </div>
                            <span class="badge text-bg-primary"><?php echo count($subs); ?> item(s)</span>
                        </div>
                    </div>
                    <div class="card-body">
                        <form method="get" class="row g-2 mb-3" id="resultApprovalQueueFilters">
                            <input type="hidden" name="page" value="results_approvals">
                            <div class="col-12">
                                <input class="form-control" name="q" value="<?php echo e($searchFilter); ?>" placeholder="Search course, lecturer, session, department...">
                            </div>
                            <div class="col-md-6">
                                <select class="form-select" name="status">
                                    <option value="">All Statuses</option>
                                    <?php foreach (['submitted','reviewed','approved','rejected','returned','published'] as $statusOpt): ?>
                                        <option value="<?php echo e($statusOpt); ?>" <?php echo $statusFilter === $statusOpt ? 'selected' : ''; ?>><?php echo e(portalResultSubmissionStatusLabel($statusOpt)); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-md-6">
                                <select class="form-select" name="session_name">
                                    <option value="">All Sessions</option>
                                    <?php foreach ($sessionOptions as $sessionOpt): $sessionOpt = trim((string)$sessionOpt); if ($sessionOpt === '') continue; ?>
                                        <option value="<?php echo e($sessionOpt); ?>" <?php echo $sessionFilter === $sessionOpt ? 'selected' : ''; ?>><?php echo e($sessionOpt); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-md-6">
                                <select class="form-select" name="semester">
                                    <option value="">All Semesters</option>
                                    <?php foreach (['First','Second'] as $semOpt): ?>
                                        <option value="<?php echo e($semOpt); ?>" <?php echo $semesterFilter === $semOpt ? 'selected' : ''; ?>><?php echo e($semOpt); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </div>
                            <div class="col-md-6">
                                <select class="form-select" name="sort">
                                    <option value="newest" <?php echo $sortFilter === 'newest' ? 'selected' : ''; ?>>Newest First</option>
                                    <option value="oldest" <?php echo $sortFilter === 'oldest' ? 'selected' : ''; ?>>Oldest First</option>
                                </select>
                            </div>
                            <div class="col-12 d-flex gap-2">
                                <button class="btn btn-primary" type="submit">Apply</button>
                                <a class="btn btn-outline-secondary" href="?page=results_approvals">Reset</a>
                            </div>
                        </form>
                        <script>
                        document.addEventListener('DOMContentLoaded', function () {
                            var form = document.getElementById('resultApprovalQueueFilters');
                            if (!form) return;
                            var autoSubmitSelectors = [
                                'select[name="status"]',
                                'select[name="session_name"]',
                                'select[name="semester"]',
                                'select[name="sort"]'
                            ];
                            autoSubmitSelectors.forEach(function (selector) {
                                var field = form.querySelector(selector);
                                if (!field) return;
                                field.addEventListener('change', function () {
                                    if (typeof form.requestSubmit === 'function') {
                                        form.requestSubmit();
                                    } else {
                                        form.submit();
                                    }
                                });
                            });
                            var searchInput = form.querySelector('input[name="q"]');
                            if (searchInput) {
                                searchInput.addEventListener('keydown', function (event) {
                                    if (event.key !== 'Enter') return;
                                    event.preventDefault();
                                    if (typeof form.requestSubmit === 'function') {
                                        form.requestSubmit();
                                    } else {
                                        form.submit();
                                    }
                                });
                            }
                        });
                        </script>

                        <div class="result-approval-list d-grid gap-2">
                            <?php foreach ($subs as $s): ?>
                                <?php $isActive = ((int)($s['id'] ?? 0) === (int)$viewId); ?>
                                <a class="approval-item <?php echo $isActive ? 'active' : ''; ?>" href="?page=results_approvals&view=<?php echo (int)$s['id']; ?>&q=<?php echo urlencode($searchFilter); ?>&status=<?php echo urlencode($statusFilter); ?>&session_name=<?php echo urlencode($sessionFilter); ?>&semester=<?php echo urlencode($semesterFilter); ?>&sort=<?php echo urlencode($sortFilter); ?>">
                                    <div class="d-flex justify-content-between gap-2 mb-1">
                                        <div class="fw-semibold"><?php echo e(trim((string)($s['course_code'] ?? '') . ' ' . (string)($s['course_title'] ?? ''))); ?></div>
                                        <span class="badge bg-<?php echo e(portalResultSubmissionStatusBadgeClass((string)($s['status'] ?? 'draft'))); ?>"><?php echo e(portalResultSubmissionStatusLabel((string)($s['status'] ?? 'draft'))); ?></span>
                                    </div>
                                    <div class="meta mb-1"><?php echo e((string)($s['session'] ?? '')); ?> • <?php echo e((string)($s['semester'] ?? '')); ?> • <?php echo e((string)($s['level'] ?? '')); ?></div>
                                    <div class="meta"><?php echo e((string)($s['department_name'] ?? '')); ?><?php echo !empty($s['programme_name']) ? ' • ' . e((string)$s['programme_name']) : ''; ?></div>
                                    <div class="meta mt-1">Submitted by: <?php echo e((string)($s['lecturer_name'] ?? '')); ?><?php echo !empty($s['submitted_at']) ? ' • ' . e(date('d M Y h:i A', strtotime((string)$s['submitted_at']))) : ''; ?></div>
                                </a>
                            <?php endforeach; ?>
                            <?php if (!$subs): ?>
                                <div class="result-approval-empty">No result submissions matched the current queue filters.</div>
                            <?php endif; ?>
                        </div>
                    </div>
                </div>
            </div>

            <div class="col-xl-8 col-xxl-8">
                <div class="card result-approval-card">
                    <div class="card-header">
                        <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
                            <div>
                                <strong>Submission Review Dashboard</strong>
                                <div class="small text-muted">Approval details, validation warnings, analytics, reviewer notes, and workflow controls.</div>
                            </div>
                            <?php if ($viewSub): ?>
                                <span class="badge bg-<?php echo e(portalResultSubmissionStatusBadgeClass((string)($viewSub['status'] ?? 'draft'))); ?>"><?php echo e(portalResultSubmissionStatusLabel((string)($viewSub['status'] ?? 'draft'))); ?></span>
                            <?php endif; ?>
                        </div>
                    </div>
                    <div class="card-body">
                        <?php if (!$viewSub): ?>
                            <div class="result-approval-empty">
                                <div class="fs-5 fw-semibold mb-2">Select a submission from the review queue</div>
                                <div>Once you open a submission, the page will show the course details, validation checks, quick analytics, review notes, workflow actions, and full audit trail here.</div>
                            </div>
                        <?php else: ?>
                            <div class="row g-3 mb-3">
                                <div class="col-md-8">
                                    <div class="border rounded-4 p-3 h-100 bg-white">
                                        <div class="fw-semibold fs-5 mb-1"><?php echo e(trim((string)($viewSub['course_code'] ?? '') . ' - ' . (string)($viewSub['course_title'] ?? ''))); ?></div>
                                        <div class="text-muted mb-2"><?php echo e((string)($viewSub['session'] ?? '')); ?> / <?php echo e((string)($viewSub['semester'] ?? '')); ?> • <?php echo e((string)($viewSub['department_name'] ?? '')); ?> • <?php echo e((string)($viewSub['programme_name'] ?? '')); ?> • Level <?php echo e((string)($viewSub['level'] ?? '')); ?></div>
                                        <div class="row g-2 small">
                                            <div class="col-md-6"><strong>Lecturer / Submitted By:</strong> <?php echo e((string)($viewSub['lecturer_name'] ?? '')); ?></div>
                                            <div class="col-md-6"><strong>Submission Date:</strong> <?php echo !empty($viewSub['submitted_at']) ? e(date('d M Y h:i A', strtotime((string)$viewSub['submitted_at']))) : '—'; ?></div>
                                            <div class="col-md-6"><strong>Approval Status:</strong> <?php echo e(portalResultSubmissionStatusLabel((string)($viewSub['status'] ?? 'draft'))); ?></div>
                                            <div class="col-md-6"><strong>Course ID:</strong> <?php echo (int)($viewSub['course_id'] ?? 0); ?></div>
                                        </div>
                                    </div>
                                </div>
                                <div class="col-md-4">
                                    <div class="border rounded-4 p-3 h-100 bg-light-subtle">
                                        <div class="fw-semibold mb-2">Quick Status</div>
                                        <div class="mb-2"><span class="badge bg-<?php echo e(portalResultSubmissionStatusBadgeClass((string)($viewSub['status'] ?? 'draft'))); ?>"><?php echo e(portalResultSubmissionStatusLabel((string)($viewSub['status'] ?? 'draft'))); ?></span></div>
                                        <div class="small text-muted">Use the action buttons below to review, approve, reject, return for correction, or publish this submission.</div>
                                    </div>
                                </div>
                            </div>

                            <div class="result-approval-stat-grid mb-3">
                                <?php $detailCards = [
                                    ['label' => 'Registered Students', 'value' => (int)($viewMetrics['registered_students'] ?? 0)],
                                    ['label' => 'Scored Students', 'value' => (int)($viewMetrics['scored_students'] ?? 0)],
                                    ['label' => 'Missing Scores', 'value' => (int)($viewMetrics['missing_scores'] ?? 0)],
                                    ['label' => 'Average Score', 'value' => number_format((float)($viewMetrics['average_score'] ?? 0), 2)],
                                    ['label' => 'Pass Count', 'value' => (int)($viewMetrics['pass_count'] ?? 0)],
                                    ['label' => 'Fail Count', 'value' => (int)($viewMetrics['fail_count'] ?? 0)],
                                    ['label' => 'Carryover Count', 'value' => (int)($viewMetrics['carryover_count'] ?? 0)],
                                    ['label' => 'Credit Unit', 'value' => rtrim(rtrim(number_format((float)($viewMetrics['course_credit_unit'] ?? 0), 2, '.', ''), '0'), '.')],
                                ]; ?>
                                <?php foreach ($detailCards as $card): ?>
                                    <div class="result-approval-stat">
                                        <div class="label"><?php echo e($card['label']); ?></div>
                                        <div class="value"><?php echo e((string)$card['value']); ?></div>
                                    </div>
                                <?php endforeach; ?>
                            </div>

                            <div class="row g-3 mb-3">
                                <div class="col-lg-6">
                                    <div class="border rounded-4 p-3 h-100">
                                        <div class="fw-semibold mb-2">Validation Warnings</div>
                                        <?php if (!$viewWarnings): ?>
                                            <div class="text-success small">No blocking validation issues were detected for this submission.</div>
                                        <?php else: ?>
                                            <div class="d-grid gap-2">
                                                <?php foreach ($viewWarnings as $warning): ?>
                                                    <div class="result-warning <?php echo e((string)($warning['level'] ?? 'warning')); ?>"><?php echo e((string)($warning['text'] ?? '')); ?></div>
                                                <?php endforeach; ?>
                                            </div>
                                        <?php endif; ?>
                                    </div>
                                </div>
                                <div class="col-lg-6">
                                    <div class="border rounded-4 p-3 h-100">
                                        <div class="fw-semibold mb-2">Review Note / Reviewer Comment</div>
                                        <form method="post" class="d-grid gap-2">
                                            <input type="hidden" name="submission_id" value="<?php echo (int)$viewSub['id']; ?>">
                                            <textarea class="form-control" rows="5" name="review_note" placeholder="Write review note, rejection reason, or correction guidance here..."><?php echo e((string)($viewSub['approval_note'] ?? $viewSub['remark'] ?? '')); ?></textarea>
                                            <div class="d-flex gap-2 flex-wrap">
                                                <button class="btn btn-outline-primary" type="submit" name="action" value="save_review_note">Save Note</button>
                                                <button class="btn btn-outline-info" type="submit" name="action" value="mark_reviewed">Mark as Reviewed</button>
                                            </div>
                                        </form>
                                    </div>
                                </div>
                            </div>

                            <div class="border rounded-4 p-3 mb-3 bg-white">
                                <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
                                    <div class="fw-semibold">Approval Actions</div>
                                    <div class="small text-muted">Actions respect the current approval status and permissions.</div>
                                </div>
                                <form method="post" class="d-flex flex-wrap gap-2 align-items-start">
                                    <input type="hidden" name="submission_id" value="<?php echo (int)$viewSub['id']; ?>">
                                    <textarea class="form-control" style="min-width:280px;max-width:420px;" rows="2" name="remark" placeholder="Optional action note / rejection reason / correction instruction"></textarea>
                                    <button class="btn btn-success" type="submit" name="action" value="approve_submission" onclick="return confirm('Approve this submission?');">Approve</button>
                                    <button class="btn btn-outline-danger" type="submit" name="action" value="reject_submission" onclick="return confirm('Reject this submission?');">Reject</button>
                                    <button class="btn btn-outline-warning" type="submit" name="action" value="return_submission" onclick="return confirm('Return this submission for correction?');">Return for Correction</button>
                                    <button class="btn btn-outline-secondary" type="submit" name="action" value="reopen_submission" onclick="return confirm('Move this submission back to pending review?');">Move to Pending Review</button>
                                    <?php if (hasStaffPermission($pdo, 'results_publish')): ?>
                                        <button class="btn btn-primary" type="submit" name="action" value="publish_submission" onclick="return confirm('Publish these results to students?');">Publish</button>
                                    <?php endif; ?>
                                    <a class="btn btn-outline-dark" href="?page=results_manager&course_id=<?php echo (int)($viewSub['course_id'] ?? 0); ?>&session_name=<?php echo urlencode((string)($viewSub['session'] ?? '')); ?>&semester=<?php echo urlencode((string)($viewSub['semester'] ?? '')); ?>">View Scoresheet</a>
                                    <a class="btn btn-outline-secondary" target="_blank" href="?page=download_scoresheet">Export</a>
                                    <button class="btn btn-outline-secondary" type="button" onclick="window.print();">Print</button>
                                </form>
                            </div>

                            <div class="row g-3">
                                <div class="col-lg-7">
                                    <div class="border rounded-4 p-3 h-100">
                                        <div class="fw-semibold mb-2">Quick Result Preview</div>
                                        <div class="table-responsive result-approval-preview-table">
                                            <table class="table table-sm align-middle mb-0">
                                                <thead>
                                                    <tr>
                                                        <th>Student</th>
                                                        <th>CA</th>
                                                        <th>Exam</th>
                                                        <th>Total</th>
                                                        <th>Grade</th>
                                                        <th>Remark</th>
                                                    </tr>
                                                </thead>
                                                <tbody>
                                                    <?php foreach (array_slice($viewRows, 0, 8) as $r): ?>
                                                        <?php $assessment = portalResultAssessment($pdo, (float)($r['ca_score'] ?? 0), (float)($r['exam_score'] ?? 0), (float)($r['total_score'] ?? 0)); ?>
                                                        <tr>
                                                            <td><?php echo e(full_name($r)); ?><div class="small text-muted"><?php echo e((string)($r['matric_no'] ?? $r['reg_no'] ?? $r['registration_no'] ?? '')); ?></div></td>
                                                            <td><?php echo e((string)($r['ca_score'] ?? '0')); ?></td>
                                                            <td><?php echo e((string)($r['exam_score'] ?? '0')); ?></td>
                                                            <td><?php echo e(number_format((float)($assessment['total_score'] ?? 0), 2)); ?></td>
                                                            <td><?php echo e((string)($assessment['grade'] ?? '')); ?></td>
                                                            <td><?php echo e((string)($assessment['remark'] ?? '')); ?></td>
                                                        </tr>
                                                    <?php endforeach; ?>
                                                    <?php if (!$viewRows): ?>
                                                        <tr><td colspan="6" class="text-muted text-center py-3">No result rows found for this submission.</td></tr>
                                                    <?php endif; ?>
                                                </tbody>
                                            </table>
                                        </div>
                                    </div>
                                </div>
                                <div class="col-lg-5">
                                    <div class="border rounded-4 p-3 h-100">
                                        <div class="fw-semibold mb-2">Approval History / Audit Trail</div>
                                        <div class="result-approval-history d-grid gap-2">
                                            <?php foreach ($viewHistory as $historyRow): ?>
                                                <div class="border rounded-3 p-2 bg-light">
                                                    <div class="d-flex justify-content-between gap-2 mb-1">
                                                        <div class="fw-semibold"><?php echo e(ucwords(str_replace('_', ' ', (string)($historyRow['action_type'] ?? '')))); ?></div>
                                                        <div class="small text-muted"><?php echo !empty($historyRow['created_at']) ? e(date('d M Y h:i A', strtotime((string)$historyRow['created_at']))) : '—'; ?></div>
                                                    </div>
                                                    <div class="small text-muted mb-1">By: <?php echo e((string)($historyRow['actor_name'] ?? ('User #' . (int)($historyRow['actor_id'] ?? 0)))); ?></div>
                                                    <?php if (!empty($historyRow['action_note'])): ?><div class="small"><?php echo nl2br(e((string)$historyRow['action_note'])); ?></div><?php endif; ?>
                                                </div>
                                            <?php endforeach; ?>
                                            <?php if (!$viewHistory): ?>
                                                <div class="text-muted small">No audit actions have been recorded yet for this submission.</div>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        <?php endif; ?>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <?php
    break;

case 'admin_registrations_manager':
    if (!isAdmin()) { redirect('?page=admin_login'); }

    $currentRegistrationSession = portalNormalizeSessionNameSafe((string)portalCurrentAcademicSessionName($pdo));
    $filterSess  = portalNormalizeSessionNameSafe((string)($_GET['session_name'] ?? $currentRegistrationSession));
    $filterSem   = trim((string)($_GET['semester'] ?? ''));
    if ($filterSem !== '' && !in_array($filterSem, ['First','Second'], true)) { $filterSem = ''; }
    $filterFac   = max(0, (int)($_GET['faculty_id'] ?? 0));
    $filterDept  = max(0, (int)($_GET['department_id'] ?? 0));
    $filterProg  = max(0, (int)($_GET['programme_id'] ?? 0));
    $filterLevel = trim((string)($_GET['level'] ?? ''));
    $q = trim((string)($_GET['q'] ?? ''));

    $sessionOptions = portalAcademicSessionOptions($pdo);
    if ($filterSess !== '' && !in_array($filterSess, $sessionOptions, true)) {
        array_unshift($sessionOptions, $filterSess);
    }
    $levelOptions = portalAcademicLevelOptions($pdo);

    $faculties = [];
    $departments = [];
    $programmes = [];
    try { $faculties = $pdo->query("SELECT * FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $faculties = []; }
    try { $departments = $pdo->query("SELECT d.*, f.name AS faculty_name FROM departments d LEFT JOIN faculties f ON f.id = d.faculty_id ORDER BY f.name ASC, d.name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $departments = []; }
    try { $programmes = $pdo->query("SELECT p.*, d.name AS department_name FROM programmes p LEFT JOIN departments d ON d.id = p.department_id ORDER BY p.name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $programmes = []; }

    $where = [];
    $params = [];
    if ($filterSess !== '') { $where[] = "COALESCE(cr.session_name,'') = ?"; $params[] = $filterSess; }
    if ($filterSem !== '') { $where[] = "cr.semester = ?"; $params[] = $filterSem; }
    if ($filterFac > 0) { $where[] = "cr.faculty_id = ?"; $params[] = $filterFac; }
    if ($filterDept > 0) { $where[] = "cr.department_id = ?"; $params[] = $filterDept; }
    if ($filterProg > 0) { $where[] = "cr.programme_id = ?"; $params[] = $filterProg; }
    if ($filterLevel !== '') { $where[] = "COALESCE(cr.level,'') = ?"; $params[] = $filterLevel; }
    if ($q !== '') {
        $studentFirstNameExpr = portalStudentFirstNameSql($pdo, 's');
        $studentOtherNameExpr = portalStudentOtherNameSql($pdo, 's');
        $where[] = "(COALESCE(s.matric_no,'') LIKE ? OR COALESCE(s.registration_no,'') LIKE ? OR COALESCE(s.application_no,'') LIKE ? OR COALESCE(s.email,'') LIKE ? OR COALESCE(s.surname,'') LIKE ? OR COALESCE({$studentFirstNameExpr},'') LIKE ? OR COALESCE({$studentOtherNameExpr},'') LIKE ?)";
        $like = '%' . $q . '%';
        array_push($params, $like, $like, $like, $like, $like, $like, $like);
    }
    $whereSql = $where ? implode(' AND ', $where) : '1=1';

    $regs = [];
    try {
        $studentSelect = portalCourseRegistrationStudentSelectList($pdo, 's');
        $stmt = $pdo->prepare("
            SELECT cr.id AS registration_id,
                   cr.*,
                   {$studentSelect},
                   f.name AS faculty_name,
                   d.name AS department_name,
                   p.name AS programme_name
            FROM course_registrations cr
            LEFT JOIN students s ON s.id = cr.student_id
            LEFT JOIN faculties f ON f.id = cr.faculty_id
            LEFT JOIN departments d ON d.id = cr.department_id
            LEFT JOIN programmes p ON p.id = cr.programme_id
            WHERE {$whereSql}
            ORDER BY COALESCE(cr.created_at, cr.updated_at) DESC, cr.id DESC
            LIMIT 500
        ");
        $stmt->execute($params);
        $regs = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {
        flash('danger', 'Unable to load course registrations right now.');
        $regs = [];
    }

    $registrationCount = count($regs);
    $totalUnitsCount = 0;
    $latestRegistrationDate = '';
    foreach ($regs as $row) {
        $totalUnitsCount += (int)($row['total_units'] ?? 0);
        $createdAt = trim((string)($row['created_at'] ?? ''));
        if ($createdAt !== '' && ($latestRegistrationDate === '' || strtotime($createdAt) > strtotime($latestRegistrationDate))) {
            $latestRegistrationDate = $createdAt;
        }
    }
    $scopeSummary = portalRegistrationScopeSummary($pdo, [
        'session_name' => $filterSess,
        'semester' => $filterSem,
        'faculty_id' => $filterFac,
        'department_id' => $filterDept,
        'programme_id' => $filterProg,
        'level' => $filterLevel,
    ]);
    if ($scopeSummary === '') {
        $scopeSummary = 'All saved course registrations';
    }
    ?>
    <style>
        .portal-regmgr-hero{background:linear-gradient(135deg,#0a3f7a 0%,#0b4f97 100%);color:#fff;border:none;border-radius:1rem;overflow:hidden;box-shadow:0 18px 44px rgba(9,46,86,.16)}
        .portal-regmgr-hero .card-body{padding:1.25rem 1.35rem}
        .portal-regmgr-chip{display:inline-flex;align-items:center;gap:.45rem;padding:.42rem .78rem;border-radius:999px;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.18);font-size:.86rem}
        .portal-regmgr-filter-card,.portal-regmgr-table-card{border:1px solid #d9e4f2;border-radius:1rem;box-shadow:0 10px 28px rgba(15,39,71,.06)}
        .portal-regmgr-stat{border:1px solid #d9e4f2;border-radius:1rem;background:#fff;box-shadow:0 8px 22px rgba(15,39,71,.05);padding:1rem 1.05rem;height:100%}
        .portal-regmgr-stat .label{font-size:.76rem;font-weight:700;letter-spacing:.08em;color:#6b7b93;text-transform:uppercase}
        .portal-regmgr-stat .value{font-size:1.85rem;line-height:1.05;font-weight:800;color:#0b3e77;margin-top:.45rem}
        .portal-regmgr-stat .sub{color:#6b7280;font-size:.9rem;margin-top:.25rem}
        .portal-regmgr-empty{border:1px dashed #c9d7eb;border-radius:1rem;background:#f8fbff;padding:2.2rem 1.25rem;text-align:center}
        .portal-regmgr-empty .icon{width:72px;height:72px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#eaf2ff;color:#0a4b8f;font-size:1.9rem;margin-bottom:.9rem}
        .portal-regmgr-table-card .table>thead>tr>th{white-space:nowrap;background:#f6f9fc;color:#12385f;font-weight:700;border-bottom-width:1px}
        .portal-regmgr-filter-card .form-label{font-weight:600;color:#183a60}
        .portal-regmgr-hint{background:#f7fbff;border:1px solid #d9e7f8;color:#35567c;border-radius:.85rem;padding:.8rem .95rem}
        @media (max-width: 991.98px){.portal-regmgr-hero .card-body{padding:1rem}.portal-regmgr-stat .value{font-size:1.5rem}}
    </style>
    <div class="container-fluid">
        <?php renderFlashes(); ?>

        <div class="card portal-regmgr-hero mb-3">
            <div class="card-body d-flex flex-wrap justify-content-between align-items-start gap-3">
                <div>
                    <div class="d-flex align-items-center gap-2 mb-2"><i class="bi bi-card-checklist"></i><span class="fw-semibold">Registration Manager</span></div>
                    <h4 class="mb-2">Manage, review, print, and download saved course registrations</h4>
                    <div class="opacity-75 small">This page now focuses only on real saved registrations. The broken student-side previous-form block has been removed from admin view so the page stays clean and reliable.</div>
                </div>
                <div class="d-flex flex-wrap gap-2">
                    <span class="portal-regmgr-chip"><i class="bi bi-diagram-3"></i> <?php echo e($scopeSummary); ?></span>
                    <a class="btn btn-sm btn-light" href="?page=admin_courses_manager"><i class="bi bi-journal-bookmark"></i> Courses</a>
                    <a class="btn btn-sm btn-outline-light" href="?page=admin_structure_manager"><i class="bi bi-diagram-3"></i> Structure</a>
                </div>
            </div>
        </div>

        <div class="card portal-regmgr-filter-card mb-3">
            <div class="card-body">
                <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
                    <div>
                        <h5 class="mb-1">Filter registrations</h5>
                        <div class="text-muted small">Narrow saved forms by session, semester, level, school scope, and student details.</div>
                    </div>
                    <div class="portal-regmgr-hint small"><i class="bi bi-info-circle"></i> Students submit registrations from <strong>Student → Course Registration</strong>. Admin can review and print only the saved records here.</div>
                </div>
                <form method="get" class="row g-3 align-items-end">
                    <input type="hidden" name="page" value="admin_registrations_manager">
                    <div class="col-12 col-md-3 col-lg-2">
                        <label class="form-label">Session</label>
                        <select class="form-select" name="session_name">
                            <option value="">All Sessions</option>
                            <?php foreach ($sessionOptions as $sessionOpt): ?>
                                <option value="<?php echo e($sessionOpt); ?>" <?php echo ((string)$filterSess === (string)$sessionOpt) ? 'selected' : ''; ?>><?php echo e($sessionOpt); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-12 col-md-3 col-lg-2">
                        <label class="form-label">Semester</label>
                        <select class="form-select" name="semester">
                            <option value="">All</option>
                            <option value="First" <?php echo ($filterSem==='First')?'selected':''; ?>>First</option>
                            <option value="Second" <?php echo ($filterSem==='Second')?'selected':''; ?>>Second</option>
                        </select>
                    </div>
                    <div class="col-12 col-md-3 col-lg-2">
                        <label class="form-label">Level</label>
                        <select class="form-select" name="level">
                            <option value="">All Levels</option>
                            <?php foreach ($levelOptions as $levelOpt): ?>
                                <option value="<?php echo e($levelOpt); ?>" <?php echo ((string)$filterLevel === (string)$levelOpt) ? 'selected' : ''; ?>><?php echo e($levelOpt); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-12 col-md-3 col-lg-3">
                        <label class="form-label">Faculty</label>
                        <select class="form-select" name="faculty_id">
                            <option value="0">All Faculties</option>
                            <?php foreach ($faculties as $f): ?>
                                <option value="<?php echo (int)$f['id']; ?>" <?php echo ($filterFac === (int)$f['id']) ? 'selected' : ''; ?>><?php echo e((string)($f['name'] ?? '')); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-12 col-md-4 col-lg-3">
                        <label class="form-label">Department</label>
                        <select class="form-select" name="department_id">
                            <option value="0">All Departments</option>
                            <?php foreach ($departments as $d): ?>
                                <option value="<?php echo (int)$d['id']; ?>" <?php echo ($filterDept === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e(trim((string)($d['name'] ?? ''))); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-12 col-md-4 col-lg-3">
                        <label class="form-label">Programme</label>
                        <select class="form-select" name="programme_id">
                            <option value="0">All Programmes</option>
                            <?php foreach ($programmes as $p): ?>
                                <option value="<?php echo (int)$p['id']; ?>" <?php echo ($filterProg === (int)$p['id']) ? 'selected' : ''; ?>><?php echo e(trim((string)($p['name'] ?? ''))); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-12 col-md-4 col-lg-3">
                        <label class="form-label">Search</label>
                        <input class="form-control" name="q" value="<?php echo e($q); ?>" placeholder="matric no / application no / name / email">
                    </div>
                    <div class="col-12 d-flex flex-wrap gap-2 pt-1">
                        <button class="btn btn-primary" type="submit"><i class="bi bi-funnel"></i> Apply Filters</button>
                        <a class="btn btn-outline-secondary" href="?page=admin_registrations_manager"><i class="bi bi-arrow-counterclockwise"></i> Reset</a>
                        <a class="btn btn-outline-dark ms-md-auto" target="_blank" href="?page=admin_registration_summary_report&mode=saved&print=1&session_name=<?php echo urlencode((string)$filterSess); ?>&semester=<?php echo urlencode((string)$filterSem); ?>&faculty_id=<?php echo (int)$filterFac; ?>&department_id=<?php echo (int)$filterDept; ?>&programme_id=<?php echo (int)$filterProg; ?>&level=<?php echo urlencode((string)$filterLevel); ?>&q=<?php echo urlencode((string)$q); ?>"><i class="bi bi-printer"></i> Print Summary</a>
                        <a class="btn btn-outline-secondary" target="_blank" href="?page=admin_registration_summary_report&mode=saved&download=1&session_name=<?php echo urlencode((string)$filterSess); ?>&semester=<?php echo urlencode((string)$filterSem); ?>&faculty_id=<?php echo (int)$filterFac; ?>&department_id=<?php echo (int)$filterDept; ?>&programme_id=<?php echo (int)$filterProg; ?>&level=<?php echo urlencode((string)$filterLevel); ?>&q=<?php echo urlencode((string)$q); ?>"><i class="bi bi-download"></i> Download Summary</a>
                    </div>
                </form>
            </div>
        </div>

        <div class="row g-3 mb-3">
            <div class="col-12 col-md-4">
                <div class="portal-regmgr-stat">
                    <div class="label">Filtered Registrations</div>
                    <div class="value"><?php echo (int)$registrationCount; ?></div>
                    <div class="sub">Saved course registration record(s)</div>
                </div>
            </div>
            <div class="col-12 col-md-4">
                <div class="portal-regmgr-stat">
                    <div class="label">Total Registered Units</div>
                    <div class="value"><?php echo (int)$totalUnitsCount; ?></div>
                    <div class="sub">Combined across the filtered list</div>
                </div>
            </div>
            <div class="col-12 col-md-4">
                <div class="portal-regmgr-stat">
                    <div class="label">Latest Saved Record</div>
                    <div class="value" style="font-size:1.2rem;"><?php echo e($latestRegistrationDate !== '' ? date('d M Y', strtotime($latestRegistrationDate)) : '—'); ?></div>
                    <div class="sub"><?php echo e($filterSess !== '' ? $filterSess : 'Across all sessions'); ?></div>
                </div>
            </div>
        </div>

        <div class="card portal-regmgr-table-card">
            <div class="card-header bg-white d-flex flex-wrap justify-content-between align-items-center gap-2">
                <div>
                    <h5 class="mb-1">Saved course registrations</h5>
                    <div class="text-muted small">Open any record to print the exact saved form or download it as PDF.</div>
                </div>
                <div class="d-flex flex-wrap gap-2 align-items-center">
                    <span class="badge text-bg-primary"><?php echo (int)$registrationCount; ?> record(s)</span>
                    <a class="btn btn-sm btn-outline-primary" target="_blank" href="?page=admin_registration_summary_report&mode=saved&print=1&session_name=<?php echo urlencode((string)$filterSess); ?>&semester=<?php echo urlencode((string)$filterSem); ?>&faculty_id=<?php echo (int)$filterFac; ?>&department_id=<?php echo (int)$filterDept; ?>&programme_id=<?php echo (int)$filterProg; ?>&level=<?php echo urlencode((string)$filterLevel); ?>&q=<?php echo urlencode((string)$q); ?>"><i class="bi bi-printer"></i> Print Table Sheet</a>
                    <a class="btn btn-sm btn-outline-secondary" target="_blank" href="?page=admin_registration_summary_report&mode=saved&download=1&session_name=<?php echo urlencode((string)$filterSess); ?>&semester=<?php echo urlencode((string)$filterSem); ?>&faculty_id=<?php echo (int)$filterFac; ?>&department_id=<?php echo (int)$filterDept; ?>&programme_id=<?php echo (int)$filterProg; ?>&level=<?php echo urlencode((string)$filterLevel); ?>&q=<?php echo urlencode((string)$q); ?>"><i class="bi bi-download"></i> Download Table Sheet</a>
                </div>
            </div>
            <div class="card-body">
                <?php if (!$regs): ?>
                    <div class="portal-regmgr-empty">
                        <div class="icon"><i class="bi bi-journal-x"></i></div>
                        <div class="fw-semibold mb-2">No saved registrations match the current filters.</div>
                        <div class="text-muted">Adjust the session, semester, level, or school filters above, then try again.</div>
                    </div>
                <?php else: ?>
                    <div class="table-responsive">
                        <table class="table table-hover align-middle mb-0">
                            <thead>
                                <tr>
                                    <th>Name</th>
                                    <th>Matric No.</th>
                                    <th>Email Address</th>
                                    <th>Faculty</th>
                                    <th>Department</th>
                                    <th>Programme</th>
                                    <th>Mode of Study</th>
                                    <th>Session</th>
                                    <th>Semester</th>
                                    <th>Level</th>
                                    <th>Total Units</th>
                                    <th>Date</th>
                                    <th class="text-end">Actions</th>
                                </tr>
                            </thead>
                            <tbody>
                            <?php foreach ($regs as $r): ?>
                                <?php
                                    $studentNumber = trim((string)($r['matric_no'] ?? ''));
                                    if ($studentNumber === '') $studentNumber = trim((string)($r['registration_no'] ?? ''));
                                    if ($studentNumber === '') $studentNumber = trim((string)($r['application_no'] ?? ''));
                                    $registrationEmail = trim((string)($r['email'] ?? ''));
                                    $registrationFaculty = (string)($r['faculty_name'] ?? ($r['faculty_text'] ?? ''));
                                    $registrationDepartment = (string)($r['department_name'] ?? ($r['department_text'] ?? ''));
                                    $registrationActions = portalCourseRegistrationActionUrls($pdo, $r, 'admin_registrations_manager');
                                ?>
                                <tr>
                                    <td><div class="fw-semibold"><?php echo e(full_name($r)); ?></div></td>
                                    <td><?php echo e($studentNumber !== '' ? $studentNumber : 'No number'); ?></td>
                                    <td><?php echo e($registrationEmail !== '' ? $registrationEmail : '—'); ?></td>
                                    <td><?php echo e($registrationFaculty !== '' ? $registrationFaculty : '—'); ?></td>
                                    <td><?php echo e($registrationDepartment !== '' ? $registrationDepartment : '—'); ?></td>
                                    <td><?php echo e((string)($r['programme_name'] ?? ($r['programme_name_cache'] ?? ($r['programme_text'] ?? '')))); ?></td>
                                    <td><?php echo e(portalInferModeOfStudy((array)$r)); ?></td>
                                    <td><?php echo e((string)($r['session_name'] ?? '')); ?></td>
                                    <td><?php echo e((string)($r['semester'] ?? '')); ?></td>
                                    <td><?php echo e((string)($r['level'] ?? '')); ?></td>
                                    <td><?php echo (int)($r['total_units'] ?? 0); ?></td>
                                    <td><?php echo e(!empty($r['created_at']) ? date('d M Y', strtotime((string)$r['created_at'])) : '—'); ?></td>
                                    <td class="text-end">
                                        <div class="d-inline-flex gap-2 flex-wrap justify-content-end">
                                            <?php if (!empty($registrationActions['view'])): ?>
                                                <a class="btn btn-sm btn-outline-primary" href="<?php echo e((string)$registrationActions['view']); ?>" target="_blank" rel="noopener"><i class="bi bi-printer"></i> View / Print</a>
                                                <a class="btn btn-sm btn-outline-secondary" href="<?php echo e((string)$registrationActions['download']); ?>" target="_blank" rel="noopener"><i class="bi bi-download"></i> Download PDF</a>
                                            <?php else: ?>
                                                <button class="btn btn-sm btn-outline-primary" type="button" disabled title="Saved course form not found for this record."><i class="bi bi-printer"></i> View / Print</button>
                                                <button class="btn btn-sm btn-outline-secondary" type="button" disabled title="Saved course form not found for this record."><i class="bi bi-download"></i> Download PDF</button>
                                            <?php endif; ?>
                                        </div>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                <?php endif; ?>
                <div class="text-muted small mt-3">This page now shows only real saved registrations and direct print/download actions. The broken extra previous-form widget has been removed from admin view to keep the environment clean and error-free.</div>
            </div>
        </div>
    </div>

    

    <?php
    break;

case 'student_course_registration':
    if (!isStudent()) { redirect('?page=login'); }
    portalEnsureCourseRegistrationProfileSupport($pdo);
    $studentId = (int)($_SESSION['user_id'] ?? 0);
    $student = [];
    try { $st = $pdo->prepare("SELECT * FROM students WHERE id=? LIMIT 1"); $st->execute([$studentId]); $student = $st->fetch(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    $regFeeStatus = (string)($student['registration_fee_status'] ?? 'Unpaid');
    if ($regFeeStatus !== 'Paid') { flash('danger', 'You must pay your Registration Fee before you can register courses.'); redirect('?page=payment&tab=registration'); }
    $sessionName = portalNormalizeSessionNameSafe((string)portalCurrentAcademicSessionName($pdo));
    $requestedSemester = (string)($_GET['semester'] ?? '');
    if (!in_array($requestedSemester, ['First','Second'], true)) $requestedSemester = '';
    $tab = (string)($_GET['tab'] ?? 'register'); if (!in_array($tab, ['register','print'], true)) $tab = 'register';
    $allowedRegisterSemesters = portalAcademicSessionAllowedRegistrationSemesters($pdo, $sessionName);
    $semesterRegistrationAccess = portalAcademicSessionRegistrationAccess($pdo, $sessionName);
    $semesterAccessMessage = '';
    if ($tab === 'register') {
        if (!$allowedRegisterSemesters) {
            $semester = 'First';
            $semesterAccessMessage = 'Semester registration is currently disabled for both semesters for ' . $sessionName . '.';
        } else {
            if ($requestedSemester !== '' && !in_array($requestedSemester, $allowedRegisterSemesters, true)) {
                $semesterAccessMessage = 'Semester registration access for ' . $sessionName . ' is currently limited to ' . portalAcademicSessionRegistrationAccessLabel($semesterRegistrationAccess) . '. The form has been adjusted automatically.';
            } elseif (count($allowedRegisterSemesters) === 1) {
                $semesterAccessMessage = 'Semester registration access for ' . $sessionName . ' is currently limited to ' . portalAcademicSessionRegistrationAccessLabel($semesterRegistrationAccess) . '.';
            }
            $semester = in_array($requestedSemester, $allowedRegisterSemesters, true) ? $requestedSemester : (string)($allowedRegisterSemesters[0] ?? 'First');
        }
    } else {
        $semester = $requestedSemester !== '' ? $requestedSemester : 'First';
    }
    $courseRegistrationFeeGuard = portalCourseRegistrationSchoolFeeGuard($pdo, (array)$student, $sessionName, $semester);
    if (($tab === 'print') && $_SERVER['REQUEST_METHOD'] === 'POST' && ((string)($_POST['portal_prev_course_form_api'] ?? '') === '1' || (string)($_GET['portal_prev_course_form_api'] ?? '') === '1')) {
        $apiPrevDisplayErrors = (string)ini_get('display_errors');
        @ini_set('display_errors', '0');
        if (ob_get_level() > 0) { @ob_clean(); }
        ob_start();
        $emitPrevCourseFormJson = static function(array $payload) use ($apiPrevDisplayErrors): void {
            $noise = trim((string)ob_get_clean());
            if ($noise !== '') {
                error_log('Previous course form API noise: ' . preg_replace('/\s+/', ' ', $noise));
            }
            @ini_set('display_errors', $apiPrevDisplayErrors !== '' ? $apiPrevDisplayErrors : '1');
            if (!headers_sent()) {
                header('Content-Type: application/json; charset=utf-8');
                header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
                header('Pragma: no-cache');
            }
            echo json_encode($payload);
            exit;
        };

        $s = trim((string)($_POST['session_name'] ?? ''));
        $sem = (string)($_POST['semester'] ?? '');
        $lvl = trim((string)($_POST['level'] ?? ''));

        if ($s === '' || !in_array($sem, ['First','Second'], true) || $lvl === '') {
            $emitPrevCourseFormJson(['ok' => false, 'message' => 'Please select valid Year/Session, Semester, and Level.']);
        }

        try {
            $stPrev = $pdo->prepare("SELECT cr.*, " . portalCourseRegistrationStudentSelectList($pdo, 's') . ", f.name AS faculty_name, d.name AS department_name, p.name AS programme_name FROM course_registrations cr LEFT JOIN students s ON s.id = cr.student_id LEFT JOIN faculties f ON f.id = cr.faculty_id LEFT JOIN departments d ON d.id = cr.department_id LEFT JOIN programmes p ON p.id = cr.programme_id WHERE cr.student_id=? AND COALESCE(cr.session_name,'')=? AND cr.semester=? AND COALESCE(cr.level,'')=? ORDER BY cr.id DESC LIMIT 1");
            $stPrev->execute([$studentId, $s, $sem, $lvl]);
            $prevReg = $stPrev->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$prevReg) {
                $emitPrevCourseFormJson(['ok' => true, 'html' => '', 'message' => 'No previous course form was found for the selected Year/Session, Semester, and Level.']);
            }

            $prevItems = portalFetchSynchronizedCourseRegistrationItems($pdo, (int)$prevReg['id']);
            ob_start();
            portalRenderCourseRegistrationPrintable($pdo, $prevReg, $prevItems, false, false);
            $html = (string)ob_get_clean();

            $emitPrevCourseFormJson([
                'ok' => true,
                'html' => $html,
                'reg_id' => (int)($prevReg['id'] ?? 0),
                'print_url' => '?page=course_form&reg_id=' . (int)($prevReg['id'] ?? 0) . '&print=1',
                'download_url' => '?page=course_form&reg_id=' . (int)($prevReg['id'] ?? 0) . '&download=1',
                'csv_url' => '?page=student_course_registration&tab=print&export=csv&session_name=' . urlencode($s) . '&semester=' . urlencode($sem) . '&level=' . urlencode($lvl),
            ]);
        } catch (Throwable $e) {
            error_log('Previous course form API error: ' . $e->getMessage());
            $emitPrevCourseFormJson(['ok' => false, 'message' => 'The previous course form could not be loaded right now.']);
        }
    }
    if (($tab === 'print') && isset($_GET['export']) && (string)$_GET['export'] === 'csv') {
        $s = trim((string)($_GET['session_name'] ?? '')); $sem = (string)($_GET['semester'] ?? ''); $lvl = trim((string)($_GET['level'] ?? ''));
        if ($s !== '' && in_array($sem, ['First','Second'], true) && $lvl !== '') {
            try { $st = $pdo->prepare("SELECT id FROM course_registrations WHERE student_id=? AND COALESCE(session_name,'')=? AND semester=? AND COALESCE(level,'')=? ORDER BY id DESC LIMIT 1"); $st->execute([$studentId,$s,$sem,$lvl]); $regId = (int)($st->fetchColumn() ?? 0); if ($regId > 0) { $rows = portalFetchSynchronizedCourseRegistrationItems($pdo, $regId); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="course_form_' . preg_replace('/[^a-zA-Z0-9_-]+/','_', $s . '_' . $sem . '_' . $lvl) . '.csv"'); $out = fopen('php://output', 'w'); fputcsv($out, ['Course Code','Course Title','Credit Unit','Type']); foreach ($rows as $r) { fputcsv($out, [(string)($r['course_code']??''),(string)($r['course_title']??''),(int)($r['credit_unit']??0),(string)($r['course_type']??'C')]); } fclose($out); exit; } } catch (Throwable $e) {}
        }
        flash('danger', 'Unable to export. Please select valid Year/Session, Semester, and Level.'); redirect('?page=student_course_registration&tab=print');
    }
    if (($tab === 'print') && isset($_GET['print']) && (string)$_GET['print'] === '1') {
        $s = trim((string)($_GET['session_name'] ?? '')); $sem = (string)($_GET['semester'] ?? ''); $lvl = trim((string)($_GET['level'] ?? ''));
        if ($s !== '' && in_array($sem, ['First','Second'], true) && $lvl !== '') {
            try { $st = $pdo->prepare("SELECT id FROM course_registrations WHERE student_id=? AND COALESCE(session_name,'')=? AND semester=? AND COALESCE(level,'')=? ORDER BY id DESC LIMIT 1"); $st->execute([$studentId,$s,$sem,$lvl]); $regId = (int)($st->fetchColumn() ?? 0); if ($regId > 0) redirect('?page=course_form&reg_id=' . $regId); } catch (Throwable $e) {}
        }
        flash('danger', 'Unable to print. Please select valid Year/Session, Semester, and Level.'); redirect('?page=student_course_registration&tab=print');
    }
    $studentSavedLevel = portalStudentResolvedCurrentLevel($pdo, (array)$student, $sessionName);
    $level = ($tab === 'register') ? $studentSavedLevel : trim((string)($_GET['level'] ?? $studentSavedLevel)); if ($level === '') $level = $studentSavedLevel;
    $studentScopeContext = portalStudentContext($pdo, (array)$student);
    $facultyId = (int)($studentScopeContext['faculty_id'] ?? ($student['faculty_id'] ?? 0));
    $departmentId = (int)($studentScopeContext['department_id'] ?? ($student['department_id'] ?? 0));
    $programmeId = (int)($studentScopeContext['programme_id'] ?? ($student['programme_id'] ?? 0));
    $modeOfStudy = portalResolveStudentModeOfStudy($pdo, (array)$student, (array)$studentScopeContext, $programmeId);
    if ($modeOfStudy === '') {
        $modeOfStudy = 'Full-Time';
    }
    $courseSyncWarnings = [];
    if ($facultyId <= 0 && $departmentId <= 0 && $programmeId <= 0) {
        $courseSyncWarnings[] = 'Your faculty/department/programme mapping is incomplete, so synchronized courses cannot be loaded yet.';
    }
    $regSet = portalRegistrationRuleForContext($pdo, [
        'session_name' => $sessionName,
        'semester' => $semester,
        'faculty_id' => $facultyId,
        'department_id' => $departmentId,
        'programme_id' => $programmeId,
        'level' => $level,
    ]);
    $existing = null; $existingItems = [];
    try { $st = $pdo->prepare("SELECT * FROM course_registrations WHERE student_id=? AND COALESCE(session_name,'')=? AND semester=? AND COALESCE(level,'')=? ORDER BY id DESC LIMIT 1"); $st->execute([$studentId,(string)$sessionName,$semester,(string)$level]); $existing = $st->fetch(PDO::FETCH_ASSOC) ?: null; if ($existing) { $existingItems = portalFetchSynchronizedCourseRegistrationItems($pdo, (int)$existing['id']); } } catch (Throwable $e) {}
    $itemPartitions = portalPartitionCourseRegistrationItems($existingItems);
    $existingRegularItems = (array)($itemPartitions['regular'] ?? []);
    $existingCarryItems = (array)($itemPartitions['carry'] ?? []);
    $selectedIds = [];
    foreach ($existingRegularItems as $it) { if (!empty($it['course_id'])) $selectedIds[] = (int)$it['course_id']; }
    $selectedCarryIds = [];
    foreach ($existingCarryItems as $it) { if (!empty($it['course_id'])) $selectedCarryIds[] = (int)$it['course_id']; }
    $studyYear = portalStudentRegistrableCourseYear($pdo, (array)$student);
    $available = [];
    try {
        $availableBundle = portalFetchScopedRegistrableCourses($pdo, [
            'faculty_id' => $facultyId,
            'department_id' => $departmentId,
            'programme_id' => $programmeId,
            'semester' => $semester,
            'session_name' => $sessionName,
            'level' => $level,
            'year' => $studyYear,
            'mode_of_study' => $modeOfStudy,
            'require_registrable' => true,
        ]);
        $available = (array)($availableBundle['courses'] ?? []);
        $courseSyncWarnings = array_values(array_unique(array_merge($courseSyncWarnings, (array)($availableBundle['warnings'] ?? []))));
    } catch (Throwable $e) {
        $courseSyncWarnings[] = 'Synchronized course loading failed. Please refresh the page or contact the administrator.';
    }
    $carryOverAvailableRows = portalFetchEligibleCarryOverCourses($pdo, (array)$student, $sessionName, $semester);
    if ($existingCarryItems) {
        $carryDisplayMap = [];
        foreach ($carryOverAvailableRows as $carryRow) {
            $carryDisplayMap[(int)($carryRow['course_id'] ?? 0)] = true;
        }
        foreach ($existingCarryItems as $existingCarryItem) {
            $carryCourseId = (int)($existingCarryItem['course_id'] ?? 0);
            if ($carryCourseId > 0 && isset($carryDisplayMap[$carryCourseId])) continue;
            $carryOverAvailableRows[] = [
                'course_id' => $carryCourseId,
                'course_code' => (string)($existingCarryItem['course_code'] ?? ''),
                'course_title' => (string)($existingCarryItem['course_title'] ?? ''),
                'credit_unit' => (int)($existingCarryItem['credit_unit'] ?? 0),
                'course_type' => (string)($existingCarryItem['course_type'] ?? 'C'),
                'source_session' => (string)($existingCarryItem['carry_over_source_session'] ?? ''),
                'source_semester' => (string)($existingCarryItem['carry_over_source_semester'] ?? ''),
                'assessment_status' => 'CARRY OVER',
                'assessment_remark' => 'Previously Selected',
            ];
        }
    }
    usort($carryOverAvailableRows, static function(array $a, array $b): int {
        return strcasecmp((string)($a['course_code'] ?? ''), (string)($b['course_code'] ?? ''));
    });
    $carryOverAvailableCount = count($carryOverAvailableRows);
    $courseProfile = portalResolveCourseFormProfile($pdo, array_merge($student, ['faculty_id' => $facultyId, 'department_id' => $departmentId, 'programme_id' => $programmeId, 'mode_of_study' => $modeOfStudy]));
    $facultyName = (string)($courseProfile['school'] ?? 'Not Set');
    $departmentName = (string)($courseProfile['course_in_view'] ?? 'Not Set');
    $programmeName = (string)($courseProfile['programme'] ?? 'Not Set');
    $courseOfStudy = (string)($courseProfile['course_of_study'] ?? 'Not Set');
    $otherNamesValue = portalCourseRegistrationOtherNames($student);
    $carryOverSelected = (string)($existing['carry_over_status'] ?? ($carryOverAvailableCount > 0 ? 'Yes' : 'No')); if (!in_array($carryOverSelected, ['Yes','No'], true)) $carryOverSelected = 'No'; if (!$existing && $carryOverAvailableCount > 0) $carryOverSelected = 'Yes'; if ($existingCarryItems) $carryOverSelected = 'Yes';
    $carryOverNoteSelected = trim((string)($existing['carry_over_note'] ?? ''));
    $semesterRegistrationBlocked = ($tab === 'register' && empty($allowedRegisterSemesters)); $courseRegistrationClosed = !portalRegistrationIsOpen($regSet['is_open'] ?? 'Yes'); if (empty($courseRegistrationFeeGuard['allowed']) || $semesterRegistrationBlocked) { $courseRegistrationClosed = true; } $semesterTitle = portalNormalizeSemesterName($semester); $logoPath = portalSchoolLogoPath(); $schoolName = trim((string)setting('portal_title', 'HontTech System')); $matricDisplay = trim((string)($student['matric_no'] ?? '')) ?: trim((string)($student['registration_no'] ?? $student['application_no'] ?? '')) ?: 'Processing';
    $canSubmitRegistration = (!$courseRegistrationClosed && (!empty($available) || $carryOverAvailableCount > 0));
    $initialTotalUnits = 0; foreach ($available as $courseRow) { if (in_array((int)$courseRow['id'], $selectedIds, true)) $initialTotalUnits += (int)($courseRow['credit_unit'] ?? 0); }
    foreach ($carryOverAvailableRows as $carryCourseRow) { if (in_array((int)($carryCourseRow['course_id'] ?? 0), $selectedCarryIds, true)) $initialTotalUnits += (int)($carryCourseRow['credit_unit'] ?? 0); }
    ?>
    <style><?php echo portalCourseRegistrationCss(); ?> .course-reg-register-table thead th{background:#f2f4f7;font-size:.9rem;}.course-reg-register-table td,.course-reg-register-table th{vertical-align:middle;}.course-reg-status-pill{display:inline-flex;align-items:center;gap:8px;padding:8px 12px;border-radius:999px;font-weight:700;font-size:.9rem;}.course-reg-status-pill.open{background:#ecfdf3;color:#027a48;border:1px solid #abefc6;}.course-reg-status-pill.closed{background:#fef3f2;color:#b42318;border:1px solid #fecdca;}</style>
    <div class="card"><div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2"><div><i class="bi bi-clipboard-check"></i> Course Registration</div><?php if ($existing): ?><div class="d-flex gap-2 flex-wrap"><a class="btn btn-sm btn-outline-primary" href="?page=course_form&reg_id=<?php echo (int)$existing['id']; ?>" target="_blank"><i class="bi bi-printer"></i> View/Print Form</a><a class="btn btn-sm btn-outline-secondary" href="?page=course_form&reg_id=<?php echo (int)$existing['id']; ?>&download=1"><i class="bi bi-download"></i> Download Form</a><?php $publicCourseUrl = portalCourseFormPublicUrl($pdo, (int)$existing['id']); if ($publicCourseUrl !== ''): ?><a class="btn btn-sm btn-outline-dark" href="<?php echo e($publicCourseUrl); ?>" target="_blank"><i class="bi bi-qr-code"></i> Public Verify</a><?php endif; ?></div><?php endif; ?></div><div class="card-body"><ul class="nav nav-tabs mt-1" role="tablist"><li class="nav-item"><a class="nav-link <?=( $tab==='register' ? 'active' : '' )?>" href="?page=student_course_registration&tab=register&semester=<?=urlencode($semester)?>&level=<?=urlencode($level)?>">Register Courses</a></li><li class="nav-item"><a class="nav-link <?=( $tab==='print' ? 'active' : '' )?>" href="?page=student_course_registration&tab=print">Print Previous Course Form</a></li></ul><?php renderFlashes(); ?><?php if ($tab === 'register'): ?><div class="course-reg-pdf-shell mt-3"><div class="course-reg-head"><div class="course-reg-brand"><?php if ($logoPath): ?><img src="<?php echo e($logoPath); ?>" alt="School Logo"><?php else: ?><div class="course-reg-brand-fallback">LOGO</div><?php endif; ?><div><div class="course-reg-school"><?php echo e($schoolName); ?></div><div class="course-reg-title">Course Registration Form</div></div></div><div class="course-reg-session"><?php echo e($sessionName); ?> Session</div></div><form method="get" class="course-reg-mini-meta d-print-none"><input type="hidden" name="page" value="student_course_registration"><input type="hidden" name="tab" value="register"><div class="course-reg-mini-card"><div class="label">Current Session</div><div class="value"><?php echo e($sessionName !== '' ? $sessionName : 'Not Set'); ?></div></div><div class="course-reg-mini-card"><label class="label">Semester</label><?php if (count($allowedRegisterSemesters) > 1): ?><select class="form-select mt-1" name="semester" onchange="this.form.submit()"><?php foreach ($allowedRegisterSemesters as $semesterOption): ?><option value="<?php echo e($semesterOption); ?>" <?php echo ($semester===$semesterOption)?'selected':''; ?>><?php echo e($semesterOption); ?></option><?php endforeach; ?></select><?php else: ?><input type="hidden" name="semester" value="<?php echo e($semester); ?>"><input type="text" class="form-control mt-1" value="<?php echo e(!empty($allowedRegisterSemesters) ? $semester : 'Registration Disabled'); ?>" readonly><?php endif; ?></div><div class="course-reg-mini-card"><label class="label">Level</label><input type="hidden" name="level" value="<?php echo e($studentSavedLevel); ?>"><input type="text" class="form-control mt-1" value="<?php echo e($studentSavedLevel); ?>" readonly></div></form><?php if ($semesterAccessMessage !== ''): ?><div class="alert <?php echo empty($allowedRegisterSemesters) ? 'alert-danger' : 'alert-info'; ?> mb-2"><?php echo e($semesterAccessMessage); ?></div><?php endif; ?><div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-2"><div class="course-reg-note-title">Important Notice</div><span class="course-reg-status-pill <?php echo $courseRegistrationClosed ? 'closed' : 'open'; ?>">Registration: <?php echo e(portalRegistrationOpenLabel($regSet['is_open'] ?? 'Yes')); ?></span></div><div class="course-reg-note"><ol><li>Student registering should make sure he/she updates his/her biodata in the Personal Details section below.</li><li>Select the appropriate courses to be registered in the list of courses displayed and click <strong>Submit Registration</strong>.</li></ol></div><table class="course-reg-summary-table"><thead><tr><th>Matric No</th><th>School</th><th>Course of Study</th><th>Level</th><th>Programme</th><th>Mode of Study</th></tr></thead><tbody><tr><td><?php echo e($matricDisplay); ?></td><td><?php echo e($facultyName !== '' ? $facultyName : 'Not Set'); ?></td><td><?php echo e($courseOfStudy); ?></td><td><?php echo e($level); ?></td><td><?php echo e($programmeName !== '' ? $programmeName : 'Not Set'); ?></td><td><?php echo e($modeOfStudy); ?></td></tr></tbody></table><div class="course-reg-alert-line">Make sure you click on UPDATE / SUBMIT REGISTRATION button below to finish your registration.</div><?php if (!empty($courseRegistrationFeeGuard['current_summary']) && is_array($courseRegistrationFeeGuard['current_summary'])): $currentSchoolSummary = (array)$courseRegistrationFeeGuard['current_summary']; ?><div class="alert alert-info">School fee status for <?php echo e($sessionName); ?>: <strong><?php echo e((string)($currentSchoolSummary['status_label'] ?? 'Not Paid')); ?></strong><?php if ((float)($currentSchoolSummary['required_amount'] ?? 0) > 0): ?> • Paid: <strong>₦<?php echo number_format((float)($currentSchoolSummary['total_paid'] ?? 0), 2); ?></strong> / <strong>₦<?php echo number_format((float)($currentSchoolSummary['required_amount'] ?? 0), 2); ?></strong><?php endif; ?> • System semester: <strong><?php echo e((string)($courseRegistrationFeeGuard['system_semester'] ?? portalCurrentAcademicSemesterName($pdo))); ?></strong>.</div><?php endif; ?><?php if (empty($courseRegistrationFeeGuard['allowed'])): ?><div class="alert alert-danger"><?php echo e((string)($courseRegistrationFeeGuard['message'] ?? 'School fee clearance is required before course registration.')); ?></div><?php endif; ?><?php if ($semesterRegistrationBlocked && !empty($courseRegistrationFeeGuard['allowed'])): ?><div class="alert alert-danger">Course registration is currently disabled for both semesters for <?php echo e($sessionName); ?>. You can still view or print an already saved form.</div><?php elseif ($courseRegistrationClosed && !empty($courseRegistrationFeeGuard['allowed'])): $scopeText = portalRegistrationScopeSummary($pdo, ['session_name' => $sessionName, 'semester' => $semester, 'faculty_id' => $facultyId, 'department_id' => $departmentId, 'programme_id' => $programmeId, 'level' => $level]); ?><div class="alert alert-danger">Course registration is closed for your session/semester/department/programme/level<?php echo $scopeText !== '' ? ' (' . e($scopeText) . ')' : ''; ?>. You can still view or print an already saved form.</div><?php endif; ?><?php if (!empty($courseSyncWarnings)): ?><div class="alert alert-warning"><?php echo e(implode(' ', array_values(array_unique(array_filter($courseSyncWarnings))))); ?></div><?php endif; ?><form method="post" id="courseRegPdfForm"><input type="hidden" name="action" value="save_course_registration"><input type="hidden" name="semester" value="<?php echo e($semester); ?>"><input type="hidden" name="session_name" value="<?php echo e($sessionName); ?>"><input type="hidden" name="level" value="<?php echo e($level); ?>"><input type="hidden" name="mode_of_study" value="<?php echo e($modeOfStudy); ?>"><div class="course-reg-section-title">Personal Details</div><div class="course-reg-personal-grid"><div class="course-reg-field"><label>Title</label><select class="form-select" name="title"><?php $titleVal = (string)($student['title'] ?? ''); ?><option value="">Select</option><?php foreach (['Mr.','Mrs.','Miss','Dr.','Prof.'] as $opt): ?><option value="<?php echo e($opt); ?>" <?php echo ($titleVal===$opt)?'selected':''; ?>><?php echo e($opt); ?></option><?php endforeach; ?></select></div><div class="course-reg-field"><label>Surname</label><input class="form-control" name="surname" value="<?php echo e((string)($student['surname'] ?? '')); ?>" required></div><div class="course-reg-field"><label>Other Names</label><input class="form-control" name="other_names" value="<?php echo e($otherNamesValue); ?>" required></div><div class="course-reg-field"><label>Maiden Name</label><input class="form-control" name="maiden_name" value="<?php echo e((string)($student['maiden_name'] ?? '')); ?>"></div><div class="course-reg-inline-three"><div class="course-reg-field"><label>Date of Birth</label><input type="date" class="form-control" name="date_of_birth" value="<?php echo e((string)($student['date_of_birth'] ?? '')); ?>" required></div><div class="course-reg-field"><label>Marital Status</label><?php $maritalVal = (string)($student['marital_status'] ?? ''); ?><select class="form-select" name="marital_status"><option value="">Select</option><?php foreach (['Single','Married','Divorced','Widowed'] as $opt): ?><option value="<?php echo e($opt); ?>" <?php echo ($maritalVal===$opt)?'selected':''; ?>><?php echo e($opt); ?></option><?php endforeach; ?></select></div><div class="course-reg-field"><label>Sex</label><?php $sexVal = (string)($student['sex'] ?? 'Male'); ?><select class="form-select" name="sex"><option value="Male" <?php echo ($sexVal==='Male')?'selected':''; ?>>Male</option><option value="Female" <?php echo ($sexVal==='Female')?'selected':''; ?>>Female</option></select></div></div><div class="course-reg-inline-three"><div class="course-reg-field"><label>Nationality</label><input class="form-control" name="nationality" value="<?php echo e((string)($student['nationality'] ?? '')); ?>" required></div><div class="course-reg-field"><label>State</label><input class="form-control" name="state_of_origin" value="<?php echo e((string)($student['state_of_origin'] ?? '')); ?>" required></div><div class="course-reg-field"><label>Local Govt. Area</label><input class="form-control" name="lga" value="<?php echo e((string)($student['lga'] ?? '')); ?>" required></div></div><div class="course-reg-field" style="grid-column:1 / -1;"><label>Permanent Home Address</label><textarea class="form-control" name="permanent_address" rows="2" required><?php echo e((string)($student['permanent_address'] ?? '')); ?></textarea></div><div class="course-reg-inline-two"><div class="course-reg-field"><label>Email Address</label><input type="email" class="form-control" name="email" value="<?php echo e((string)($student['email'] ?? '')); ?>" required></div><div class="course-reg-field"><label>Phone Number</label><input class="form-control" name="mobile_phone" value="<?php echo e((string)($student['mobile_phone'] ?? '')); ?>" required></div></div></div><div class="course-reg-carry"><div class="course-reg-carry-card"><div class="course-reg-carry-head"><div class="course-reg-carry-title">Carry-over course selection</div><div class="course-reg-carry-badge"><?php echo (int)$carryOverAvailableCount; ?> available</div></div><div class="course-reg-carry-controls"><div class="course-reg-field"><label for="carry_over_status">Do you have any carry-over courses?</label><select class="form-select" id="carry_over_status" name="carry_over_status"><option value="No" <?php echo ($carryOverSelected==='No')?'selected':''; ?>>No</option><option value="Yes" <?php echo ($carryOverSelected==='Yes')?'selected':''; ?>>Yes</option></select><p class="course-reg-carry-help">Select <strong>Yes</strong> to show eligible carry-over courses linked to this student&apos;s saved result history.</p></div><div class="course-reg-field"><label>Carry-over status</label><div class="form-control" style="min-height:46px;display:flex;align-items:center;background:#fff;"><?php if ($carryOverAvailableCount > 0): ?>Eligible carry-over courses were detected for this student.<?php else: ?>No carry-over courses were detected for this student.<?php endif; ?></div><p class="course-reg-carry-help">The selector is connected to the real carry-over workflow and only shows valid carry-over courses.</p></div></div><div class="course-reg-carry-panel" id="carry_over_panel" <?php echo ($carryOverSelected==='Yes') ? '' : 'style="display:none;"'; ?>><?php if ($carryOverAvailableRows): ?><div class="course-reg-subtitle">Carry-Over Courses</div><div class="course-reg-red-note">*Select only the eligible carry-over courses you want to register with this form.</div><div class="table-responsive"><table class="table table-bordered course-reg-register-table course-reg-carry-table"><thead><tr><th style="width:80px;">Select</th><th style="width:130px;">Course Code</th><th>Course Title</th><th style="width:110px;">Credit Units</th><th style="width:110px;">Course Type</th><th style="width:120px;">Semester</th><th style="width:150px;">Session</th><th style="width:140px;">Status</th></tr></thead><tbody><?php foreach ($carryOverAvailableRows as $carryCourse): ?><?php $carryCid = (int)($carryCourse['course_id'] ?? 0); ?><tr><td class="text-center"><input class="form-check-input course-reg-course-check course-reg-carry-check" type="checkbox" name="carry_over_course_ids[]" value="<?php echo $carryCid; ?>" data-credit-unit="<?php echo (int)($carryCourse['credit_unit'] ?? 0); ?>" <?php echo in_array($carryCid, $selectedCarryIds, true) ? 'checked' : ''; ?>></td><td class="fw-bold"><?php echo e((string)($carryCourse['course_code'] ?? '')); ?></td><td><?php echo e((string)($carryCourse['course_title'] ?? '')); ?></td><td><?php echo (int)($carryCourse['credit_unit'] ?? 0); ?></td><td><?php echo e((string)($carryCourse['course_type'] ?? 'C')); ?></td><td><?php echo e((string)($carryCourse['source_semester'] ?? '')); ?></td><td><?php echo e((string)($carryCourse['source_session'] ?? '')); ?></td><td class="course-reg-carry-status"><?php echo e((string)($carryCourse['assessment_remark'] ?? $carryCourse['assessment_status'] ?? 'Carry Over')); ?></td></tr><?php endforeach; ?></tbody></table></div><?php else: ?><div class="alert alert-info course-reg-carry-empty mb-0">No carry-over courses available for this student.</div><?php endif; ?><div class="course-reg-field course-reg-carry-note-row" id="carry_over_note_wrap"><label for="carry_over_note">Carry-over note (optional)</label><textarea class="form-control" id="carry_over_note" name="carry_over_note" rows="2" placeholder="Add any note about this carry-over registration if needed."><?php echo e($carryOverNoteSelected); ?></textarea></div></div></div></div><div class="course-reg-subtitle">Courses for <?php echo e($semesterTitle); ?> <?php echo e($sessionName); ?> Session</div><div class="course-reg-subtitle">Regular Courses</div><div class="course-reg-red-note">*Note: Select Appropriate Courses To Be Registered Below</div><?php if (!$available): ?><?php if ($carryOverAvailableRows): ?><div class="alert alert-info">No synchronized regular courses are currently available for your faculty/department/programme/session/semester/level/year selection. You can still continue with eligible carry-over course registration below.</div><?php else: ?><div class="alert alert-warning">No synchronized courses are currently available for your faculty/department/programme/session/semester/level/year selection.</div><?php endif; ?><?php else: ?><div class="table-responsive"><table class="table table-bordered course-reg-register-table"><thead><tr><th style="width:80px;">Select</th><th style="width:130px;">Course Code</th><th>Course Title</th><th style="width:110px;">Credit Units</th><th style="width:110px;">Course Type</th><th style="width:120px;">Semester</th></tr></thead><tbody><?php foreach ($available as $c): ?><?php $cid = (int)$c['id']; ?><tr><td class="text-center"><input class="form-check-input course-reg-course-check" type="checkbox" name="course_ids[]" value="<?php echo $cid; ?>" data-credit-unit="<?php echo (int)($c['credit_unit'] ?? 0); ?>" <?php echo in_array($cid, $selectedIds, true) ? 'checked' : ''; ?>></td><td class="fw-bold"><?php echo e((string)($c['course_code'] ?? '')); ?></td><td><?php echo e((string)($c['course_title'] ?? '')); ?></td><td><?php echo (int)($c['credit_unit'] ?? 0); ?></td><td><?php echo e((string)($c['course_type'] ?? 'C')); ?></td><td><?php echo e((string)($c['semester'] ?? $semester)); ?></td></tr><?php endforeach; ?></tbody></table></div><?php endif; ?><div class="course-reg-total"><strong>Total Credit Units:</strong> <span id="course_total_units"><?php echo (int)$initialTotalUnits; ?></span></div><div class="course-reg-submit-row"><button class="btn btn-primary" type="submit" <?php echo $canSubmitRegistration ? '' : 'disabled'; ?>>Submit Registration</button><button class="btn btn-outline-secondary" type="submit" name="preview_after_save" value="1" <?php echo $canSubmitRegistration ? '' : 'disabled'; ?>>Preview Form</button></div></form><?php if ($existing): ?><form method="post" action="" class="mt-3 d-print-none"><input type="hidden" name="action" value="delete_course_registration"><input type="hidden" name="registration_id" value="<?php echo (int)$existing['id']; ?>"><input type="hidden" name="semester" value="<?php echo e($semester); ?>"><input type="hidden" name="level" value="<?php echo e($level); ?>"><button type="submit" class="btn btn-outline-danger" onclick="return confirm('Delete this registration form?');">Delete Form</button></form><?php endif; ?></div><script>(function(){const checks=Array.from(document.querySelectorAll('.course-reg-course-check'));const totalEl=document.getElementById('course_total_units');const carrySelect=document.getElementById('carry_over_status');const carryPanel=document.getElementById('carry_over_panel');const carryWrap=document.getElementById('carry_over_note_wrap');const carryChecks=Array.from(document.querySelectorAll('.course-reg-carry-check'));function updateTotal(){let total=0;checks.forEach(function(el){if(!el.disabled&&el.checked) total += parseInt(el.getAttribute('data-credit-unit') || '0',10)||0;});if(totalEl) totalEl.textContent=String(total);}checks.forEach(function(el){el.addEventListener('change', updateTotal);});if(carrySelect){const sync=function(){const showCarry = carrySelect.value === 'Yes'; if(carryPanel) carryPanel.style.display = showCarry ? '' : 'none'; if(carryWrap){carryWrap.style.display = showCarry ? '' : 'none'; const textarea = carryWrap.querySelector('textarea'); if(textarea) textarea.disabled = !showCarry;} carryChecks.forEach(function(el){el.disabled = !showCarry;}); updateTotal();};carrySelect.addEventListener('change', sync); sync();}updateTotal();})();</script><?php else: ?><div class="course-reg-prev-layout mt-3"><div class="course-reg-prev-filter-card"><div class="course-reg-prev-filter-head"><div><div class="course-reg-prev-filter-kicker">Previous Forms</div><h5 class="mb-1"><i class="bi bi-funnel"></i> Select Year / Semester / Level</h5><div class="text-muted small">Load any previously submitted course form from the database, then print or save it as PDF.</div></div></div><div class="mb-3"><label class="form-label">Year / Session</label><select id="prev_session" class="form-select"><option value="">-- Select --</option><?php $sessions=[]; try{$q=$pdo->query("SELECT name FROM academic_sessions ORDER BY id DESC"); $sessions=$q ? ($q->fetchAll(PDO::FETCH_COLUMN) ?: []) : [];}catch(Throwable $e){} if(!$sessions){ try{$q=$pdo->query("SELECT DISTINCT session_name FROM course_registrations WHERE student_id=" . (int)$studentId . " AND COALESCE(session_name,'')<>'' ORDER BY session_name DESC"); $sessions=$q ? ($q->fetchAll(PDO::FETCH_COLUMN) ?: []) : [];}catch(Throwable $e){}} $currentPrintSession = trim((string)$sessionName); if ($currentPrintSession !== '' && !in_array($currentPrintSession, array_map('strval', $sessions), true)) { array_unshift($sessions, $currentPrintSession); } $selectedPrintSession = trim((string)($_GET['session_name'] ?? $currentPrintSession)); foreach($sessions as $sn){$sn=(string)$sn; echo '<option value="'.htmlspecialchars($sn).'" ' . ($selectedPrintSession === $sn ? 'selected' : '') . '>' . htmlspecialchars($sn) . '</option>'; } ?></select></div><div class="mb-3"><label class="form-label">Semester</label><select id="prev_semester" class="form-select"><option value="">-- Select --</option><option value="First" <?php echo ($semester==='First')?'selected':''; ?>>First</option><option value="Second" <?php echo ($semester==='Second')?'selected':''; ?>>Second</option></select></div><div class="mb-3"><label class="form-label">Level</label><select id="prev_level" class="form-select"><option value="">-- Select --</option><?php foreach(['100','200','300','400','500','600','700'] as $lv): ?><option value="<?php echo e($lv); ?>" <?php echo ((string)$level===(string)$lv)?'selected':''; ?>><?php echo e($lv); ?></option><?php endforeach; ?></select></div><div class="d-grid gap-2"><button id="btnLoadPrev" class="btn btn-primary"><i class="bi bi-search"></i> Load Form</button><a id="btnPrintPrev" class="btn btn-outline-primary disabled" href="#" target="_blank"><i class="bi bi-printer"></i> Print / Save as PDF</a><a id="btnDownloadPrev" class="btn btn-outline-secondary disabled" href="#" target="_blank"><i class="bi bi-download"></i> Download PDF</a></div><div class="course-reg-prev-help">Select the exact session, semester, and level. The system will load the real saved course form for that record and enable print and PDF actions automatically.</div></div><div class="course-reg-prev-preview-card"><div class="course-reg-prev-preview-head"><div><div class="course-reg-prev-filter-kicker">Preview Area</div><h5 class="mb-1">Previous Course Form Preview</h5></div></div><div id="prev_form_area" class="course-reg-prev-preview-body shadow-sm"><div class="course-reg-prev-empty-state"><div class="icon"><i class="bi bi-file-earmark-richtext"></i></div><div class="title">No form loaded yet</div><div class="text">Select Year/Session, Semester, and Level, then click <strong>Load Form</strong> to preview the exact previously saved course registration form.</div></div></div></div></div><script>
(function(){
    const btn = document.getElementById('btnLoadPrev');
    const s = document.getElementById('prev_session');
    const sem = document.getElementById('prev_semester');
    const lvl = document.getElementById('prev_level');
    const area = document.getElementById('prev_form_area');
    const btnPrint = document.getElementById('btnPrintPrev');
    const btnDownload = document.getElementById('btnDownloadPrev');
    const btnCsv = document.getElementById('btnCsvPrev');

    function setExports(payload, sessionName, semester, level){
        const base = '?page=student_course_registration&tab=print'
            + '&session_name=' + encodeURIComponent(sessionName)
            + '&semester=' + encodeURIComponent(semester)
            + '&level=' + encodeURIComponent(level);
        if (btnPrint) {
            btnPrint.href = (payload && payload.print_url) ? payload.print_url : (base + '&print=1');
            btnPrint.classList.remove('disabled');
        }
        if (btnDownload) {
            btnDownload.href = (payload && payload.download_url) ? payload.download_url : '#';
            btnDownload.classList.toggle('disabled', !(payload && payload.download_url));
        }
        if (btnCsv) {
            btnCsv.href = (payload && payload.csv_url) ? payload.csv_url : (base + '&export=csv');
            btnCsv.classList.remove('disabled');
        }
    }

    function resetExports(){
        [btnPrint, btnDownload, btnCsv].forEach(function(node){
            if (!node) return;
            node.href = '#';
            node.classList.add('disabled');
        });
    }

    function escapeHtml(value){
        return String(value == null ? '' : value)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
    }

    function safeParsePayload(raw){
        const source = String(raw == null ? '' : raw).trim();
        if (!source) return null;
        try {
            return JSON.parse(source);
        } catch (primaryError) {
            const firstBrace = source.indexOf('{');
            const lastBrace = source.lastIndexOf('}');
            if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
                const candidate = source.slice(firstBrace, lastBrace + 1);
                try {
                    return JSON.parse(candidate);
                } catch (secondaryError) {}
            }
        }
        return null;
    }

    function loadPrev(){
        const sv = (s && s.value) ? s.value.trim() : '';
        const sm = (sem && sem.value) ? sem.value.trim() : '';
        const lv = (lvl && lvl.value) ? lvl.value.trim() : '';

        resetExports();

        if (!sv || !sm || !lv) {
            area.innerHTML = '<div class="alert alert-warning mb-0">Please select Year/Session, Semester, and Level.</div>';
            return;
        }

        area.innerHTML = '<div class="course-reg-prev-loading"><div class="spinner-border text-primary" role="status"></div><div class="mt-2">Loading previous course form...</div></div>';

        const body = new URLSearchParams();
        body.append('portal_prev_course_form_api', '1');
        body.append('page', 'student_course_registration');
        body.append('tab', 'print');
        body.append('ajax', 'load_prev_course_form');
        body.append('ajax_action', 'load_prev_course_form');
        body.append('session_name', sv);
        body.append('semester', sm);
        body.append('level', lv);
        body.append('_ts', String(Date.now()));

        fetch('?page=student_course_registration&tab=print&portal_prev_course_form_api=1', {
            method: 'POST',
            credentials: 'same-origin',
            cache: 'no-store',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                'X-Requested-With': 'XMLHttpRequest',
                'Accept': 'application/json'
            },
            body: body.toString()
        })
        .then(async function(response){
            const raw = await response.text();
            const payload = safeParsePayload(raw);

            if (!payload) {
                throw new Error('The previous course form could not be loaded. Please refresh the page and try again.');
            }

            if (!response.ok || !payload.ok) {
                throw new Error(payload.message ? payload.message : 'Unable to load the previous course form.');
            }

            if (!payload.html || !String(payload.html).trim()) {
                area.innerHTML = '<div class="course-reg-prev-empty-state"><div class="icon"><i class="bi bi-search"></i></div><div class="title">No saved form found</div><div class="text">No previous course form was found for the selected Year/Session, Semester, and Level. Try another combination that already has a submitted registration.</div></div>';
                return;
            }

            area.innerHTML = payload.html;
            setExports(payload, sv, sm, lv);
        })
        .catch(function(error){
            const message = (error && error.message) ? String(error.message).trim() : 'Unable to load the previous course form. Please try again.';
            area.innerHTML = '<div class="alert alert-danger mb-0">' + escapeHtml(message) + '</div>';
        });
    }

    if (btn) {
        btn.addEventListener('click', function(e){
            e.preventDefault();
            loadPrev();
        });
    }

    if (s && !s.value && s.options.length > 1) {
        s.selectedIndex = 1;
    }
})();
</script><?php endif; ?></div></div>
    <?php
    break;

case 'course_form':
    $regId = (int)($_GET['reg_id'] ?? ($_GET['registration_id'] ?? 0));
    if ($regId <= 0) { flash('danger','Invalid registration selected.'); redirect('?page=home'); }
    portalEnsureCourseRegistrationProfileSupport($pdo);
    $reg = null; $items = [];
    try {
        $st = $pdo->prepare("SELECT cr.*, " . portalCourseRegistrationStudentSelectList($pdo, 's') . ", f.name AS faculty_name, d.name AS department_name, p.name AS programme_name FROM course_registrations cr LEFT JOIN students s ON s.id = cr.student_id LEFT JOIN faculties f ON f.id = cr.faculty_id LEFT JOIN departments d ON d.id = cr.department_id LEFT JOIN programmes p ON p.id = cr.programme_id WHERE cr.id=? LIMIT 1");
        $st->execute([$regId]); $reg = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        if ($reg) { $items = portalFetchSynchronizedCourseRegistrationItems($pdo, $regId); }
    } catch (Throwable $e) {}
    if (!$reg) { flash('danger','Registration not found.'); redirect('?page=home'); }
    $uid = (int)($_SESSION['user_id'] ?? 0); $ut = (string)($_SESSION['user_type'] ?? '');
    if (!($ut === 'admin' || ($ut === 'student' && $uid === (int)$reg['student_id']))) { flash('danger','Unauthorized.'); redirect('?page=home'); }
    $autoDownloadPdf = isset($_GET['download']) && (string)$_GET['download'] === '1';
    $autoPrintCourseForm = isset($_GET['print']) && (string)$_GET['print'] === '1';
    $returnSource = trim((string)($_GET['source'] ?? ''));
    if (!$items) {
        flash('warning', $autoDownloadPdf
            ? 'No saved course items were found for this registration, so a PDF could not be generated. The saved course form preview is being opened instead.'
            : 'No saved course items were found for this registration. The student course form preview is being shown with an empty course table.');
        $autoDownloadPdf = false;
    }
    if ($autoDownloadPdf && !headers_sent()) {
        header('Content-Type: text/html; charset=utf-8');
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
        header('Pragma: no-cache');
    }
    portalRenderCourseRegistrationPrintable($pdo, $reg, $items, true, $autoDownloadPdf, $autoPrintCourseForm);
    break;

case 'admin_courses_manager':
    if (!isAdmin()) { redirect('?page=admin_login'); }
    portalEnsureRegistrableCourseScopeSchema($pdo);

    if (isset($_GET['download_sample']) && (string)$_GET['download_sample'] === '1') {
        moduleCsvDownload('registrable_courses_sample.csv',
            ['course_code','course_title','credit_unit','course_type','registrable','faculty','department','programme','mode_of_study','level','semester','year','session','is_general_course'],
            [
                ['BED101','Introduction to Accounting',2,'C','Yes','School of Business','Accounting Department','NCE','Full-Time','100','First','1','2025/2026','0'],
                ['GSE101','Use of English',2,'C','Yes','School of Business','Accounting Department','NCE','Full-Time','100','First','1','2025/2026','1'],
                ['CSC111','Computer Logic',1,'E','Yes','School of Science','Computer Science','Degree Full-Time','Weekend','100','First','1','2025/2026','0'],
            ]
        );
    }

    $normalizedManagerFilter = portalNormalizeRegistrableCourseManagerFilter($pdo, [
        'faculty_id' => $_GET['faculty_id'] ?? 0,
        'department_id' => $_GET['department_id'] ?? 0,
        'programme_id' => $_GET['programme_id'] ?? 0,
        'mode_of_study' => $_GET['mode_of_study'] ?? '',
        'level' => $_GET['level'] ?? '',
        'semester' => $_GET['semester'] ?? '',
        'session_name' => $_GET['session_name'] ?? '',
        'year' => $_GET['year'] ?? '',
    ]);

    $filterFaculty = (int)($normalizedManagerFilter['faculty_id'] ?? 0);
    $filterDept    = (int)($normalizedManagerFilter['department_id'] ?? 0);
    $filterProg    = (int)($normalizedManagerFilter['programme_id'] ?? 0);
    $filterMode    = portalNormalizeRegistrableCourseModeOfStudy((string)($normalizedManagerFilter['mode_of_study'] ?? ''), '');
    $filterLevel   = trim((string)($normalizedManagerFilter['level'] ?? ''));
    $filterSem     = (string)($normalizedManagerFilter['semester'] ?? '');
    $filterSess    = trim((string)($normalizedManagerFilter['session_name'] ?? ''));
    $filterYear    = trim((string)($normalizedManagerFilter['year'] ?? ''));
    $activeCourseSession = trim((string)($normalizedManagerFilter['active_session_name'] ?? ''));
    $filterNormalizationNotes = $normalizedManagerFilter['notes'] ?? [];

    $faculties = [];
    $departments = [];
    $programmes = [];
    try { $faculties = $pdo->query("SELECT * FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $departments = $pdo->query("SELECT d.*, f.name AS faculty_name FROM departments d LEFT JOIN faculties f ON f.id=d.faculty_id ORDER BY f.name ASC, d.name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    try { $programmes = $pdo->query("SELECT p.*, d.name AS department_name, d.faculty_id AS faculty_id FROM programmes p LEFT JOIN departments d ON d.id=p.department_id ORDER BY p.name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
    $registrableModeOptions = portalRegistrableCourseModeOfStudyOptionValues($pdo);
    $registrableCourseOld = $_SESSION['__registrable_course_form_old'] ?? [];
    unset($_SESSION['__registrable_course_form_old']);
    if (!is_array($registrableCourseOld)) { $registrableCourseOld = []; }
    $oldCourseType = strtoupper(trim((string)($registrableCourseOld['course_type'] ?? 'C')));
    $oldRegistrable = ucfirst(strtolower(trim((string)($registrableCourseOld['registrable'] ?? 'Yes'))));
    $addCourseDefaults = [
        'course_code' => trim((string)($registrableCourseOld['course_code'] ?? '')),
        'course_title' => trim((string)($registrableCourseOld['course_title'] ?? '')),
        'credit_unit' => trim((string)($registrableCourseOld['credit_unit'] ?? '0')),
        'course_type' => in_array($oldCourseType, ['C','E'], true) ? $oldCourseType : 'C',
        'registrable' => in_array($oldRegistrable, ['Yes','No'], true) ? $oldRegistrable : 'Yes',
        'faculty_id' => max(0, (int)($registrableCourseOld['faculty_id'] ?? $filterFaculty)),
        'department_id' => max(0, (int)($registrableCourseOld['department_id'] ?? $filterDept)),
        'programme_id' => max(0, (int)($registrableCourseOld['programme_id'] ?? $filterProg)),
        'mode_of_study' => portalNormalizeRegistrableCourseModeOfStudy((string)($registrableCourseOld['mode_of_study'] ?? ($filterProg > 0 ? portalProgrammeModeOfStudyValueById($pdo, $filterProg, 'Full-Time') : 'Full-Time')), 'Full-Time'),
        'level' => trim((string)($registrableCourseOld['level'] ?? ($filterLevel !== '' ? $filterLevel : '100'))),
        'semester' => in_array((string)($registrableCourseOld['semester'] ?? ($filterSem !== '' ? $filterSem : 'First')), ['First','Second'], true) ? (string)($registrableCourseOld['semester'] ?? ($filterSem !== '' ? $filterSem : 'First')) : 'First',
        'year' => trim((string)($registrableCourseOld['year'] ?? ($filterYear !== '' ? $filterYear : '1'))),
        'session_name' => trim((string)($registrableCourseOld['session_name'] ?? ($filterSess !== '' ? $filterSess : ($activeCourseSession !== '' ? $activeCourseSession : setting('academic_session', ''))))),
        'is_general_course' => !empty($registrableCourseOld['is_general_course']) ? 1 : 0,
    ];

    $where = "1=1";
    $params = [];
    if ($filterFaculty > 0) { $where .= " AND COALESCE(rc.faculty_id,0) = ?"; $params[] = $filterFaculty; }
    if ($filterDept > 0) { $where .= " AND COALESCE(rc.department_id,0) = ?"; $params[] = $filterDept; }
    if ($filterProg > 0) { $where .= " AND COALESCE(rc.programme_id,0) = ?"; $params[] = $filterProg; }
    if ($filterMode !== '') { $where .= " AND (COALESCE(NULLIF(TRIM(rc.mode_of_study), ''), '') = '' OR LOWER(TRIM(COALESCE(rc.mode_of_study, ''))) = LOWER(TRIM(?)))"; $params[] = $filterMode; }
    if ($filterLevel !== '') { $where .= " AND COALESCE(TRIM(rc.level),'') = ?"; $params[] = $filterLevel; }
    if (in_array($filterSem, ['First','Second'], true)) { $where .= " AND COALESCE(TRIM(rc.semester),'') = ?"; $params[] = $filterSem; }
    if ($filterSess !== '') {
        if ($activeCourseSession !== '') {
            $where .= " AND COALESCE(NULLIF(TRIM(rc.session_name),''), ?) = ?";
            $params[] = $activeCourseSession;
            $params[] = $filterSess;
        } else {
            $where .= " AND COALESCE(TRIM(rc.session_name),'') = ?";
            $params[] = $filterSess;
        }
    }
    if ($filterYear !== '') { $where .= " AND COALESCE(TRIM(rc.year),'') = ?"; $params[] = $filterYear; }

    $courses = [];
    $courseListLoadError = '';
    try {
        $stmt = $pdo->prepare("SELECT rc.*, COALESCE(NULLIF(TRIM(rc.session_name),''), ?) AS session_name_display, f.name AS faculty_name, d.name AS department_name, p.name AS programme_name
            FROM registrable_courses rc
            LEFT JOIN faculties f ON f.id = rc.faculty_id
            LEFT JOIN departments d ON d.id = rc.department_id
            LEFT JOIN programmes p ON p.id = rc.programme_id
            WHERE {$where}
            ORDER BY COALESCE(NULLIF(TRIM(rc.session_name),''), ?) DESC, COALESCE(NULLIF(TRIM(rc.year),''), '0') DESC, COALESCE(NULLIF(TRIM(rc.semester),''), 'First') ASC, COALESCE(NULLIF(TRIM(rc.level),''), '') ASC, COALESCE(NULLIF(TRIM(rc.course_code),''), '') ASC");
        $stmt->execute(array_merge([$activeCourseSession, $activeCourseSession], $params));
        $courses = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) {
        $courseListLoadError = trim((string)$e->getMessage());
        $courses = [];
    }

    $totalCoursesCount = 0;
    try { $totalCoursesCount = (int)($pdo->query("SELECT COUNT(*) FROM registrable_courses")->fetchColumn() ?: 0); } catch (Throwable $e) {}

    $matchingCoursesCount = count($courses);
    $generalCoursesCount = 0;
    foreach ($courses as $courseRow) {
        if ((int)($courseRow['is_general_course'] ?? 0) === 1) {
            $generalCoursesCount++;
        }
    }
    $strictCoursesCount = max(0, $matchingCoursesCount - $generalCoursesCount);

    $scopeSummaryParts = [];
    if ($filterFaculty > 0) {
        foreach ($faculties as $facultyRow) {
            if ((int)($facultyRow['id'] ?? 0) === $filterFaculty) { $scopeSummaryParts[] = trim((string)($facultyRow['name'] ?? '')); break; }
        }
    }
    if ($filterDept > 0) {
        foreach ($departments as $departmentRow) {
            if ((int)($departmentRow['id'] ?? 0) === $filterDept) { $scopeSummaryParts[] = trim((string)($departmentRow['name'] ?? '')); break; }
        }
    }
    if ($filterProg > 0) {
        foreach ($programmes as $programmeRow) {
            if ((int)($programmeRow['id'] ?? 0) === $filterProg) { $scopeSummaryParts[] = trim((string)($programmeRow['name'] ?? '')); break; }
        }
    }
    if ($filterMode !== '') $scopeSummaryParts[] = $filterMode;
    if ($filterSess !== '') $scopeSummaryParts[] = $filterSess;
    if ($filterSem !== '') $scopeSummaryParts[] = $filterSem;
    if ($filterLevel !== '') $scopeSummaryParts[] = 'Level ' . $filterLevel;
    if ($filterYear !== '') $scopeSummaryParts[] = 'Year ' . $filterYear;
    $scopeSummaryText = $scopeSummaryParts ? implode(' • ', array_values(array_filter($scopeSummaryParts))) : 'All saved course scopes';
    $filtersAreActive = ($filterFaculty > 0 || $filterDept > 0 || $filterProg > 0 || $filterMode !== '' || $filterLevel !== '' || $filterSem !== '' || $filterSess !== '' || $filterYear !== '');
    $emptyStateMessage = $totalCoursesCount > 0
        ? 'Try adjusting the filters or add a new course. The current filter combination is stricter than the saved course mappings.'
        : 'Try adjusting the filters or add a new course. No registrable courses have been created yet.';
    $courseManagerReturnFilters = [
        'faculty_id' => $filterFaculty,
        'department_id' => $filterDept,
        'programme_id' => $filterProg,
        'mode_of_study' => $filterMode,
        'level' => $filterLevel,
        'semester' => $filterSem,
        'session_name' => $filterSess,
        'year' => $filterYear,
    ];

    $courseImportFeedback = $_SESSION['__course_import_feedback'] ?? [];
    unset($_SESSION['__course_import_feedback']);

    ?>
    <style>
        .registrable-courses-manager .registrable-manager-grid { align-items: stretch; }
        .registrable-courses-manager .registrable-panel {
            border: 1px solid rgba(13,110,253,.12);
            border-radius: 1rem;
            padding: 1rem;
            background: #fff;
            box-shadow: 0 .35rem .9rem rgba(13,110,253,.05);
            height: 100%;
        }
        .registrable-courses-manager .registrable-panel-title {
            font-size: 1rem;
            font-weight: 700;
            color: #0d3f8a;
            margin-bottom: .2rem;
        }
        .registrable-courses-manager .registrable-panel-subtitle {
            color: #6c757d;
            font-size: .875rem;
            margin-bottom: 1rem;
        }
        .registrable-courses-manager .registrable-metric {
            border: 1px solid rgba(13,110,253,.10);
            border-radius: .9rem;
            padding: .85rem .95rem;
            background: linear-gradient(180deg, rgba(13,110,253,.05), rgba(13,110,253,.015));
            height: 100%;
        }
        .registrable-courses-manager .registrable-metric .label {
            font-size: .75rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: .03em;
            color: #6c757d;
            margin-bottom: .25rem;
        }
        .registrable-courses-manager .registrable-metric .value {
            font-size: 1.35rem;
            font-weight: 700;
            line-height: 1.1;
            color: #0d3f8a;
        }
        .registrable-courses-manager .registrable-metric .meta {
            font-size: .8rem;
            color: #6c757d;
            margin-top: .3rem;
        }
        .registrable-courses-manager .registrable-list-shell {
            min-height: 100%;
            display: flex;
            flex-direction: column;
            gap: 1rem;
        }
        .registrable-courses-manager .registrable-course-table th {
            white-space: nowrap;
            font-size: .84rem;
        }
        .registrable-courses-manager .registrable-course-table td {
            vertical-align: top;
            font-size: .92rem;
        }
        .registrable-courses-manager .registrable-course-scope {
            font-size: .8rem;
            color: #6c757d;
            line-height: 1.45;
        }
        .registrable-courses-manager .registrable-table-wrap {
            flex: 1 1 auto;
            min-height: 0;
        }
        .registrable-courses-manager .registrable-empty-state {
            min-height: 330px;
            border: 1px dashed rgba(13,110,253,.25);
            border-radius: 1rem;
            background: linear-gradient(180deg, rgba(13,110,253,.04), rgba(13,110,253,.01));
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            text-align: center;
            padding: 1.5rem;
            gap: .75rem;
        }
        .registrable-courses-manager .registrable-empty-state .icon {
            width: 64px;
            height: 64px;
            border-radius: 50%;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            background: rgba(13,110,253,.08);
            color: #0d6efd;
            font-size: 1.6rem;
        }
        .registrable-courses-manager .registrable-empty-state .title {
            font-size: 1.05rem;
            font-weight: 700;
            color: #0d3f8a;
        }
        .registrable-courses-manager .registrable-empty-state .text {
            color: #6c757d;
            max-width: 540px;
        }
        .registrable-courses-manager .registrable-add-stack {
            display: flex;
            flex-direction: column;
            gap: 1rem;
        }
        @media (max-width: 1199.98px) {
            .registrable-courses-manager .registrable-empty-state { min-height: 260px; }
        }
        @media (max-width: 767.98px) {
            .registrable-courses-manager .registrable-manager-tabs .nav-link {
                padding: .6rem .9rem;
                font-size: .92rem;
            }
        }
    </style>
    <div class="card registrable-courses-manager">
        <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
            <div><i class="bi bi-journal-bookmark"></i> Registrable Courses Manager</div>
            <div class="d-flex gap-2 flex-wrap">
                <a class="btn btn-sm btn-outline-success" href="?page=admin_courses_manager&download_sample=1"><i class="bi bi-filetype-csv"></i> Download CSV Sample</a>
                <a class="btn btn-sm btn-outline-primary" href="?page=admin_structure_manager"><i class="bi bi-diagram-3"></i> Structure</a>
            </div>
        </div>
        <div class="card-body">
            <?php renderFlashes(); ?>

            <div class="registrable-tabs-shell">
                <ul class="nav nav-tabs registrable-manager-tabs" id="registrableCourseManagerTabs" role="tablist">
                    <li class="nav-item" role="presentation">
                        <button class="nav-link active" id="registrableCourseSavedTab" data-bs-toggle="tab" data-bs-target="#registrableCourseSavedPane" type="button" role="tab" aria-controls="registrableCourseSavedPane" aria-selected="true" data-rc-tab-button="saved">
                            <i class="bi bi-journal-text me-1"></i> Saved Courses
                        </button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link" id="registrableCourseAddTab" data-bs-toggle="tab" data-bs-target="#registrableCourseAddPane" type="button" role="tab" aria-controls="registrableCourseAddPane" aria-selected="false" data-rc-tab-button="add">
                            <i class="bi bi-plus-circle me-1"></i> Add Courses
                        </button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link" id="registrableCourseImportTab" data-bs-toggle="tab" data-bs-target="#registrableCourseImportPane" type="button" role="tab" aria-controls="registrableCourseImportPane" aria-selected="false" data-rc-tab-button="import">
                            <i class="bi bi-upload me-1"></i> Import Courses
                        </button>
                    </li>
                </ul>

                <div class="tab-content registrable-manager-tab-content" id="registrableCourseManagerTabContent">
                    <div class="tab-pane fade show active registrable-manager-tab-pane" id="registrableCourseSavedPane" role="tabpanel" aria-labelledby="registrableCourseSavedTab" tabindex="0">
                        <div class="registrable-tab-stack">
<div class="border rounded p-3 registrable-filter-shell">
                <h6 class="mb-3">Filter</h6>
                <form method="get" class="row g-2 align-items-end">
                    <input type="hidden" name="page" value="admin_courses_manager">
                    <div class="col-xl-3 col-md-6">
                        <label class="form-label">Faculty</label>
                        <select class="form-select" name="faculty_id">
                            <option value="0">All</option>
                            <?php foreach ($faculties as $f): ?>
                                <option value="<?php echo (int)$f['id']; ?>" <?php echo ($filterFaculty === (int)$f['id']) ? 'selected' : ''; ?>><?php echo e($f['name']); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-xl-3 col-md-6">
                        <label class="form-label">Department</label>
                        <select class="form-select" name="department_id">
                            <option value="0">All</option>
                            <?php foreach ($departments as $d): ?>
                                <option value="<?php echo (int)$d['id']; ?>" <?php echo ($filterDept === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e(($d['faculty_name'] ?? '') . ' - ' . $d['name']); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-xl-3 col-md-6">
                        <label class="form-label">Programme</label>
                        <select class="form-select" name="programme_id" data-rc-programme>
                            <option value="0">All Programmes</option>
                            <?php foreach ($programmes as $p): ?>
                                <option value="<?php echo (int)$p['id']; ?>" <?php echo ($filterProg === (int)$p['id']) ? 'selected' : ''; ?>><?php echo e(($p['department_name'] ?? '') . ' - ' . $p['name']); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-xl-3 col-md-6">
                        <label class="form-label">Mode of Study</label>
                        <select class="form-select" name="mode_of_study">
                            <option value="">All Mode of Study</option>
                            <?php foreach ($registrableModeOptions as $modeOption): ?>
                                <option value="<?php echo e($modeOption); ?>" <?php echo (strcasecmp($filterMode, (string)$modeOption) === 0) ? 'selected' : ''; ?>><?php echo e($modeOption); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-xl-2 col-md-4 col-sm-6">
                        <label class="form-label">Level</label>
                        <input class="form-control" name="level" value="<?php echo e($filterLevel); ?>" placeholder="100">
                    </div>
                    <div class="col-xl-2 col-md-4 col-sm-6">
                        <label class="form-label">Semester</label>
                        <select class="form-select" name="semester">
                            <option value="">All</option>
                            <option value="First" <?php echo ($filterSem === 'First') ? 'selected' : ''; ?>>First</option>
                            <option value="Second" <?php echo ($filterSem === 'Second') ? 'selected' : ''; ?>>Second</option>
                        </select>
                    </div>
                    <div class="col-xl-3 col-md-4 col-sm-6">
                        <label class="form-label">Session</label>
                        <input class="form-control" name="session_name" value="<?php echo e($filterSess); ?>" placeholder="<?php echo e($activeCourseSession !== '' ? $activeCourseSession : setting('academic_session', '')); ?>">
                    </div>
                    <div class="col-xl-2 col-md-4 col-sm-6">
                        <label class="form-label">Year</label>
                        <input class="form-control" name="year" value="<?php echo e($filterYear); ?>" placeholder="1">
                    </div>
                    <div class="col-xl-2 col-md-4 col-sm-6 d-grid">
                        <button class="btn btn-outline-primary">Apply</button>
                    </div>
                </form>
                <?php if (!empty($filterNormalizationNotes)): ?>
                    <div class="alert alert-info mt-3 mb-0">
                        <?php echo e(implode(' ', $filterNormalizationNotes)); ?>
                    </div>
                <?php endif; ?>
            </div>

<div class="registrable-panel registrable-list-shell">
                        <div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
                            <div>
                                <div class="registrable-panel-title mb-1">Saved Course List</div>
                                <div class="registrable-panel-subtitle mb-0"><?php echo e($scopeSummaryText); ?></div>
                            </div>
                            <div class="d-flex flex-wrap gap-2">
                                <?php if ($activeCourseSession !== ''): ?>
                                    <span class="badge text-bg-light border">Active Session: <?php echo e($activeCourseSession); ?></span>
                                <?php endif; ?>
                                <span class="badge text-bg-primary"><?php echo (int)$matchingCoursesCount; ?> record<?php echo $matchingCoursesCount === 1 ? '' : 's'; ?></span>
                            </div>
                        </div>

                        <div class="row g-2">
                            <div class="col-sm-4">
                                <div class="registrable-metric">
                                    <div class="label">Matching Scope</div>
                                    <div class="value"><?php echo (int)$matchingCoursesCount; ?></div>
                                    <div class="meta"><?php echo e($filtersAreActive ? 'Records that satisfy the active filter set.' : 'All currently saved registrable courses.'); ?></div>
                                </div>
                            </div>
                            <div class="col-sm-4">
                                <div class="registrable-metric">
                                    <div class="label">General Courses</div>
                                    <div class="value"><?php echo (int)$generalCoursesCount; ?></div>
                                    <div class="meta">Shared within the selected valid scope only.</div>
                                </div>
                            </div>
                            <div class="col-sm-4">
                                <div class="registrable-metric">
                                    <div class="label">Strict Courses</div>
                                    <div class="value"><?php echo (int)$strictCoursesCount; ?></div>
                                    <div class="meta">Locked to exact faculty, department, programme, session, semester, level, and year mappings.</div>
                                </div>
                            </div>
                        </div>

                        <?php if ($courseListLoadError !== ''): ?>
                            <div class="alert alert-danger mb-0">The course list could not be loaded right now. <?php echo e($courseListLoadError); ?></div>
                        <?php endif; ?>

                        <?php if ($courses): ?>
                            <div class="table-responsive registrable-table-wrap">
                                <table class="table table-hover align-middle registrable-course-table">
                                    <thead>
                                        <tr>
                                            <th>Code</th>
                                            <th>Title</th>
                                            <th>CU</th>
                                            <th>C/E</th>
                                            <th>Registrable</th>
                                            <th>Scope</th>
                                            <th style="width:340px;">Actions</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                    <?php foreach ($courses as $c): ?>
                                        <tr>
                                            <td><?php echo e($c['course_code']); ?></td>
                                            <td><?php echo e($c['course_title']); ?></td>
                                            <td><?php echo (int)($c['credit_unit'] ?? 0); ?></td>
                                            <td><span class="badge bg-<?php echo ($c['course_type'] === 'C') ? 'primary' : 'warning'; ?>"><?php echo e($c['course_type']); ?></span></td>
                                            <td><?php echo ($c['registrable'] === 'Yes') ? '<span class="badge bg-success">Yes</span>' : '<span class="badge bg-secondary">No</span>'; ?></td>
                                            <td class="registrable-course-scope">
                                                <?php
                                                    $parts = [];
                                                    $parts[] = ((int)($c['is_general_course'] ?? 0) === 1) ? 'General' : 'Strict';
                                                    if (!empty($c['faculty_name'])) $parts[] = e($c['faculty_name']);
                                                    if (!empty($c['department_name'])) $parts[] = e($c['department_name']);
                                                    if (!empty($c['programme_name'])) $parts[] = e($c['programme_name']);
                                                    if (!empty($c['mode_of_study'])) $parts[] = e(portalNormalizeRegistrableCourseModeOfStudy((string)$c['mode_of_study'], 'Full-Time'));
                                                    if (!empty($c['session_name_display'])) $parts[] = e($c['session_name_display']);
                                                    elseif (!empty($c['session_name'])) $parts[] = e($c['session_name']);
                                                    if (!empty($c['semester'])) $parts[] = e($c['semester']);
                                                    if (!empty($c['level'])) $parts[] = 'L' . e($c['level']);
                                                    if (!empty($c['year'])) $parts[] = 'Y' . e($c['year']);
                                                    echo implode(' • ', $parts);
                                                ?>
                                            </td>
                                            <td>
                                                <details>
                                                    <summary class="btn btn-sm btn-outline-primary">Edit</summary>
                                                    <div class="mt-2 border rounded p-2">
                                                        <form method="post" class="row g-2">
                                                            <input type="hidden" name="action" value="edit_course">
                                                            <input type="hidden" name="id" value="<?php echo (int)$c['id']; ?>">
                                                            <?php portalRenderRegistrableCourseManagerReturnFilterInputs($courseManagerReturnFilters); ?>
                                                            <div class="col-md-4">
                                                                <label class="form-label">Code</label>
                                                                <input class="form-control form-control-sm" name="course_code" value="<?php echo e($c['course_code']); ?>" required>
                                                            </div>
                                                            <div class="col-md-8">
                                                                <label class="form-label">Title</label>
                                                                <input class="form-control form-control-sm" name="course_title" value="<?php echo e($c['course_title']); ?>" required>
                                                            </div>
                                                            <div class="col-md-3">
                                                                <label class="form-label">Credit</label>
                                                                <input type="number" class="form-control form-control-sm" name="credit_unit" min="0" value="<?php echo (int)($c['credit_unit'] ?? 0); ?>" required>
                                                            </div>
                                                            <div class="col-md-3">
                                                                <label class="form-label">Type</label>
                                                                <select class="form-select form-select-sm" name="course_type" required>
                                                                    <option value="C" <?php echo ($c['course_type'] === 'C') ? 'selected' : ''; ?>>C</option>
                                                                    <option value="E" <?php echo ($c['course_type'] === 'E') ? 'selected' : ''; ?>>E</option>
                                                                </select>
                                                            </div>
                                                            <div class="col-md-3">
                                                                <label class="form-label">Registrable</label>
                                                                <select class="form-select form-select-sm" name="registrable" required>
                                                                    <option value="Yes" <?php echo ($c['registrable'] === 'Yes') ? 'selected' : ''; ?>>Yes</option>
                                                                    <option value="No" <?php echo ($c['registrable'] === 'No') ? 'selected' : ''; ?>>No</option>
                                                                </select>
                                                            </div>
                                                            <div class="col-md-3">
                                                                <label class="form-label">Semester</label>
                                                                <select class="form-select form-select-sm" name="semester" required>
                                                                    <option value="First" <?php echo ($c['semester'] === 'First') ? 'selected' : ''; ?>>First</option>
                                                                    <option value="Second" <?php echo ($c['semester'] === 'Second') ? 'selected' : ''; ?>>Second</option>
                                                                </select>
                                                            </div>
                                                            <div class="col-12">
                                                                <div class="form-check form-switch mt-1">
                                                                    <input class="form-check-input" type="checkbox" role="switch" id="general_<?php echo (int)$c['id']; ?>" name="is_general_course" value="1" <?php echo ((int)($c['is_general_course'] ?? 0) === 1) ? 'checked' : ''; ?>>
                                                                    <label class="form-check-label" for="general_<?php echo (int)$c['id']; ?>">General Course within selected scope</label>
                                                                </div>
                                                            </div>
                                                            <div class="col-md-4">
                                                                <label class="form-label">Faculty</label>
                                                                <select class="form-select form-select-sm" name="faculty_id" data-rc-faculty data-all-label="All Faculty">
                                                                    <option value="0">All Faculty</option>
                                                                    <?php foreach ($faculties as $f): ?>
                                                                        <option value="<?php echo (int)$f['id']; ?>" <?php echo ((int)($c['faculty_id'] ?? 0) === (int)$f['id']) ? 'selected' : ''; ?>><?php echo e($f['name']); ?></option>
                                                                    <?php endforeach; ?>
                                                                </select>
                                                            </div>
                                                            <div class="col-md-4">
                                                                <label class="form-label">Department</label>
                                                                <select class="form-select form-select-sm" name="department_id" data-rc-department data-all-label="All Department">
                                                                    <option value="0">All Department</option>
                                                                    <?php foreach ($departments as $d): ?>
                                                                        <option value="<?php echo (int)$d['id']; ?>" <?php echo ((int)($c['department_id'] ?? 0) === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e(($d['faculty_name'] ?? '') . ' - ' . $d['name']); ?></option>
                                                                    <?php endforeach; ?>
                                                                </select>
                                                            </div>
                                                            <div class="col-md-4">
                                                                <label class="form-label">Programme</label>
                                                                <select class="form-select form-select-sm" name="programme_id" data-rc-programme>
                                                                    <option value="0">-- Optional --</option>
                                                                    <?php foreach ($programmes as $p): ?>
                                                                        <option value="<?php echo (int)$p['id']; ?>" <?php echo ((int)($c['programme_id'] ?? 0) === (int)$p['id']) ? 'selected' : ''; ?> data-mode-of-study="<?php echo e(portalNormalizeRegistrableCourseModeOfStudy((string)($p['mode_of_study'] ?? portalProgrammeModeOfStudyDisplay((array)$p, 'Full-Time')), 'Full-Time')); ?>"><?php echo e(($p['department_name'] ?? '') . ' - ' . $p['name']); ?></option>
                                                                    <?php endforeach; ?>
                                                                </select>
                                                            </div>
                                                            <div class="col-md-4">
                                                                <label class="form-label">Mode of Study</label>
                                                                <?php $courseModeValue = portalNormalizeRegistrableCourseModeOfStudy((string)($c['mode_of_study'] ?? ($c['programme_id'] ? portalProgrammeModeOfStudyValueById($pdo, (int)$c['programme_id'], 'Full-Time') : 'Full-Time')), 'Full-Time'); ?>
                                                                <select class="form-select form-select-sm" name="mode_of_study" data-rc-mode-of-study>
                                                                    <?php foreach ($registrableModeOptions as $modeOption): ?>
                                                                        <option value="<?php echo e($modeOption); ?>" <?php echo (strcasecmp($courseModeValue, (string)$modeOption) === 0) ? 'selected' : ''; ?>><?php echo e($modeOption); ?></option>
                                                                    <?php endforeach; ?>
                                                                </select>
                                                            </div>
                                                            <div class="col-md-2">
                                                                <label class="form-label">Level</label>
                                                                <input class="form-control form-control-sm" name="level" value="<?php echo e($c['level'] ?? ''); ?>" required>
                                                            </div>
                                                            <div class="col-md-5">
                                                                <label class="form-label">Session</label>
                                                                <input class="form-control form-control-sm" name="session_name" value="<?php echo e(($c['session_name_display'] ?? $c['session_name'] ?? '')); ?>" required>
                                                            </div>
                                                            <div class="col-md-2">
                                                                <label class="form-label">Year</label>
                                                                <input class="form-control form-control-sm" name="year" value="<?php echo e($c['year'] ?? ''); ?>" required>
                                                            </div>
                                                            <div class="col-12 d-flex gap-2">
                                                                <button class="btn btn-sm btn-primary">Save</button>
                                                            </div>
                                                        </form>
                                                    </div>
                                                </details>
                                                <form method="post" onsubmit="return confirm('Delete this course?');" class="mt-2">
                                                    <input type="hidden" name="action" value="delete_course">
                                                    <input type="hidden" name="id" value="<?php echo (int)$c['id']; ?>">
                                                    <?php portalRenderRegistrableCourseManagerReturnFilterInputs($courseManagerReturnFilters); ?>
                                                    <button class="btn btn-sm btn-outline-danger">Delete</button>
                                                </form>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    </tbody>
                                </table>
                            </div>
                        <?php else: ?>
                            <div class="registrable-empty-state">
                                <div class="icon"><i class="bi bi-journal-x"></i></div>
                                <div class="title">No courses found for the selected filter</div>
                                <div class="text"><?php echo e($emptyStateMessage); ?></div>
                                <div class="d-flex justify-content-center gap-2 flex-wrap">
                                    <?php if ($filtersAreActive): ?>
                                        <a class="btn btn-sm btn-outline-primary" href="?page=admin_courses_manager">Reset Filters</a>
                                    <?php endif; ?>
                                    <a class="btn btn-sm btn-primary" href="#registrableCourseAddPane" data-rc-open-tab="#registrableCourseAddPane" data-rc-scroll-to="#registrableCourseAddForm">Add New Course</a>
                                </div>
                            </div>
                        <?php endif; ?>

                        <div class="form-text mb-0">Validation still blocks duplicate and conflicting scope mappings. The list query now normalizes faculty, department, and programme filter relationships, preserves filter context after save/delete/import, and falls back to the active session for legacy rows whose session name is blank.</div>
                    </div>
                        </div>
                    </div>

                    <div class="tab-pane fade registrable-manager-tab-pane" id="registrableCourseAddPane" role="tabpanel" aria-labelledby="registrableCourseAddTab" tabindex="0">
                        <div class="registrable-tab-stack">
                            <div class="registrable-add-stack">
<div class="registrable-panel" id="registrableCourseAddForm">
                            <div class="registrable-panel-title">Add Course</div>
                            <div class="registrable-panel-subtitle">Create a new registrable course inside the current academic scope without changing the existing workflow.</div>
                            <form method="post" novalidate>
                                <input type="hidden" name="action" value="add_course">
                                <?php portalRenderRegistrableCourseManagerReturnFilterInputs($courseManagerReturnFilters); ?>
                                <div class="row">
                                    <div class="col-md-5 mb-2">
                                        <label class="form-label">Course Code</label>
                                        <input class="form-control" name="course_code" value="<?php echo e($addCourseDefaults['course_code']); ?>" required>
                                    </div>
                                    <div class="col-md-7 mb-2">
                                        <label class="form-label">Credit Unit</label>
                                        <input type="number" class="form-control" name="credit_unit" min="0" value="<?php echo e($addCourseDefaults['credit_unit']); ?>" required>
                                    </div>
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Course Title</label>
                                    <input class="form-control" name="course_title" value="<?php echo e($addCourseDefaults['course_title']); ?>" required>
                                </div>
                                <div class="row">
                                    <div class="col-md-6 mb-2">
                                        <label class="form-label">Course Type</label>
                                        <select class="form-select" name="course_type" required>
                                            <option value="C" <?php echo ($addCourseDefaults['course_type'] === 'C') ? 'selected' : ''; ?>>C (Compulsory)</option>
                                            <option value="E" <?php echo ($addCourseDefaults['course_type'] === 'E') ? 'selected' : ''; ?>>E (Elective)</option>
                                        </select>
                                    </div>
                                    <div class="col-md-6 mb-2">
                                        <label class="form-label">Registrable</label>
                                        <select class="form-select" name="registrable" required>
                                            <option value="Yes" <?php echo ($addCourseDefaults['registrable'] === 'Yes') ? 'selected' : ''; ?>>Yes</option>
                                            <option value="No" <?php echo ($addCourseDefaults['registrable'] === 'No') ? 'selected' : ''; ?>>No</option>
                                        </select>
                                    </div>
                                </div>
                                <div class="form-check form-switch mb-3">
                                    <input class="form-check-input" type="checkbox" role="switch" id="is_general_course_add" name="is_general_course" value="1" <?php echo !empty($addCourseDefaults['is_general_course']) ? 'checked' : ''; ?>>
                                    <label class="form-check-label" for="is_general_course_add">General Course within selected scope</label>
                                </div>
                                <div class="row">
                                    <div class="col-md-6 mb-2">
                                        <label class="form-label">Faculty</label>
                                        <select class="form-select" name="faculty_id" data-rc-faculty data-all-label="All Faculty">
                                            <option value="0">All Faculty</option>
                                            <?php foreach ($faculties as $f): ?>
                                                <option value="<?php echo (int)$f['id']; ?>" <?php echo ($addCourseDefaults['faculty_id'] === (int)$f['id']) ? 'selected' : ''; ?>><?php echo e($f['name']); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-6 mb-2">
                                        <label class="form-label">Department</label>
                                        <select class="form-select" name="department_id" data-rc-department data-all-label="All Department">
                                            <option value="0">All Department</option>
                                            <?php foreach ($departments as $d): ?>
                                                <option value="<?php echo (int)$d['id']; ?>" <?php echo ($addCourseDefaults['department_id'] === (int)$d['id']) ? 'selected' : ''; ?>><?php echo e(($d['faculty_name'] ?? '') . ' - ' . $d['name']); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-6 mb-2">
                                        <label class="form-label">Programme</label>
                                        <select class="form-select" name="programme_id" data-rc-programme>
                                            <option value="0">-- Optional --</option>
                                            <?php foreach ($programmes as $p): ?>
                                                <option value="<?php echo (int)$p['id']; ?>" <?php echo ($addCourseDefaults['programme_id'] === (int)$p['id']) ? 'selected' : ''; ?> data-mode-of-study="<?php echo e(portalNormalizeRegistrableCourseModeOfStudy((string)($p['mode_of_study'] ?? portalProgrammeModeOfStudyDisplay((array)$p, 'Full-Time')), 'Full-Time')); ?>"><?php echo e(($p['department_name'] ?? '') . ' - ' . $p['name']); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                    <div class="col-md-6 mb-2">
                                        <label class="form-label">Mode of Study</label>
                                        <select class="form-select" name="mode_of_study" data-rc-mode-of-study>
                                            <?php foreach ($registrableModeOptions as $modeOption): ?>
                                                <option value="<?php echo e($modeOption); ?>" <?php echo (strcasecmp((string)$addCourseDefaults['mode_of_study'], (string)$modeOption) === 0) ? 'selected' : ''; ?>><?php echo e($modeOption); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </div>
                                </div>
                                <div class="row">
                                    <div class="col-md-4 mb-2">
                                        <label class="form-label">Level</label>
                                        <input class="form-control" name="level" value="<?php echo e($addCourseDefaults['level']); ?>" required>
                                    </div>
                                    <div class="col-md-4 mb-2">
                                        <label class="form-label">Semester</label>
                                        <select class="form-select" name="semester" required>
                                            <option value="First" <?php echo ($addCourseDefaults['semester'] === 'First') ? 'selected' : ''; ?>>First</option>
                                            <option value="Second" <?php echo ($addCourseDefaults['semester'] === 'Second') ? 'selected' : ''; ?>>Second</option>
                                        </select>
                                    </div>
                                    <div class="col-md-4 mb-2">
                                        <label class="form-label">Year</label>
                                        <input class="form-control" name="year" value="<?php echo e($addCourseDefaults['year']); ?>" required>
                                    </div>
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Session</label>
                                    <input class="form-control" name="session_name" value="<?php echo e($addCourseDefaults['session_name']); ?>" required>
                                </div>
                                <div class="d-grid mt-2"><button class="btn btn-primary">Save Course</button></div>
                                <div class="form-text mt-2">Strict courses stay exactly in their assigned scope. General courses are shared only inside the selected scope and will not leak into unrelated faculties, departments, or programmes.</div>
                            </form>
                        </div>
                            </div>
                        </div>
                    </div>

                    <div class="tab-pane fade registrable-manager-tab-pane" id="registrableCourseImportPane" role="tabpanel" aria-labelledby="registrableCourseImportTab" tabindex="0">
                        <div class="registrable-tab-stack">
<?php if (!empty($courseImportFeedback) && is_array($courseImportFeedback)): ?>
                <div class="border rounded p-3 mb-3 bg-light">
                    <h6 class="mb-3">Import Feedback</h6>
                    <div class="table-responsive">
                        <table class="table table-sm table-bordered align-middle mb-0">
                            <thead><tr><th style="width:90px;">Row</th><th style="width:120px;">Status</th><th>Message</th></tr></thead>
                            <tbody>
                            <?php foreach ($courseImportFeedback as $fb): ?>
                                <tr>
                                    <td><?php echo (int)($fb['row'] ?? 0); ?></td>
                                    <td><span class="badge bg-<?php echo (($fb['status'] ?? '') === 'success') ? 'success' : 'danger'; ?>"><?php echo e(($fb['status'] ?? '') === 'success' ? 'Success' : 'Error'); ?></span></td>
                                    <td><?php echo e((string)($fb['message'] ?? '')); ?></td>
                                </tr>
                            <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            <?php endif; ?>

                            <div class="registrable-add-stack">
<div class="registrable-panel" id="registrableCourseImportForm">
                            <div class="registrable-panel-title">Import Courses</div>
                            <div class="registrable-panel-subtitle">Bulk upload CSV rows into the current course manager without breaking the existing validation rules.</div>
                            <form method="post" enctype="multipart/form-data">
                                <input type="hidden" name="action" value="import_courses">
                                <?php portalRenderRegistrableCourseManagerReturnFilterInputs($courseManagerReturnFilters); ?>
                                <div class="mb-2">
                                    <label class="form-label">CSV File</label>
                                    <input type="file" class="form-control" name="courses_csv" accept=".csv,text/csv" required>
                                </div>
                                <div class="d-grid gap-2">
                                    <button class="btn btn-outline-primary"><i class="bi bi-upload"></i> Import Courses</button>
                                    <a class="btn btn-outline-secondary" href="?page=admin_courses_manager&download_sample=1"><i class="bi bi-download"></i> Download CSV Sample</a>
                                </div>
                                <div class="form-text mt-2">CSV headers: course_code, course_title, credit_unit, course_type, registrable, faculty, department, programme, mode_of_study, level, semester, year, session, is_general_course. The mode_of_study column is supported and will fall back safely when omitted.</div>
                            </form>
                        </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
    <script>
    (function(){
        const root = document.querySelector('.registrable-courses-manager');
        if (!root) return;
        if (typeof bootstrap === 'undefined' || !bootstrap.Tab) return;

        const tabButtons = Array.from(root.querySelectorAll('[data-rc-tab-button]'));
        if (!tabButtons.length) return;

        const storageKey = 'honttech_registrable_courses_manager_active_tab';
        const findButtonByTarget = function(target){
            return tabButtons.find(function(btn){
                return (btn.getAttribute('data-bs-target') || '') === target;
            }) || null;
        };
        const activateTab = function(target, syncHash){
            const button = findButtonByTarget(target);
            if (!button) return;
            bootstrap.Tab.getOrCreateInstance(button).show();
            try { window.localStorage.setItem(storageKey, target); } catch (e) {}
            if (syncHash && typeof history !== 'undefined' && history.replaceState) {
                history.replaceState(null, '', window.location.pathname + window.location.search + target);
            }
        };

        let preferredTarget = <?php echo (!empty($courseImportFeedback) && is_array($courseImportFeedback)) ? "'#registrableCourseImportPane'" : "'#registrableCourseSavedPane'"; ?>;
        if (window.location.hash && findButtonByTarget(window.location.hash)) {
            preferredTarget = window.location.hash;
        } else if (preferredTarget === '#registrableCourseSavedPane') {
            try {
                const storedTarget = window.localStorage.getItem(storageKey) || '';
                if (storedTarget && findButtonByTarget(storedTarget)) {
                    preferredTarget = storedTarget;
                }
            } catch (e) {}
        }
        activateTab(preferredTarget, false);

        tabButtons.forEach(function(button){
            button.addEventListener('shown.bs.tab', function(){
                const target = this.getAttribute('data-bs-target') || '#registrableCourseSavedPane';
                try { window.localStorage.setItem(storageKey, target); } catch (e) {}
                if (typeof history !== 'undefined' && history.replaceState) {
                    history.replaceState(null, '', window.location.pathname + window.location.search + target);
                }
            });
        });

        root.querySelectorAll('[data-rc-open-tab]').forEach(function(link){
            link.addEventListener('click', function(event){
                event.preventDefault();
                const target = this.getAttribute('data-rc-open-tab') || '#registrableCourseAddPane';
                activateTab(target, true);
                const scrollSelector = this.getAttribute('data-rc-scroll-to') || target;
                const scrollTarget = root.querySelector(scrollSelector);
                if (scrollTarget && typeof scrollTarget.scrollIntoView === 'function') {
                    scrollTarget.scrollIntoView({ behavior: 'smooth', block: 'start' });
                }
            });
        });
    })();
    </script>

    <script>
    (function(){
        const root = document.querySelector('.registrable-courses-manager');
        if (!root) return;
        const departments = <?php echo json_encode(array_values(array_map(static function($row){ return [
            'id' => (int)($row['id'] ?? 0),
            'faculty_id' => (int)($row['faculty_id'] ?? 0),
            'name' => (string)($row['name'] ?? ''),
            'faculty_name' => (string)($row['faculty_name'] ?? ''),
        ]; }, $departments)), JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); ?>;
        const programmes = <?php echo json_encode(array_values(array_map(static function($row){ return [
            'id' => (int)($row['id'] ?? 0),
            'faculty_id' => (int)($row['faculty_id'] ?? 0),
            'department_id' => (int)($row['department_id'] ?? 0),
            'name' => (string)($row['name'] ?? ''),
            'department_name' => (string)($row['department_name'] ?? ''),
            'mode_of_study' => (string)portalNormalizeRegistrableCourseModeOfStudy((string)($row['mode_of_study'] ?? portalProgrammeModeOfStudyDisplay((array)$row, 'Full-Time')), 'Full-Time'),
        ]; }, $programmes)), JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); ?>;

        function refillSelect(select, rows, selectedValue, firstLabel, labelFn) {
            if (!select) return;
            const current = String(selectedValue ?? select.value ?? '0');
            select.innerHTML = '';
            const first = document.createElement('option');
            first.value = '0';
            first.textContent = firstLabel;
            select.appendChild(first);
            rows.forEach(function(row){
                const opt = document.createElement('option');
                opt.value = String(row.id || 0);
                opt.textContent = labelFn(row);
                if (row.mode_of_study) opt.setAttribute('data-mode-of-study', row.mode_of_study);
                if (String(row.id || 0) === current) opt.selected = true;
                select.appendChild(opt);
            });
            if (![...select.options].some(function(opt){ return opt.selected; })) {
                select.value = '0';
            }
        }

        function syncForm(form) {
            const faculty = form.querySelector('[data-rc-faculty]');
            const department = form.querySelector('[data-rc-department]');
            const programme = form.querySelector('[data-rc-programme]');
            const mode = form.querySelector('[data-rc-mode-of-study]');
            if (!faculty || !department || !programme) return;

            const facultyId = parseInt(faculty.value || '0', 10) || 0;
            const previousDepartment = department.value || '0';
            const filteredDepartments = departments.filter(function(row){
                return facultyId <= 0 || Number(row.faculty_id || 0) === facultyId;
            });
            refillSelect(department, filteredDepartments, previousDepartment, department.getAttribute('data-all-label') || 'All Department', function(row){
                return facultyId > 0 ? row.name : ((row.faculty_name ? row.faculty_name + ' - ' : '') + row.name);
            });

            const departmentId = parseInt(department.value || '0', 10) || 0;
            const previousProgramme = programme.value || '0';
            const filteredProgrammes = programmes.filter(function(row){
                if (departmentId > 0) return Number(row.department_id || 0) === departmentId;
                if (facultyId > 0) return Number(row.faculty_id || 0) === facultyId;
                return true;
            });
            refillSelect(programme, filteredProgrammes, previousProgramme, 'All Programmes', function(row){
                return row.department_name ? (row.department_name + ' - ' + row.name) : row.name;
            });

            if (mode && (!mode.value || mode.getAttribute('data-mode-auto') === '1')) {
                const selectedOpt = programme.options[programme.selectedIndex];
                const selectedMode = selectedOpt ? (selectedOpt.getAttribute('data-mode-of-study') || '') : '';
                if (selectedMode) {
                    mode.value = selectedMode;
                    mode.setAttribute('data-mode-auto', '1');
                }
            }
        }

        root.querySelectorAll('form').forEach(function(form){
            const faculty = form.querySelector('[data-rc-faculty]');
            const department = form.querySelector('[data-rc-department]');
            const programme = form.querySelector('[data-rc-programme]');
            const mode = form.querySelector('[data-rc-mode-of-study]');
            if (!faculty || !department || !programme) return;
            if (mode && mode.value) {
                mode.setAttribute('data-mode-auto', '0');
                mode.addEventListener('change', function(){ mode.setAttribute('data-mode-auto', '0'); });
            }
            faculty.addEventListener('change', function(){ syncForm(form); });
            department.addEventListener('change', function(){ syncForm(form); });
            programme.addEventListener('change', function(){
                if (mode) mode.setAttribute('data-mode-auto', '1');
                syncForm(form);
            });
            syncForm(form);
        });
    })();
    </script>

    <?php
    break;






            case 'notification_settings':
                if (!isLoggedIn() || !isAdmin()) { redirect('?page=login'); }
                requireStaffPermission($pdo, 'staff_roles_manage');
                ensureMessagingInfrastructure($pdo);

                // Email / SMS template actions
                if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_email_template') {
                    portalEnsureNotificationTemplateSchema($pdo, 'email');
                    $templateId = (int)($_POST['template_id'] ?? 0);
                    $templateKey = portalNormalizeNotificationCommandKey((string)($_POST['command_key'] ?? $_POST['template_key'] ?? ''));
                    $catalog = portalNotificationCommandCatalog();
                    $templateName = trim((string)($_POST['template_name'] ?? ''));
                    if ($templateName === '' && isset($catalog[$templateKey])) {
                        $templateName = $catalog[$templateKey];
                    }
                    $subject = trim((string)($_POST['subject'] ?? ''));
                    $bodyHtml = (string)($_POST['body_html'] ?? '');
                    $isActive = (int)($_POST['is_active'] ?? 0) === 1 ? 1 : 0;
                    if ($templateKey === '' || $templateName === '' || $subject === '') {
                        flash('danger', 'Command, name and subject are required.');
                    } else {
                        try {
                            $dup = $pdo->prepare("SELECT id FROM email_templates WHERE (command_key = ? OR event_key = ? OR template_key = ?) AND id <> ? LIMIT 1");
                            $dup->execute([$templateKey, $templateKey, $templateKey, $templateId]);
                            if ($dup->fetchColumn()) {
                                flash('danger', 'Another email template already uses that command.');
                            } else {
                                if ($templateId > 0) {
                                    $stmt = $pdo->prepare("UPDATE email_templates SET template_key = ?, command_key = ?, event_key = ?, template_name = ?, subject = ?, body_html = ?, body = ?, content = ?, is_active = ?, is_enabled = ?, status = ? WHERE id = ?");
                                    $stmt->execute([$templateKey, $templateKey, $templateKey, $templateName, $subject, $bodyHtml, $bodyHtml, $bodyHtml, $isActive, $isActive, ($isActive ? 'enabled' : 'disabled'), $templateId]);
                                } else {
                                    $stmt = $pdo->prepare("INSERT INTO email_templates (template_key, command_key, event_key, template_name, subject, body_html, body, content, is_active, is_enabled, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
                                    $stmt->execute([$templateKey, $templateKey, $templateKey, $templateName, $subject, $bodyHtml, $bodyHtml, $bodyHtml, $isActive, $isActive, ($isActive ? 'enabled' : 'disabled')]);
                                }
                                flash('success', 'Email template saved successfully.');
                            }
                        } catch (Throwable $e) {
                            flash('danger', 'Unable to save email template.');
                        }
                    }
                    redirect('?page=notification_settings&tab=templates');
                }

                if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete_email_template') {
                    $templateId = (int)($_POST['template_id'] ?? 0);
                    if ($templateId > 0) {
                        try {
                            $stmt = $pdo->prepare("DELETE FROM email_templates WHERE id = ? LIMIT 1");
                            $stmt->execute([$templateId]);
                            flash('success', 'Email template deleted successfully.');
                        } catch (Throwable $e) {
                            flash('danger', 'Unable to delete email template.');
                        }
                    }
                    redirect('?page=notification_settings&tab=templates');
                }

                if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'toggle_email_template') {
                    portalEnsureNotificationTemplateSchema($pdo, 'email');
                    $templateId = (int)($_POST['template_id'] ?? 0);
                    $isActive = (int)($_POST['set_active'] ?? 0) === 1 ? 1 : 0;
                    if ($templateId > 0) {
                        try {
                            $stmt = $pdo->prepare("UPDATE email_templates SET is_active = ?, is_enabled = ?, status = ? WHERE id = ? LIMIT 1");
                            $stmt->execute([$isActive, $isActive, ($isActive ? 'enabled' : 'disabled'), $templateId]);
                            flash('success', 'Email template status updated.');
                        } catch (Throwable $e) {
                            flash('danger', 'Unable to update email template status.');
                        }
                    }
                    redirect('?page=notification_settings&tab=templates');
                }

                if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_sms_template') {
                    portalEnsureNotificationTemplateSchema($pdo, 'sms');
                    $templateId = (int)($_POST['template_id'] ?? 0);
                    $templateKey = portalNormalizeNotificationCommandKey((string)($_POST['command_key'] ?? $_POST['template_key'] ?? ''));
                    $catalog = portalNotificationCommandCatalog();
                    $templateName = trim((string)($_POST['template_name'] ?? ''));
                    if ($templateName === '' && isset($catalog[$templateKey])) {
                        $templateName = $catalog[$templateKey];
                    }
                    $messageText = portalSmsNormalizeTemplateForStorage($pdo, trim((string)($_POST['message_text'] ?? '')), $templateKey);
                    $isActive = (int)($_POST['is_active'] ?? 0) === 1 ? 1 : 0;
                    if ($templateKey === '' || $templateName === '' || $messageText === '') {
                        flash('danger', 'SMS command, name and message are required.');
                    } else {
                        try {
                            $dup = $pdo->prepare("SELECT id FROM sms_templates WHERE (command_key = ? OR event_key = ? OR template_key = ?) AND id <> ? LIMIT 1");
                            $dup->execute([$templateKey, $templateKey, $templateKey, $templateId]);
                            if ($dup->fetchColumn()) {
                                flash('danger', 'Another SMS template already uses that command.');
                            } else {
                                if ($templateId > 0) {
                                    $stmt = $pdo->prepare("UPDATE sms_templates SET template_key = ?, command_key = ?, event_key = ?, template_name = ?, message_text = ?, body = ?, content = ?, is_active = ?, is_enabled = ?, status = ? WHERE id = ?");
                                    $stmt->execute([$templateKey, $templateKey, $templateKey, $templateName, $messageText, $messageText, $messageText, $isActive, $isActive, ($isActive ? 'enabled' : 'disabled'), $templateId]);
                                } else {
                                    $stmt = $pdo->prepare("INSERT INTO sms_templates (template_key, command_key, event_key, template_name, message_text, body, content, is_active, is_enabled, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
                                    $stmt->execute([$templateKey, $templateKey, $templateKey, $templateName, $messageText, $messageText, $messageText, $isActive, $isActive, ($isActive ? 'enabled' : 'disabled')]);
                                }
                                flash('success', 'SMS template saved successfully.');
                            }
                        } catch (Throwable $e) {
                            flash('danger', 'Unable to save SMS template.');
                        }
                    }
                    redirect('?page=notification_settings&tab=sms_templates');
                }

                if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete_sms_template') {
                    $templateId = (int)($_POST['template_id'] ?? 0);
                    if ($templateId > 0) {
                        try {
                            $stmt = $pdo->prepare("DELETE FROM sms_templates WHERE id = ? LIMIT 1");
                            $stmt->execute([$templateId]);
                            flash('success', 'SMS template deleted successfully.');
                        } catch (Throwable $e) {
                            flash('danger', 'Unable to delete SMS template.');
                        }
                    }
                    redirect('?page=notification_settings&tab=sms_templates');
                }

                if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'toggle_sms_template') {
                    portalEnsureNotificationTemplateSchema($pdo, 'sms');
                    $templateId = (int)($_POST['template_id'] ?? 0);
                    $isActive = (int)($_POST['set_active'] ?? 0) === 1 ? 1 : 0;
                    if ($templateId > 0) {
                        try {
                            $stmt = $pdo->prepare("UPDATE sms_templates SET is_active = ?, is_enabled = ?, status = ? WHERE id = ? LIMIT 1");
                            $stmt->execute([$isActive, $isActive, ($isActive ? 'enabled' : 'disabled'), $templateId]);
                            flash('success', 'SMS template status updated.');
                        } catch (Throwable $e) {
                            flash('danger', 'Unable to update SMS template status.');
                        }
                    }
                    redirect('?page=notification_settings&tab=sms_templates');
                }

                // Save settings
                if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_notification_settings') {
                    $currentNs = portalSmsSettingsMerged(getNotificationSettings($pdo));

                    $emailEnabled = (int)($_POST['email_enabled'] ?? 0) === 1 ? 1 : 0;
                    $smsEnabled   = (int)($_POST['sms_enabled'] ?? 0) === 1 ? 1 : 0;

                    $fromEmail = trim((string)($_POST['from_email'] ?? ''));
                    $fromName  = trim((string)($_POST['from_name'] ?? ''));

                    $smtpHost  = trim((string)($_POST['smtp_host'] ?? ''));
                    $smtpPort  = (int)($_POST['smtp_port'] ?? 0);
                    $smtpUser  = trim((string)($_POST['smtp_user'] ?? ''));
                    $smtpPass  = trim((string)($_POST['smtp_pass'] ?? ''));
                    $smtpSecure= trim((string)($_POST['smtp_secure'] ?? ''));

                    $save = portalSmsSettingsMerged($currentNs);
                    $save['sms_enabled'] = $smsEnabled;
                    $save['sms_provider'] = portalNormalizeSmsProvider((string)($_POST['sms_provider'] ?? ''));
                    $save['sms_sender_id'] = portalSmsCleanText((string)($_POST['sms_sender_id'] ?? ''), 40);
                    $save['sms_sender_number'] = portalSmsCleanText((string)($_POST['sms_sender_number'] ?? ''), 40);
                    $save['sms_test_number'] = portalSmsCleanText((string)($_POST['sms_test_number'] ?? ''), 30);
                    $save['sms_timeout_seconds'] = max(5, min(60, (int)($_POST['sms_timeout_seconds'] ?? 12)));
                    $save['sms_retry_attempts'] = max(0, min(5, (int)($_POST['sms_retry_attempts'] ?? 0)));

                    $save['bulksmsnigeria_base_url'] = portalSmsCleanText((string)($_POST['bulksmsnigeria_base_url'] ?? $save['bulksmsnigeria_base_url']), 255);
                    $save['bulksmsnigeria_sandbox_url'] = portalSmsCleanText((string)($_POST['bulksmsnigeria_sandbox_url'] ?? $save['bulksmsnigeria_sandbox_url']), 255);
                    $save['bulksmsnigeria_api_token'] = portalSmsSecretPostValue($_POST, $currentNs, 'bulksmsnigeria_api_token');
                    $save['bulksmsnigeria_sender_id'] = portalSmsCleanText((string)($_POST['bulksmsnigeria_sender_id'] ?? ''), 40);
                    $save['bulksmsnigeria_environment'] = strtolower(trim((string)($_POST['bulksmsnigeria_environment'] ?? 'production')));
                    if (!in_array($save['bulksmsnigeria_environment'], ['production', 'sandbox'], true)) $save['bulksmsnigeria_environment'] = 'production';

                    $save['termii_base_url'] = portalSmsCleanText((string)($_POST['termii_base_url'] ?? $save['termii_base_url']), 255);
                    $save['termii_api_key'] = portalSmsSecretPostValue($_POST, $currentNs, 'termii_api_key');
                    $save['termii_sender_id'] = portalSmsCleanText((string)($_POST['termii_sender_id'] ?? ''), 40);
                    $save['termii_channel'] = portalSmsCleanText((string)($_POST['termii_channel'] ?? 'dnd'), 30);
                    $save['termii_message_type'] = portalSmsCleanText((string)($_POST['termii_message_type'] ?? 'plain'), 20);

                    $save['twilio_account_sid'] = portalSmsCleanText((string)($_POST['twilio_account_sid'] ?? ''), 80);
                    $save['twilio_api_key'] = portalSmsCleanText((string)($_POST['twilio_api_key'] ?? ''), 80);
                    $save['twilio_api_key_secret'] = portalSmsSecretPostValue($_POST, $currentNs, 'twilio_api_key_secret');
                    $save['twilio_auth_token'] = portalSmsSecretPostValue($_POST, $currentNs, 'twilio_auth_token');
                    $save['twilio_from_number'] = portalSmsCleanText((string)($_POST['twilio_from_number'] ?? ''), 40);
                    $save['twilio_messaging_service_sid'] = portalSmsCleanText((string)($_POST['twilio_messaging_service_sid'] ?? ''), 80);
                    $save['twilio_base_url'] = portalSmsCleanText((string)($_POST['twilio_base_url'] ?? $save['twilio_base_url']), 255);

                    $save['vonage_api_key'] = portalSmsCleanText((string)($_POST['vonage_api_key'] ?? ''), 120);
                    $save['vonage_api_secret'] = portalSmsSecretPostValue($_POST, $currentNs, 'vonage_api_secret');
                    $save['vonage_sender_id'] = portalSmsCleanText((string)($_POST['vonage_sender_id'] ?? ''), 40);
                    $save['vonage_base_url'] = portalSmsCleanText((string)($_POST['vonage_base_url'] ?? $save['vonage_base_url']), 255);

                    $save['infobip_base_url'] = portalSmsCleanText((string)($_POST['infobip_base_url'] ?? $save['infobip_base_url']), 255);
                    $save['infobip_api_key'] = portalSmsSecretPostValue($_POST, $currentNs, 'infobip_api_key');
                    $save['infobip_sender'] = portalSmsCleanText((string)($_POST['infobip_sender'] ?? ''), 40);

                    // Compatibility mirrors
                    $save['sms_api_url'] = '';
                    $save['sms_api_key'] = '';
                    switch ($save['sms_provider']) {
                        case 'bulksmsnigeria':
                            $save['sms_api_url'] = ($save['bulksmsnigeria_environment'] === 'sandbox') ? $save['bulksmsnigeria_sandbox_url'] : $save['bulksmsnigeria_base_url'];
                            $save['sms_api_key'] = $save['bulksmsnigeria_api_token'];
                            $save['sms_sender_id'] = $save['bulksmsnigeria_sender_id'];
                            break;
                        case 'termii':
                            $save['sms_api_url'] = $save['termii_base_url'];
                            $save['sms_api_key'] = $save['termii_api_key'];
                            $save['sms_sender_id'] = $save['termii_sender_id'];
                            break;
                        case 'twilio':
                            $save['sms_api_url'] = $save['twilio_base_url'];
                            $save['sms_api_key'] = $save['twilio_api_key'] !== '' ? $save['twilio_api_key'] : $save['twilio_account_sid'];
                            $save['sms_sender_id'] = $save['twilio_from_number'] !== '' ? $save['twilio_from_number'] : $save['twilio_messaging_service_sid'];
                            break;
                        case 'vonage':
                            $save['sms_api_url'] = $save['vonage_base_url'];
                            $save['sms_api_key'] = $save['vonage_api_key'];
                            $save['sms_sender_id'] = $save['vonage_sender_id'];
                            break;
                        case 'infobip':
                            $save['sms_api_url'] = $save['infobip_base_url'];
                            $save['sms_api_key'] = $save['infobip_api_key'];
                            $save['sms_sender_id'] = $save['infobip_sender'];
                            break;
                    }

                    if ($emailEnabled && $fromEmail !== '' && !filter_var($fromEmail, FILTER_VALIDATE_EMAIL)) {
                        flash('danger', 'Invalid From Email address.');
                        redirect('?page=notification_settings');
                    }
                    if ($smtpPort < 0) $smtpPort = 0;

                    $smsErrors = portalSmsValidateSettings($save, true);
                    if (!empty($smsErrors)) {
                        flash('danger', implode(' ', $smsErrors));
                        redirect('?page=notification_settings');
                    }

                    try {
                        $settingsId = 0;
                        try {
                            $settingsId = (int)$pdo->query("SELECT id FROM notification_settings ORDER BY id ASC LIMIT 1")->fetchColumn();
                        } catch (Throwable $e) {
                            $settingsId = 0;
                        }
                        if ($settingsId <= 0) {
                            $pdo->exec("INSERT INTO notification_settings (email_enabled, sms_enabled, sms_timeout_seconds, sms_retry_attempts) VALUES (0,0,12,0)");
                            $settingsId = (int)$pdo->lastInsertId();
                            if ($settingsId <= 0) {
                                $settingsId = (int)$pdo->query("SELECT id FROM notification_settings ORDER BY id ASC LIMIT 1")->fetchColumn();
                            }
                        }

                        $sql = "UPDATE notification_settings SET
                            email_enabled=?, sms_enabled=?, from_email=?, from_name=?, smtp_host=?, smtp_port=?, smtp_user=?, smtp_pass=?, smtp_secure=?,
                            sms_provider=?, sms_api_url=?, sms_api_key=?, sms_sender_id=?, sms_test_number=?, sms_timeout_seconds=?, sms_retry_attempts=?, sms_sender_number=?,
                            bulksmsnigeria_base_url=?, bulksmsnigeria_sandbox_url=?, bulksmsnigeria_api_token=?, bulksmsnigeria_sender_id=?, bulksmsnigeria_environment=?,
                            termii_base_url=?, termii_api_key=?, termii_sender_id=?, termii_channel=?, termii_message_type=?,
                            twilio_account_sid=?, twilio_api_key=?, twilio_api_key_secret=?, twilio_auth_token=?, twilio_from_number=?, twilio_messaging_service_sid=?, twilio_base_url=?,
                            vonage_api_key=?, vonage_api_secret=?, vonage_sender_id=?, vonage_base_url=?,
                            infobip_base_url=?, infobip_api_key=?, infobip_sender=?
                            WHERE id = ?";
                        $stmt = $pdo->prepare($sql);
                        $stmt->execute([
                            $emailEnabled,
                            $smsEnabled,
                            ($fromEmail !== '' ? $fromEmail : null),
                            ($fromName !== '' ? $fromName : null),
                            ($smtpHost !== '' ? $smtpHost : null),
                            ($smtpPort > 0 ? $smtpPort : null),
                            ($smtpUser !== '' ? $smtpUser : null),
                            ($smtpPass !== '' ? $smtpPass : ($currentNs['smtp_pass'] ?? null)),
                            ($smtpSecure !== '' ? $smtpSecure : null),
                            $save['sms_provider'] !== '' ? $save['sms_provider'] : null,
                            $save['sms_api_url'] !== '' ? $save['sms_api_url'] : null,
                            $save['sms_api_key'] !== '' ? $save['sms_api_key'] : null,
                            $save['sms_sender_id'] !== '' ? $save['sms_sender_id'] : null,
                            $save['sms_test_number'] !== '' ? $save['sms_test_number'] : null,
                            $save['sms_timeout_seconds'],
                            $save['sms_retry_attempts'],
                            $save['sms_sender_number'] !== '' ? $save['sms_sender_number'] : null,
                            $save['bulksmsnigeria_base_url'] !== '' ? $save['bulksmsnigeria_base_url'] : null,
                            $save['bulksmsnigeria_sandbox_url'] !== '' ? $save['bulksmsnigeria_sandbox_url'] : null,
                            $save['bulksmsnigeria_api_token'] !== '' ? $save['bulksmsnigeria_api_token'] : null,
                            $save['bulksmsnigeria_sender_id'] !== '' ? $save['bulksmsnigeria_sender_id'] : null,
                            $save['bulksmsnigeria_environment'] !== '' ? $save['bulksmsnigeria_environment'] : null,
                            $save['termii_base_url'] !== '' ? $save['termii_base_url'] : null,
                            $save['termii_api_key'] !== '' ? $save['termii_api_key'] : null,
                            $save['termii_sender_id'] !== '' ? $save['termii_sender_id'] : null,
                            $save['termii_channel'] !== '' ? $save['termii_channel'] : null,
                            $save['termii_message_type'] !== '' ? $save['termii_message_type'] : null,
                            $save['twilio_account_sid'] !== '' ? $save['twilio_account_sid'] : null,
                            $save['twilio_api_key'] !== '' ? $save['twilio_api_key'] : null,
                            $save['twilio_api_key_secret'] !== '' ? $save['twilio_api_key_secret'] : null,
                            $save['twilio_auth_token'] !== '' ? $save['twilio_auth_token'] : null,
                            $save['twilio_from_number'] !== '' ? $save['twilio_from_number'] : null,
                            $save['twilio_messaging_service_sid'] !== '' ? $save['twilio_messaging_service_sid'] : null,
                            $save['twilio_base_url'] !== '' ? $save['twilio_base_url'] : null,
                            $save['vonage_api_key'] !== '' ? $save['vonage_api_key'] : null,
                            $save['vonage_api_secret'] !== '' ? $save['vonage_api_secret'] : null,
                            $save['vonage_sender_id'] !== '' ? $save['vonage_sender_id'] : null,
                            $save['vonage_base_url'] !== '' ? $save['vonage_base_url'] : null,
                            $save['infobip_base_url'] !== '' ? $save['infobip_base_url'] : null,
                            $save['infobip_api_key'] !== '' ? $save['infobip_api_key'] : null,
                            $save['infobip_sender'] !== '' ? $save['infobip_sender'] : null,
                            $settingsId,
                        ]);
                        flash('success', 'Notification settings saved.');
                    } catch (Throwable $e) {
                        flash('danger', 'Failed to save notification settings.');
                    }
                    redirect('?page=notification_settings');
                }

                if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'send_test_sms') {
                    $ns = portalSmsSettingsMerged(getNotificationSettings($pdo));
                    $testNumber = trim((string)($_POST['test_sms_number'] ?? ($ns['sms_test_number'] ?? '')));
                    $testMessage = trim((string)($_POST['test_sms_message'] ?? ''));
                    if ($testNumber === '') {
                        flash('danger', 'Enter a valid test phone number.');
                    } elseif ($testMessage === '') {
                        flash('danger', 'Enter a test SMS message.');
                    } else {
                        $ok = sendSmsNotification($pdo, $testNumber, $testMessage, 'test_sms', 'admin_test_sms', 'admin', (int)($_SESSION['user_id'] ?? 0));
                        if ($ok) {
                            flash('success', 'Test SMS sent successfully through the active provider.');
                        } else {
                            flash('danger', 'Test SMS failed. Check the provider settings and SMS log.');
                        }
                    }
                    redirect('?page=notification_settings');
                }

                $ns = portalSmsSettingsMerged(getNotificationSettings($pdo));
                $requestedTab = strtolower(trim((string)($_GET['tab'] ?? 'settings')));
                $settingsTab = in_array($requestedTab, ['settings', 'templates', 'sms_templates'], true) ? $requestedTab : 'settings';
                $editEmailTemplateId = (int)($_GET['edit_email_template'] ?? 0);
                $editSmsTemplateId = (int)($_GET['edit_sms_template'] ?? 0);
                $emailTemplates = [];
                $smsTemplates = [];
                $editEmailTemplate = null;
                $editSmsTemplate = null;
                $notificationCommandOptions = portalNotificationCommandCatalog();
                try { portalEnsureNotificationTemplateSchema($pdo, 'email'); } catch (Throwable $e) {}
                try { portalEnsureNotificationTemplateSchema($pdo, 'sms'); } catch (Throwable $e) {}
                try { $emailTemplates = $pdo->query("SELECT * FROM email_templates ORDER BY template_name ASC, id ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
                try { $smsTemplates = $pdo->query("SELECT * FROM sms_templates ORDER BY template_name ASC, id ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {}
                foreach ($emailTemplates as &$__emailTplRow) {
                    $__emailTplRow['command_key'] = portalTemplateRowCommandKey($__emailTplRow);
                    $__emailTplRow['is_active'] = portalTemplateRowEnabled($__emailTplRow) ? 1 : 0;
                }
                unset($__emailTplRow);
                foreach ($smsTemplates as &$__smsTplRow) {
                    $__smsTplRow['command_key'] = portalTemplateRowCommandKey($__smsTplRow);
                    $__smsTplRow['is_active'] = portalTemplateRowEnabled($__smsTplRow) ? 1 : 0;
                }
                unset($__smsTplRow);
                if ($editEmailTemplateId > 0) {
                    try {
                        $st = $pdo->prepare("SELECT * FROM email_templates WHERE id = ? LIMIT 1");
                        $st->execute([$editEmailTemplateId]);
                        $editEmailTemplate = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                    } catch (Throwable $e) {}
                }
                if ($editSmsTemplateId > 0) {
                    try {
                        $st = $pdo->prepare("SELECT * FROM sms_templates WHERE id = ? LIMIT 1");
                        $st->execute([$editSmsTemplateId]);
                        $editSmsTemplate = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                    } catch (Throwable $e) {}
                }
                if (is_array($editEmailTemplate)) {
                    $editEmailTemplate['command_key'] = portalTemplateRowCommandKey($editEmailTemplate);
                    $editEmailTemplate['is_active'] = portalTemplateRowEnabled($editEmailTemplate) ? 1 : 0;
                }
                if (is_array($editSmsTemplate)) {
                    $editSmsTemplate['command_key'] = portalTemplateRowCommandKey($editSmsTemplate);
                    $editSmsTemplate['is_active'] = portalTemplateRowEnabled($editSmsTemplate) ? 1 : 0;
                }
                $providerOptions = portalSmsProviderOptions();
                ?>
                <div class="container-fluid">
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                    <div>Email / SMS Notification Settings</div>
                                    <small class="text-muted">Self-healing: messaging settings and SMS log auto-created</small>
                                </div>
                                <div class="card-body">
                                    <?php renderFlashes(); ?>
                                    <ul class="nav nav-tabs mb-3">
                                        <li class="nav-item"><a class="nav-link <?php echo $settingsTab === 'settings' ? 'active' : ''; ?>" href="?page=notification_settings&tab=settings">Settings</a></li>
                                        <li class="nav-item"><a class="nav-link <?php echo $settingsTab === 'templates' ? 'active' : ''; ?>" href="?page=notification_settings&tab=templates">Email Templates</a></li>
                                        <li class="nav-item"><a class="nav-link <?php echo $settingsTab === 'sms_templates' ? 'active' : ''; ?>" href="?page=notification_settings&tab=sms_templates">SMS Templates</a></li>
                                    </ul>
                                    <?php if ($settingsTab === 'settings'): ?>
                                    <form method="post" id="notificationSettingsForm">
                                        <input type="hidden" name="action" value="save_notification_settings">

                                        <div class="row g-4">
                                            <div class="col-lg-6">
                                                <div class="border rounded p-3 h-100">
                                                    <h6 class="mb-3">Email</h6>
                                                    <div class="form-check form-switch mb-3">
                                                        <input class="form-check-input" type="checkbox" id="email_enabled" name="email_enabled" value="1" <?php echo ((int)($ns['email_enabled'] ?? 0)===1)?'checked':''; ?>>
                                                        <label class="form-check-label" for="email_enabled">Enable Email Notifications</label>
                                                    </div>

                                                    <div class="mb-2">
                                                        <label class="form-label">From Email</label>
                                                        <input type="email" name="from_email" class="form-control" value="<?php echo htmlspecialchars($ns['from_email'] ?? ''); ?>">
                                                    </div>
                                                    <div class="mb-2">
                                                        <label class="form-label">From Name</label>
                                                        <input type="text" name="from_name" class="form-control" value="<?php echo htmlspecialchars($ns['from_name'] ?? ''); ?>">
                                                    </div>

                                                    <div class="alert alert-secondary small mb-3">
                                                        This build uses PHP <code>mail()</code> by default. SMTP fields are stored for future upgrades.
                                                    </div>

                                                    <h6 class="mt-3">SMTP (optional)</h6>
                                                    <div class="mb-2">
                                                        <label class="form-label">SMTP Host</label>
                                                        <input type="text" name="smtp_host" class="form-control" value="<?php echo htmlspecialchars($ns['smtp_host'] ?? ''); ?>">
                                                    </div>
                                                    <div class="mb-2">
                                                        <label class="form-label">SMTP Port</label>
                                                        <input type="number" name="smtp_port" class="form-control" value="<?php echo htmlspecialchars((string)($ns['smtp_port'] ?? '')); ?>">
                                                    </div>
                                                    <div class="mb-2">
                                                        <label class="form-label">SMTP User</label>
                                                        <input type="text" name="smtp_user" class="form-control" value="<?php echo htmlspecialchars($ns['smtp_user'] ?? ''); ?>">
                                                    </div>
                                                    <div class="mb-2">
                                                        <label class="form-label">SMTP Password</label>
                                                        <input type="password" name="smtp_pass" class="form-control" value="<?php echo htmlspecialchars((string)($ns['smtp_pass'] ?? '')); ?>">
                                                    </div>
                                                    <div class="mb-2">
                                                        <label class="form-label">SMTP Secure</label>
                                                        <select name="smtp_secure" class="form-select">
                                                            <?php $sec = (string)($ns['smtp_secure'] ?? ''); ?>
                                                            <option value="" <?php echo $sec===''?'selected':''; ?>>None</option>
                                                            <option value="tls" <?php echo $sec==='tls'?'selected':''; ?>>TLS</option>
                                                            <option value="ssl" <?php echo $sec==='ssl'?'selected':''; ?>>SSL</option>
                                                        </select>
                                                    </div>
                                                </div>
                                            </div>

                                            <div class="col-lg-6">
                                                <div class="border rounded p-3 h-100">
                                                    <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
                                                        <h6 class="mb-0">SMS</h6>
                                                        <span class="badge bg-<?php echo ((int)($ns['sms_enabled'] ?? 0) === 1) ? 'success' : 'secondary'; ?>"><?php echo ((int)($ns['sms_enabled'] ?? 0) === 1) ? 'Active' : 'Inactive'; ?></span>
                                                    </div>
                                                    <div class="form-check form-switch mb-3">
                                                        <input class="form-check-input" type="checkbox" id="sms_enabled" name="sms_enabled" value="1" <?php echo ((int)($ns['sms_enabled'] ?? 0)===1)?'checked':''; ?>>
                                                        <label class="form-check-label" for="sms_enabled">Enable SMS Notifications</label>
                                                    </div>

                                                    <div class="row g-3">
                                                        <div class="col-md-6">
                                                            <label class="form-label">Active Provider</label>
                                                            <select name="sms_provider" id="sms_provider" class="form-select">
                                                                <?php foreach ($providerOptions as $key => $label): ?>
                                                                    <option value="<?php echo e($key); ?>" <?php echo (($ns['sms_provider'] ?? '') === $key) ? 'selected' : ''; ?>><?php echo e($label); ?></option>
                                                                <?php endforeach; ?>
                                                            </select>
                                                        </div>
                                                        <div class="col-md-6">
                                                            <label class="form-label">Default Sender ID / Sender</label>
                                                            <input type="text" name="sms_sender_id" class="form-control" value="<?php echo htmlspecialchars((string)($ns['sms_sender_id'] ?? '')); ?>">
                                                        </div>
                                                        <div class="col-md-6">
                                                            <label class="form-label">Sender Number (optional)</label>
                                                            <input type="text" name="sms_sender_number" class="form-control" value="<?php echo htmlspecialchars((string)($ns['sms_sender_number'] ?? '')); ?>">
                                                        </div>
                                                        <div class="col-md-6">
                                                            <label class="form-label">Default Test Number</label>
                                                            <input type="text" name="sms_test_number" class="form-control" value="<?php echo htmlspecialchars((string)($ns['sms_test_number'] ?? '')); ?>" placeholder="e.g. 2348012345678">
                                                        </div>
                                                        <div class="col-md-6">
                                                            <label class="form-label">Timeout (seconds)</label>
                                                            <input type="number" min="5" max="60" name="sms_timeout_seconds" class="form-control" value="<?php echo (int)($ns['sms_timeout_seconds'] ?? 12); ?>">
                                                        </div>
                                                        <div class="col-md-6">
                                                            <label class="form-label">Retry Attempts</label>
                                                            <input type="number" min="0" max="5" name="sms_retry_attempts" class="form-control" value="<?php echo (int)($ns['sms_retry_attempts'] ?? 0); ?>">
                                                        </div>
                                                    </div>

                                                    <div class="mt-3">
                                                        <div class="alert alert-info small mb-0">
                                                            Only the selected active provider is used for SMS sending. Save settings first before using the test SMS action. Secret fields never render their stored values in HTML.
                                                        </div>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>

                                        <div class="row g-4 mt-1">
                                            <div class="col-12">
                                                <div class="border rounded p-3">
                                                    <h6 class="mb-3">Provider Configuration</h6>

                                                    <div data-sms-provider-section="bulksmsnigeria" class="sms-provider-section">
                                                        <div class="row g-3">
                                                            <div class="col-md-4"><label class="form-label">BulkSMSNigeria Base URL</label><input type="text" name="bulksmsnigeria_base_url" class="form-control" value="<?php echo htmlspecialchars((string)$ns['bulksmsnigeria_base_url']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">BulkSMSNigeria Sandbox URL</label><input type="text" name="bulksmsnigeria_sandbox_url" class="form-control" value="<?php echo htmlspecialchars((string)$ns['bulksmsnigeria_sandbox_url']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">Environment</label><select name="bulksmsnigeria_environment" class="form-select"><option value="production" <?php echo (($ns['bulksmsnigeria_environment'] ?? 'production') === 'production') ? 'selected' : ''; ?>>Production</option><option value="sandbox" <?php echo (($ns['bulksmsnigeria_environment'] ?? '') === 'sandbox') ? 'selected' : ''; ?>>Sandbox</option></select></div>
                                                            <div class="col-md-6"><label class="form-label">API Token</label><input type="password" name="bulksmsnigeria_api_token" class="form-control" autocomplete="new-password" placeholder="Saved token hidden. Leave blank to keep current value."></div>
                                                            <div class="col-md-6"><label class="form-label">Sender ID / From</label><input type="text" name="bulksmsnigeria_sender_id" class="form-control" value="<?php echo htmlspecialchars((string)$ns['bulksmsnigeria_sender_id']); ?>"></div>
                                                        </div>
                                                    </div>

                                                    <div data-sms-provider-section="termii" class="sms-provider-section">
                                                        <div class="row g-3">
                                                            <div class="col-md-4"><label class="form-label">Termii Base URL</label><input type="text" name="termii_base_url" class="form-control" value="<?php echo htmlspecialchars((string)$ns['termii_base_url']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">API Key</label><input type="password" name="termii_api_key" class="form-control" autocomplete="new-password" placeholder="Saved API key hidden. Leave blank to keep current value."></div>
                                                            <div class="col-md-4"><label class="form-label">Sender ID</label><input type="text" name="termii_sender_id" class="form-control" value="<?php echo htmlspecialchars((string)$ns['termii_sender_id']); ?>"></div>
                                                            <div class="col-md-6"><label class="form-label">Channel / Route</label><select name="termii_channel" class="form-select"><option value="dnd" <?php echo (($ns['termii_channel'] ?? 'dnd') === 'dnd') ? 'selected' : ''; ?>>DND</option><option value="generic" <?php echo (($ns['termii_channel'] ?? '') === 'generic') ? 'selected' : ''; ?>>Generic</option><option value="whatsapp" <?php echo (($ns['termii_channel'] ?? '') === 'whatsapp') ? 'selected' : ''; ?>>WhatsApp</option><option value="voice" <?php echo (($ns['termii_channel'] ?? '') === 'voice') ? 'selected' : ''; ?>>Voice</option></select></div>
                                                            <div class="col-md-6"><label class="form-label">Message Type</label><select name="termii_message_type" class="form-select"><option value="plain" <?php echo (($ns['termii_message_type'] ?? 'plain') === 'plain') ? 'selected' : ''; ?>>Plain</option><option value="unicode" <?php echo (($ns['termii_message_type'] ?? '') === 'unicode') ? 'selected' : ''; ?>>Unicode</option></select></div>
                                                        </div>
                                                    </div>

                                                    <div data-sms-provider-section="twilio" class="sms-provider-section">
                                                        <div class="row g-3">
                                                            <div class="col-md-4"><label class="form-label">Twilio Base URL</label><input type="text" name="twilio_base_url" class="form-control" value="<?php echo htmlspecialchars((string)$ns['twilio_base_url']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">Account SID</label><input type="text" name="twilio_account_sid" class="form-control" value="<?php echo htmlspecialchars((string)$ns['twilio_account_sid']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">From Number</label><input type="text" name="twilio_from_number" class="form-control" value="<?php echo htmlspecialchars((string)$ns['twilio_from_number']); ?>" placeholder="e.g. +15017122661"></div>
                                                            <div class="col-md-4"><label class="form-label">API Key SID</label><input type="text" name="twilio_api_key" class="form-control" value="<?php echo htmlspecialchars((string)$ns['twilio_api_key']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">API Key Secret</label><input type="password" name="twilio_api_key_secret" class="form-control" autocomplete="new-password" placeholder="Saved secret hidden. Leave blank to keep current value."></div>
                                                            <div class="col-md-4"><label class="form-label">Auth Token Fallback</label><input type="password" name="twilio_auth_token" class="form-control" autocomplete="new-password" placeholder="Saved token hidden. Leave blank to keep current value."></div>
                                                            <div class="col-md-6"><label class="form-label">Messaging Service SID</label><input type="text" name="twilio_messaging_service_sid" class="form-control" value="<?php echo htmlspecialchars((string)$ns['twilio_messaging_service_sid']); ?>"></div>
                                                            <div class="col-md-6"><div class="alert alert-secondary small mb-0 h-100 d-flex align-items-center">Twilio sends with <code>To</code>, <code>Body</code>, and either <code>From</code> or <code>MessagingServiceSid</code>.</div></div>
                                                        </div>
                                                    </div>

                                                    <div data-sms-provider-section="vonage" class="sms-provider-section">
                                                        <div class="row g-3">
                                                            <div class="col-md-4"><label class="form-label">Vonage Base URL</label><input type="text" name="vonage_base_url" class="form-control" value="<?php echo htmlspecialchars((string)$ns['vonage_base_url']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">API Key</label><input type="text" name="vonage_api_key" class="form-control" value="<?php echo htmlspecialchars((string)$ns['vonage_api_key']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">Sender ID / From</label><input type="text" name="vonage_sender_id" class="form-control" value="<?php echo htmlspecialchars((string)$ns['vonage_sender_id']); ?>"></div>
                                                            <div class="col-md-6"><label class="form-label">API Secret</label><input type="password" name="vonage_api_secret" class="form-control" autocomplete="new-password" placeholder="Saved secret hidden. Leave blank to keep current value."></div>
                                                        </div>
                                                    </div>

                                                    <div data-sms-provider-section="infobip" class="sms-provider-section">
                                                        <div class="row g-3">
                                                            <div class="col-md-4"><label class="form-label">Infobip Base URL</label><input type="text" name="infobip_base_url" class="form-control" value="<?php echo htmlspecialchars((string)$ns['infobip_base_url']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">Sender</label><input type="text" name="infobip_sender" class="form-control" value="<?php echo htmlspecialchars((string)$ns['infobip_sender']); ?>"></div>
                                                            <div class="col-md-4"><label class="form-label">API Key</label><input type="password" name="infobip_api_key" class="form-control" autocomplete="new-password" placeholder="Saved API key hidden. Leave blank to keep current value."></div>
                                                        </div>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>

                                        <div class="mt-3 d-flex flex-wrap gap-2">
                                            <button class="btn btn-primary"><i class="bi bi-save"></i> Save Settings</button>
                                        </div>
                                    </form>

                                    <div class="card mt-4 border-0 shadow-sm">
                                        <div class="card-header bg-light"><strong>Send Test SMS</strong></div>
                                        <div class="card-body">
                                            <form method="post" class="row g-3">
                                                <input type="hidden" name="action" value="send_test_sms">
                                                <div class="col-md-4">
                                                    <label class="form-label">Test Phone Number</label>
                                                    <input type="text" name="test_sms_number" class="form-control" value="<?php echo htmlspecialchars((string)($ns['sms_test_number'] ?? '')); ?>" placeholder="e.g. 2348012345678">
                                                </div>
                                                <div class="col-md-8">
                                                    <label class="form-label">Test Message</label>
                                                    <input type="text" name="test_sms_message" class="form-control" value="<?php echo htmlspecialchars('Test SMS from ' . portalNotificationMailSubjectPrefix($pdo) . ' at ' . date('Y-m-d H:i:s')); ?>">
                                                </div>
                                                <div class="col-12">
                                                    <button class="btn btn-outline-success" type="submit"><i class="bi bi-send"></i> Send Test SMS</button>
                                                </div>
                                            </form>
                                        </div>
                                    </div>

                                    <script>
                                    (function(){
                                        const select = document.getElementById('sms_provider');
                                        const sections = Array.from(document.querySelectorAll('[data-sms-provider-section]'));
                                        function syncSmsProviderSections(){
                                            const active = (select && select.value) ? String(select.value) : '';
                                            sections.forEach(function(section){
                                                const show = section.getAttribute('data-sms-provider-section') === active;
                                                section.style.display = show ? '' : 'none';
                                            });
                                        }
                                        if (select) {
                                            select.addEventListener('change', syncSmsProviderSections);
                                        }
                                        syncSmsProviderSections();
                                    })();
                                    </script>
                                    <?php elseif ($settingsTab === 'templates'): ?>
                                    <?php $emailForm = $editEmailTemplate ?? ['id' => 0, 'command_key' => '', 'template_key' => '', 'template_name' => '', 'subject' => '', 'body_html' => '', 'is_active' => 1]; ?>
                                    <div class="row g-4">
                                        <div class="col-lg-5">
                                            <div class="card border-0 shadow-sm">
                                                <div class="card-header bg-light d-flex justify-content-between align-items-center flex-wrap gap-2">
                                                    <strong><?php echo !empty($emailForm['id']) ? 'Edit Email Template' : 'Add Email Template'; ?></strong>
                                                    <?php if (!empty($emailForm['id'])): ?>
                                                        <a href="?page=notification_settings&tab=templates" class="btn btn-sm btn-outline-secondary">Cancel Edit</a>
                                                    <?php endif; ?>
                                                </div>
                                                <div class="card-body">
                                                    <form method="post" class="row g-3">
                                                        <input type="hidden" name="action" value="save_email_template">
                                                        <input type="hidden" name="template_id" value="<?php echo (int)($emailForm['id'] ?? 0); ?>">
                                                        <div class="col-12">
                                                            <label class="form-label">Command / Event</label>
                                                            <select name="command_key" class="form-select" required>
                                                                <option value="">Select command</option>
                                                                <?php $emailSelectedCommand = (string)($emailForm['command_key'] ?? $emailForm['template_key'] ?? ''); ?>
                                                                <?php foreach ($notificationCommandOptions as $commandValue => $commandLabel): ?>
                                                                    <option value="<?php echo e($commandValue); ?>" <?php echo ($emailSelectedCommand === $commandValue) ? 'selected' : ''; ?>><?php echo e($commandLabel . ' (' . $commandValue . ')'); ?></option>
                                                                <?php endforeach; ?>
                                                                <?php if ($emailSelectedCommand !== '' && !isset($notificationCommandOptions[$emailSelectedCommand])): ?>
                                                                    <option value="<?php echo e($emailSelectedCommand); ?>" selected><?php echo e($emailSelectedCommand); ?></option>
                                                                <?php endif; ?>
                                                            </select>
                                                            <div class="form-text">This command key is the live notification event used for template lookup and enable or disable enforcement.</div>
                                                        </div>
                                                        <div class="col-12">
                                                            <label class="form-label">Template Name</label>
                                                            <input type="text" name="template_name" class="form-control" value="<?php echo htmlspecialchars((string)($emailForm['template_name'] ?? '')); ?>" required>
                                                        </div>
                                                        <div class="col-12">
                                                            <label class="form-label">Subject</label>
                                                            <input type="text" name="subject" class="form-control" value="<?php echo htmlspecialchars((string)($emailForm['subject'] ?? '')); ?>" required>
                                                        </div>
                                                        <div class="col-12">
                                                            <label class="form-label">Body HTML</label>
                                                            <textarea name="body_html" class="form-control js-rich-editor" data-richtext="tinymce" data-richtext-profile="html_email" rows="10" placeholder="Optional HTML email body"><?php echo htmlspecialchars((string)($emailForm['body_html'] ?? '')); ?></textarea>
                                                        </div>
                                                        <div class="col-12">
                                                            <div class="form-check">
                                                                <input class="form-check-input" type="checkbox" name="is_active" value="1" id="tpl_active_email" <?php echo ((int)($emailForm['is_active'] ?? 1) === 1) ? 'checked' : ''; ?>>
                                                                <label class="form-check-label" for="tpl_active_email">Active</label>
                                                            </div>
                                                            <div class="form-text">Disabled email templates are blocked for live notifications on this command until re-enabled.</div>
                                                        </div>
                                                        <div class="col-12 d-flex flex-wrap gap-2">
                                                            <button class="btn btn-primary" type="submit"><i class="bi bi-save"></i> <?php echo !empty($emailForm['id']) ? 'Update Template' : 'Save Template'; ?></button>
                                                            <?php if (!empty($emailForm['id'])): ?>
                                                                <a href="?page=notification_settings&tab=templates" class="btn btn-outline-secondary">Cancel</a>
                                                            <?php endif; ?>
                                                        </div>
                                                    </form>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-lg-7">
                                            <div class="card border-0 shadow-sm">
                                                <div class="card-header bg-light"><strong>Email Templates</strong></div>
                                                <div class="card-body">
                                                    <div class="table-responsive">
                                                        <table class="table table-striped align-middle">
                                                            <thead>
                                                                <tr><th>Name</th><th>Command</th><th>Subject</th><th>Status</th><th class="text-end">Actions</th></tr>
                                                            </thead>
                                                            <tbody>
                                                            <?php foreach ($emailTemplates as $tpl): ?>
                                                                <tr>
                                                                    <td><?php echo e((string)$tpl['template_name']); ?></td>
                                                                    <td><code><?php echo e((string)($tpl['command_key'] ?? $tpl['template_key'])); ?></code></td>
                                                                    <td><?php echo e((string)$tpl['subject']); ?></td>
                                                                    <td><?php echo ((int)($tpl['is_active'] ?? 1) === 1) ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Disabled</span>'; ?></td>
                                                                    <td class="text-end">
                                                                        <div class="d-inline-flex flex-wrap gap-1 justify-content-end">
                                                                            <a href="?page=notification_settings&tab=templates&edit_email_template=<?php echo (int)$tpl['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
                                                                            <form method="post" class="d-inline">
                                                                                <input type="hidden" name="action" value="toggle_email_template">
                                                                                <input type="hidden" name="template_id" value="<?php echo (int)$tpl['id']; ?>">
                                                                                <input type="hidden" name="set_active" value="<?php echo ((int)($tpl['is_active'] ?? 1) === 1) ? 0 : 1; ?>">
                                                                                <button class="btn btn-sm btn-outline-<?php echo ((int)($tpl['is_active'] ?? 1) === 1) ? 'warning' : 'success'; ?>" type="submit"><?php echo ((int)($tpl['is_active'] ?? 1) === 1) ? 'Disable' : 'Enable'; ?></button>
                                                                            </form>
                                                                            <form method="post" class="d-inline" onsubmit="return confirm('Delete this email template?');">
                                                                                <input type="hidden" name="action" value="delete_email_template">
                                                                                <input type="hidden" name="template_id" value="<?php echo (int)$tpl['id']; ?>">
                                                                                <button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
                                                                            </form>
                                                                        </div>
                                                                    </td>
                                                                </tr>
                                                            <?php endforeach; ?>
                                                            <?php if (empty($emailTemplates)): ?><tr><td colspan="5" class="text-center text-muted">No email templates available.</td></tr><?php endif; ?>
                                                            </tbody>
                                                        </table>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                    <?php else: ?>
                                    <?php $smsForm = $editSmsTemplate ?? ['id' => 0, 'command_key' => '', 'template_key' => '', 'template_name' => '', 'message_text' => '', 'is_active' => 1]; ?>
                                    <div class="row g-4">
                                        <div class="col-lg-5">
                                            <div class="card border-0 shadow-sm">
                                                <div class="card-header bg-light d-flex justify-content-between align-items-center flex-wrap gap-2">
                                                    <strong><?php echo !empty($smsForm['id']) ? 'Edit SMS Template' : 'Add SMS Template'; ?></strong>
                                                    <?php if (!empty($smsForm['id'])): ?>
                                                        <a href="?page=notification_settings&tab=sms_templates" class="btn btn-sm btn-outline-secondary">Cancel Edit</a>
                                                    <?php endif; ?>
                                                </div>
                                                <div class="card-body">
                                                    <form method="post" class="row g-3">
                                                        <input type="hidden" name="action" value="save_sms_template">
                                                        <input type="hidden" name="template_id" value="<?php echo (int)($smsForm['id'] ?? 0); ?>">
                                                        <div class="col-12">
                                                            <label class="form-label">Command / Event</label>
                                                            <select name="command_key" class="form-select" required>
                                                                <option value="">Select command</option>
                                                                <?php $smsSelectedCommand = (string)($smsForm['command_key'] ?? $smsForm['template_key'] ?? ''); ?>
                                                                <?php foreach ($notificationCommandOptions as $commandValue => $commandLabel): ?>
                                                                    <option value="<?php echo e($commandValue); ?>" <?php echo ($smsSelectedCommand === $commandValue) ? 'selected' : ''; ?>><?php echo e($commandLabel . ' (' . $commandValue . ')'); ?></option>
                                                                <?php endforeach; ?>
                                                                <?php if ($smsSelectedCommand !== '' && !isset($notificationCommandOptions[$smsSelectedCommand])): ?>
                                                                    <option value="<?php echo e($smsSelectedCommand); ?>" selected><?php echo e($smsSelectedCommand); ?></option>
                                                                <?php endif; ?>
                                                            </select>
                                                            <div class="form-text">Disabled SMS templates are blocked for automatic sending, manual sending, retry paths, and command-based test paths.</div>
                                                        </div>
                                                        <div class="col-12">
                                                            <label class="form-label">Template Name</label>
                                                            <input type="text" name="template_name" class="form-control" value="<?php echo htmlspecialchars((string)($smsForm['template_name'] ?? '')); ?>" required>
                                                        </div>
                                                        <div class="col-12">
                                                            <label class="form-label">Message Text</label>
                                                            <textarea name="message_text" class="form-control" rows="10" placeholder="SMS text. You can use {{message}}, {{portal_name}}, {{event_type}}, and context-specific placeholders."><?php echo htmlspecialchars((string)($smsForm['message_text'] ?? '')); ?></textarea>
                                                        </div>
                                                        <div class="col-12">
                                                            <div class="form-check">
                                                                <input class="form-check-input" type="checkbox" name="is_active" value="1" id="tpl_active_sms" <?php echo ((int)($smsForm['is_active'] ?? 1) === 1) ? 'checked' : ''; ?>>
                                                                <label class="form-check-label" for="tpl_active_sms">Active</label>
                                                            </div>
                                                        </div>
                                                        <div class="col-12">
                                                            <div class="alert alert-secondary small mb-0">
                                                                Helpful placeholders: <code>{{message}}</code>, <code>{{portal_name}}</code>, <code>{{event_type}}</code>, <code>{{context}}</code>. For seeded notification keys, the same placeholders used in email templates can also be used here.
                                                            </div>
                                                        </div>
                                                        <div class="col-12 d-flex flex-wrap gap-2">
                                                            <button class="btn btn-primary" type="submit"><i class="bi bi-save"></i> <?php echo !empty($smsForm['id']) ? 'Update Template' : 'Save Template'; ?></button>
                                                            <?php if (!empty($smsForm['id'])): ?>
                                                                <a href="?page=notification_settings&tab=sms_templates" class="btn btn-outline-secondary">Cancel</a>
                                                            <?php endif; ?>
                                                        </div>
                                                    </form>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-lg-7">
                                            <div class="card border-0 shadow-sm">
                                                <div class="card-header bg-light"><strong>SMS Templates</strong></div>
                                                <div class="card-body">
                                                    <div class="table-responsive">
                                                        <table class="table table-striped align-middle">
                                                            <thead>
                                                                <tr><th>Name</th><th>Command</th><th>Preview</th><th>Status</th><th class="text-end">Actions</th></tr>
                                                            </thead>
                                                            <tbody>
                                                            <?php foreach ($smsTemplates as $tpl): ?>
                                                                <tr>
                                                                    <td><?php echo e((string)$tpl['template_name']); ?></td>
                                                                    <td><code><?php echo e((string)($tpl['command_key'] ?? $tpl['template_key'])); ?></code></td>
                                                                    <td class="small"><?php echo e(function_exists('mb_strimwidth') ? mb_strimwidth((string)$tpl['message_text'], 0, 90, '…', 'UTF-8') : substr((string)$tpl['message_text'], 0, 90)); ?></td>
                                                                    <td><?php echo ((int)($tpl['is_active'] ?? 1) === 1) ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Disabled</span>'; ?></td>
                                                                    <td class="text-end">
                                                                        <div class="d-inline-flex flex-wrap gap-1 justify-content-end">
                                                                            <a href="?page=notification_settings&tab=sms_templates&edit_sms_template=<?php echo (int)$tpl['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
                                                                            <form method="post" class="d-inline">
                                                                                <input type="hidden" name="action" value="toggle_sms_template">
                                                                                <input type="hidden" name="template_id" value="<?php echo (int)$tpl['id']; ?>">
                                                                                <input type="hidden" name="set_active" value="<?php echo ((int)($tpl['is_active'] ?? 1) === 1) ? 0 : 1; ?>">
                                                                                <button class="btn btn-sm btn-outline-<?php echo ((int)($tpl['is_active'] ?? 1) === 1) ? 'warning' : 'success'; ?>" type="submit"><?php echo ((int)($tpl['is_active'] ?? 1) === 1) ? 'Disable' : 'Enable'; ?></button>
                                                                            </form>
                                                                            <form method="post" class="d-inline" onsubmit="return confirm('Delete this SMS template?');">
                                                                                <input type="hidden" name="action" value="delete_sms_template">
                                                                                <input type="hidden" name="template_id" value="<?php echo (int)$tpl['id']; ?>">
                                                                                <button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
                                                                            </form>
                                                                        </div>
                                                                    </td>
                                                                </tr>
                                                            <?php endforeach; ?>
                                                            <?php if (empty($smsTemplates)): ?><tr><td colspan="5" class="text-center text-muted">No SMS templates available.</td></tr><?php endif; ?>
                                                            </tbody>
                                                        </table>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                    <?php endif; ?>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

            case 'admin_pages':
                if (!isLoggedIn() || !isAdmin()) { redirect('?page=login'); }
                $pageContent = portalPageContent($pdo);
                $pageToggles = portalManagedContentToggleDefaults();
                $docTabs = portalManagedDocumentTabs();
                $docDefaults = portalDocumentContentDefaults();
                $registrationScopeRows = portalRegistrationPageScopeRows($pdo);
                if (!$registrationScopeRows) {
                    $registrationScopeRows = [[
                        'session_name' => '',
                        'faculty_id' => 0,
                        'department_id' => 0,
                        'programme_id' => 0,
                        'mode_of_study' => '',
                        'mode_of_study_custom' => '',
                        'is_enabled' => 'Yes',
                        'disabled_message' => '',
                    ]];
                }
                $pageSessions = [];
                $pageFaculties = [];
                $pageDepartments = [];
                $pageProgrammes = [];
                try { $pageSessions = $pdo->query("SELECT name FROM academic_sessions ORDER BY id DESC")->fetchAll(PDO::FETCH_COLUMN) ?: []; } catch (Throwable $e) { $pageSessions = []; }
                try { $pageFaculties = $pdo->query("SELECT id, name FROM faculties ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $pageFaculties = []; }
                try { $pageDepartments = $pdo->query("SELECT id, faculty_id, name FROM departments ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $pageDepartments = []; }
                try { $pageProgrammes = portalProgrammeSelectorOptions($pdo, 0, 0); } catch (Throwable $e) { $pageProgrammes = []; }
                ?>
                <div class="container-fluid">
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                                    <div>Pages & Document Content</div>
                                    <small class="text-muted">Manage homepage, about page, and generated document texts from one place</small>
                                </div>
                                <div class="card-body">
                                    <?php renderFlashes(); ?>
                                    <form method="post">
                                        <input type="hidden" name="action" value="admin_save_pages">

                                        <ul class="nav nav-tabs mb-3" role="tablist">
                                            <li class="nav-item" role="presentation">
                                                <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#pages_home" type="button" role="tab">Homepage</button>
                                            </li>
                                            <li class="nav-item" role="presentation">
                                                <button class="nav-link" data-bs-toggle="tab" data-bs-target="#pages_about" type="button" role="tab">About Page</button>
                                            </li>
                                            <li class="nav-item" role="presentation">
                                                <button class="nav-link" data-bs-toggle="tab" data-bs-target="#pages_registration" type="button" role="tab">Registration</button>
                                            </li>
                                            <?php foreach ($docTabs as $slug => $label): ?>
                                                <li class="nav-item" role="presentation">
                                                    <button class="nav-link" data-bs-toggle="tab" data-bs-target="#doc_<?php echo e($slug); ?>" type="button" role="tab"><?php echo e($label); ?></button>
                                                </li>
                                            <?php endforeach; ?>
                                        </ul>

                                        <div class="tab-content">
                                            <div class="tab-pane fade show active" id="pages_home" role="tabpanel">
                                                <div class="alert alert-light border mb-3 d-flex justify-content-between align-items-center flex-wrap gap-2">
                                                    <div>Control whether the public <strong>Homepage</strong> is visible and accessible on the portal.</div>
                                                    <div class="form-check form-switch fs-5 mb-0">
                                                        <input type="hidden" name="home_page_enabled_present" value="1">
                                                        <input class="form-check-input" type="checkbox" role="switch" id="home_page_enabled_toggle" name="home_page_enabled_toggle" value="1" <?php echo strtoupper(trim((string)($pageContent['home_page_enabled'] ?? 'Yes'))) !== 'NO' ? 'checked' : ''; ?>>
                                                        <label class="form-check-label" for="home_page_enabled_toggle">Enable Homepage</label>
                                                    </div>
                                                </div>
                                                <div class="row">
                                                    <div class="col-md-6 mb-3">
                                                        <label class="form-label">Banner Title</label>
                                                        <input type="text" class="form-control" name="home_banner_title" value="<?php echo e((string)($pageContent['home_banner_title'] ?? '')); ?>">
                                                    </div>
                                                    <div class="col-md-6 mb-3">
                                                        <label class="form-label">Banner Subtitle</label>
                                                        <input type="text" class="form-control" name="home_banner_subtitle" value="<?php echo e((string)($pageContent['home_banner_subtitle'] ?? '')); ?>">
                                                    </div>
                                                    <div class="col-md-12 mb-3">
                                                        <label class="form-label">Affiliation Text</label>
                                                        <input type="text" class="form-control" name="home_affiliation_text" value="<?php echo e((string)($pageContent['home_affiliation_text'] ?? '')); ?>">
                                                    </div>
                                                    <div class="col-md-12 mb-3">
                                                        <label class="form-label">Form Title</label>
                                                        <input type="text" class="form-control" name="home_form_title" value="<?php echo e((string)($pageContent['home_form_title'] ?? '')); ?>">
                                                    </div>
                                                    <div class="col-md-12 mb-3">
                                                        <label class="form-label">Instruction Text</label>
                                                        <textarea class="form-control" name="home_instruction_text" rows="4"><?php echo e((string)($pageContent['home_instruction_text'] ?? '')); ?></textarea>
                                                    </div>
                                                    <div class="col-md-3 mb-3">
                                                        <label class="form-label">Apply Button Text</label>
                                                        <input type="text" class="form-control" name="home_apply_button_text" value="<?php echo e((string)($pageContent['home_apply_button_text'] ?? '')); ?>">
                                                    </div>
                                                    <div class="col-md-3 mb-3">
                                                        <label class="form-label">Apply Button Link</label>
                                                        <input type="text" class="form-control" name="home_apply_button_link" value="<?php echo e((string)($pageContent['home_apply_button_link'] ?? '')); ?>">
                                                    </div>
                                                    <div class="col-md-3 mb-3">
                                                        <label class="form-label">Login Button Text</label>
                                                        <input type="text" class="form-control" name="home_login_button_text" value="<?php echo e((string)($pageContent['home_login_button_text'] ?? '')); ?>">
                                                    </div>
                                                    <div class="col-md-3 mb-3">
                                                        <label class="form-label">Login Button Link</label>
                                                        <input type="text" class="form-control" name="home_login_button_link" value="<?php echo e((string)($pageContent['home_login_button_link'] ?? '')); ?>">
                                                    </div>
                                                    <?php for ($i = 1; $i <= 3; $i++): ?>
                                                        <div class="col-md-4">
                                                            <div class="border rounded p-3 mb-3 h-100">
                                                                <div class="mb-2">
                                                                    <label class="form-label">Card <?php echo $i; ?> Icon</label>
                                                                    <input type="text" class="form-control" name="home_card_<?php echo $i; ?>_icon" value="<?php echo e((string)($pageContent['home_card_' . $i . '_icon'] ?? '')); ?>">
                                                                </div>
                                                                <div class="mb-2">
                                                                    <label class="form-label">Card <?php echo $i; ?> Title</label>
                                                                    <input type="text" class="form-control" name="home_card_<?php echo $i; ?>_title" value="<?php echo e((string)($pageContent['home_card_' . $i . '_title'] ?? '')); ?>">
                                                                </div>
                                                                <div>
                                                                    <label class="form-label">Card <?php echo $i; ?> Text</label>
                                                                    <textarea class="form-control" name="home_card_<?php echo $i; ?>_text" rows="4"><?php echo e((string)($pageContent['home_card_' . $i . '_text'] ?? '')); ?></textarea>
                                                                </div>
                                                            </div>
                                                        </div>
                                                    <?php endfor; ?>
                                                </div>
                                            </div>

                                            <div class="tab-pane fade" id="pages_about" role="tabpanel">
                                                <div class="alert alert-light border mb-3 d-flex justify-content-between align-items-center flex-wrap gap-2">
                                                    <div>Control whether the public <strong>About Page</strong> is visible and accessible on the portal.</div>
                                                    <div class="form-check form-switch fs-5 mb-0">
                                                        <input type="hidden" name="about_page_enabled_present" value="1">
                                                        <input class="form-check-input" type="checkbox" role="switch" id="about_page_enabled_toggle" name="about_page_enabled_toggle" value="1" <?php echo strtoupper(trim((string)($pageContent['about_page_enabled'] ?? 'Yes'))) !== 'NO' ? 'checked' : ''; ?>>
                                                        <label class="form-check-label" for="about_page_enabled_toggle">Enable About Page</label>
                                                    </div>
                                                </div>
                                                <div class="row">
                                                    <?php foreach ([
                                                        'about_page_title' => 'Page Title',
                                                        'about_intro_title' => 'Intro Title',
                                                        'about_programme_title' => 'Programme Title',
                                                        'about_affiliation_title' => 'Affiliation Title',
                                                        'about_benefits_title' => 'Benefits Title',
                                                        'about_requirements_title' => 'Requirements Title'
                                                    ] as $key => $label): ?>
                                                        <div class="col-md-6 mb-3">
                                                            <label class="form-label"><?php echo e($label); ?></label>
                                                            <input type="text" class="form-control" name="<?php echo e($key); ?>" value="<?php echo e((string)($pageContent[$key] ?? '')); ?>">
                                                        </div>
                                                    <?php endforeach; ?>
                                                    <?php foreach ([
                                                        'about_intro_text' => 'Intro Text',
                                                        'about_programme_text' => 'Programme Text',
                                                        'about_affiliation_text' => 'Affiliation Text',
                                                        'about_benefits_list' => 'Benefits List (one per line)',
                                                        'about_requirements_list' => 'Requirements List (one per line)'
                                                    ] as $key => $label): ?>
                                                        <div class="col-md-12 mb-3">
                                                            <label class="form-label"><?php echo e($label); ?></label>
                                                            <textarea class="form-control" name="<?php echo e($key); ?>" rows="4"><?php echo e((string)($pageContent[$key] ?? '')); ?></textarea>
                                                        </div>
                                                    <?php endforeach; ?>
                                                </div>
                                            </div>

                                            <div class="tab-pane fade" id="pages_registration" role="tabpanel">
                                                <div class="alert alert-light border mb-3">
                                                    Control whether the public student registration page is available globally and by scoped academic filters. Exact scoped rules override broad ones using this priority: <strong>Mode of Study → Programme → Department → Faculty → Session → Global</strong>.
                                                </div>
                                                <div class="row">
                                                    <div class="col-md-6 mb-3">
                                                        <label class="form-label d-block">Registration Form Status</label>
                                                        <div class="form-check form-switch fs-5">
                                                            <input type="hidden" name="registration_form_enabled_present" value="1">
                                                            <input class="form-check-input" type="checkbox" role="switch" id="registration_form_enabled_toggle" name="registration_form_enabled_toggle" value="1" <?php echo strtoupper(trim((string)($pageContent['registration_form_enabled'] ?? 'Yes'))) !== 'NO' ? 'checked' : ''; ?>>
                                                            <label class="form-check-label" for="registration_form_enabled_toggle">Enable Student Registration Form</label>
                                                        </div>
                                                        <div class="form-text">This is the global default. Scoped rules below can override it for a specific session, faculty, department, programme, or mode of study.</div>
                                                    </div>
                                                    <div class="col-md-12 mb-3">
                                                        <label class="form-label">Disabled Notice Message</label>
                                                        <textarea class="form-control" name="registration_form_disabled_message" rows="3"><?php echo e((string)($pageContent['registration_form_disabled_message'] ?? 'Student registration is currently disabled by the administrator. Please try again later.')); ?></textarea>
                                                        <div class="form-text">This message is shown to users whenever the effective registration page status is disabled.</div>
                                                    </div>
                                                    <div class="col-12">
                                                        <div class="border rounded p-3 bg-light-subtle">
                                                            <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-2">
                                                                <div>
                                                                    <h6 class="mb-1">Scoped Registration Visibility Rules</h6>
                                                                    <div class="small text-muted">Add rules to enable or disable the public registration page by session, faculty, department, programme, and mode of study. Leave broad fields empty only when a higher-level scope is enough.</div>
                                                                </div>
                                                                <button type="button" class="btn btn-sm btn-outline-primary" id="addRegistrationScopeRow"><i class="bi bi-plus-circle"></i> Add Rule</button>
                                                            </div>
                                                            <div class="table-responsive">
                                                                <table class="table table-bordered align-middle mb-0" id="registrationScopeTable">
                                                                    <thead class="table-light">
                                                                        <tr>
                                                                            <th style="min-width:140px;">Session</th>
                                                                            <th style="min-width:170px;">Faculty</th>
                                                                            <th style="min-width:190px;">Department</th>
                                                                            <th style="min-width:210px;">Programme</th>
                                                                            <th style="min-width:220px;">Mode of Study</th>
                                                                            <th style="min-width:130px;">Status</th>
                                                                            <th style="min-width:260px;">Disabled Message</th>
                                                                            <th style="width:80px;">Action</th>
                                                                        </tr>
                                                                    </thead>
                                                                    <tbody>
                                                                    <?php foreach ($registrationScopeRows as $scopeRow): ?>
                                                                        <tr>
                                                                            <td>
                                                                                <select class="form-select form-select-sm" name="registration_scope_session_name[]">
                                                                                    <option value="">All Sessions</option>
                                                                                    <?php foreach ($pageSessions as $sessionName): ?>
                                                                                        <option value="<?php echo e((string)$sessionName); ?>" <?php echo ((string)($scopeRow['session_name'] ?? '') === (string)$sessionName) ? 'selected' : ''; ?>><?php echo e((string)$sessionName); ?></option>
                                                                                    <?php endforeach; ?>
                                                                                </select>
                                                                            </td>
                                                                            <td>
                                                                                <select class="form-select form-select-sm" name="registration_scope_faculty_id[]">
                                                                                    <option value="0">All Faculties</option>
                                                                                    <?php foreach ($pageFaculties as $facultyRow): ?>
                                                                                        <option value="<?php echo (int)($facultyRow['id'] ?? 0); ?>" <?php echo ((int)($scopeRow['faculty_id'] ?? 0) === (int)($facultyRow['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e((string)($facultyRow['name'] ?? '')); ?></option>
                                                                                    <?php endforeach; ?>
                                                                                </select>
                                                                            </td>
                                                                            <td>
                                                                                <select class="form-select form-select-sm" name="registration_scope_department_id[]">
                                                                                    <option value="0">All Departments</option>
                                                                                    <?php foreach ($pageDepartments as $departmentRow): ?>
                                                                                        <option value="<?php echo (int)($departmentRow['id'] ?? 0); ?>" <?php echo ((int)($scopeRow['department_id'] ?? 0) === (int)($departmentRow['id'] ?? 0)) ? 'selected' : ''; ?>><?php echo e((string)($departmentRow['name'] ?? '')); ?></option>
                                                                                    <?php endforeach; ?>
                                                                                </select>
                                                                            </td>
                                                                            <td>
                                                                                <?php $scopeProgrammeId = (int)($scopeRow['programme_id'] ?? 0); ?>
                                                                                <select class="form-select form-select-sm" name="registration_scope_programme_id[]">
                                                                                    <option value="0">All Programmes</option>
                                                                                    <?php foreach ($pageProgrammes as $programmeRow): ?>
                                                                                        <?php $programmeOptionId = (int)($programmeRow['id'] ?? 0); $programmeIsSelected = portalProgrammeOptionSelected($pdo, $scopeProgrammeId, $programmeOptionId); ?>
                                                                                        <option value="<?php echo $programmeOptionId; ?>" <?php echo $programmeIsSelected ? 'selected' : ''; ?>><?php echo e((string)($programmeRow['name'] ?? '')); ?></option>
                                                                                    <?php endforeach; ?>
                                                                                </select>
                                                                            </td>
                                                                            <td>
                                                                                <?php $scopeModeType = trim((string)($scopeRow['mode_of_study'] ?? '')); $scopeModeCustom = trim((string)($scopeRow['mode_of_study_custom'] ?? '')); if ($scopeModeType === '' && $scopeModeCustom !== '') { $scopeModeType = 'Others'; } if ($scopeModeType !== '' && !in_array($scopeModeType, ['Full-Time','Part-Time','Others'], true)) { if ($scopeModeCustom === '') { $scopeModeCustom = $scopeModeType; } $scopeModeType = 'Others'; } ?>
                                                                                <select class="form-select form-select-sm registration-scope-mode-select" name="registration_scope_mode_of_study[]">
                                                                                    <option value="">All Modes of Study</option>
                                                                                    <option value="Full-Time" <?php echo ($scopeModeType === 'Full-Time') ? 'selected' : ''; ?>>Full-Time</option>
                                                                                    <option value="Part-Time" <?php echo ($scopeModeType === 'Part-Time') ? 'selected' : ''; ?>>Part-Time</option>
                                                                                    <option value="Others" <?php echo ($scopeModeType === 'Others') ? 'selected' : ''; ?>>Others</option>
                                                                                </select>
                                                                                <input type="text" class="form-control form-control-sm mt-1 registration-scope-mode-custom" name="registration_scope_mode_of_study_custom[]" value="<?php echo e($scopeModeCustom); ?>" placeholder="Enter custom mode of study" <?php echo ($scopeModeType === 'Others') ? '' : 'style="display:none;"'; ?> <?php echo ($scopeModeType === 'Others') ? 'required' : ''; ?>>
                                                                            </td>
                                                                            <td>
                                                                                <select class="form-select form-select-sm" name="registration_scope_is_enabled[]">
                                                                                    <option value="Yes" <?php echo strtoupper(trim((string)($scopeRow['is_enabled'] ?? 'Yes'))) !== 'NO' ? 'selected' : ''; ?>>Enabled</option>
                                                                                    <option value="No" <?php echo strtoupper(trim((string)($scopeRow['is_enabled'] ?? 'Yes'))) === 'NO' ? 'selected' : ''; ?>>Disabled</option>
                                                                                </select>
                                                                            </td>
                                                                            <td>
                                                                                <input type="text" class="form-control form-control-sm" name="registration_scope_disabled_message[]" value="<?php echo e((string)($scopeRow['disabled_message'] ?? '')); ?>" placeholder="Optional scoped disabled message">
                                                                            </td>
                                                                            <td class="text-center">
                                                                                <button type="button" class="btn btn-sm btn-outline-danger registration-scope-remove-row"><i class="bi bi-trash"></i></button>
                                                                            </td>
                                                                        </tr>
                                                                    <?php endforeach; ?>
                                                                    </tbody>
                                                                </table>
                                                            </div>
                                                            <div class="small text-muted mt-2">Tip: A mode-of-study rule is the most specific. If no scoped rule matches, the system falls back to the global Registration Form Status above.</div>
                                                        </div>
                                                    </div>
                                                </div>
                                                <template id="registrationScopeRowTemplate">
                                                    <tr>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="registration_scope_session_name[]">
                                                                <option value="">All Sessions</option>
                                                                <?php foreach ($pageSessions as $sessionName): ?>
                                                                    <option value="<?php echo e((string)$sessionName); ?>"><?php echo e((string)$sessionName); ?></option>
                                                                <?php endforeach; ?>
                                                            </select>
                                                        </td>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="registration_scope_faculty_id[]">
                                                                <option value="0">All Faculties</option>
                                                                <?php foreach ($pageFaculties as $facultyRow): ?>
                                                                    <option value="<?php echo (int)($facultyRow['id'] ?? 0); ?>"><?php echo e((string)($facultyRow['name'] ?? '')); ?></option>
                                                                <?php endforeach; ?>
                                                            </select>
                                                        </td>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="registration_scope_department_id[]">
                                                                <option value="0">All Departments</option>
                                                                <?php foreach ($pageDepartments as $departmentRow): ?>
                                                                    <option value="<?php echo (int)($departmentRow['id'] ?? 0); ?>"><?php echo e((string)($departmentRow['name'] ?? '')); ?></option>
                                                                <?php endforeach; ?>
                                                            </select>
                                                        </td>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="registration_scope_programme_id[]">
                                                                <option value="0">All Programmes</option>
                                                                <?php foreach ($pageProgrammes as $programmeRow): ?>
                                                                    <option value="<?php echo (int)($programmeRow['id'] ?? 0); ?>"><?php echo e((string)($programmeRow['name'] ?? '')); ?></option>
                                                                <?php endforeach; ?>
                                                            </select>
                                                        </td>
                                                        <td>
                                                            <select class="form-select form-select-sm registration-scope-mode-select" name="registration_scope_mode_of_study[]">
                                                                <option value="">All Modes of Study</option>
                                                                <option value="Full-Time">Full-Time</option>
                                                                <option value="Part-Time">Part-Time</option>
                                                                <option value="Others">Others</option>
                                                            </select>
                                                            <input type="text" class="form-control form-control-sm mt-1 registration-scope-mode-custom" name="registration_scope_mode_of_study_custom[]" value="" placeholder="Enter custom mode of study" style="display:none;">
                                                        </td>
                                                        <td>
                                                            <select class="form-select form-select-sm" name="registration_scope_is_enabled[]">
                                                                <option value="Yes" selected>Enabled</option>
                                                                <option value="No">Disabled</option>
                                                            </select>
                                                        </td>
                                                        <td>
                                                            <input type="text" class="form-control form-control-sm" name="registration_scope_disabled_message[]" value="" placeholder="Optional scoped disabled message">
                                                        </td>
                                                        <td class="text-center">
                                                            <button type="button" class="btn btn-sm btn-outline-danger registration-scope-remove-row"><i class="bi bi-trash"></i></button>
                                                        </td>
                                                    </tr>
                                                </template>
                                                <script>
                                                (function(){
                                                    var addBtn = document.getElementById('addRegistrationScopeRow');
                                                    var table = document.getElementById('registrationScopeTable');
                                                    var tpl = document.getElementById('registrationScopeRowTemplate');
                                                    function syncModeInput(row){
                                                        if (!row) return;
                                                        var select = row.querySelector('.registration-scope-mode-select');
                                                        var custom = row.querySelector('.registration-scope-mode-custom');
                                                        if (!select || !custom) return;
                                                        var show = String(select.value || '') === 'Others';
                                                        custom.style.display = show ? '' : 'none';
                                                        custom.required = show;
                                                        if (!show) custom.value = '';
                                                    }
                                                    function bindModeInput(row){
                                                        if (!row) return;
                                                        var select = row.querySelector('.registration-scope-mode-select');
                                                        if (!select) return;
                                                        if (select.dataset.boundModeScope !== '1') {
                                                            select.dataset.boundModeScope = '1';
                                                            select.addEventListener('change', function(){ syncModeInput(row); });
                                                        }
                                                        syncModeInput(row);
                                                    }
                                                    if (table) {
                                                        Array.prototype.forEach.call(table.querySelectorAll('tbody tr'), bindModeInput);
                                                    }
                                                    if (addBtn && table && tpl && !addBtn.dataset.boundRegistrationScope) {
                                                        addBtn.dataset.boundRegistrationScope = '1';
                                                        addBtn.addEventListener('click', function(){
                                                            var tbody = table.querySelector('tbody');
                                                            if (!tbody) return;
                                                            var row = tpl.content.firstElementChild.cloneNode(true);
                                                            tbody.appendChild(row);
                                                            bindModeInput(row);
                                                        });
                                                        table.addEventListener('click', function(ev){
                                                            var btn = ev.target.closest('.registration-scope-remove-row');
                                                            if (!btn) return;
                                                            var tbody = table.querySelector('tbody');
                                                            var rows = tbody ? tbody.querySelectorAll('tr') : [];
                                                            if (rows.length <= 1) {
                                                                Array.prototype.forEach.call(btn.closest('tr').querySelectorAll('input'), function(el){ el.value = ''; });
                                                                Array.prototype.forEach.call(btn.closest('tr').querySelectorAll('select'), function(el){ el.selectedIndex = 0; });
                                                                bindModeInput(btn.closest('tr'));
                                                                return;
                                                            }
                                                            btn.closest('tr').remove();
                                                        });
                                                    }
                                                })();
                                                </script>
                                            </div>

                                            <?php foreach ($docTabs as $slug => $label): $docContent = portalDocumentContent($pdo, $slug); ?>
                                                <div class="tab-pane fade" id="doc_<?php echo e($slug); ?>" role="tabpanel">
                                                    <?php $docEnabledSettingKey = portalManagedContentSettingKey($slug); ?>
                                                    <div class="alert alert-light border mb-3 d-flex justify-content-between align-items-center flex-wrap gap-2">
                                                        <div>Edit all visible text content for <strong><?php echo e($label); ?></strong>. Changes save to the database and are reused anywhere this document is generated, viewed, printed, or downloaded.</div>
                                                        <?php if ($docEnabledSettingKey !== ''): ?>
                                                        <div class="form-check form-switch fs-5 mb-0">
                                                            <input type="hidden" name="<?php echo e($docEnabledSettingKey); ?>_present" value="1">
                                                            <input class="form-check-input" type="checkbox" role="switch" id="<?php echo e($docEnabledSettingKey); ?>_toggle" name="<?php echo e($docEnabledSettingKey); ?>_toggle" value="1" <?php echo strtoupper(trim((string)($pageContent[$docEnabledSettingKey] ?? 'Yes'))) !== 'NO' ? 'checked' : ''; ?>>
                                                            <label class="form-check-label" for="<?php echo e($docEnabledSettingKey); ?>_toggle">Enable <?php echo e($label); ?></label>
                                                        </div>
                                                        <?php endif; ?>
                                                    </div>
                                                    <div class="row">
                                                        <div class="col-md-6 mb-3">
                                                            <label class="form-label">Tab / Document Title</label>
                                                            <input type="text" class="form-control" name="doc_<?php echo e($slug); ?>_title" value="<?php echo e((string)($docContent['title'] ?? '')); ?>">
                                                        </div>
                                                        <div class="col-md-6 mb-3">
                                                            <label class="form-label">Heading</label>
                                                            <input type="text" class="form-control" name="doc_<?php echo e($slug); ?>_heading" value="<?php echo e((string)($docContent['heading'] ?? '')); ?>">
                                                        </div>
                                                        <div class="col-md-12 mb-3">
                                                            <label class="form-label">Subheading</label>
                                                            <input type="text" class="form-control" name="doc_<?php echo e($slug); ?>_subheading" value="<?php echo e((string)($docContent['subheading'] ?? '')); ?>">
                                                        </div>
                                                        <div class="col-md-12 mb-3">
                                                            <label class="form-label">Body Text</label>
                                                            <textarea class="form-control" name="doc_<?php echo e($slug); ?>_body_text" rows="5"><?php echo e((string)($docContent['body_text'] ?? '')); ?></textarea>
                                                        </div>
                                                        <div class="col-md-12 mb-3">
                                                            <label class="form-label">Footer Text</label>
                                                            <textarea class="form-control" name="doc_<?php echo e($slug); ?>_footer_text" rows="3"><?php echo e((string)($docContent['footer_text'] ?? '')); ?></textarea>
                                                        </div>
                                                        <div class="col-md-12 mb-3">
                                                            <label class="form-label">Labels</label>
                                                            <textarea class="form-control" name="doc_<?php echo e($slug); ?>_labels_text" rows="4"><?php echo e((string)($docContent['labels_text'] ?? '')); ?></textarea>
                                                            <div class="form-text">Use one label per line for headings, labels, and short captions that appear on the page.</div>
                                                        </div>
                                                        <div class="col-md-12 mb-3">
                                                            <label class="form-label">Notice / Instructions</label>
                                                            <textarea class="form-control" name="doc_<?php echo e($slug); ?>_notice_text" rows="4"><?php echo e((string)($docContent['notice_text'] ?? '')); ?></textarea>
                                                        </div>
                                                        <div class="col-md-6 mb-3">
                                                            <label class="form-label">Signature / Title Label</label>
                                                            <input type="text" class="form-control" name="doc_<?php echo e($slug); ?>_signature_title" value="<?php echo e((string)($docContent['signature_title'] ?? '')); ?>">
                                                        </div>
                                                        <div class="col-md-6 mb-3">
                                                            <label class="form-label">Signature / Officer Name</label>
                                                            <input type="text" class="form-control" name="doc_<?php echo e($slug); ?>_signature_name" value="<?php echo e((string)($docContent['signature_name'] ?? '')); ?>">
                                                        </div>
                                                    </div>
                                                </div>
                                            <?php endforeach; ?>
                                        </div>

                                        <div class="mt-3 d-flex justify-content-end">
                                            <button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> Save Pages Content</button>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;

            case 'feature_toggles':
                if (!isLoggedIn() || !isAdmin()) { redirect('?page=login'); }
                requireStaffPermission($pdo, 'staff_roles_manage');

                $selectedRole = trim((string)($_GET['role'] ?? 'Admin'));
                if ($selectedRole === '') $selectedRole = 'Admin';

                // Save staff role toggles
                if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_feature_toggles') {
                    $targetType = trim((string)($_POST['target_type'] ?? 'staff')); // staff|student
                    if ($targetType === 'student') {
                        $keys = $_POST['student_keys'] ?? [];
                        $allKeys = featureToggleCatalogStudent();
                        foreach ($allKeys as $k => $_label) {
                            $enabled = isset($keys[$k]) ? 1 : 0;
                            try {
                                $pdo->prepare("INSERT INTO student_menu_toggles (menu_key, enabled) VALUES (?,?)
                                    ON DUPLICATE KEY UPDATE enabled=VALUES(enabled)")->execute([$k,$enabled]);
                            } catch (Throwable $e) { /* ignore */ }
                        }
                        flash('success','Student feature toggles saved.');
                        redirect('?page=feature_toggles&tab=students');
                    } else {
                        $role = trim((string)($_POST['role_name'] ?? 'Admin'));
                        if ($role === '') $role = 'Admin';
                        $keys = $_POST['role_keys'] ?? [];
                        $allKeys = featureToggleCatalogStaff();
                        foreach ($allKeys as $k => $_label) {
                            $enabled = isset($keys[$k]) ? 1 : 0;
                            try {
                                $pdo->prepare("INSERT INTO staff_menu_toggles (role_name, menu_key, enabled) VALUES (?,?,?)
                                    ON DUPLICATE KEY UPDATE enabled=VALUES(enabled)")->execute([$role,$k,$enabled]);
                            } catch (Throwable $e) { /* ignore */ }
                        }
                        flash('success','Role feature toggles saved for '.$role.'.');
                        redirect('?page=feature_toggles&role='.urlencode($role));
                    }
                }

                // Catalog helpers (keep inside file)
                if (!function_exists('featureToggleCatalogStaff')) {
                    function featureToggleCatalogStaff(): array {
                        return [
                            'menu_staff_users' => 'Staff Users',
                            'menu_staff_roles' => 'Roles & Permissions',
                            'menu_results_manager' => 'Manage Results',
                            'menu_results_approvals' => 'Result Approvals',
                            'menu_download_scoresheet' => 'Download Scoresheet',
                            'menu_numbering_settings' => 'Admission / Matric Numbering',
                            'menu_notification_settings' => 'Email/SMS Settings',
                            'menu_feature_toggles' => 'Feature Toggles',
            'menu_pages' => 'Pages',
                        ];
                    }
                }
                if (!function_exists('featureToggleCatalogStudent')) {
                    function featureToggleCatalogStudent(): array {
                        return [
                            'menu_student_dashboard' => 'Dashboard',
                            'menu_student_application_form' => 'Application Form',
                            'menu_student_course_registration' => 'Course Registration',
                            'menu_student_admission_letter' => 'Admission Letter',
                            'menu_student_id_card' => 'Student ID Card',
                            'menu_student_payments' => 'Payments Overview',
                            'menu_student_wallet' => 'Wallet',
                            'menu_student_registration_fee' => 'Registration Fee',
                            'menu_student_acceptance_fee' => 'Acceptance Fee',
                            'menu_student_hostel_fee' => 'Hostel Fee',
                            'menu_student_school_fee' => 'School Fee',
                            'menu_student_payment_history' => 'Payment History',
                            'menu_student_results' => 'Results / CGPA',
                            'menu_student_transcript' => 'Transcripts',
                            'menu_student_library' => 'Library',
                            'menu_student_lms' => 'LMS',
                            'menu_student_cbt' => 'CBT',
                            'menu_student_messages' => 'Messages',
                            'menu_student_notifications' => 'Notifications',
                            'menu_student_profile' => 'Profile',
            'menu_student_clearance_form' => 'Clearance Form',
                        ];
                    }
                }

                // Load roles
                $roles = [];
                try {
                    $roles = $pdo->query("SELECT role_name FROM staff_roles ORDER BY role_name ASC")->fetchAll(PDO::FETCH_COLUMN);
                } catch (Throwable $e) { $roles = ['Admin']; }

                // Load current toggles for selected role
                $staffToggles = [];
                try {
                    $st = $pdo->prepare("SELECT menu_key, enabled FROM staff_menu_toggles WHERE role_name = ?");
                    $st->execute([$selectedRole]);
                    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $r) $staffToggles[$r['menu_key']] = (int)$r['enabled'];
                } catch (Throwable $e) { /* ignore */ }

                // Load student toggles
                $studentToggles = [];
                try {
                    $st = $pdo->query("SELECT menu_key, enabled FROM student_menu_toggles");
                    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $r) $studentToggles[$r['menu_key']] = (int)$r['enabled'];
                } catch (Throwable $e) { /* ignore */ }

                $tab = trim((string)($_GET['tab'] ?? 'staff'));
                if ($tab !== 'students') $tab = 'staff';

                ?>
                <div class="container-fluid">
                    <div class="row">
                        <div class="col-md-12">
                            <div class="card">
                                <div class="card-header d-flex justify-content-between align-items-center">
                                    <div>Feature Toggles (Role-based + Students)</div>
                                    <small class="text-muted">Disabled modules will not appear in menus</small>
                                </div>
                                <div class="card-body">
                                    <ul class="nav nav-tabs mb-3">
                                        <li class="nav-item">
                                            <a class="nav-link <?php echo $tab==='staff'?'active':''; ?>" href="?page=feature_toggles&role=<?php echo urlencode($selectedRole); ?>">Staff Roles</a>
                                        </li>
                                        <li class="nav-item">
                                            <a class="nav-link <?php echo $tab==='students'?'active':''; ?>" href="?page=feature_toggles&tab=students">Students</a>
                                        </li>
                                    </ul>

                                    <?php if ($tab === 'staff'): ?>
                                        <form class="row g-2 mb-3" method="get">
                                            <input type="hidden" name="page" value="feature_toggles">
                                            <div class="col-md-4">
                                                <label class="form-label">Select Role</label>
                                                <select name="role" class="form-select" onchange="this.form.submit()">
                                                    <?php foreach ($roles as $r): ?>
                                                        <option value="<?php echo htmlspecialchars($r); ?>" <?php echo $r===$selectedRole?'selected':''; ?>>
                                                            <?php echo htmlspecialchars($r); ?>
                                                        </option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                        </form>

                                        <form method="post">
                                            <input type="hidden" name="action" value="save_feature_toggles">
                                            <input type="hidden" name="target_type" value="staff">
                                            <input type="hidden" name="role_name" value="<?php echo htmlspecialchars($selectedRole); ?>">

                                            <div class="row">
                                                <?php foreach (featureToggleCatalogStaff() as $k => $label): ?>
                                                    <?php $enabled = isset($staffToggles[$k]) ? (int)$staffToggles[$k] : 1; ?>
                                                    <div class="col-md-4 mb-2">
                                                        <div class="form-check">
                                                            <input class="form-check-input" type="checkbox" id="<?php echo htmlspecialchars($k); ?>" name="role_keys[<?php echo htmlspecialchars($k); ?>]" value="1" <?php echo $enabled===1?'checked':''; ?>>
                                                            <label class="form-check-label" for="<?php echo htmlspecialchars($k); ?>">
                                                                <?php echo htmlspecialchars($label); ?>
                                                            </label>
                                                        </div>
                                                    </div>
                                                <?php endforeach; ?>
                                            </div>

                                            <button class="btn btn-primary mt-2"><i class="bi bi-save"></i> Save Role Toggles</button>
                                        </form>
                                    <?php else: ?>
                                        <form method="post">
                                            <input type="hidden" name="action" value="save_feature_toggles">
                                            <input type="hidden" name="target_type" value="student">

                                            <div class="row">
                                                <?php foreach (featureToggleCatalogStudent() as $k => $label): ?>
                                                    <?php $enabled = isset($studentToggles[$k]) ? (int)$studentToggles[$k] : 1; ?>
                                                    <div class="col-md-4 mb-2">
                                                        <div class="form-check">
                                                            <input class="form-check-input" type="checkbox" id="<?php echo htmlspecialchars($k); ?>" name="student_keys[<?php echo htmlspecialchars($k); ?>]" value="1" <?php echo $enabled===1?'checked':''; ?>>
                                                            <label class="form-check-label" for="<?php echo htmlspecialchars($k); ?>">
                                                                <?php echo htmlspecialchars($label); ?>
                                                            </label>
                                                        </div>
                                                    </div>
                                                <?php endforeach; ?>
                                            </div>

                                            <button class="btn btn-primary mt-2"><i class="bi bi-save"></i> Save Student Toggles</button>
                                        </form>
                                    <?php endif; ?>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <?php
                break;



            case 'student_lms':
                if (!isStudent()) { redirect('?page=login'); }
                ensureLmsSchema($pdo);
                $studentId = (int)($_SESSION['student_id'] ?? ($_SESSION['user_id'] ?? 0));
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
                if (!$student) { flash('danger','Student record not found.'); redirect('?page=student_dashboard'); }
                $tab = trim((string)($_GET['tab'] ?? 'materials'));
                $tab = in_array($tab, ['materials','assignments'], true) ? $tab : 'materials';
                $studentSummary = portalDashboardSummary($pdo, $student);
                
                $studentView = trim((string)($_GET['view'] ?? ''));
                $studentPrint = trim((string)($_GET['print'] ?? ''));
                if ($attemptId > 0 && ($studentView === 'answer_review' || in_array($studentPrint, ['result_slip','certificate'], true))) {
                    $attemptContext = portalCbtAttemptContext($pdo, $attemptId);
                    if (!$attemptContext || (int)($attemptContext['student_id'] ?? 0) !== $studentId) {
                        flash('danger', 'The selected CBT record could not be found.');
                        redirect('?page=student_cbt&tab=history');
                    }
                    if ($studentPrint === 'result_slip') {
                        if ((int)($attemptContext['results_published'] ?? 0) !== 1 && strtolower((string)($attemptContext['result_status'] ?? 'pending')) !== 'published') {
                            flash('danger', 'Result slip is not yet available for this exam.');
                            redirect('?page=student_cbt&tab=history');
                        }
                        portalCbtRenderResultSlipPrintable($pdo, $attemptContext, true);
                        break;
                    }
                    if ($studentPrint === 'certificate') {
                        if (!portalManagedContentEnabled($pdo, 'certificate')) {
                            flash('danger', portalManagedContentDisabledMessage($pdo, 'certificate'));
                            redirect('?page=student_cbt&tab=history');
                        }
                        if ((int)($attemptContext['results_published'] ?? 0) !== 1 || strtolower((string)($attemptContext['pass_fail_status'] ?? '')) !== 'passed') {
                            flash('danger', 'Certificate is only available for published passed results.');
                            redirect('?page=student_cbt&tab=history');
                        }
                        portalCbtRenderCertificatePrintable($pdo, $attemptContext, true);
                        break;
                    }
                    if ($studentView === 'answer_review') {
                        if ((int)($attemptContext['results_published'] ?? 0) !== 1 || empty($attemptContext['answer_review_enabled'])) {
                            flash('danger', 'Answer review is disabled for this exam or the result is not yet published.');
                            redirect('?page=student_cbt&tab=history');
                        }
                        portalCbtRenderAnswerReviewPage($pdo, $attemptContext, true);
                        break;
                    }
                }

                $filterFacultyId = (int)($student['faculty_id'] ?? 0);
                $filterDepartmentId = (int)($student['department_id'] ?? 0);
                $filterProgrammeId = (int)($student['programme_id'] ?? 0);
                $filterLevel = trim((string)($student['level'] ?? ''));
                $filterSession = trim((string)($studentSummary['session_name'] ?? portalCurrentAcademicSessionName($pdo)));
                $filterSemester = trim((string)($studentSummary['semester'] ?? portalCurrentSemesterName($pdo)));
                $lmsMaterials = [];
                $lmsAssignments = [];
                try {
                    $st = $pdo->prepare("SELECT * FROM lms_materials
                        WHERE (faculty_id IS NULL OR faculty_id=0 OR faculty_id=?)
                          AND (department_id IS NULL OR department_id=0 OR department_id=?)
                          AND (programme_id IS NULL OR programme_id=0 OR programme_id=?)
                          AND (level IS NULL OR level='' OR level=?)
                          AND (session_name IS NULL OR session_name='' OR session_name=?)
                          AND (semester IS NULL OR semester='' OR semester=?)
                        ORDER BY id DESC LIMIT 200");
                    $st->execute([$filterFacultyId, $filterDepartmentId, $filterProgrammeId, $filterLevel, $filterSession, $filterSemester]);
                    $lmsMaterials = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) { $lmsMaterials = []; }
                try {
                    $st = $pdo->prepare("SELECT * FROM lms_assignments
                        WHERE (faculty_id IS NULL OR faculty_id=0 OR faculty_id=?)
                          AND (department_id IS NULL OR department_id=0 OR department_id=?)
                          AND (programme_id IS NULL OR programme_id=0 OR programme_id=?)
                          AND (level IS NULL OR level='' OR level=?)
                          AND (session_name IS NULL OR session_name='' OR session_name=?)
                          AND (semester IS NULL OR semester='' OR semester=?)
                        ORDER BY COALESCE(due_date, '9999-12-31') ASC, id DESC LIMIT 200");
                    $st->execute([$filterFacultyId, $filterDepartmentId, $filterProgrammeId, $filterLevel, $filterSession, $filterSemester]);
                    $lmsAssignments = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) { $lmsAssignments = []; }
                ?>
                <div class="card">
                    <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                        <div>
                            <h4 class="mb-0">Learning Management System</h4>
                            <div class="small text-muted">Session: <?php echo e($filterSession ?: 'Current'); ?> • Semester: <?php echo e($filterSemester ?: 'Current'); ?> • Level: <?php echo e($filterLevel ?: 'N/A'); ?></div>
                        </div>
                        <div class="d-flex gap-2">
                            <a class="btn btn-outline-primary btn-sm <?php echo $tab==='materials' ? 'active' : ''; ?>" href="?page=student_lms&tab=materials">Materials</a>
                            <a class="btn btn-outline-primary btn-sm <?php echo $tab==='assignments' ? 'active' : ''; ?>" href="?page=student_lms&tab=assignments">Assignments</a>
                        </div>
                    </div>
                    <div class="card-body">
                        <?php if ($tab === 'materials'): ?>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle">
                                    <thead><tr><th>Title</th><th>Course</th><th>Session</th><th>Semester</th><th>File</th></tr></thead>
                                    <tbody>
                                    <?php foreach ($lmsMaterials as $row): ?>
                                        <tr>
                                            <td>
                                                <div class="fw-semibold"><?php echo e((string)($row['title'] ?? 'Untitled Material')); ?></div>
                                                <?php if (trim((string)($row['description'] ?? '')) !== ''): ?>
                                                    <div class="small text-muted"><?php echo nl2br(e((string)$row['description'])); ?></div>
                                                <?php endif; ?>
                                            </td>
                                            <td><?php echo e((string)($row['course_code'] ?? '')); ?></td>
                                            <td><?php echo e((string)($row['session_name'] ?? '')); ?></td>
                                            <td><?php echo e((string)($row['semester'] ?? '')); ?></td>
                                            <td>
                                                <?php if (!empty($row['file_path'])): ?>
                                                    <a class="btn btn-sm btn-outline-secondary" href="<?php echo e((string)$row['file_path']); ?>" target="_blank" rel="noopener">Download</a>
                                                <?php else: ?>
                                                    <span class="text-muted">No file</span>
                                                <?php endif; ?>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$lmsMaterials): ?><tr><td colspan="5" class="text-muted text-center">No LMS materials are available for your current session, semester, and level yet.</td></tr><?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        <?php else: ?>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle">
                                    <thead><tr><th>Assignment</th><th>Due Date</th><th>Session</th><th>Semester</th><th>Attachment</th></tr></thead>
                                    <tbody>
                                    <?php foreach ($lmsAssignments as $row): ?>
                                        <tr>
                                            <td>
                                                <div class="fw-semibold"><?php echo e((string)($row['title'] ?? 'Untitled Assignment')); ?></div>
                                                <?php if (trim((string)($row['instructions'] ?? '')) !== ''): ?>
                                                    <div class="small text-muted"><?php echo nl2br(e((string)$row['instructions'])); ?></div>
                                                <?php endif; ?>
                                            </td>
                                            <td><?php echo !empty($row['due_date']) ? e((string)$row['due_date']) : '<span class="text-muted">Not set</span>'; ?></td>
                                            <td><?php echo e((string)($row['session_name'] ?? '')); ?></td>
                                            <td><?php echo e((string)($row['semester'] ?? '')); ?></td>
                                            <td>
                                                <?php if (!empty($row['attachment_path'])): ?>
                                                    <a class="btn btn-sm btn-outline-secondary" href="<?php echo e((string)$row['attachment_path']); ?>" target="_blank" rel="noopener">Open</a>
                                                <?php else: ?>
                                                    <span class="text-muted">No attachment</span>
                                                <?php endif; ?>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$lmsAssignments): ?><tr><td colspan="5" class="text-muted text-center">No assignments are available for your current session, semester, and level yet.</td></tr><?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        <?php endif; ?>
                    </div>
                </div>
                <?php
                break;

            case 'student_library':
                if (!isStudent()) { redirect('?page=login'); }
                ensureLibrarySchema($pdo);
                $studentId = (int)($_SESSION['student_id'] ?? ($_SESSION['user_id'] ?? 0));
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
                if (!$student) { flash('danger','Student record not found.'); redirect('?page=student_dashboard'); }
                $tab = trim((string)($_GET['tab'] ?? 'browse'));
                $tab = in_array($tab, ['browse','loans'], true) ? $tab : 'browse';
                if ($_SERVER['REQUEST_METHOD'] === 'POST' && (($_POST['action'] ?? '') === 'request_library_book')) {
                    $bookId = (int)($_POST['book_id'] ?? 0);
                    if ($bookId > 0) {
                        try {
                            $st = $pdo->prepare("SELECT * FROM library_books WHERE id=? LIMIT 1");
                            $st->execute([$bookId]);
                            $book = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                            if (!$book) {
                                flash('danger', 'Selected book was not found.');
                            } elseif ((int)($book['copies_available'] ?? 0) <= 0) {
                                flash('danger', 'This book is currently unavailable.');
                            } else {
                                $st = $pdo->prepare("SELECT COUNT(*) FROM library_loans WHERE book_id=? AND student_id=? AND status IN ('requested','issued','approved','borrowed')");
                                $st->execute([$bookId, $studentId]);
                                $exists = (int)($st->fetchColumn() ?: 0);
                                if ($exists > 0) {
                                    flash('info', 'You already have an active request or loan for this book.');
                                } else {
                                    $pdo->prepare("INSERT INTO library_loans (book_id, student_id, status) VALUES (?, ?, 'requested')")->execute([$bookId, $studentId]);
                                    flash('success', 'Book request submitted successfully.');
                                }
                            }
                        } catch (Throwable $e) {
                            flash('danger', 'Unable to submit library request right now.');
                        }
                    }
                    redirect('?page=student_library&tab=browse');
                }
                $bookSearch = trim((string)($_GET['q'] ?? ''));
                $libraryBooks = [];
                $libraryLoans = [];
                try {
                    if ($bookSearch !== '') {
                        $st = $pdo->prepare("SELECT * FROM library_books WHERE title LIKE ? OR author LIKE ? OR isbn LIKE ? OR category LIKE ? ORDER BY title ASC LIMIT 300");
                        $like = '%' . $bookSearch . '%';
                        $st->execute([$like, $like, $like, $like]);
                    } else {
                        $st = $pdo->query("SELECT * FROM library_books ORDER BY title ASC LIMIT 300");
                    }
                    $libraryBooks = $st ? ($st->fetchAll(PDO::FETCH_ASSOC) ?: []) : [];
                } catch (Throwable $e) { $libraryBooks = []; }
                try {
                    $st = $pdo->prepare("SELECT l.*, b.title AS book_title, b.author AS book_author, b.category AS book_category
                        FROM library_loans l
                        LEFT JOIN library_books b ON b.id=l.book_id
                        WHERE l.student_id=?
                        ORDER BY l.id DESC LIMIT 300");
                    $st->execute([$studentId]);
                    $libraryLoans = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) { $libraryLoans = []; }
                ?>
                <div class="card">
                    <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
                        <div>
                            <h4 class="mb-0">Library</h4>
                            <div class="small text-muted">Browse available books and track your requests or active loans.</div>
                        </div>
                        <div class="d-flex gap-2">
                            <a class="btn btn-outline-primary btn-sm <?php echo $tab==='browse' ? 'active' : ''; ?>" href="?page=student_library&tab=browse">Browse Books</a>
                            <a class="btn btn-outline-primary btn-sm <?php echo $tab==='loans' ? 'active' : ''; ?>" href="?page=student_library&tab=loans">My Loans</a>
                        </div>
                    </div>
                    <div class="card-body">
                        <?php if ($tab === 'browse'): ?>
                            <form class="row g-2 mb-3" method="get">
                                <input type="hidden" name="page" value="student_library">
                                <input type="hidden" name="tab" value="browse">
                                <div class="col-md-8"><input class="form-control" name="q" value="<?php echo e($bookSearch); ?>" placeholder="Search by title, author, ISBN, or category"></div>
                                <div class="col-md-4 d-grid"><button class="btn btn-primary" type="submit">Search</button></div>
                            </form>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle">
                                    <thead><tr><th>Book</th><th>Author</th><th>Category</th><th>Available</th><th></th></tr></thead>
                                    <tbody>
                                    <?php foreach ($libraryBooks as $book): ?>
                                        <tr>
                                            <td>
                                                <div class="fw-semibold"><?php echo e((string)($book['title'] ?? '')); ?></div>
                                                <div class="small text-muted">ISBN: <?php echo e((string)($book['isbn'] ?? 'N/A')); ?></div>
                                            </td>
                                            <td><?php echo e((string)($book['author'] ?? '')); ?></td>
                                            <td><?php echo e((string)($book['category'] ?? '')); ?></td>
                                            <td><?php echo (int)($book['copies_available'] ?? 0); ?> / <?php echo (int)($book['copies_total'] ?? 0); ?></td>
                                            <td class="text-end">
                                                <form method="post" action="?page=student_library&tab=browse" class="d-inline">
                                                    <input type="hidden" name="action" value="request_library_book">
                                                    <input type="hidden" name="book_id" value="<?php echo (int)$book['id']; ?>">
                                                    <button class="btn btn-sm btn-outline-primary" type="submit" <?php echo ((int)($book['copies_available'] ?? 0) <= 0) ? 'disabled' : ''; ?>>Request</button>
                                                </form>
                                                <?php if (!empty($book['file_path'])): ?>
                                                    <a class="btn btn-sm btn-outline-secondary" href="<?php echo e((string)$book['file_path']); ?>" target="_blank" rel="noopener">Open File</a>
                                                <?php endif; ?>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$libraryBooks): ?><tr><td colspan="5" class="text-muted text-center">No library books found.</td></tr><?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        <?php else: ?>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle">
                                    <thead><tr><th>Book</th><th>Status</th><th>Requested</th><th>Due</th><th>Returned</th></tr></thead>
                                    <tbody>
                                    <?php foreach ($libraryLoans as $loan): ?>
                                        <tr>
                                            <td>
                                                <div class="fw-semibold"><?php echo e((string)($loan['book_title'] ?? '')); ?></div>
                                                <div class="small text-muted"><?php echo e((string)($loan['book_author'] ?? '')); ?><?php echo !empty($loan['book_category']) ? ' • ' . e((string)$loan['book_category']) : ''; ?></div>
                                            </td>
                                            <td><span class="badge bg-secondary"><?php echo e(ucfirst((string)($loan['status'] ?? 'requested'))); ?></span></td>
                                            <td><?php echo e((string)($loan['requested_at'] ?? '')); ?></td>
                                            <td><?php echo e((string)($loan['due_at'] ?? '')); ?></td>
                                            <td><?php echo e((string)($loan['returned_at'] ?? '')); ?></td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$libraryLoans): ?><tr><td colspan="5" class="text-muted text-center">You have no library requests or loans yet.</td></tr><?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        <?php endif; ?>
                    </div>
                </div>
                <?php
                break;

            
            case 'student_cbt':
                if (!isStudent()) { redirect('?page=login'); }
                portalCbtEnsureEnterpriseSchema($pdo);
                $cbtCsrf = portalCbtCsrfToken();
                $studentId = (int)($_SESSION['student_id'] ?? ($_SESSION['user_id'] ?? 0));
                $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ? LIMIT 1");
                $stmt->execute([$studentId]);
                $student = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
                if (!$student) { flash('danger','Student record not found.'); redirect('?page=student_dashboard'); }

                $tab = trim((string)($_GET['tab'] ?? 'available'));
                $tab = in_array($tab, ['available','history'], true) ? $tab : 'available';
                $act = trim((string)($_POST['act'] ?? $_GET['act'] ?? ''));
                $format = strtolower(trim((string)($_GET['format'] ?? 'html')));
                $selectedTestId = (int)($_GET['selected_test'] ?? $_GET['test_id'] ?? $_POST['test_id'] ?? 0);
                $attemptId = (int)($_GET['attempt_id'] ?? $_POST['attempt_id'] ?? 0);
                $questionNo = max(1, (int)($_GET['qno'] ?? $_POST['current_question_no'] ?? 1));
                $examMode = (int)($_GET['exam'] ?? 0) === 1;
                $queryText = trim((string)($_GET['q'] ?? ''));
                $statusFilter = strtolower(trim((string)($_GET['status'] ?? 'all')));

                $protectedActs = ['cbt_start_attempt','cbt_save_progress','cbt_submit_attempt','cbt_incident_ping'];
                if (in_array($act, $protectedActs, true) && !portalCbtVerifyCsrf()) {
                    if ($format === 'json') {
                        portalCbtJsonResponse(['ok' => false, 'message' => 'Your CBT session token has expired. Refresh and try again.']);
                    }
                    flash('danger', 'Your CBT session token has expired. Refresh and try again.');
                    redirect('?page=student_cbt&tab=' . urlencode($tab));
                }

                if ($act === 'cbt_start_attempt' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $testId = (int)($_POST['test_id'] ?? 0);
                    $testRow = [];
                    try {
                        $st = $pdo->prepare("SELECT * FROM cbt_tests WHERE id = ? LIMIT 1");
                        $st->execute([$testId]);
                        $testRow = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                    } catch (Throwable $e) {}
                    if (!$testRow) {
                        flash('danger', 'CBT exam could not be found.');
                        redirect('?page=student_cbt&tab=available');
                    }
                    $existingAttempt = portalCbtAttemptRow($pdo, $testId, $studentId);
                    $eligibility = portalCbtEligibility($pdo, $testRow, $student, $existingAttempt);
                    if (!$eligibility['allowed']) {
                        flash('danger', (string)$eligibility['blocked_reason']);
                        redirect('?page=student_cbt&tab=available&selected_test=' . $testId);
                    }
                    try {
                        $attemptRow = portalCbtStartOrResumeAttempt($pdo, $testRow, $student);
                        portalCbtLogActivity($pdo, 'attempt', (int)($attemptRow['id'] ?? 0), empty($existingAttempt) ? 'start_attempt' : 'resume_attempt', 'Student opened CBT workspace', ['test_id' => $testId], 'student', $studentId);
                        redirect('?page=student_cbt&tab=available&selected_test=' . $testId . '&exam=1&attempt_id=' . (int)($attemptRow['id'] ?? 0) . '#cbt-live-workspace');
                    } catch (Throwable $e) {
                        flash('danger', 'Unable to open the live CBT workspace right now.');
                        redirect('?page=student_cbt&tab=available&selected_test=' . $testId);
                    }
                }

                if ($act === 'cbt_incident_ping' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $incidentAttemptId = (int)($_POST['attempt_id'] ?? 0);
                    $incidentType = trim((string)($_POST['incident_type'] ?? ''));
                    $incidentDetails = trim((string)($_POST['incident_details'] ?? ''));
                    $attemptRow = [];
                    try {
                        $st = $pdo->prepare("SELECT a.*, t.browser_tolerance_count, t.auto_submit_on_timeout FROM cbt_attempts a INNER JOIN cbt_tests t ON t.id = a.test_id WHERE a.id = ? AND a.student_id = ? LIMIT 1");
                        $st->execute([$incidentAttemptId, $studentId]);
                        $attemptRow = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                    } catch (Throwable $e) {}
                    if ($attemptRow && empty($attemptRow['submitted_at'])) {
                        portalCbtAppendIncident($pdo, $incidentAttemptId, (int)($attemptRow['test_id'] ?? 0), $studentId, $incidentType, $incidentDetails);
                        $limit = max(0, (int)($attemptRow['browser_tolerance_count'] ?? 0));
                        $used = (int)($attemptRow['tolerance_used'] ?? 0) + 1;
                        $autoSubmitted = false;
                        if ($limit > 0 && $used > $limit && !empty($attemptRow['auto_submit_on_timeout'])) {
                            portalCbtFinalizeAttempt($pdo, $incidentAttemptId);
                            $autoSubmitted = true;
                        }
                        portalCbtJsonResponse(['ok' => true, 'used' => $used, 'limit' => $limit, 'auto_submitted' => $autoSubmitted]);
                    }
                    portalCbtJsonResponse(['ok' => false, 'message' => 'Attempt not found.']);
                }

                if ($act === 'cbt_save_progress' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $attemptIdPost = (int)($_POST['attempt_id'] ?? 0);
                    $questionIdPost = (int)($_POST['question_id'] ?? 0);
                    $remainingSecondsPost = max(0, (int)($_POST['remaining_seconds'] ?? 0));
                    $flagged = !empty($_POST['flagged']);
                    $answerValue = $_POST['selected_option'] ?? ($_POST['answer_text'] ?? ($_POST['selected_options'] ?? ''));
                    $attemptRow = [];
                    $questionRow = [];
                    try {
                        $st = $pdo->prepare("SELECT * FROM cbt_attempts WHERE id = ? AND student_id = ? LIMIT 1");
                        $st->execute([$attemptIdPost, $studentId]);
                        $attemptRow = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                    } catch (Throwable $e) {}
                    try {
                        $st = $pdo->prepare("SELECT * FROM cbt_questions WHERE id = ? LIMIT 1");
                        $st->execute([$questionIdPost]);
                        $questionRow = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                    } catch (Throwable $e) {}
                    if (!$attemptRow || !$questionRow || !empty($attemptRow['submitted_at'])) {
                        if ($format === 'json') portalCbtJsonResponse(['ok' => false, 'message' => 'Attempt is no longer editable.']);
                        flash('danger', 'Attempt is no longer editable.');
                        redirect('?page=student_cbt&tab=available');
                    }
                    $freshAttempt = portalCbtSaveAttemptProgress($pdo, $attemptRow, $questionRow, $answerValue, $flagged, max(1, (int)($_POST['current_question_no'] ?? 1)), $remainingSecondsPost);
                    if ($format === 'json') {
                        portalCbtJsonResponse(['ok' => true, 'message' => 'Progress saved.', 'remaining_seconds' => (int)($freshAttempt['remaining_seconds'] ?? $remainingSecondsPost), 'current_question_no' => (int)($freshAttempt['current_question_no'] ?? 1), 'saved_at' => date('H:i:s')]);
                    }
                    flash('success', 'Progress saved.');
                    redirect('?page=student_cbt&tab=available&selected_test=' . (int)($attemptRow['test_id'] ?? 0) . '&exam=1&attempt_id=' . (int)($attemptRow['id'] ?? 0) . '&qno=' . max(1, (int)($_POST['current_question_no'] ?? 1)));
                }

                if ($act === 'cbt_submit_attempt' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $attemptIdPost = (int)($_POST['attempt_id'] ?? 0);
                    $questionIdPost = (int)($_POST['question_id'] ?? 0);
                    $attemptRow = [];
                    try {
                        $st = $pdo->prepare("SELECT * FROM cbt_attempts WHERE id = ? AND student_id = ? LIMIT 1");
                        $st->execute([$attemptIdPost, $studentId]);
                        $attemptRow = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                    } catch (Throwable $e) {}
                    if (!$attemptRow) {
                        flash('danger', 'Attempt could not be found.');
                        redirect('?page=student_cbt&tab=available');
                    }
                    if ($questionIdPost > 0 && empty($attemptRow['submitted_at'])) {
                        $questionRow = [];
                        try {
                            $st = $pdo->prepare("SELECT * FROM cbt_questions WHERE id = ? LIMIT 1");
                            $st->execute([$questionIdPost]);
                            $questionRow = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                        } catch (Throwable $e) {}
                        if ($questionRow) {
                            $answerValue = $_POST['selected_option'] ?? ($_POST['answer_text'] ?? ($_POST['selected_options'] ?? ''));
                            $attemptRow = portalCbtSaveAttemptProgress($pdo, $attemptRow, $questionRow, $answerValue, !empty($_POST['flagged']), max(1, (int)($_POST['current_question_no'] ?? 1)), max(0, (int)($_POST['remaining_seconds'] ?? 0)));
                        }
                    }
                    $finalAttempt = portalCbtFinalizeAttempt($pdo, $attemptIdPost);
                    portalCbtLogActivity($pdo, 'attempt', $attemptIdPost, 'submit_attempt', 'Student submitted CBT attempt', ['test_id' => (int)($finalAttempt['test_id'] ?? 0)], 'student', $studentId);
                    flash('success', 'Your CBT attempt has been submitted successfully.');
                    redirect('?page=student_cbt&tab=history');
                }

                $filterFacultyId = (int)($student['faculty_id'] ?? 0);
                $filterDepartmentId = (int)($student['department_id'] ?? 0);
                $filterProgrammeId = (int)($student['programme_id'] ?? 0);
                $filterLevel = trim((string)($student['level'] ?? ''));
                $filterSession = trim((string)($student['session'] ?? (function_exists('portalCurrentAcademicSessionName') ? portalCurrentAcademicSessionName($pdo) : '')));
                $filterSemester = trim((string)($student['semester'] ?? (function_exists('portalCurrentSemesterName') ? portalCurrentSemesterName($pdo) : '')));
                $classGroup = trim((string)($student['class_group'] ?? ($student['group_name'] ?? '')));

                $availableTests = [];
                try {
                    $sql = "SELECT * FROM cbt_tests
                        WHERE status = 'active'
                          AND published = 1
                          AND LOWER(COALESCE(exam_mode, 'draft')) = 'live'
                          AND archived_at IS NULL
                          AND (faculty_id IS NULL OR faculty_id = 0 OR faculty_id = ?)
                          AND (department_id IS NULL OR department_id = 0 OR department_id = ?)
                          AND (programme_id IS NULL OR programme_id = 0 OR programme_id = ?)
                          AND (level IS NULL OR level = '' OR level = ?)
                          AND (session_name IS NULL OR session_name = '' OR session_name = ?)
                          AND (semester IS NULL OR semester = '' OR semester = ?)";
                    $params = [$filterFacultyId, $filterDepartmentId, $filterProgrammeId, $filterLevel, $filterSession, $filterSemester];
                    if ($queryText !== '') {
                        $sql .= " AND (title LIKE ? OR exam_code LIKE ? OR course_code LIKE ? OR subject_name LIKE ?)";
                        $like = '%' . $queryText . '%';
                        $params = array_merge($params, [$like, $like, $like, $like]);
                    }
                    $sql .= " ORDER BY COALESCE(start_at, '9999-12-31 23:59:59') ASC, id DESC LIMIT 200";
                    $st = $pdo->prepare($sql);
                    $st->execute($params);
                    $availableTests = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) { $availableTests = []; }

                $attemptHistory = [];
                try {
                    $st = $pdo->prepare("SELECT a.*, t.title AS test_title, t.course_code, t.exam_code, t.pass_mark, t.results_published, t.duration_minutes, t.question_per_page, t.negative_mark_per_wrong, t.answer_review_enabled, t.instant_result_enabled
                        FROM cbt_attempts a
                        INNER JOIN cbt_tests t ON t.id = a.test_id
                        WHERE a.student_id = ?
                        ORDER BY COALESCE(a.submitted_at, a.started_at) DESC, a.id DESC
                        LIMIT 300");
                    $st->execute([$studentId]);
                    $attemptHistory = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) { $attemptHistory = []; }

                $attemptByTest = [];
                foreach ($attemptHistory as $row) {
                    $tid = (int)($row['test_id'] ?? 0);
                    if ($tid > 0 && !isset($attemptByTest[$tid])) $attemptByTest[$tid] = $row;
                }

                $selectedTest = [];
                foreach ($availableTests as $testRow) {
                    $tid = (int)($testRow['id'] ?? 0);
                    $attemptRow = $attemptByTest[$tid] ?? portalCbtAttemptRow($pdo, $tid, $studentId);
                    $eligibility = portalCbtEligibility($pdo, $testRow, $student, $attemptRow);
                    $testRow['eligibility'] = $eligibility;
                    $testRow['attempt_row'] = $attemptRow;
                    if ($attemptRow) $attemptByTest[$tid] = $attemptRow;
                    if ($statusFilter !== 'all') {
                        $label = $eligibility['allowed'] ? (!empty($attemptRow['submitted_at']) ? 'completed' : 'open') : 'blocked';
                        if ($statusFilter === 'completed' && empty($attemptRow['submitted_at'])) continue;
                        if ($statusFilter === 'open' && (!$eligibility['allowed'] || !empty($attemptRow['submitted_at']))) continue;
                        if ($statusFilter === 'blocked' && $eligibility['allowed']) continue;
                    }
                    if ($selectedTestId > 0 && $tid === $selectedTestId) $selectedTest = $testRow;
                    $preparedTests[] = $testRow;
                }
                $availableTests = $preparedTests ?? $availableTests;
                if (!$selectedTest && $availableTests) $selectedTest = $availableTests[0];

                $liveAttempt = null;
                $liveQuestions = [];
                $liveQuestion = [];
                $liveQuestionStats = ['answered' => 0, 'flagged' => 0, 'visited' => 0, 'unanswered' => 0, 'total' => 0];
                if ($examMode && $selectedTest) {
                    $liveAttempt = $attemptId > 0 ? null : ($attemptByTest[(int)($selectedTest['id'] ?? 0)] ?? null);
                    if ($attemptId > 0) {
                        try {
                            $st = $pdo->prepare("SELECT * FROM cbt_attempts WHERE id = ? AND student_id = ? LIMIT 1");
                            $st->execute([$attemptId, $studentId]);
                            $liveAttempt = $st->fetch(PDO::FETCH_ASSOC) ?: null;
                        } catch (Throwable $e) { $liveAttempt = null; }
                    }
                    if ($liveAttempt) {
                        $order = portalCbtJsonDecode($liveAttempt['question_order_json'] ?? '[]', []);
                        $liveQuestions = portalCbtFetchQuestionsByIds($pdo, $order);
                        $liveQuestionStats['total'] = count($liveQuestions);
                        $liveAnswers = portalCbtJsonDecode($liveAttempt['answers_json'] ?? '{}', []);
                        foreach ($order as $qidRaw) {
                            $qidKey = (string)(int)$qidRaw;
                            $answerRow = $liveAnswers[$qidKey] ?? null;
                            if ($answerRow) {
                                $selectedValue = $answerRow['selected'] ?? '';
                                $hasAnswer = is_array($selectedValue) ? !empty($selectedValue) : trim((string)$selectedValue) !== '';
                                if ($hasAnswer) $liveQuestionStats['answered']++;
                                if (!empty($answerRow['flagged'])) $liveQuestionStats['flagged']++;
                                if (!empty($answerRow['visited']) || $hasAnswer || !empty($answerRow['flagged'])) $liveQuestionStats['visited']++;
                            }
                        }
                        $liveQuestionStats['unanswered'] = max(0, $liveQuestionStats['total'] - $liveQuestionStats['answered']);
                        $questionNo = min(max(1, $questionNo), max(1, $liveQuestionStats['total']));
                        $liveQuestion = $liveQuestions[$questionNo - 1] ?? [];
                    }
                }

                $historyStats = [
                    'available' => count($availableTests),
                    'attempts' => count($attemptHistory),
                    'completed' => count(array_filter($attemptHistory, static fn($row) => !empty($row['submitted_at']))),
                    'published_results' => count(array_filter($attemptHistory, static fn($row) => (int)($row['results_published'] ?? 0) === 1)),
                    'best_percent' => 0,
                ];
                $bestPercent = 0.0;
                foreach ($attemptHistory as $row) {
                    $bestPercent = max($bestPercent, (float)($row['percentage_score'] ?? 0));
                }
                $historyStats['best_percent'] = round($bestPercent, 1);

                ?>
                <style>
                    .cbt-student-shell{display:grid;gap:16px}
                    .cbt-student-card{border:1px solid #dbe7ff;border-radius:18px;background:#fff;box-shadow:0 10px 28px rgba(13,110,253,.08)}
                    .cbt-student-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px}
                    .cbt-student-kpi{background:linear-gradient(135deg,#0d6efd,#4f8dff);color:#fff;border-radius:18px;padding:18px}
                    .cbt-student-kpi .value{font-size:1.65rem;font-weight:700}
                    .cbt-soft-box{background:#f6f9ff;border:1px solid #dde8ff;border-radius:14px;padding:14px}
                    .cbt-palette{display:grid;grid-template-columns:repeat(auto-fill,minmax(44px,1fr));gap:8px}
                    .cbt-palette a{display:flex;align-items:center;justify-content:center;height:44px;border-radius:12px;border:1px solid #d9e6ff;background:#f8fbff;color:#0d3b8c;text-decoration:none;font-weight:600}
                    .cbt-palette a.current{background:#0d6efd;color:#fff;border-color:#0d6efd}
                    .cbt-palette a.answered{background:#d1fae5;border-color:#a7f3d0;color:#065f46}
                    .cbt-palette a.flagged{background:#fef3c7;border-color:#fde68a;color:#92400e}
                    .cbt-palette a.visited{background:#eef2ff;border-color:#c7d2fe;color:#4338ca}
                    .cbt-live-header{position:sticky;top:8px;z-index:20}
                    .cbt-question-option{border:1px solid #dbe7ff;border-radius:14px;padding:12px}
                    .cbt-question-option.active{border-color:#0d6efd;background:#eef5ff}
                    .cbt-calculator-panel{position:fixed;right:16px;bottom:16px;z-index:1050;max-width:280px;background:#fff;border:1px solid #dbe7ff;border-radius:16px;box-shadow:0 10px 28px rgba(13,110,253,.15);display:none}
                    @media (max-width: 767.98px){.cbt-live-layout{display:block}.cbt-live-header{top:0}}
                    @media (min-width: 768px){.cbt-live-layout{display:grid;grid-template-columns:1fr;gap:16px}}
                </style>
                <div class="container-fluid py-3">
                    <?php renderFlashes(); ?>
                    <div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3">
                        <div>
                            <h3 class="mb-1">Student CBT Dashboard</h3>
                            <div class="text-muted">Your live HontTech CBT control center for available exams, readiness checks, exam workspace, results, and anti-cheat safe exam flow.</div>
                        </div>
                        <div class="d-flex gap-2 flex-wrap">
                            <a class="btn btn-outline-primary btn-sm <?php echo $tab === 'available' ? 'active' : ''; ?>" href="?page=student_cbt&tab=available">Available Exams</a>
                            <a class="btn btn-outline-primary btn-sm <?php echo $tab === 'history' ? 'active' : ''; ?>" href="?page=student_cbt&tab=history">My Results</a>
                        </div>
                    </div>

                    <div class="cbt-student-kpis mb-4">
                        <div class="cbt-student-kpi"><div class="small text-white-50">Available Exams</div><div class="value"><?php echo (int)$historyStats['available']; ?></div><div class="small">Scope-aware live exams</div></div>
                        <div class="cbt-student-kpi"><div class="small text-white-50">Attempt History</div><div class="value"><?php echo (int)$historyStats['attempts']; ?></div><div class="small">All recorded CBT attempts</div></div>
                        <div class="cbt-student-kpi"><div class="small text-white-50">Completed</div><div class="value"><?php echo (int)$historyStats['completed']; ?></div><div class="small">Submitted exam sessions</div></div>
                        <div class="cbt-student-kpi"><div class="small text-white-50">Published Results</div><div class="value"><?php echo (int)$historyStats['published_results']; ?></div><div class="small">Results visible to you</div></div>
                        <div class="cbt-student-kpi"><div class="small text-white-50">Best Score</div><div class="value"><?php echo number_format((float)$historyStats['best_percent'], 1); ?>%</div><div class="small">Across graded attempts</div></div>
                    </div>

                    <?php if ($tab === 'available'): ?>
                        <div class="cbt-student-card p-3 p-lg-4 mb-4">
                            <form method="get" class="row g-3">
                                <input type="hidden" name="page" value="student_cbt">
                                <input type="hidden" name="tab" value="available">
                                <div class="col-md-6"><label class="form-label">Search Exams</label><input type="text" class="form-control" name="q" value="<?php echo e($queryText); ?>" placeholder="Exam title, code, course code, subject"></div>
                                <div class="col-md-3"><label class="form-label">Status</label><select class="form-select" name="status"><option value="all">All</option><option value="open" <?php echo $statusFilter === 'open' ? 'selected' : ''; ?>>Open / Resume</option><option value="completed" <?php echo $statusFilter === 'completed' ? 'selected' : ''; ?>>Completed</option><option value="blocked" <?php echo $statusFilter === 'blocked' ? 'selected' : ''; ?>>Blocked</option></select></div>
                                <div class="col-md-3 d-flex align-items-end"><button class="btn btn-outline-primary w-100">Apply Filter</button></div>
                            </form>
                        </div>

                        <div class="cbt-student-card p-3 p-lg-4 mb-4">
                            <div class="d-flex justify-content-between align-items-center mb-3">
                                <h5 class="mb-0">Available Exams</h5>
                                <span class="badge text-bg-light border"><?php echo (int)count($availableTests); ?> exam(s)</span>
                            </div>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle mb-0">
                                    <thead><tr><th>Exam</th><th>Window</th><th>Details</th><th>Access</th><th>Action</th></tr></thead>
                                    <tbody>
                                    <?php foreach ($availableTests as $row): ?>
                                        <?php
                                            $eligibility = $row['eligibility'] ?? ['allowed' => false, 'blocked_reason' => 'Unavailable', 'window_text' => ''];
                                            $attemptRow = $row['attempt_row'] ?? null;
                                            $actionLabel = 'Start Exam';
                                            if ($attemptRow && empty($attemptRow['submitted_at'])) $actionLabel = 'Resume Exam';
                                            if ($attemptRow && !empty($attemptRow['submitted_at']) && ($eligibility['allowed'] ?? false)) $actionLabel = 'Retake Exam';
                                        ?>
                                        <tr>
                                            <td><div class="fw-semibold"><?php echo e((string)($row['title'] ?? '')); ?></div><div class="small text-muted"><?php echo e((string)($row['exam_code'] ?? '')); ?> • <?php echo e((string)($row['course_code'] ?? '')); ?></div></td>
                                            <td class="small"><?php echo e((string)($eligibility['window_text'] ?? '')); ?></td>
                                            <td class="small"><?php echo e((string)($row['subject_name'] ?? '')); ?><?php echo ($row['class_group'] ?? '') ? ' • ' . e((string)$row['class_group']) : ''; ?><br><?php echo (int)($row['duration_minutes'] ?? 0); ?> mins • Attempt limit <?php echo (int)($row['attempt_limit'] ?? 1); ?></td>
                                            <td>
                                                <?php if (!empty($eligibility['allowed'])): ?>
                                                    <span class="badge text-bg-success">Eligible</span>
                                                <?php else: ?>
                                                    <span class="badge text-bg-danger">Blocked</span>
                                                    <div class="small text-muted mt-1"><?php echo e((string)($eligibility['blocked_reason'] ?? 'Unavailable')); ?></div>
                                                <?php endif; ?>
                                            </td>
                                            <td>
                                                <?php if (!empty($eligibility['allowed'])): ?>
                                                    <div class="d-flex flex-wrap gap-2">
                                                        <form method="post" class="d-inline">
                                                            <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                                            <input type="hidden" name="act" value="cbt_start_attempt">
                                                            <input type="hidden" name="test_id" value="<?php echo (int)($row['id'] ?? 0); ?>">
                                                            <button class="btn btn-sm btn-primary"><?php echo e($actionLabel); ?></button>
                                                        </form>
                                                        <a class="btn btn-sm btn-outline-secondary" href="?page=student_cbt&tab=available&selected_test=<?php echo (int)($row['id'] ?? 0); ?>#cbt-readiness">Readiness</a>
                                                    </div>
                                                <?php else: ?>
                                                    <button class="btn btn-sm btn-outline-secondary" disabled>Unavailable</button>
                                                <?php endif; ?>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$availableTests): ?><tr><td colspan="5" class="text-muted">No live CBT exam is currently available for your scope.</td></tr><?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>

                        <?php if ($selectedTest): ?>
                            <?php
                                $selectedEligibility = $selectedTest['eligibility'] ?? ['allowed' => false, 'blocked_reason' => 'Unavailable', 'window_text' => ''];
                                $selectedAttempt = $selectedTest['attempt_row'] ?? null;
                            ?>
                            <div class="row g-4 mb-4">
                                <div class="col-12">
                                    <div class="cbt-student-card p-3 p-lg-4 h-100" id="cbt-readiness">
                                        <h5 class="mb-3">Readiness Panel</h5>
                                        <div class="soft-box"></div>
                                        <div class="cbt-soft-box mb-3">
                                            <div class="fw-semibold mb-1"><?php echo e((string)($selectedTest['title'] ?? '')); ?></div>
                                            <div class="small text-muted"><?php echo e((string)($selectedTest['exam_code'] ?? '')); ?> • <?php echo e((string)($selectedTest['course_code'] ?? '')); ?></div>
                                        </div>
                                        <div class="cbt-soft-box mb-3">
                                            <div class="small text-muted mb-1">Window</div>
                                            <div class="fw-semibold"><?php echo e((string)($selectedEligibility['window_text'] ?? 'Always available')); ?></div>
                                        </div>
                                        <div class="cbt-soft-box mb-3">
                                            <div class="small text-muted mb-1">Rules</div>
                                            <div class="small richtext-output"><?php echo portalRenderSafeRichText((string)($selectedTest['instructions'] ?? ''), 'Read all instructions carefully before starting. Do not refresh the browser during a live exam.'); ?></div>
                                        </div>
                                        <div class="cbt-soft-box mb-3">
                                            <div class="row g-2 text-center">
                                                <div class="col-6"><div class="fw-bold"><?php echo (int)($selectedTest['duration_minutes'] ?? 0); ?></div><div class="small text-muted">Minutes</div></div>
                                                <div class="col-6"><div class="fw-bold"><?php echo e((string)($selectedTest['pass_mark'] ?? '0')); ?>%</div><div class="small text-muted">Pass Mark</div></div>
                                                <div class="col-6"><div class="fw-bold"><?php echo (int)($selectedTest['browser_tolerance_count'] ?? 0); ?></div><div class="small text-muted">Tolerance</div></div>
                                                <div class="col-6"><div class="fw-bold"><?php echo e((string)($selectedTest['question_per_page'] ?? '1')); ?></div><div class="small text-muted">Question / Page</div></div>
                                            </div>
                                        </div>
                                        <?php if (!empty($selectedEligibility['allowed'])): ?>
                                            <form method="post" class="d-flex flex-wrap gap-2">
                                                <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                                <input type="hidden" name="act" value="cbt_start_attempt">
                                                <input type="hidden" name="test_id" value="<?php echo (int)($selectedTest['id'] ?? 0); ?>">
                                                <button class="btn btn-primary"><?php echo ($selectedAttempt && empty($selectedAttempt['submitted_at'])) ? 'Resume Exam' : 'Enter Live Workspace'; ?></button>
                                                <?php if ($examMode && $liveAttempt): ?><a class="btn btn-outline-secondary" href="?page=student_cbt&tab=available&selected_test=<?php echo (int)($selectedTest['id'] ?? 0); ?>">Close Workspace</a><?php endif; ?>
                                            </form>
                                        <?php else: ?>
                                            <div class="alert alert-warning mb-0"><?php echo e((string)($selectedEligibility['blocked_reason'] ?? 'This exam is unavailable right now.')); ?></div>
                                        <?php endif; ?>
                                    </div>
                                </div>
                                <div class="col-12">
                                    <div class="cbt-student-card p-3 p-lg-4 h-100">
                                        <h5 class="mb-3">Workspace Preview</h5>
                                        <?php if ($liveAttempt && $examMode): ?>
                                            <div class="cbt-soft-box mb-3">
                                                <div class="row g-2 text-center">
                                                    <div class="col-3"><div class="fw-bold"><?php echo (int)$liveQuestionStats['answered']; ?></div><div class="small text-muted">Answered</div></div>
                                                    <div class="col-3"><div class="fw-bold"><?php echo (int)$liveQuestionStats['unanswered']; ?></div><div class="small text-muted">Unanswered</div></div>
                                                    <div class="col-3"><div class="fw-bold"><?php echo (int)$liveQuestionStats['visited']; ?></div><div class="small text-muted">Visited</div></div>
                                                    <div class="col-3"><div class="fw-bold"><?php echo (int)$liveQuestionStats['flagged']; ?></div><div class="small text-muted">Flagged</div></div>
                                                </div>
                                            </div>
                                            <div class="small text-muted">Use the live workspace below to answer, autosave, flag, navigate, and submit your CBT attempt securely.</div>
                                        <?php else: ?>
                                            <div class="cbt-soft-box">Once you enter the live workspace, your timer, palette states, autosave status, flagged items, anti-cheat tracker, and submission controls will appear here.</div>
                                        <?php endif; ?>
                                    </div>
                                </div>
                            </div>
                        <?php endif; ?>

                        <?php if ($examMode && $selectedTest && $liveAttempt && $liveQuestion): ?>
                            <?php
                                $liveAnswers = portalCbtJsonDecode($liveAttempt['answers_json'] ?? '{}', []);
                                $currentAnswerRow = $liveAnswers[(string)(int)($liveQuestion['id'] ?? 0)] ?? [];
                                $currentSelected = $currentAnswerRow['selected'] ?? '';
                                $questionOptions = portalCbtQuestionOptions($liveQuestion);
                                $optionPairs = [];
                                foreach ($questionOptions as $optKey => $optText) {
                                    $optionPairs[] = ['key' => (string)$optKey, 'text' => (string)$optText, 'seed' => crc32((string)($liveAttempt['id'] ?? 0) . '|' . (string)($liveQuestion['id'] ?? 0) . '|' . (string)$optKey)];
                                }
                                if (!empty($selectedTest['randomize_options'])) {
                                    usort($optionPairs, static fn($a, $b) => ($a['seed'] <=> $b['seed']));
                                }
                            ?>
                            <div class="cbt-student-shell" id="cbt-live-workspace">
                                <div class="cbt-student-card p-3 p-lg-4 cbt-live-header">
                                    <div class="d-flex flex-wrap justify-content-between align-items-center gap-3">
                                        <div>
                                            <h5 class="mb-1"><?php echo e((string)($selectedTest['title'] ?? '')); ?></h5>
                                            <div class="small text-muted"><?php echo e((string)($selectedTest['exam_code'] ?? '')); ?> • Attempt #<?php echo (int)($liveAttempt['attempt_number'] ?? 1); ?></div>
                                        </div>
                                        <div class="d-flex flex-wrap gap-2 align-items-center">
                                            <span class="badge text-bg-light border">Question <?php echo (int)$questionNo; ?> / <?php echo (int)$liveQuestionStats['total']; ?></span>
                                            <span class="badge text-bg-primary" id="cbt-timer-badge">Time Left: <span id="cbt-timer-text">--:--</span></span>
                                            <?php if (!empty($selectedTest['fullscreen_required'])): ?><button type="button" class="btn btn-outline-primary btn-sm" id="cbt-fullscreen-btn">Enter Fullscreen</button><?php endif; ?>
                                            <?php if (!empty($selectedTest['calculator_enabled'])): ?><button type="button" class="btn btn-outline-secondary btn-sm" id="cbt-calculator-btn">Calculator</button><?php endif; ?>
                                            <button type="button" class="btn btn-outline-danger btn-sm" data-bs-toggle="modal" data-bs-target="#cbtSubmitModal">Submit Exam</button>
                                        </div>
                                    </div>
                                </div>

                                <div class="cbt-live-layout">
                                    <div class="cbt-student-card p-3 p-lg-4">
                                        <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
                                            <div>
                                                <div class="small text-muted mb-1"><?php echo e(ucwords(str_replace('_', ' ', portalCbtQuestionType($liveQuestion['question_type'] ?? 'single_choice')))); ?><?php echo ($liveQuestion['difficulty_level'] ?? '') ? ' • ' . e(ucfirst((string)$liveQuestion['difficulty_level'])) : ''; ?></div>
                                                <h5 class="mb-0">Question <?php echo (int)$questionNo; ?></h5>
                                            </div>
                                            <span class="badge text-bg-light border"><?php echo e((string)($liveQuestion['marks'] ?? '1')); ?> mark(s)</span>
                                        </div>
                                        <div class="mb-3">
                                            <?php echo portalRenderSafeRichText((string)($liveQuestion['question'] ?? '')); ?>
                                        </div>
                                        <?php if (!empty($liveQuestion['question_image_path'])): ?>
                                            <div class="mb-3"><img src="<?php echo e((string)$liveQuestion['question_image_path']); ?>" alt="Question image" class="img-fluid rounded border"></div>
                                        <?php endif; ?>

                                        <form method="post" id="cbt-answer-form" class="row g-3">
                                            <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                            <input type="hidden" name="act" value="cbt_save_progress">
                                            <input type="hidden" name="test_id" value="<?php echo (int)($selectedTest['id'] ?? 0); ?>">
                                            <input type="hidden" name="attempt_id" value="<?php echo (int)($liveAttempt['id'] ?? 0); ?>">
                                            <input type="hidden" name="question_id" value="<?php echo (int)($liveQuestion['id'] ?? 0); ?>">
                                            <input type="hidden" name="current_question_no" id="cbt-current-question-no" value="<?php echo (int)$questionNo; ?>">
                                            <input type="hidden" name="remaining_seconds" id="cbt-remaining-seconds" value="<?php echo (int)($liveAttempt['remaining_seconds'] ?? 0); ?>">

                                            <?php if (in_array(portalCbtQuestionType($liveQuestion['question_type'] ?? ''), ['single_choice','true_false'], true)): ?>
                                                <div class="col-12">
                                                    <?php foreach ($optionPairs as $option): ?>
                                                        <label class="cbt-question-option d-flex align-items-start gap-2 mb-2 <?php echo strtoupper((string)$currentSelected) === strtoupper((string)$option['key']) ? 'active' : ''; ?>">
                                                            <input class="form-check-input mt-1 cbt-answer-input" type="radio" name="selected_option" value="<?php echo e((string)$option['key']); ?>" <?php echo strtoupper((string)$currentSelected) === strtoupper((string)$option['key']) ? 'checked' : ''; ?>>
                                                            <span><strong><?php echo e((string)$option['key']); ?>.</strong> <?php echo e((string)$option['text']); ?></span>
                                                        </label>
                                                    <?php endforeach; ?>
                                                </div>
                                            <?php elseif (portalCbtQuestionType($liveQuestion['question_type'] ?? '') === 'multiple_choice'): ?>
                                                <?php $selectedMultiple = is_array($currentSelected) ? $currentSelected : portalCbtNormalizeSubmittedAnswer('multiple_choice', $currentSelected); ?>
                                                <div class="col-12">
                                                    <?php foreach ($optionPairs as $option): ?>
                                                        <label class="cbt-question-option d-flex align-items-start gap-2 mb-2 <?php echo in_array((string)$option['key'], array_map('strval', (array)$selectedMultiple), true) ? 'active' : ''; ?>">
                                                            <input class="form-check-input mt-1 cbt-answer-checkbox" type="checkbox" name="selected_options[]" value="<?php echo e((string)$option['key']); ?>" <?php echo in_array((string)$option['key'], array_map('strval', (array)$selectedMultiple), true) ? 'checked' : ''; ?>>
                                                            <span><strong><?php echo e((string)$option['key']); ?>.</strong> <?php echo e((string)$option['text']); ?></span>
                                                        </label>
                                                    <?php endforeach; ?>
                                                </div>
                                            <?php elseif (portalCbtQuestionType($liveQuestion['question_type'] ?? '') === 'essay'): ?>
                                                <div class="col-12"><label class="form-label">Essay Answer</label><textarea class="form-control cbt-answer-input" name="answer_text" rows="8" placeholder="Type your theory answer here"><?php echo e(is_array($currentSelected) ? implode(', ', $currentSelected) : (string)$currentSelected); ?></textarea></div>
                                            <?php elseif (in_array(portalCbtQuestionType($liveQuestion['question_type'] ?? ''), ['short_answer','fill_blank'], true)): ?>
                                                <div class="col-12"><label class="form-label">Answer</label><input type="text" class="form-control cbt-answer-input" name="answer_text" value="<?php echo e(is_array($currentSelected) ? implode(', ', $currentSelected) : (string)$currentSelected); ?>" placeholder="Enter your answer"></div>
                                            <?php endif; ?>

                                            <div class="col-12">
                                                <div class="form-check"><input class="form-check-input" type="checkbox" id="cbt-flagged" name="flagged" <?php echo !empty($currentAnswerRow['flagged']) ? 'checked' : ''; ?>><label class="form-check-label" for="cbt-flagged">Flag for review</label></div>
                                            </div>

                                            <div class="col-12 d-flex flex-wrap gap-2 justify-content-between">
                                                <div class="d-flex gap-2 flex-wrap">
                                                    <?php if ($questionNo > 1): ?><a class="btn btn-outline-secondary" href="?page=student_cbt&tab=available&selected_test=<?php echo (int)($selectedTest['id'] ?? 0); ?>&exam=1&attempt_id=<?php echo (int)($liveAttempt['id'] ?? 0); ?>&qno=<?php echo (int)($questionNo - 1); ?>#cbt-live-workspace">Previous</a><?php endif; ?>
                                                    <?php if ($questionNo < $liveQuestionStats['total']): ?><a class="btn btn-outline-primary" href="?page=student_cbt&tab=available&selected_test=<?php echo (int)($selectedTest['id'] ?? 0); ?>&exam=1&attempt_id=<?php echo (int)($liveAttempt['id'] ?? 0); ?>&qno=<?php echo (int)($questionNo + 1); ?>#cbt-live-workspace">Next</a><?php endif; ?>
                                                </div>
                                                <div class="d-flex gap-2 flex-wrap">
                                                    <button type="button" class="btn btn-outline-secondary" id="cbt-clear-answer-btn">Clear Answer</button>
                                                    <button class="btn btn-primary" id="cbt-save-btn">Save Progress</button>
                                                </div>
                                            </div>
                                        </form>
                                    </div>

                                    <div class="cbt-student-card p-3 p-lg-4">
                                        <h6 class="mb-3">Question Palette</h6>
                                        <div class="cbt-palette mb-3">
                                            <?php
                                                $liveAnswersPalette = portalCbtJsonDecode($liveAttempt['answers_json'] ?? '{}', []);
                                                foreach ($liveQuestions as $idx => $questionRow):
                                                    $qKey = (string)(int)($questionRow['id'] ?? 0);
                                                    $paletteAnswer = $liveAnswersPalette[$qKey] ?? [];
                                                    $paletteSelected = $paletteAnswer['selected'] ?? '';
                                                    $paletteHasAnswer = is_array($paletteSelected) ? !empty($paletteSelected) : trim((string)$paletteSelected) !== '';
                                                    $paletteClass = '';
                                                    if (($idx + 1) === $questionNo) $paletteClass = 'current';
                                                    elseif (!empty($paletteAnswer['flagged'])) $paletteClass = 'flagged';
                                                    elseif ($paletteHasAnswer) $paletteClass = 'answered';
                                                    elseif (!empty($paletteAnswer['visited'])) $paletteClass = 'visited';
                                            ?>
                                                <a class="<?php echo e($paletteClass); ?>" href="?page=student_cbt&tab=available&selected_test=<?php echo (int)($selectedTest['id'] ?? 0); ?>&exam=1&attempt_id=<?php echo (int)($liveAttempt['id'] ?? 0); ?>&qno=<?php echo (int)($idx + 1); ?>#cbt-live-workspace"><?php echo (int)($idx + 1); ?></a>
                                            <?php endforeach; ?>
                                        </div>
                                        <div class="cbt-soft-box">
                                            <div class="small text-muted mb-2">Exam Summary</div>
                                            <div class="d-flex justify-content-between"><span>Answered</span><strong><?php echo (int)$liveQuestionStats['answered']; ?></strong></div>
                                            <div class="d-flex justify-content-between"><span>Unanswered</span><strong><?php echo (int)$liveQuestionStats['unanswered']; ?></strong></div>
                                            <div class="d-flex justify-content-between"><span>Visited</span><strong><?php echo (int)$liveQuestionStats['visited']; ?></strong></div>
                                            <div class="d-flex justify-content-between"><span>Flagged</span><strong><?php echo (int)$liveQuestionStats['flagged']; ?></strong></div>
                                        </div>
                                        <div class="cbt-soft-box mt-3">
                                            <div class="small text-muted mb-1">Anti-cheat Mode</div>
                                            <div class="small">Tab switch, focus loss, copy/paste, right-click, browser/device logging, and tolerance counts are being monitored for this live exam.</div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <div class="modal fade" id="cbtSubmitModal" tabindex="-1" aria-hidden="true">
                                <div class="modal-dialog">
                                    <div class="modal-content">
                                        <div class="modal-header"><h5 class="modal-title">Submit CBT Attempt</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div>
                                        <div class="modal-body">
                                            <p class="mb-2">You are about to submit your attempt for <strong><?php echo e((string)($selectedTest['title'] ?? '')); ?></strong>.</p>
                                            <div class="cbt-soft-box">
                                                <div class="d-flex justify-content-between"><span>Answered</span><strong><?php echo (int)$liveQuestionStats['answered']; ?></strong></div>
                                                <div class="d-flex justify-content-between"><span>Unanswered</span><strong><?php echo (int)$liveQuestionStats['unanswered']; ?></strong></div>
                                                <div class="d-flex justify-content-between"><span>Flagged</span><strong><?php echo (int)$liveQuestionStats['flagged']; ?></strong></div>
                                            </div>
                                        </div>
                                        <div class="modal-footer">
                                            <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Continue Exam</button>
                                            <form method="post" id="cbt-submit-form">
                                                <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                                <input type="hidden" name="act" value="cbt_submit_attempt">
                                                <input type="hidden" name="test_id" value="<?php echo (int)($selectedTest['id'] ?? 0); ?>">
                                                <input type="hidden" name="attempt_id" value="<?php echo (int)($liveAttempt['id'] ?? 0); ?>">
                                                <input type="hidden" name="question_id" value="<?php echo (int)($liveQuestion['id'] ?? 0); ?>">
                                                <input type="hidden" name="current_question_no" value="<?php echo (int)$questionNo; ?>">
                                                <input type="hidden" name="remaining_seconds" id="cbt-submit-remaining-seconds" value="<?php echo (int)($liveAttempt['remaining_seconds'] ?? 0); ?>">
                                                <button class="btn btn-danger">Submit Final Attempt</button>
                                            </form>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <div class="cbt-calculator-panel p-3" id="cbt-calculator-panel">
                                <div class="d-flex justify-content-between align-items-center mb-2"><strong>Calculator</strong><button type="button" class="btn-close btn-sm" id="cbt-calculator-close"></button></div>
                                <input type="text" class="form-control mb-2" id="cbt-calculator-input" placeholder="e.g. (12+6)/3">
                                <button type="button" class="btn btn-primary w-100" id="cbt-calculator-eval">Evaluate</button>
                                <div class="small text-muted mt-2" id="cbt-calculator-result">Ready.</div>
                            </div>

                            <script>
                            (function(){
                                const answerForm = document.getElementById('cbt-answer-form');
                                if (!answerForm) return;
                                const timerText = document.getElementById('cbt-timer-text');
                                const remainingInput = document.getElementById('cbt-remaining-seconds');
                                const submitRemaining = document.getElementById('cbt-submit-remaining-seconds');
                                let remaining = parseInt(remainingInput.value || '0', 10) || 0;
                                function renderTimer() {
                                    const hrs = String(Math.floor(remaining / 3600)).padStart(2,'0');
                                    const mins = String(Math.floor((remaining % 3600) / 60)).padStart(2,'0');
                                    const secs = String(remaining % 60).padStart(2,'0');
                                    if (timerText) timerText.textContent = hrs + ':' + mins + ':' + secs;
                                    if (submitRemaining) submitRemaining.value = String(remaining);
                                }
                                renderTimer();
                                const interval = setInterval(function(){
                                    if (remaining > 0) {
                                        remaining -= 1;
                                        remainingInput.value = String(remaining);
                                        renderTimer();
                                        if (remaining % 10 === 0) autoSave();
                                    } else {
                                        clearInterval(interval);
                                        document.getElementById('cbt-submit-form')?.submit();
                                    }
                                }, 1000);

                                function autoSave() {
                                    const url = new URL(window.location.href);
                                    url.searchParams.set('format', 'json');
                                    const formData = new FormData(answerForm);
                                    formData.set('remaining_seconds', String(remaining));
                                    fetch(url.toString(), {method:'POST', body:formData, credentials:'same-origin'})
                                        .then(res => res.json())
                                        .then(data => {
                                            if (data && data.ok && typeof data.remaining_seconds !== 'undefined') {
                                                remaining = parseInt(data.remaining_seconds, 10) || remaining;
                                                remainingInput.value = String(remaining);
                                                renderTimer();
                                            }
                                        })
                                        .catch(() => {});
                                }

                                document.querySelectorAll('.cbt-answer-input, .cbt-answer-checkbox, #cbt-flagged').forEach(function(el){
                                    el.addEventListener('change', function(){ autoSave(); });
                                    el.addEventListener('input', function(){ autoSave(); });
                                });

                                const clearBtn = document.getElementById('cbt-clear-answer-btn');
                                if (clearBtn) {
                                    clearBtn.addEventListener('click', function(){
                                        answerForm.querySelectorAll('input[type="radio"], input[type="checkbox"]').forEach(i => i.checked = false);
                                        answerForm.querySelectorAll('input[type="text"], textarea').forEach(i => i.value = '');
                                        autoSave();
                                    });
                                }

                                function sendIncident(type, details) {
                                    const url = new URL(window.location.href);
                                    url.searchParams.set('format', 'json');
                                    const data = new FormData();
                                    data.append('cbt_csrf_token', <?php echo json_encode($cbtCsrf); ?>);
                                    data.append('act', 'cbt_incident_ping');
                                    data.append('attempt_id', <?php echo json_encode((string)($liveAttempt['id'] ?? 0)); ?>);
                                    data.append('incident_type', type);
                                    data.append('incident_details', details || '');
                                    fetch(url.toString(), {method:'POST', body:data, credentials:'same-origin'})
                                        .then(res => res.json())
                                        .then(data => {
                                            if (data && data.auto_submitted) {
                                                window.location.href = '?page=student_cbt&tab=history';
                                            }
                                        })
                                        .catch(() => {});
                                }

                                document.addEventListener('visibilitychange', function(){
                                    if (document.hidden) sendIncident('tab_switch', 'Document became hidden');
                                });
                                window.addEventListener('blur', function(){ sendIncident('focus_loss', 'Window lost focus'); });

                                <?php if (!empty($selectedTest['anti_copy_enabled'])): ?>
                                ['copy','cut','paste','contextmenu'].forEach(function(ev){
                                    document.addEventListener(ev, function(e){ e.preventDefault(); sendIncident(ev, ev + ' prevented'); });
                                });
                                <?php endif; ?>

                                const fullscreenBtn = document.getElementById('cbt-fullscreen-btn');
                                if (fullscreenBtn) {
                                    fullscreenBtn.addEventListener('click', function(){
                                        const target = document.documentElement;
                                        if (target.requestFullscreen) target.requestFullscreen();
                                    });
                                }

                                const calcBtn = document.getElementById('cbt-calculator-btn');
                                const calcPanel = document.getElementById('cbt-calculator-panel');
                                if (calcBtn && calcPanel) {
                                    calcBtn.addEventListener('click', function(){ calcPanel.style.display = calcPanel.style.display === 'block' ? 'none' : 'block'; });
                                    document.getElementById('cbt-calculator-close')?.addEventListener('click', function(){ calcPanel.style.display = 'none'; });
                                    document.getElementById('cbt-calculator-eval')?.addEventListener('click', function(){
                                        const input = document.getElementById('cbt-calculator-input');
                                        const result = document.getElementById('cbt-calculator-result');
                                        try {
                                            const sanitized = String(input.value || '').replace(/[^-+*/().0-9 ]/g, '');
                                            result.textContent = 'Result: ' + Function('return (' + sanitized + ')')();
                                        } catch (e) {
                                            result.textContent = 'Invalid expression.';
                                        }
                                    });
                                }
                            })();
                            </script>
                        <?php endif; ?>
                    <?php endif; ?>

                    <?php if ($tab === 'history'): ?>
                        <div class="cbt-student-card p-3 p-lg-4">
                            <div class="d-flex justify-content-between align-items-center mb-3">
                                <h5 class="mb-0">My CBT Results & Attempt History</h5>
                                <span class="badge text-bg-light border"><?php echo (int)count($attemptHistory); ?> record(s)</span>
                            </div>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle mb-0">
                                    <thead><tr><th>Exam</th><th>Attempt</th><th>Status</th><th>Score</th><th>Pass Mark</th><th>Submitted</th><th>Result</th><th>Actions</th></tr></thead>
                                    <tbody>
                                    <?php foreach ($attemptHistory as $row): ?>
                                        <tr>
                                            <td><div class="fw-semibold"><?php echo e((string)($row['test_title'] ?? '')); ?></div><div class="small text-muted"><?php echo e((string)($row['exam_code'] ?? '')); ?> • <?php echo e((string)($row['course_code'] ?? '')); ?></div></td>
                                            <td>#<?php echo (int)($row['attempt_number'] ?? 1); ?></td>
                                            <td><span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['status'] ?? 'started')); ?>"><?php echo e(ucfirst((string)($row['status'] ?? 'started'))); ?></span> <span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['grading_status'] ?? 'pending')); ?>"><?php echo e(ucfirst(str_replace('_',' ', (string)($row['grading_status'] ?? 'pending')))); ?></span></td>
                                            <td>
                                                <?php if ((int)($row['results_published'] ?? 0) === 1 && $row['score'] !== null && $row['score'] !== ''): ?>
                                                    <div class="fw-semibold"><?php echo e((string)$row['score']); ?> / <?php echo e((string)($row['max_score'] ?? '0')); ?></div>
                                                    <div class="small text-muted"><?php echo e((string)($row['percentage_score'] ?? '0')); ?>% • <?php echo e((string)($row['grade_label'] ?? '')); ?></div>
                                                <?php elseif ($row['score'] !== null && $row['score'] !== '' && strtolower((string)($row['grading_status'] ?? '')) === 'graded'): ?>
                                                    <span class="text-muted">Awaiting publication</span>
                                                <?php else: ?>
                                                    <span class="text-muted">Pending</span>
                                                <?php endif; ?>
                                            </td>
                                            <td><?php echo e((string)($row['pass_mark'] ?? '0')); ?>%</td>
                                            <td><?php echo !empty($row['submitted_at']) ? e(date('d M Y h:i A', strtotime((string)$row['submitted_at']))) : '<span class="text-muted">Not submitted</span>'; ?></td>
                                            <td><span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['result_status'] ?? 'pending')); ?>"><?php echo e(ucfirst(str_replace('_',' ', (string)($row['result_status'] ?? 'pending')))); ?></span></td>
                                            <td><div class="d-flex flex-wrap gap-1"><?php if ((int)($row['results_published'] ?? 0) === 1 && !empty($row['answer_review_enabled'])): ?><a class="btn btn-sm btn-outline-primary" href="?page=student_cbt&tab=history&attempt_id=<?php echo (int)($row['id'] ?? 0); ?>&view=answer_review">Review</a><?php endif; ?><?php if ((int)($row['results_published'] ?? 0) === 1): ?><a class="btn btn-sm btn-outline-secondary" href="?page=student_cbt&tab=history&attempt_id=<?php echo (int)($row['id'] ?? 0); ?>&print=result_slip">Result Slip</a><?php if (strtolower((string)($row['pass_fail_status'] ?? '')) === 'passed'): ?><a class="btn btn-sm btn-outline-success" href="?page=student_cbt&tab=history&attempt_id=<?php echo (int)($row['id'] ?? 0); ?>&print=certificate">Certificate</a><?php endif; ?><?php else: ?><span class="text-muted small">Awaiting publication</span><?php endif; ?></div></td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$attemptHistory): ?><tr><td colspan="8" class="text-muted">No CBT attempts found yet. Start from the Available Exams tab when a live exam is published for your scope.</td></tr><?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    <?php endif; ?>
                </div>
                <?php
                break;



            case 'cbt_admin':
                if (!isAdmin()) { redirect('?page=login'); }
                if (!menuEnabled($pdo, 'menu_cbt_admin') || !hasStaffPermission($pdo, 'cbt_manage')) {
                    flash('danger','You do not have permission to access CBT module.');
                    redirect('?page=admin_dashboard');
                }

                portalCbtEnsureEnterpriseSchema($pdo);
                $cbtCsrf = portalCbtCsrfToken();
                $adminId = portalCbtCurrentAdminId();
                $tab = trim((string)($_GET['tab'] ?? 'overview'));
                $tab = in_array($tab, ['overview','tests','questions','attempts','results','reports','students','settings','logs'], true) ? $tab : 'overview';
                $act = trim((string)($_POST['act'] ?? $_GET['act'] ?? ''));
                $test_id = (int)($_GET['test_id'] ?? $_POST['test_id'] ?? 0);
                $edit_test_id = (int)($_GET['edit_test_id'] ?? 0);
                $edit_question_id = (int)($_GET['edit_question_id'] ?? 0);
                $grade_attempt_id = (int)($_GET['attempt_id'] ?? $_POST['attempt_id'] ?? 0);
                $student_search = trim((string)($_GET['student_q'] ?? ''));
                $student_status_filter = strtolower(trim((string)($_GET['student_status'] ?? 'all')));
                $report_test_id = (int)($_GET['report_test_id'] ?? 0);
                $report_status = strtolower(trim((string)($_GET['report_status'] ?? 'all')));
                $report_from = trim((string)($_GET['report_from'] ?? ''));
                $report_to = trim((string)($_GET['report_to'] ?? ''));
                $reportDownload = trim((string)($_GET['download'] ?? ''));
                $reportPrint = trim((string)($_GET['print'] ?? ''));

                $requiresToken = in_array($act, ['save_test','clone_test','delete_test','save_question','delete_question','toggle_test_publish','toggle_result_publish','save_assignment','update_student_access','save_settings','grade_attempt','reset_attempt','set_attempt_result_status'], true);
                if ($requiresToken && !portalCbtVerifyCsrf()) {
                    flash('danger', 'Your CBT form session expired. Please try again.');
                    redirect('?page=cbt_admin&tab=' . urlencode($tab));
                }

                if ($act === 'save_test' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $id = (int)($_POST['id'] ?? 0);
                    $title = trim((string)($_POST['title'] ?? ''));
                    $examCode = strtoupper(trim((string)($_POST['exam_code'] ?? '')));
                    $description = trim((string)($_POST['description'] ?? ''));
                    $instructions = trim((string)($_POST['instructions'] ?? ''));
                    $courseCode = trim((string)($_POST['course_code'] ?? ''));
                    $subjectName = trim((string)($_POST['subject_name'] ?? ''));
                    $classGroup = trim((string)($_POST['class_group'] ?? ''));
                    $duration = max(1, (int)($_POST['duration_minutes'] ?? 30));
                    $startAt = trim((string)($_POST['start_at'] ?? ''));
                    $endAt = trim((string)($_POST['end_at'] ?? ''));
                    $lateEntry = max(0, (int)($_POST['late_entry_minutes'] ?? 0));
                    $attemptLimit = max(1, (int)($_POST['attempt_limit'] ?? 1));
                    $sessionName = trim((string)($_POST['session_name'] ?? ''));
                    $semester = trim((string)($_POST['semester'] ?? ''));
                    $level = trim((string)($_POST['level'] ?? ''));
                    $facultyId = max(0, (int)($_POST['faculty_id'] ?? 0));
                    $departmentId = max(0, (int)($_POST['department_id'] ?? 0));
                    $programmeId = max(0, (int)($_POST['programme_id'] ?? 0));
                    $status = strtolower(trim((string)($_POST['status'] ?? 'active')));
                    if (!in_array($status, ['active','inactive','suspended'], true)) $status = 'active';
                    $passMark = max(0, (float)($_POST['pass_mark'] ?? 0));
                    $questionPerPage = max(1, (int)($_POST['question_per_page'] ?? 1));
                    $negativeMark = max(0, (float)($_POST['negative_mark_per_wrong'] ?? 0));
                    $markPerQuestion = max(0.01, (float)($_POST['mark_per_question'] ?? 1));
                    $published = isset($_POST['published']) ? 1 : 0;
                    $resultsPublished = isset($_POST['results_published']) ? 1 : 0;
                    $randomizeQuestions = isset($_POST['randomize_questions']) ? 1 : 0;
                    $randomizeOptions = isset($_POST['randomize_options']) ? 1 : 0;
                    $instantResult = isset($_POST['instant_result_enabled']) ? 1 : 0;
                    $answerReview = isset($_POST['answer_review_enabled']) ? 1 : 0;
                    $negativeMarkingEnabled = isset($_POST['negative_marking_enabled']) ? 1 : 0;
                    $browserTolerance = max(0, (int)($_POST['browser_tolerance_count'] ?? 3));
                    $autoSubmit = isset($_POST['auto_submit_on_timeout']) ? 1 : 0;
                    $sectional = isset($_POST['sectional_enabled']) ? 1 : 0;
                    $calculatorEnabled = isset($_POST['calculator_enabled']) ? 1 : 0;
                    $fullscreenRequired = isset($_POST['fullscreen_required']) ? 1 : 0;
                    $antiCopy = isset($_POST['anti_copy_enabled']) ? 1 : 0;
                    $antiSwitch = isset($_POST['anti_switch_enabled']) ? 1 : 0;
                    $examMode = strtolower(trim((string)($_POST['exam_mode'] ?? ($published ? 'live' : 'draft'))));
                    if (!in_array($examMode, ['draft','live'], true)) $examMode = $published ? 'live' : 'draft';
                    if ($examCode === '') {
                        $baseCode = preg_replace('/[^A-Z0-9]+/', '', strtoupper(($courseCode !== '' ? $courseCode : substr($title, 0, 12))));
                        $examCode = substr(($baseCode !== '' ? $baseCode : 'CBT') . date('ymdHis'), 0, 30);
                    }

                    if ($title === '') {
                        flash('danger', 'Exam title is required.');
                        redirect('?page=cbt_admin&tab=tests');
                    }

                    try {
                        if ($id > 0) {
                            $sql = "UPDATE cbt_tests SET title=?, exam_code=?, description=?, instructions=?, course_code=?, subject_name=?, class_group=?, duration_minutes=?, start_at=?, end_at=?, late_entry_minutes=?, attempt_limit=?, session_name=?, semester=?, level=?, faculty_id=?, department_id=?, programme_id=?, status=?, pass_mark=?, question_per_page=?, negative_mark_per_wrong=?, randomize_questions=?, randomize_options=?, published=?, results_published=?, instant_result_enabled=?, answer_review_enabled=?, negative_marking_enabled=?, mark_per_question=?, browser_tolerance_count=?, auto_submit_on_timeout=?, sectional_enabled=?, calculator_enabled=?, fullscreen_required=?, anti_copy_enabled=?, anti_switch_enabled=?, exam_mode=?, updated_by=?, updated_at=NOW() WHERE id=?";
                            $params = [$title, $examCode, $description, $instructions, $courseCode, $subjectName, $classGroup, $duration, $startAt ?: null, $endAt ?: null, $lateEntry, $attemptLimit, $sessionName, $semester, $level, $facultyId ?: null, $departmentId ?: null, $programmeId ?: null, $status, $passMark, $questionPerPage, $negativeMark, $randomizeQuestions, $randomizeOptions, $published, $resultsPublished, $instantResult, $answerReview, $negativeMarkingEnabled, $markPerQuestion, $browserTolerance, $autoSubmit, $sectional, $calculatorEnabled, $fullscreenRequired, $antiCopy, $antiSwitch, $examMode, $adminId ?: null, $id];
                            $pdo->prepare($sql)->execute($params);
                            $testIdSaved = $id;
                        } else {
                            $sql = "INSERT INTO cbt_tests (title, exam_code, description, instructions, course_code, subject_name, class_group, duration_minutes, start_at, end_at, late_entry_minutes, attempt_limit, session_name, semester, level, faculty_id, department_id, programme_id, status, pass_mark, question_per_page, negative_mark_per_wrong, randomize_questions, randomize_options, published, results_published, instant_result_enabled, answer_review_enabled, negative_marking_enabled, mark_per_question, browser_tolerance_count, auto_submit_on_timeout, sectional_enabled, calculator_enabled, fullscreen_required, anti_copy_enabled, anti_switch_enabled, exam_mode, created_by, updated_by, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?, NOW(), NOW())";
                            $params = [$title, $examCode, $description, $instructions, $courseCode, $subjectName, $classGroup, $duration, $startAt ?: null, $endAt ?: null, $lateEntry, $attemptLimit, $sessionName, $semester, $level, $facultyId ?: null, $departmentId ?: null, $programmeId ?: null, $status, $passMark, $questionPerPage, $negativeMark, $randomizeQuestions, $randomizeOptions, $published, $resultsPublished, $instantResult, $answerReview, $negativeMarkingEnabled, $markPerQuestion, $browserTolerance, $autoSubmit, $sectional, $calculatorEnabled, $fullscreenRequired, $antiCopy, $antiSwitch, $examMode, $adminId ?: null, $adminId ?: null];
                            $pdo->prepare($sql)->execute($params);
                            $testIdSaved = (int)$pdo->lastInsertId();
                        }

                        $upload = moduleSaveUpload($_FILES['test_file'] ?? [], 'cbt_tests', 'test', $testIdSaved);
                        if ($upload && !empty($upload['path'])) {
                            $pdo->prepare("UPDATE cbt_tests SET attachment_path=?, attachment_name=?, attachment_mime=?, attachment_size=?, updated_at=NOW() WHERE id=?")
                                ->execute([(string)$upload['path'], (string)($upload['name'] ?? ''), (string)($upload['mime'] ?? ''), (int)($upload['size'] ?? 0), $testIdSaved]);
                        }

                        portalCbtSyncTestTotals($pdo, $testIdSaved);
                        portalCbtLogActivity($pdo, 'test', $testIdSaved, 'save_test', $id > 0 ? 'CBT exam updated' : 'CBT exam created', ['title' => $title, 'exam_code' => $examCode], 'admin', $adminId ?: null);
                        flash('success', $id > 0 ? 'CBT exam updated successfully.' : 'CBT exam created successfully.');
                    } catch (Throwable $e) {
                        flash('danger', 'Failed to save CBT exam.');
                    }
                    redirect('?page=cbt_admin&tab=tests&edit_test_id=' . (int)($testIdSaved ?? $id));
                }

                if ($act === 'clone_test') {
                    $id = (int)($_GET['id'] ?? 0);
                    if ($id > 0) {
                        try {
                            $st = $pdo->prepare("SELECT * FROM cbt_tests WHERE id = ? LIMIT 1");
                            $st->execute([$id]);
                            $testRow = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                            if ($testRow) {
                                unset($testRow['id']);
                                $testRow['title'] = trim((string)$testRow['title']) . ' (Clone)';
                                $testRow['published'] = 0;
                                $testRow['results_published'] = 0;
                                $testRow['exam_mode'] = 'draft';
                                $testRow['created_by'] = $adminId ?: null;
                                $testRow['updated_by'] = $adminId ?: null;
                                $columns = array_keys($testRow);
                                $columns = array_values(array_filter($columns, static fn($c) => !in_array($c, ['created_at','updated_at','approved_at','archived_at'], true)));
                                $placeholders = implode(',', array_fill(0, count($columns), '?'));
                                $sql = "INSERT INTO cbt_tests (" . implode(',', $columns) . ", created_at, updated_at) VALUES (" . $placeholders . ", NOW(), NOW())";
                                $pdo->prepare($sql)->execute(array_map(static fn($c) => $testRow[$c], $columns));
                                $newTestId = (int)$pdo->lastInsertId();

                                $qs = $pdo->prepare("SELECT * FROM cbt_questions WHERE test_id = ? ORDER BY id ASC");
                                $qs->execute([$id]);
                                $questionRows = $qs->fetchAll(PDO::FETCH_ASSOC) ?: [];
                                foreach ($questionRows as $questionRow) {
                                    unset($questionRow['id']);
                                    $questionRow['test_id'] = $newTestId;
                                    $questionRow['created_by'] = $adminId ?: null;
                                    $questionRow['updated_by'] = $adminId ?: null;
                                    $qCols = array_keys($questionRow);
                                    $qCols = array_values(array_filter($qCols, static fn($c) => !in_array($c, ['created_at','updated_at'], true)));
                                    $qSql = "INSERT INTO cbt_questions (" . implode(',', $qCols) . ", created_at, updated_at) VALUES (" . implode(',', array_fill(0, count($qCols), '?')) . ", NOW(), NOW())";
                                    $pdo->prepare($qSql)->execute(array_map(static fn($c) => $questionRow[$c], $qCols));
                                }
                                portalCbtSyncTestTotals($pdo, $newTestId);
                                portalCbtLogActivity($pdo, 'test', $newTestId, 'clone_test', 'CBT exam cloned', ['source_test_id' => $id], 'admin', $adminId ?: null);
                                flash('success', 'CBT exam cloned successfully.');
                            }
                        } catch (Throwable $e) {
                            flash('danger', 'Failed to clone CBT exam.');
                        }
                    }
                    redirect('?page=cbt_admin&tab=tests');
                }

                if ($act === 'delete_test') {
                    $id = (int)($_GET['id'] ?? 0);
                    if ($id > 0) {
                        try {
                            $pdo->prepare("DELETE FROM cbt_manual_scores WHERE attempt_id IN (SELECT id FROM cbt_attempts WHERE test_id = ?)")->execute([$id]);
                        } catch (Throwable $e) {}
                        try { $pdo->prepare("DELETE FROM cbt_incidents WHERE test_id = ?")->execute([$id]); } catch (Throwable $e) {}
                        try { $pdo->prepare("DELETE FROM cbt_assignments WHERE test_id = ?")->execute([$id]); } catch (Throwable $e) {}
                        try { $pdo->prepare("DELETE FROM cbt_attempt_archives WHERE test_id = ?")->execute([$id]); } catch (Throwable $e) {}
                        try { $pdo->prepare("DELETE FROM cbt_attempts WHERE test_id = ?")->execute([$id]); } catch (Throwable $e) {}
                        try { $pdo->prepare("DELETE FROM cbt_questions WHERE test_id = ?")->execute([$id]); } catch (Throwable $e) {}
                        try { $pdo->prepare("DELETE FROM cbt_tests WHERE id = ?")->execute([$id]); } catch (Throwable $e) {}
                        portalCbtLogActivity($pdo, 'test', $id, 'delete_test', 'CBT exam deleted', [], 'admin', $adminId ?: null);
                        flash('success', 'CBT exam deleted.');
                    }
                    redirect('?page=cbt_admin&tab=tests');
                }

                if ($act === 'save_question' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $qid = (int)($_POST['id'] ?? 0);
                    $test_id = (int)($_POST['test_id'] ?? 0);
                    $questionText = trim((string)($_POST['question'] ?? ''));
                    $questionType = portalCbtQuestionType($_POST['question_type'] ?? 'single_choice');
                    $sectionName = trim((string)($_POST['section_name'] ?? ''));
                    $subjectName = trim((string)($_POST['subject_name'] ?? ''));
                    $difficulty = strtolower(trim((string)($_POST['difficulty_level'] ?? 'medium')));
                    if (!in_array($difficulty, ['easy','medium','hard'], true)) $difficulty = 'medium';
                    $topicTag = trim((string)($_POST['topic_tag'] ?? ''));
                    $optionA = trim((string)($_POST['option_a'] ?? ''));
                    $optionB = trim((string)($_POST['option_b'] ?? ''));
                    $optionC = trim((string)($_POST['option_c'] ?? ''));
                    $optionD = trim((string)($_POST['option_d'] ?? ''));
                    $extraOptionsRaw = trim((string)($_POST['extra_options'] ?? ''));
                    $correctOption = strtoupper(trim((string)($_POST['correct_option'] ?? '')));
                    $correctAnswerText = trim((string)($_POST['correct_answer_text'] ?? ''));
                    $correctMulti = array_values(array_unique(array_filter(array_map(static fn($v) => strtoupper(trim((string)$v)), (array)($_POST['correct_multi'] ?? [])), static fn($v) => $v !== '')));
                    sort($correctMulti);
                    $marks = max(0.01, (float)($_POST['marks'] ?? 1));
                    $questionStatus = strtolower(trim((string)($_POST['status'] ?? 'active')));
                    if (!in_array($questionStatus, ['active','inactive','draft'], true)) $questionStatus = 'active';
                    $explanation = trim((string)($_POST['explanation'] ?? ''));

                    if ($test_id <= 0 || $questionText === '') {
                        flash('danger', 'Select a test and enter the question text.');
                        redirect('?page=cbt_admin&tab=questions&test_id=' . $test_id);
                    }

                    $optionsPayload = [];
                    foreach (['A' => $optionA, 'B' => $optionB, 'C' => $optionC, 'D' => $optionD] as $label => $value) {
                        if (trim($value) !== '') $optionsPayload[$label] = ['label' => $label, 'text' => trim($value)];
                    }
                    if ($extraOptionsRaw !== '') {
                        foreach (preg_split('/\r\n|\r|\n/', $extraOptionsRaw) as $line) {
                            $line = trim((string)$line);
                            if ($line === '') continue;
                            if (preg_match('/^\s*([A-Z0-9]+)\s*[:\-\)]\s*(.+)$/i', $line, $m)) {
                                $label = strtoupper(trim((string)$m[1]));
                                $textValue = trim((string)$m[2]);
                            } else {
                                $label = (string)(count($optionsPayload) + 1);
                                $textValue = $line;
                            }
                            if ($textValue !== '') $optionsPayload[$label] = ['label' => $label, 'text' => $textValue];
                        }
                    }

                    if ($questionType === 'true_false') {
                        $optionsPayload = ['A' => ['label' => 'A', 'text' => 'True'], 'B' => ['label' => 'B', 'text' => 'False']];
                        if (!in_array($correctOption, ['A','B'], true)) $correctOption = 'A';
                    }
                    if ($questionType === 'multiple_choice') {
                        $correctAnswerText = json_encode($correctMulti);
                        if ($correctOption === '') $correctOption = implode(',', $correctMulti);
                    } elseif (in_array($questionType, ['short_answer','fill_blank','essay'], true)) {
                        $correctOption = '';
                    } else {
                        if (!in_array($correctOption, array_keys($optionsPayload), true)) {
                            $correctOption = array_key_first($optionsPayload) ?: 'A';
                        }
                    }

                    try {
                        if ($qid > 0) {
                            $pdo->prepare("UPDATE cbt_questions SET question_type=?, section_name=?, subject_name=?, difficulty_level=?, topic_tag=?, question=?, option_a=?, option_b=?, option_c=?, option_d=?, options_json=?, correct_option=?, correct_answer_text=?, explanation=?, marks=?, status=?, updated_by=?, updated_at=NOW() WHERE id=? AND test_id=?")
                                ->execute([$questionType, $sectionName, $subjectName, $difficulty, $topicTag, $questionText, $optionA, $optionB, $optionC, $optionD, json_encode($optionsPayload), $correctOption, $correctAnswerText, $explanation, $marks, $questionStatus, $adminId ?: null, $qid, $test_id]);
                            $questionIdSaved = $qid;
                        } else {
                            $pdo->prepare("INSERT INTO cbt_questions (test_id, question_type, section_name, subject_name, difficulty_level, topic_tag, question, option_a, option_b, option_c, option_d, options_json, correct_option, correct_answer_text, explanation, marks, status, created_by, updated_by, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, NOW(), NOW())")
                                ->execute([$test_id, $questionType, $sectionName, $subjectName, $difficulty, $topicTag, $questionText, $optionA, $optionB, $optionC, $optionD, json_encode($optionsPayload), $correctOption, $correctAnswerText, $explanation, $marks, $questionStatus, $adminId ?: null, $adminId ?: null]);
                            $questionIdSaved = (int)$pdo->lastInsertId();
                        }

                        $questionImage = moduleSaveUpload($_FILES['question_image'] ?? [], 'cbt_questions', 'qimg', $questionIdSaved);
                        if ($questionImage && !empty($questionImage['path'])) {
                            $pdo->prepare("UPDATE cbt_questions SET question_image_path=?, question_image_name=?, question_image_mime=?, question_image_size=?, updated_at=NOW() WHERE id=?")
                                ->execute([(string)$questionImage['path'], (string)($questionImage['name'] ?? ''), (string)($questionImage['mime'] ?? ''), (int)($questionImage['size'] ?? 0), $questionIdSaved]);
                        }
                        $mediaFile = moduleSaveUpload($_FILES['question_media'] ?? [], 'cbt_questions', 'qmedia', $questionIdSaved);
                        if ($mediaFile && !empty($mediaFile['path'])) {
                            $pdo->prepare("UPDATE cbt_questions SET media_path=?, media_name=?, media_mime=?, media_size=?, updated_at=NOW() WHERE id=?")
                                ->execute([(string)$mediaFile['path'], (string)($mediaFile['name'] ?? ''), (string)($mediaFile['mime'] ?? ''), (int)($mediaFile['size'] ?? 0), $questionIdSaved]);
                        }

                        portalCbtSyncTestTotals($pdo, $test_id);
                        portalCbtLogActivity($pdo, 'question', $questionIdSaved, 'save_question', $qid > 0 ? 'Question updated' : 'Question created', ['test_id' => $test_id, 'question_type' => $questionType], 'admin', $adminId ?: null);
                        flash('success', 'Question saved successfully.');
                    } catch (Throwable $e) {
                        flash('danger', 'Failed to save question.');
                    }
                    redirect('?page=cbt_admin&tab=questions&test_id=' . $test_id . '&edit_question_id=' . (int)($questionIdSaved ?? $qid));
                }

                if ($act === 'delete_question') {
                    $qid = (int)($_GET['id'] ?? 0);
                    $test_id = (int)($_GET['test_id'] ?? 0);
                    if ($qid > 0) {
                        try { $pdo->prepare("DELETE FROM cbt_manual_scores WHERE question_id = ?")->execute([$qid]); } catch (Throwable $e) {}
                        try { $pdo->prepare("DELETE FROM cbt_questions WHERE id = ?")->execute([$qid]); } catch (Throwable $e) {}
                        portalCbtSyncTestTotals($pdo, $test_id);
                        portalCbtLogActivity($pdo, 'question', $qid, 'delete_question', 'Question deleted', ['test_id' => $test_id], 'admin', $adminId ?: null);
                        flash('success', 'Question deleted.');
                    }
                    redirect('?page=cbt_admin&tab=questions&test_id=' . $test_id);
                }

                if ($act === 'toggle_test_publish') {
                    $id = (int)($_GET['id'] ?? 0);
                    if ($id > 0) {
                        try {
                            $pdo->prepare("UPDATE cbt_tests SET published = CASE WHEN published = 1 THEN 0 ELSE 1 END, exam_mode = CASE WHEN published = 1 THEN 'draft' ELSE 'live' END, approved_by = ?, approved_at = NOW(), updated_by = ?, updated_at = NOW() WHERE id = ?")
                                ->execute([$adminId ?: null, $adminId ?: null, $id]);
                            portalCbtLogActivity($pdo, 'test', $id, 'toggle_test_publish', 'Exam publish state changed', [], 'admin', $adminId ?: null);
                        } catch (Throwable $e) {}
                        flash('success', 'Exam publish state updated.');
                    }
                    redirect('?page=cbt_admin&tab=tests');
                }

                if ($act === 'toggle_result_publish') {
                    $id = (int)($_GET['id'] ?? 0);
                    if ($id > 0) {
                        try {
                            $pdo->prepare("UPDATE cbt_tests SET results_published = CASE WHEN results_published = 1 THEN 0 ELSE 1 END, updated_by = ?, updated_at = NOW() WHERE id = ?")
                                ->execute([$adminId ?: null, $id]);
                            $pdo->prepare("UPDATE cbt_attempts SET result_status = CASE WHEN (SELECT results_published FROM cbt_tests WHERE id = ?) = 1 AND grading_status = 'graded' THEN 'published' WHEN grading_status = 'graded' THEN 'approved' ELSE result_status END WHERE test_id = ?")
                                ->execute([$id, $id]);
                            portalCbtLogActivity($pdo, 'test', $id, 'toggle_result_publish', 'Result visibility changed', [], 'admin', $adminId ?: null);
                            $publishedRow = []; try { $stPub = $pdo->prepare("SELECT results_published FROM cbt_tests WHERE id = ? LIMIT 1"); $stPub->execute([$id]); $publishedRow = $stPub->fetch(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) {} if (!empty($publishedRow['results_published'])) { portalCbtSendResultPublishedNotifications($pdo, $id); }
                        } catch (Throwable $e) {}
                        flash('success', 'Result visibility updated.');
                    }
                    redirect('?page=cbt_admin&tab=tests');
                }

                if ($act === 'save_assignment' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $testId = (int)($_POST['test_id'] ?? 0);
                    if ($testId > 0) {
                        try {
                            $pdo->prepare("DELETE FROM cbt_assignments WHERE test_id = ?")->execute([$testId]);
                            $scopeMap = [
                                'faculty' => array_filter(array_map('trim', explode(',', (string)($_POST['assign_faculty_ids'] ?? '')))),
                                'department' => array_filter(array_map('trim', explode(',', (string)($_POST['assign_department_ids'] ?? '')))),
                                'programme' => array_filter(array_map('trim', explode(',', (string)($_POST['assign_programme_ids'] ?? '')))),
                                'level' => array_filter(array_map('trim', explode(',', (string)($_POST['assign_levels'] ?? '')))),
                                'session' => array_filter(array_map('trim', explode(',', (string)($_POST['assign_sessions'] ?? '')))),
                                'class_group' => array_filter(array_map('trim', explode(',', (string)($_POST['assign_class_groups'] ?? '')))),
                            ];
                            $ins = $pdo->prepare("INSERT INTO cbt_assignments (test_id, scope_type, scope_value, student_id, is_whitelist, is_blacklist, can_retake, created_by) VALUES (?,?,?,?,?,?,?,?)");
                            if (isset($_POST['assign_all'])) {
                                $ins->execute([$testId, 'all', 'all', null, 1, 0, 0, $adminId ?: null]);
                            }
                            foreach ($scopeMap as $scopeType => $values) {
                                foreach ($values as $value) {
                                    $ins->execute([$testId, $scopeType, $value, null, 1, 0, 0, $adminId ?: null]);
                                }
                            }
                            foreach (array_filter(array_map('trim', explode(',', (string)($_POST['whitelist_student_ids'] ?? '')))) as $sid) {
                                $ins->execute([$testId, 'student', (string)$sid, (int)$sid ?: null, 1, 0, !empty($_POST['whitelist_can_retake']) ? 1 : 0, $adminId ?: null]);
                            }
                            foreach (array_filter(array_map('trim', explode(',', (string)($_POST['blacklist_student_ids'] ?? '')))) as $sid) {
                                $ins->execute([$testId, 'student', (string)$sid, (int)$sid ?: null, 0, 1, 0, $adminId ?: null]);
                            }
                            portalCbtLogActivity($pdo, 'assignment', $testId, 'save_assignment', 'Exam assignment rules updated', [], 'admin', $adminId ?: null);
                            portalCbtSendExamAssignmentNotifications($pdo, $testId);
                            flash('success', 'Exam assignment rules saved and synchronized to student notifications.');
                        } catch (Throwable $e) {
                            flash('danger', 'Failed to save assignment rules.');
                        }
                    }
                    redirect('?page=cbt_admin&tab=tests&edit_test_id=' . $testId);
                }

                if ($act === 'update_student_access' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $studentIdPost = (int)($_POST['student_id'] ?? 0);
                    $accessStatus = trim((string)($_POST['access_status'] ?? 'active'));
                    $note = trim((string)($_POST['access_note'] ?? ''));
                    portalCbtUpsertStudentAccess($pdo, $studentIdPost, $accessStatus, $note, $adminId ?: null);
                    portalCbtLogActivity($pdo, 'student_access', $studentIdPost, 'update_student_access', 'Student CBT access updated', ['status' => $accessStatus], 'admin', $adminId ?: null);
                    flash('success', 'Student CBT access updated.');
                    redirect('?page=cbt_admin&tab=students&student_q=' . urlencode($student_search) . '&student_status=' . urlencode($student_status_filter));
                }

                if ($act === 'save_settings' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $settingsRow = portalCbtSettings($pdo);
                    $settingId = (int)($settingsRow['id'] ?? 0);
                    $siteName = trim((string)($_POST['site_name'] ?? 'HontTech CBT Manager'));
                    $timezoneName = trim((string)($_POST['timezone_name'] ?? 'Africa/Lagos'));
                    $defaultInstructions = trim((string)($_POST['default_instructions'] ?? ''));
                    $defaultTolerance = max(0, (int)($_POST['default_browser_tolerance'] ?? 3));
                    $defaultExamUi = trim((string)($_POST['default_exam_ui'] ?? 'modern'));
                    $emailEnabled = isset($_POST['email_notifications_enabled']) ? 1 : 0;
                    $smsPlaceholder = isset($_POST['sms_placeholder_enabled']) ? 1 : 0;
                    $tplAssign = trim((string)($_POST['email_template_assignment'] ?? ''));
                    $tplResult = trim((string)($_POST['email_template_result'] ?? ''));
                    try {
                        $pdo->prepare("UPDATE cbt_settings SET site_name=?, timezone_name=?, default_instructions=?, default_browser_tolerance=?, default_exam_ui=?, email_notifications_enabled=?, sms_placeholder_enabled=?, email_template_assignment=?, email_template_result=? WHERE id=?")
                            ->execute([$siteName, $timezoneName, $defaultInstructions, $defaultTolerance, $defaultExamUi, $emailEnabled, $smsPlaceholder, $tplAssign, $tplResult, $settingId]);
                        portalCbtLogActivity($pdo, 'settings', $settingId, 'save_settings', 'CBT settings updated', [], 'admin', $adminId ?: null);
                        flash('success', 'CBT settings updated.');
                    } catch (Throwable $e) {
                        flash('danger', 'Failed to update CBT settings.');
                    }
                    redirect('?page=cbt_admin&tab=settings');
                }

                if ($act === 'grade_attempt' && $_SERVER['REQUEST_METHOD'] === 'POST') {
                    $attemptId = (int)($_POST['attempt_id'] ?? 0);
                    if ($attemptId > 0) {
                        try {
                            $stAttempt = $pdo->prepare("SELECT a.*, t.id AS test_id FROM cbt_attempts a INNER JOIN cbt_tests t ON t.id = a.test_id WHERE a.id = ? LIMIT 1");
                            $stAttempt->execute([$attemptId]);
                            $attemptRow = $stAttempt->fetch(PDO::FETCH_ASSOC) ?: [];
                            $questionIds = portalCbtJsonDecode($attemptRow['question_order_json'] ?? '[]', []);
                            $questionsForMarking = portalCbtFetchQuestionsByIds($pdo, $questionIds);
                            $scoreIns = $pdo->prepare("INSERT INTO cbt_manual_scores (attempt_id, question_id, marks_awarded, marker_comment, graded_by, graded_at) VALUES (?,?,?,?,?,NOW())
                                ON DUPLICATE KEY UPDATE marks_awarded = VALUES(marks_awarded), marker_comment = VALUES(marker_comment), graded_by = VALUES(graded_by), graded_at = NOW()");
                            foreach ($questionsForMarking as $questionRow) {
                                if (portalCbtQuestionType($questionRow['question_type'] ?? '') !== 'essay') continue;
                                $qid = (int)($questionRow['id'] ?? 0);
                                $marksAwarded = max(0, (float)($_POST['manual_marks'][$qid] ?? 0));
                                $comment = trim((string)($_POST['manual_comment'][$qid] ?? ''));
                                $scoreIns->execute([$attemptId, $qid, $marksAwarded, $comment !== '' ? $comment : null, $adminId ?: null]);
                            }
                            $pdo->prepare("UPDATE cbt_attempts SET reviewed_by = ?, reviewed_at = NOW(), grading_status = 'graded', updated_at = NOW() WHERE id = ?")->execute([$adminId ?: null, $attemptId]);
                            portalCbtAutoScoreAttempt($pdo, $attemptId);
                            portalCbtLogActivity($pdo, 'attempt', $attemptId, 'grade_attempt', 'Attempt manually graded', [], 'admin', $adminId ?: null);
                            if (!empty($attemptRow['test_id'])) { try { $stTestPublish = $pdo->prepare("SELECT results_published FROM cbt_tests WHERE id = ? LIMIT 1"); $stTestPublish->execute([(int)$attemptRow['test_id']]); $testPublishRow = $stTestPublish->fetch(PDO::FETCH_ASSOC) ?: []; if (!empty($testPublishRow['results_published'])) { portalCbtSendResultPublishedNotifications($pdo, (int)$attemptRow['test_id'], $attemptId); } } catch (Throwable $e) {} }
                            flash('success', 'Manual grading saved.');
                        } catch (Throwable $e) {
                            flash('danger', 'Failed to save manual grading.');
                        }
                    }
                    redirect('?page=cbt_admin&tab=results&attempt_id=' . $attemptId);
                }


                if ($act === 'set_attempt_result_status') {
                    $attemptId = (int)($_GET['attempt_id'] ?? $_POST['attempt_id'] ?? 0);
                    $mode = strtolower(trim((string)($_GET['mode'] ?? $_POST['mode'] ?? '')));
                    if ($attemptId > 0 && in_array($mode, ['approve','withhold','release'], true)) {
                        try {
                            $attemptCtx = portalCbtAttemptContext($pdo, $attemptId);
                            if ($attemptCtx) {
                                $newStatus = 'approved';
                                if ($mode === 'withhold') $newStatus = 'withheld';
                                else $newStatus = !empty($attemptCtx['results_published']) && strtolower((string)($attemptCtx['grading_status'] ?? 'pending')) === 'graded' ? 'published' : 'approved';
                                $pdo->prepare("UPDATE cbt_attempts SET result_status = ?, reviewed_by = COALESCE(reviewed_by, ?), reviewed_at = COALESCE(reviewed_at, NOW()), updated_at = NOW() WHERE id = ?")
                                    ->execute([$newStatus, $adminId ?: null, $attemptId]);
                                portalCbtLogActivity($pdo, 'attempt', $attemptId, 'set_attempt_result_status', 'Attempt result status updated', ['mode' => $mode, 'result_status' => $newStatus], 'admin', $adminId ?: null);
                                if ($newStatus === 'published') portalCbtSendResultPublishedNotifications($pdo, (int)($attemptCtx['test_id'] ?? 0), $attemptId);
                                flash('success', 'Result status updated successfully.');
                            }
                        } catch (Throwable $e) {
                            flash('danger', 'Failed to update result status.');
                        }
                    }
                    redirect('?page=cbt_admin&tab=results&attempt_id=' . $attemptId);
                }

                if ($act === 'reset_attempt') {
                    $attemptId = (int)($_GET['attempt_id'] ?? 0);
                    if ($attemptId > 0) {
                        try {
                            $st = $pdo->prepare("SELECT * FROM cbt_attempts WHERE id = ? LIMIT 1");
                            $st->execute([$attemptId]);
                            $attemptRow = $st->fetch(PDO::FETCH_ASSOC) ?: [];
                            if ($attemptRow) {
                                portalCbtArchiveAttemptSnapshot($pdo, $attemptRow);
                                $durationSeconds = max(60, ((int)($attemptRow['remaining_seconds'] ?? 0)) > 0 ? (int)$attemptRow['remaining_seconds'] : 1800);
                                $pdo->prepare("UPDATE cbt_attempts SET status='started', submitted_at=NULL, answers_json=?, remaining_seconds=?, current_question_no=1, score=NULL, objective_score=NULL, theory_score=NULL, percentage_score=NULL, max_score=NULL, grade_label=NULL, pass_fail_status=NULL, grading_status='pending', result_status='pending', submitted_hash=NULL, incidents_json=?, incident_count=0, tolerance_used=0, last_seen_at=NOW(), updated_at=NOW() WHERE id = ?")
                                    ->execute([json_encode(new stdClass()), $durationSeconds, json_encode([]), $attemptId]);
                                portalCbtLogActivity($pdo, 'attempt', $attemptId, 'reset_attempt', 'Attempt reset by admin', [], 'admin', $adminId ?: null);
                                flash('success', 'Attempt reset successfully.');
                            }
                        } catch (Throwable $e) {
                            flash('danger', 'Failed to reset attempt.');
                        }
                    }
                    redirect('?page=cbt_admin&tab=attempts');
                }

                $tests = [];
                try {
                    $tests = $pdo->query("SELECT t.*, COALESCE(COUNT(DISTINCT q.id),0) AS question_count, COALESCE(COUNT(DISTINCT a.id),0) AS attempt_count, COALESCE(AVG(a.percentage_score),0) AS avg_percent
                        FROM cbt_tests t
                        LEFT JOIN cbt_questions q ON q.test_id = t.id
                        LEFT JOIN cbt_attempts a ON a.test_id = t.id
                        GROUP BY t.id
                        ORDER BY COALESCE(t.updated_at, t.created_at) DESC, t.id DESC
                        LIMIT 300")->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) { $tests = []; }

                $questions = [];
                if ($test_id > 0) {
                    try {
                        $st = $pdo->prepare("SELECT * FROM cbt_questions WHERE test_id = ? ORDER BY id DESC LIMIT 500");
                        $st->execute([$test_id]);
                        $questions = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
                    } catch (Throwable $e) { $questions = []; }
                }

                $attempts = [];
                try {
                    $attempts = $pdo->query("SELECT a.*, t.title AS test_title, t.course_code, t.pass_mark, t.results_published, s.matric_no, s.registration_no, s.admission_no, s.email, s.phone, s.level, s.session, s.department, s.programme, s.class_group, CONCAT_WS(' ', NULLIF(TRIM(s.surname),''), NULLIF(TRIM(s.first_name),''), NULLIF(TRIM(s.other_name),'')) AS student_name
                        FROM cbt_attempts a
                        INNER JOIN cbt_tests t ON t.id = a.test_id
                        INNER JOIN students s ON s.id = a.student_id
                        ORDER BY COALESCE(a.submitted_at, a.started_at) DESC, a.id DESC
                        LIMIT 400")->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) { $attempts = []; }

                $pendingResults = array_values(array_filter($attempts, static fn($row) => strtolower(trim((string)($row['grading_status'] ?? ''))) === 'awaiting_manual_grading'));
                $stats = [
                    'total_tests' => count($tests),
                    'active_tests' => count(array_filter($tests, static fn($row) => strtolower(trim((string)($row['status'] ?? ''))) === 'active')),
                    'live_tests' => count(array_filter($tests, static fn($row) => (int)($row['published'] ?? 0) === 1)),
                    'total_attempts' => count($attempts),
                    'completed_attempts' => count(array_filter($attempts, static fn($row) => !empty($row['submitted_at']))),
                    'total_students' => 0,
                    'pass_rate' => 0,
                ];
                try {
                    $stats['total_students'] = (int)$pdo->query("SELECT COUNT(*) FROM students")->fetchColumn();
                } catch (Throwable $e) {}
                $gradedAttempts = array_values(array_filter($attempts, static fn($row) => strtolower(trim((string)($row['grading_status'] ?? ''))) === 'graded'));
                if ($gradedAttempts) {
                    $passed = count(array_filter($gradedAttempts, static fn($row) => strtolower(trim((string)($row['pass_fail_status'] ?? ''))) === 'passed'));
                    $stats['pass_rate'] = round(($passed / count($gradedAttempts)) * 100, 1);
                }

                $studentRows = [];
                try {
                    $sql = "SELECT s.*, csa.access_status, csa.note AS cbt_access_note,
                        CONCAT_WS(' ', NULLIF(TRIM(s.surname),''), NULLIF(TRIM(s.first_name),''), NULLIF(TRIM(s.other_name),'')) AS student_name
                        FROM students s
                        LEFT JOIN cbt_student_access csa ON csa.student_id = s.id
                        WHERE 1=1";
                    $params = [];
                    if ($student_search !== '') {
                        $sql .= " AND (s.matric_no LIKE ? OR s.registration_no LIKE ? OR s.admission_no LIKE ? OR s.email LIKE ? OR CONCAT_WS(' ', NULLIF(TRIM(s.surname),''), NULLIF(TRIM(s.first_name),''), NULLIF(TRIM(s.other_name),'')) LIKE ?)";
                        $like = '%' . $student_search . '%';
                        $params = [$like, $like, $like, $like, $like];
                    }
                    if ($student_status_filter !== '' && $student_status_filter !== 'all') {
                        $sql .= " AND COALESCE(csa.access_status, 'active') = ?";
                        $params[] = $student_status_filter;
                    }
                    $sql .= " ORDER BY s.id DESC LIMIT 300";
                    $st = $pdo->prepare($sql);
                    $st->execute($params);
                    $studentRows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) { $studentRows = []; }


                $reportBundle = portalCbtReportBundle($pdo, [
                    'test_id' => $report_test_id,
                    'status' => $report_status,
                    'date_from' => $report_from,
                    'date_to' => $report_to,
                ]);
                if ($tab === 'reports' && $reportDownload === 'cbt_reports_csv') {
                    $csvRows = [];
                    foreach (($reportBundle['rows'] ?? []) as $reportRow) {
                        $csvRows[] = [
                            'exam' => (string)($reportRow['test_title'] ?? ''), 'exam_code' => (string)($reportRow['exam_code'] ?? ''), 'course_code' => (string)($reportRow['course_code'] ?? ''),
                            'student_name' => (string)($reportRow['student_name'] ?? ''), 'matric_no' => (string)($reportRow['matric_no'] ?? ($reportRow['registration_no'] ?? $reportRow['admission_no'] ?? '')),
                            'department' => (string)($reportRow['department'] ?? ''), 'programme' => (string)($reportRow['programme'] ?? ''), 'status' => (string)($reportRow['status'] ?? ''),
                            'grading_status' => (string)($reportRow['grading_status'] ?? ''), 'result_status' => (string)($reportRow['result_status'] ?? ''), 'score' => (string)($reportRow['score'] ?? ''),
                            'max_score' => (string)($reportRow['max_score'] ?? ''), 'percentage' => (string)($reportRow['percentage_score'] ?? ''), 'grade' => (string)($reportRow['grade_label'] ?? ''),
                            'decision' => (string)($reportRow['pass_fail_status'] ?? ''), 'submitted_at' => (string)($reportRow['submitted_at'] ?? ''),
                        ];
                    }
                    moduleCsvDownload('cbt_reports_export.csv', ['exam','exam_code','course_code','student_name','matric_no','department','programme','status','grading_status','result_status','score','max_score','percentage','grade','decision','submitted_at'], $csvRows);
                }
                if ($tab === 'reports' && $reportPrint === 'attendance_sheet') {
                    echo '<div class="container py-3"><div class="d-flex justify-content-between align-items-center mb-3"><a href="?page=cbt_admin&tab=reports&report_test_id=' . (int)$report_test_id . '" class="btn btn-outline-secondary btn-sm">Back</a><button onclick="window.print()" class="btn btn-primary btn-sm">Print Attendance Sheet</button></div><div class="card p-3"><h3 class="mb-3">CBT Attendance Sheet</h3><div class="table-responsive"><table class="table table-bordered align-middle"><thead><tr><th>#</th><th>Student</th><th>Matric / Reg No.</th><th>Exam</th><th>Status</th><th>Submitted</th><th>Signature</th></tr></thead><tbody>';
                    $sn = 1;
                    foreach (($reportBundle['rows'] ?? []) as $reportRow) {
                        echo '<tr><td>' . $sn++ . '</td><td>' . e((string)($reportRow['student_name'] ?? 'Student')) . '</td><td>' . e((string)($reportRow['matric_no'] ?? ($reportRow['registration_no'] ?? $reportRow['admission_no'] ?? ''))) . '</td><td>' . e((string)($reportRow['test_title'] ?? '')) . '</td><td>' . e(ucfirst((string)($reportRow['status'] ?? ''))) . '</td><td>' . (!empty($reportRow['submitted_at']) ? e(date('d M Y h:i A', strtotime((string)$reportRow['submitted_at']))) : '—') . '</td><td style="min-width:140px"></td></tr>';
                    }
                    if (empty($reportBundle['rows'])) echo '<tr><td colspan="7" class="text-muted">No report rows found for the selected filter.</td></tr>';
                    echo '</tbody></table></div></div></div><script>window.addEventListener("load",function(){window.print();});</script>';
                    break;
                }

                $settings = portalCbtSettings($pdo);
                $logs = [];

                try {
                    $logs = $pdo->query("SELECT * FROM cbt_activity_logs ORDER BY id DESC LIMIT 200")->fetchAll(PDO::FETCH_ASSOC) ?: [];
                } catch (Throwable $e) { $logs = []; }

                $editingTest = [];
                if ($edit_test_id > 0) {
                    foreach ($tests as $t) { if ((int)($t['id'] ?? 0) === $edit_test_id) { $editingTest = $t; break; } }
                }
                if (!$editingTest) {
                    $editingTest = ['id' => 0, 'title' => '', 'exam_code' => '', 'description' => '', 'instructions' => (string)($settings['default_instructions'] ?? ''), 'course_code' => '', 'subject_name' => '', 'class_group' => '', 'duration_minutes' => 30, 'start_at' => '', 'end_at' => '', 'late_entry_minutes' => 0, 'attempt_limit' => 1, 'session_name' => '', 'semester' => '', 'level' => '', 'faculty_id' => 0, 'department_id' => 0, 'programme_id' => 0, 'status' => 'active', 'pass_mark' => 50, 'question_per_page' => 1, 'negative_mark_per_wrong' => 0, 'mark_per_question' => 1, 'published' => 0, 'results_published' => 0, 'randomize_questions' => 0, 'randomize_options' => 0, 'instant_result_enabled' => 1, 'answer_review_enabled' => 1, 'negative_marking_enabled' => 0, 'browser_tolerance_count' => (int)($settings['default_browser_tolerance'] ?? 3), 'auto_submit_on_timeout' => 1, 'sectional_enabled' => 0, 'calculator_enabled' => 0, 'fullscreen_required' => 0, 'anti_copy_enabled' => 0, 'anti_switch_enabled' => 0, 'exam_mode' => 'draft'];
                }

                $editingQuestion = [];
                if ($edit_question_id > 0) {
                    foreach ($questions as $qRow) { if ((int)($qRow['id'] ?? 0) === $edit_question_id) { $editingQuestion = $qRow; break; } }
                }
                if (!$editingQuestion) {
                    $editingQuestion = ['id' => 0, 'question_type' => 'single_choice', 'section_name' => '', 'subject_name' => '', 'difficulty_level' => 'medium', 'topic_tag' => '', 'question' => '', 'option_a' => '', 'option_b' => '', 'option_c' => '', 'option_d' => '', 'correct_option' => 'A', 'correct_answer_text' => '', 'marks' => 1, 'status' => 'active', 'explanation' => '', 'options_json' => ''];
                }

                $selectedAttemptForGrade = [];
                if ($grade_attempt_id > 0) {
                    foreach ($attempts as $attemptRow) { if ((int)($attemptRow['id'] ?? 0) === $grade_attempt_id) { $selectedAttemptForGrade = $attemptRow; break; } }
                }

                ?>
                <style>
                    .cbt-enterprise-card{border:1px solid #dbe7ff;border-radius:18px;box-shadow:0 10px 28px rgba(13,110,253,.08);background:#fff}
                    .cbt-enterprise-kpi{border-radius:18px;padding:18px;background:linear-gradient(135deg,#0d6efd 0%,#4a8cff 100%);color:#fff;min-height:120px}
                    .cbt-enterprise-kpi .value{font-size:1.85rem;font-weight:700}
                    .cbt-enterprise-soft{background:#f4f8ff;border:1px solid #dde9ff;border-radius:16px}
                    .cbt-enterprise-table thead th{white-space:nowrap;font-size:.85rem}
                    .cbt-pill{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem .7rem;border-radius:999px;font-size:.78rem;font-weight:600}
                    .cbt-question-preview img{max-width:220px;border-radius:12px}
                    .cbt-admin-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px}
                    @media (max-width: 767.98px){.cbt-enterprise-kpi{min-height:auto}}
                </style>
                <div class="container-fluid py-3">
                    <?php renderFlashes(); ?>
                    <div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3">
                        <div>
                            <h3 class="mb-1">CBT Enterprise Manager</h3>
                            <div class="text-muted">Advanced HontTech CBT control center with enterprise exam setup, question bank, grading, analytics, logs, and scope-aware student access.</div>
                        </div>
                        <div class="d-flex flex-wrap gap-2">
                            <span class="badge text-bg-primary">Role: <?php echo e((string)(function_exists('currentStaffRole') ? currentStaffRole($pdo) : 'Admin')); ?></span>
                            <span class="badge text-bg-light border">CSRF Protected</span>
                        </div>
                    </div>

                    <ul class="nav nav-tabs flex-wrap mb-4">
                        <?php foreach (['overview' => 'Overview', 'tests' => 'Tests', 'questions' => 'Question Bank', 'attempts' => 'Attempts', 'results' => 'Results', 'reports' => 'Reports', 'students' => 'Students', 'settings' => 'Settings', 'logs' => 'Logs'] as $tabKey => $tabLabel): ?>
                            <li class="nav-item"><a class="nav-link <?php echo $tab === $tabKey ? 'active' : ''; ?>" href="?page=cbt_admin&tab=<?php echo e($tabKey); ?>"><?php echo e($tabLabel); ?></a></li>
                        <?php endforeach; ?>
                    </ul>

                    <?php if ($tab === 'overview'): ?>
                        <div class="cbt-admin-grid mb-4">
                            <div class="cbt-enterprise-kpi"><div class="small text-white-50">Total Exams</div><div class="value"><?php echo (int)$stats['total_tests']; ?></div><div class="small">All CBT test records</div></div>
                            <div class="cbt-enterprise-kpi"><div class="small text-white-50">Live Exams</div><div class="value"><?php echo (int)$stats['live_tests']; ?></div><div class="small">Published and visible exams</div></div>
                            <div class="cbt-enterprise-kpi"><div class="small text-white-50">Attempts</div><div class="value"><?php echo (int)$stats['total_attempts']; ?></div><div class="small">Started and submitted attempts</div></div>
                            <div class="cbt-enterprise-kpi"><div class="small text-white-50">Students</div><div class="value"><?php echo (int)$stats['total_students']; ?></div><div class="small">Linked student records</div></div>
                            <div class="cbt-enterprise-kpi"><div class="small text-white-50">Completed</div><div class="value"><?php echo (int)$stats['completed_attempts']; ?></div><div class="small">Submitted CBT sessions</div></div>
                            <div class="cbt-enterprise-kpi"><div class="small text-white-50">Pass Rate</div><div class="value"><?php echo number_format((float)$stats['pass_rate'], 1); ?>%</div><div class="small">Across graded attempts</div></div>
                        </div>

                        <div class="row g-4">
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4 h-100">
                                    <div class="d-flex justify-content-between align-items-center mb-3">
                                        <h5 class="mb-0">Exam Pipeline</h5>
                                        <span class="badge text-bg-light border"><?php echo (int)count($pendingResults); ?> manual grading queue</span>
                                    </div>
                                    <div class="table-responsive">
                                        <table class="table table-striped align-middle cbt-enterprise-table mb-0">
                                            <thead><tr><th>Exam</th><th>Mode</th><th>Questions</th><th>Attempts</th><th>Average</th><th>Actions</th></tr></thead>
                                            <tbody>
                                            <?php foreach (array_slice($tests, 0, 12) as $row): ?>
                                                <tr>
                                                    <td><div class="fw-semibold"><?php echo e((string)($row['title'] ?? '')); ?></div><div class="small text-muted"><?php echo e((string)($row['exam_code'] ?? '')); ?> • <?php echo e((string)($row['course_code'] ?? '')); ?></div></td>
                                                    <td><span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['published'] ?? 0) === '1' ? 'live' : (string)($row['exam_mode'] ?? 'draft')); ?>"><?php echo e((int)($row['published'] ?? 0) === 1 ? 'Published' : ucfirst((string)($row['exam_mode'] ?? 'draft'))); ?></span></td>
                                                    <td><?php echo (int)($row['question_count'] ?? 0); ?></td>
                                                    <td><?php echo (int)($row['attempt_count'] ?? 0); ?></td>
                                                    <td><?php echo number_format((float)($row['avg_percent'] ?? 0), 1); ?>%</td>
                                                    <td><a class="btn btn-sm btn-outline-primary" href="?page=cbt_admin&tab=tests&edit_test_id=<?php echo (int)($row['id'] ?? 0); ?>">Open</a></td>
                                                </tr>
                                            <?php endforeach; ?>
                                            <?php if (!$tests): ?><tr><td colspan="6" class="text-muted">No CBT exams found yet.</td></tr><?php endif; ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4 h-100">
                                    <h5 class="mb-3">Manual Grading Queue</h5>
                                    <?php if ($pendingResults): ?>
                                        <?php foreach (array_slice($pendingResults, 0, 8) as $row): ?>
                                            <div class="cbt-enterprise-soft p-3 mb-2">
                                                <div class="d-flex justify-content-between align-items-start gap-2">
                                                    <div>
                                                        <div class="fw-semibold"><?php echo e((string)($row['test_title'] ?? '')); ?></div>
                                                        <div class="small text-muted"><?php echo e((string)($row['student_name'] ?? 'Student')); ?> • Attempt #<?php echo (int)($row['attempt_number'] ?? 1); ?></div>
                                                    </div>
                                                    <a class="btn btn-sm btn-primary" href="?page=cbt_admin&tab=results&attempt_id=<?php echo (int)($row['id'] ?? 0); ?>">Grade</a>
                                                </div>
                                            </div>
                                        <?php endforeach; ?>
                                    <?php else: ?>
                                        <div class="text-muted">No attempts are waiting for manual grading right now.</div>
                                    <?php endif; ?>
                                </div>
                            </div>
                        </div>
                    <?php endif; ?>

                    <?php if ($tab === 'tests'): ?>
                        <div class="row g-4">
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4">
                                    <h5 class="mb-3"><?php echo !empty($editingTest['id']) ? 'Edit CBT Exam' : 'Create CBT Exam'; ?></h5>
                                    <form method="post" enctype="multipart/form-data" class="row g-3">
                                        <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                        <input type="hidden" name="act" value="save_test">
                                        <input type="hidden" name="id" value="<?php echo (int)($editingTest['id'] ?? 0); ?>">
                                        <div class="col-md-8"><label class="form-label">Exam Title</label><input type="text" class="form-control" name="title" required value="<?php echo e((string)($editingTest['title'] ?? '')); ?>"></div>
                                        <div class="col-md-4"><label class="form-label">Exam Code</label><input type="text" class="form-control" name="exam_code" value="<?php echo e((string)($editingTest['exam_code'] ?? '')); ?>"></div>
                                        <div class="col-md-6"><label class="form-label">Course Code</label><input type="text" class="form-control" name="course_code" value="<?php echo e((string)($editingTest['course_code'] ?? '')); ?>"></div>
                                        <div class="col-md-6"><label class="form-label">Subject</label><input type="text" class="form-control" name="subject_name" value="<?php echo e((string)($editingTest['subject_name'] ?? '')); ?>"></div>
                                        <div class="col-md-6"><label class="form-label">Class / Group</label><input type="text" class="form-control" name="class_group" value="<?php echo e((string)($editingTest['class_group'] ?? '')); ?>"></div>
                                        <div class="col-md-3"><label class="form-label">Level</label><input type="text" class="form-control" name="level" value="<?php echo e((string)($editingTest['level'] ?? '')); ?>"></div>
                                        <div class="col-md-3"><label class="form-label">Semester</label><input type="text" class="form-control" name="semester" value="<?php echo e((string)($editingTest['semester'] ?? '')); ?>"></div>
                                        <div class="col-md-6"><label class="form-label">Session</label><input type="text" class="form-control" name="session_name" value="<?php echo e((string)($editingTest['session_name'] ?? '')); ?>"></div>
                                        <div class="col-md-6"><label class="form-label">Status</label><select class="form-select" name="status"><?php foreach (['active','inactive','suspended'] as $statusOption): ?><option value="<?php echo e($statusOption); ?>" <?php echo strtolower((string)($editingTest['status'] ?? 'active')) === $statusOption ? 'selected' : ''; ?>><?php echo e(ucfirst($statusOption)); ?></option><?php endforeach; ?></select></div>
                                        <div class="col-12"><label class="form-label">Description</label><textarea class="form-control js-rich-editor" data-richtext="tinymce" data-richtext-profile="cbt_exam" name="description" rows="2"><?php echo e((string)($editingTest['description'] ?? '')); ?></textarea></div>
                                        <div class="col-12"><label class="form-label">Instructions</label><textarea class="form-control js-rich-editor" data-richtext="tinymce" data-richtext-profile="cbt_exam" name="instructions" rows="3"><?php echo e((string)($editingTest['instructions'] ?? '')); ?></textarea></div>
                                        <div class="col-md-4"><label class="form-label">Duration (mins)</label><input type="number" min="1" class="form-control" name="duration_minutes" value="<?php echo (int)($editingTest['duration_minutes'] ?? 30); ?>"></div>
                                        <div class="col-md-4"><label class="form-label">Late Entry</label><input type="number" min="0" class="form-control" name="late_entry_minutes" value="<?php echo (int)($editingTest['late_entry_minutes'] ?? 0); ?>"></div>
                                        <div class="col-md-4"><label class="form-label">Attempt Limit</label><input type="number" min="1" class="form-control" name="attempt_limit" value="<?php echo (int)($editingTest['attempt_limit'] ?? 1); ?>"></div>
                                        <div class="col-md-6"><label class="form-label">Start Date/Time</label><input type="datetime-local" class="form-control" name="start_at" value="<?php echo !empty($editingTest['start_at']) ? e(date('Y-m-d\TH:i', strtotime((string)$editingTest['start_at']))) : ''; ?>"></div>
                                        <div class="col-md-6"><label class="form-label">End Date/Time</label><input type="datetime-local" class="form-control" name="end_at" value="<?php echo !empty($editingTest['end_at']) ? e(date('Y-m-d\TH:i', strtotime((string)$editingTest['end_at']))) : ''; ?>"></div>
                                        <div class="col-md-3"><label class="form-label">Pass %</label><input type="number" step="0.01" min="0" class="form-control" name="pass_mark" value="<?php echo e((string)($editingTest['pass_mark'] ?? '50')); ?>"></div>
                                        <div class="col-md-3"><label class="form-label">Q / Page</label><input type="number" min="1" class="form-control" name="question_per_page" value="<?php echo (int)($editingTest['question_per_page'] ?? 1); ?>"></div>
                                        <div class="col-md-3"><label class="form-label">Mark / Q</label><input type="number" step="0.01" min="0.01" class="form-control" name="mark_per_question" value="<?php echo e((string)($editingTest['mark_per_question'] ?? '1')); ?>"></div>
                                        <div class="col-md-3"><label class="form-label">Neg. Mark</label><input type="number" step="0.01" min="0" class="form-control" name="negative_mark_per_wrong" value="<?php echo e((string)($editingTest['negative_mark_per_wrong'] ?? '0')); ?>"></div>
                                        <div class="col-md-6"><label class="form-label">Browser Tolerance Count</label><input type="number" min="0" class="form-control" name="browser_tolerance_count" value="<?php echo (int)($editingTest['browser_tolerance_count'] ?? 3); ?>"></div>
                                        <div class="col-md-6"><label class="form-label">Support File</label><input type="file" class="form-control" name="test_file"></div>
                                        <div class="col-12">
                                            <div class="row row-cols-2 row-cols-md-3 g-2">
                                                <?php
                                                    $testChecks = [
                                                        'published' => 'Published',
                                                        'results_published' => 'Results Published',
                                                        'randomize_questions' => 'Random Questions',
                                                        'randomize_options' => 'Random Options',
                                                        'instant_result_enabled' => 'Instant Result',
                                                        'answer_review_enabled' => 'Answer Review',
                                                        'negative_marking_enabled' => 'Negative Marking',
                                                        'auto_submit_on_timeout' => 'Auto Submit',
                                                        'sectional_enabled' => 'Sectional Exam',
                                                        'calculator_enabled' => 'Calculator',
                                                        'fullscreen_required' => 'Require Fullscreen',
                                                        'anti_copy_enabled' => 'Disable Copy/Paste',
                                                        'anti_switch_enabled' => 'Track Tab Switch',
                                                    ];
                                                ?>
                                                <?php foreach ($testChecks as $field => $label): ?>
                                                    <div class="col"><div class="form-check"><input class="form-check-input" type="checkbox" name="<?php echo e($field); ?>" id="<?php echo e($field); ?>" <?php echo !empty($editingTest[$field]) ? 'checked' : ''; ?>><label class="form-check-label" for="<?php echo e($field); ?>"><?php echo e($label); ?></label></div></div>
                                                <?php endforeach; ?>
                                            </div>
                                        </div>
                                        <div class="col-md-4"><label class="form-label">Faculty ID</label><input type="number" min="0" class="form-control" name="faculty_id" value="<?php echo (int)($editingTest['faculty_id'] ?? 0); ?>"></div>
                                        <div class="col-md-4"><label class="form-label">Department ID</label><input type="number" min="0" class="form-control" name="department_id" value="<?php echo (int)($editingTest['department_id'] ?? 0); ?>"></div>
                                        <div class="col-md-4"><label class="form-label">Programme ID</label><input type="number" min="0" class="form-control" name="programme_id" value="<?php echo (int)($editingTest['programme_id'] ?? 0); ?>"></div>
                                        <div class="col-12 d-flex flex-wrap gap-2">
                                            <button class="btn btn-primary"><?php echo !empty($editingTest['id']) ? 'Update Exam' : 'Create Exam'; ?></button>
                                            <a class="btn btn-outline-secondary" href="?page=cbt_admin&tab=tests">Reset Form</a>
                                        </div>
                                    </form>

                                    <?php if (!empty($editingTest['id'])): ?>
                                        <hr class="my-4">
                                        <h6 class="mb-3">Candidate Assignment & Visibility</h6>
                                        <form method="post" class="row g-3">
                                            <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                            <input type="hidden" name="act" value="save_assignment">
                                            <input type="hidden" name="test_id" value="<?php echo (int)($editingTest['id'] ?? 0); ?>">
                                            <div class="col-12"><div class="form-check"><input class="form-check-input" type="checkbox" name="assign_all" id="assign_all"><label class="form-check-label" for="assign_all">Assign to all eligible students</label></div></div>
                                            <div class="col-md-6"><label class="form-label">Faculty IDs</label><input type="text" class="form-control" name="assign_faculty_ids" placeholder="e.g. 1,2"></div>
                                            <div class="col-md-6"><label class="form-label">Department IDs</label><input type="text" class="form-control" name="assign_department_ids" placeholder="e.g. 3,4"></div>
                                            <div class="col-md-6"><label class="form-label">Programme IDs</label><input type="text" class="form-control" name="assign_programme_ids" placeholder="e.g. 5,8"></div>
                                            <div class="col-md-6"><label class="form-label">Levels</label><input type="text" class="form-control" name="assign_levels" placeholder="e.g. 100,200"></div>
                                            <div class="col-md-6"><label class="form-label">Sessions</label><input type="text" class="form-control" name="assign_sessions" placeholder="e.g. 2025/2026"></div>
                                            <div class="col-md-6"><label class="form-label">Class Groups</label><input type="text" class="form-control" name="assign_class_groups" placeholder="e.g. ND1A,HND2B"></div>
                                            <div class="col-md-6"><label class="form-label">Whitelist Student IDs</label><input type="text" class="form-control" name="whitelist_student_ids" placeholder="e.g. 12,14,18"></div>
                                            <div class="col-md-6"><label class="form-label">Blacklist Student IDs</label><input type="text" class="form-control" name="blacklist_student_ids" placeholder="e.g. 22,33"></div>
                                            <div class="col-12"><div class="form-check"><input class="form-check-input" type="checkbox" name="whitelist_can_retake" id="whitelist_can_retake"><label class="form-check-label" for="whitelist_can_retake">Allow retake for whitelisted students</label></div></div>
                                            <div class="col-12"><button class="btn btn-outline-primary">Save Assignment Rules</button></div>
                                        </form>
                                    <?php endif; ?>
                                </div>
                            </div>
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4">
                                    <div class="d-flex flex-wrap justify-content-between align-items-center mb-3 gap-2">
                                        <h5 class="mb-0">Exam Records</h5>
                                        <div class="small text-muted"><?php echo (int)count($tests); ?> exams tracked</div>
                                    </div>
                                    <div class="table-responsive">
                                        <table class="table table-striped align-middle cbt-enterprise-table mb-0">
                                            <thead><tr><th>Exam</th><th>Scope</th><th>Window</th><th>Questions</th><th>Attempts</th><th>Status</th><th>Actions</th></tr></thead>
                                            <tbody>
                                            <?php foreach ($tests as $row): ?>
                                                <tr>
                                                    <td><div class="fw-semibold"><?php echo e((string)($row['title'] ?? '')); ?></div><div class="small text-muted"><?php echo e((string)($row['exam_code'] ?? '')); ?> • <?php echo e((string)($row['course_code'] ?? '')); ?></div></td>
                                                    <td><div class="small"><?php echo e((string)($row['session_name'] ?? '')); ?> <?php echo e((string)($row['semester'] ?? '')); ?> • <?php echo e((string)($row['level'] ?? '')); ?></div><div class="small text-muted">F<?php echo (int)($row['faculty_id'] ?? 0); ?> / D<?php echo (int)($row['department_id'] ?? 0); ?> / P<?php echo (int)($row['programme_id'] ?? 0); ?></div></td>
                                                    <td class="small"><?php echo !empty($row['start_at']) ? e(date('d M Y h:i A', strtotime((string)$row['start_at']))) : 'Open'; ?><br><?php echo !empty($row['end_at']) ? e(date('d M Y h:i A', strtotime((string)$row['end_at']))) : 'No close time'; ?></td>
                                                    <td><?php echo (int)($row['question_count'] ?? 0); ?></td>
                                                    <td><?php echo (int)($row['attempt_count'] ?? 0); ?></td>
                                                    <td>
                                                        <span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((int)($row['published'] ?? 0) === 1 ? 'published' : (string)($row['exam_mode'] ?? 'draft')); ?>"><?php echo e((int)($row['published'] ?? 0) === 1 ? 'Published' : ucfirst((string)($row['exam_mode'] ?? 'draft'))); ?></span>
                                                        <span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['status'] ?? 'active')); ?>"><?php echo e(ucfirst((string)($row['status'] ?? 'active'))); ?></span>
                                                    </td>
                                                    <td>
                                                        <div class="d-flex flex-wrap gap-1">
                                                            <a class="btn btn-sm btn-outline-primary" href="?page=cbt_admin&tab=tests&edit_test_id=<?php echo (int)($row['id'] ?? 0); ?>">Edit</a>
                                                            <a class="btn btn-sm btn-outline-secondary" href="?page=cbt_admin&tab=questions&test_id=<?php echo (int)($row['id'] ?? 0); ?>">Questions</a>
                                                            <a class="btn btn-sm btn-outline-info" href="?page=cbt_admin&tab=tests&act=clone_test&id=<?php echo (int)($row['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>">Clone</a>
                                                            <a class="btn btn-sm btn-outline-success" href="?page=cbt_admin&tab=tests&act=toggle_test_publish&id=<?php echo (int)($row['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>"><?php echo (int)($row['published'] ?? 0) === 1 ? 'Unpublish' : 'Publish'; ?></a>
                                                            <a class="btn btn-sm btn-outline-warning" href="?page=cbt_admin&tab=tests&act=toggle_result_publish&id=<?php echo (int)($row['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>"><?php echo (int)($row['results_published'] ?? 0) === 1 ? 'Hide Results' : 'Publish Results'; ?></a>
                                                            <a class="btn btn-sm btn-outline-danger" href="?page=cbt_admin&tab=tests&act=delete_test&id=<?php echo (int)($row['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>" onclick="return confirm('Delete this exam, its questions, and linked attempts?');">Delete</a>
                                                        </div>
                                                    </td>
                                                </tr>
                                            <?php endforeach; ?>
                                            <?php if (!$tests): ?><tr><td colspan="7" class="text-muted">No CBT exams have been created yet.</td></tr><?php endif; ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    <?php endif; ?>

                    <?php if ($tab === 'questions'): ?>
                        <div class="row g-4">
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4">
                                    <h5 class="mb-3"><?php echo !empty($editingQuestion['id']) ? 'Edit Question' : 'Add Question'; ?></h5>
                                    <form method="get" class="mb-3">
                                        <input type="hidden" name="page" value="cbt_admin">
                                        <input type="hidden" name="tab" value="questions">
                                        <label class="form-label">Select Exam</label>
                                        <select class="form-select" name="test_id" onchange="this.form.submit()">
                                            <option value="0">-- Select CBT Exam --</option>
                                            <?php foreach ($tests as $row): ?>
                                                <option value="<?php echo (int)($row['id'] ?? 0); ?>" <?php echo (int)$test_id === (int)($row['id'] ?? 0) ? 'selected' : ''; ?>><?php echo e((string)($row['title'] ?? '')); ?></option>
                                            <?php endforeach; ?>
                                        </select>
                                    </form>
                                    <?php if ($test_id > 0): ?>
                                        <form method="post" enctype="multipart/form-data" class="row g-3">
                                            <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                            <input type="hidden" name="act" value="save_question">
                                            <input type="hidden" name="id" value="<?php echo (int)($editingQuestion['id'] ?? 0); ?>">
                                            <input type="hidden" name="test_id" value="<?php echo (int)$test_id; ?>">
                                            <div class="col-md-6"><label class="form-label">Question Type</label><select class="form-select" name="question_type"><?php foreach (['single_choice','multiple_choice','true_false','short_answer','essay','fill_blank'] as $qType): ?><option value="<?php echo e($qType); ?>" <?php echo portalCbtQuestionType($editingQuestion['question_type'] ?? '') === $qType ? 'selected' : ''; ?>><?php echo e(ucwords(str_replace('_',' ', $qType))); ?></option><?php endforeach; ?></select></div>
                                            <div class="col-md-6"><label class="form-label">Difficulty</label><select class="form-select" name="difficulty_level"><?php foreach (['easy','medium','hard'] as $levelOption): ?><option value="<?php echo e($levelOption); ?>" <?php echo strtolower((string)($editingQuestion['difficulty_level'] ?? 'medium')) === $levelOption ? 'selected' : ''; ?>><?php echo e(ucfirst($levelOption)); ?></option><?php endforeach; ?></select></div>
                                            <div class="col-md-6"><label class="form-label">Section</label><input type="text" class="form-control" name="section_name" value="<?php echo e((string)($editingQuestion['section_name'] ?? '')); ?>"></div>
                                            <div class="col-md-6"><label class="form-label">Subject</label><input type="text" class="form-control" name="subject_name" value="<?php echo e((string)($editingQuestion['subject_name'] ?? '')); ?>"></div>
                                            <div class="col-12"><label class="form-label">Topic Tags</label><input type="text" class="form-control" name="topic_tag" value="<?php echo e((string)($editingQuestion['topic_tag'] ?? '')); ?>" placeholder="e.g. Fractions, Algebra, GST"></div>
                                            <div class="col-12"><label class="form-label">Question</label><textarea class="form-control js-rich-editor" data-richtext="tinymce" data-richtext-profile="cbt_question" name="question" rows="3" required><?php echo e((string)($editingQuestion['question'] ?? '')); ?></textarea></div>
                                            <div class="col-md-6"><label class="form-label">Option A</label><input class="form-control" name="option_a" value="<?php echo e((string)($editingQuestion['option_a'] ?? '')); ?>"></div>
                                            <div class="col-md-6"><label class="form-label">Option B</label><input class="form-control" name="option_b" value="<?php echo e((string)($editingQuestion['option_b'] ?? '')); ?>"></div>
                                            <div class="col-md-6"><label class="form-label">Option C</label><input class="form-control" name="option_c" value="<?php echo e((string)($editingQuestion['option_c'] ?? '')); ?>"></div>
                                            <div class="col-md-6"><label class="form-label">Option D</label><input class="form-control" name="option_d" value="<?php echo e((string)($editingQuestion['option_d'] ?? '')); ?>"></div>
                                            <div class="col-12"><label class="form-label">Extra Options (one per line; optional)</label><textarea class="form-control" name="extra_options" rows="2"><?php
                                                $extraLines = [];
                                                $existingOptions = portalCbtJsonDecode($editingQuestion['options_json'] ?? '[]', []);
                                                foreach ($existingOptions as $optKey => $optVal) {
                                                    $label = is_array($optVal) ? (string)($optVal['label'] ?? $optKey) : (string)$optKey;
                                                    $textValue = is_array($optVal) ? (string)($optVal['text'] ?? '') : (string)$optVal;
                                                    if (!in_array($label, ['A','B','C','D'], true) && trim($textValue) !== '') $extraLines[] = $label . ': ' . $textValue;
                                                }
                                                echo e(implode("\n", $extraLines));
                                            ?></textarea></div>
                                            <div class="col-md-4"><label class="form-label">Correct Option</label><input class="form-control" name="correct_option" value="<?php echo e((string)($editingQuestion['correct_option'] ?? '')); ?>" placeholder="A"></div>
                                            <div class="col-md-8"><label class="form-label">Correct Text / Short Answer</label><input class="form-control" name="correct_answer_text" value="<?php echo e((string)($editingQuestion['correct_answer_text'] ?? '')); ?>" placeholder="Used for short answer, fill blank, essay guide, or multiple answer JSON"></div>
                                            <div class="col-12">
                                                <label class="form-label">Multiple Correct Options</label>
                                                <?php $correctMultiSaved = portalCbtJsonDecode($editingQuestion['correct_answer_text'] ?? '[]', []); ?>
                                                <div class="d-flex flex-wrap gap-3">
                                                    <?php foreach (['A','B','C','D'] as $letter): ?>
                                                        <div class="form-check"><input class="form-check-input" type="checkbox" name="correct_multi[]" value="<?php echo e($letter); ?>" id="correct_multi_<?php echo e($letter); ?>" <?php echo in_array($letter, array_map('strtoupper', array_map('strval', (array)$correctMultiSaved)), true) ? 'checked' : ''; ?>><label class="form-check-label" for="correct_multi_<?php echo e($letter); ?>"><?php echo e($letter); ?></label></div>
                                                    <?php endforeach; ?>
                                                </div>
                                            </div>
                                            <div class="col-md-4"><label class="form-label">Marks</label><input type="number" min="0.01" step="0.01" class="form-control" name="marks" value="<?php echo e((string)($editingQuestion['marks'] ?? '1')); ?>"></div>
                                            <div class="col-md-4"><label class="form-label">Status</label><select class="form-select" name="status"><?php foreach (['active','inactive','draft'] as $qs): ?><option value="<?php echo e($qs); ?>" <?php echo strtolower((string)($editingQuestion['status'] ?? 'active')) === $qs ? 'selected' : ''; ?>><?php echo e(ucfirst($qs)); ?></option><?php endforeach; ?></select></div>
                                            <div class="col-md-4"><label class="form-label">Question Image</label><input type="file" class="form-control" name="question_image"></div>
                                            <div class="col-12"><label class="form-label">Audio / Video / Support Media</label><input type="file" class="form-control" name="question_media"></div>
                                            <div class="col-12"><label class="form-label">Explanation / Solution</label><textarea class="form-control js-rich-editor" data-richtext="tinymce" data-richtext-profile="cbt_question" name="explanation" rows="3"><?php echo e((string)($editingQuestion['explanation'] ?? '')); ?></textarea></div>
                                            <div class="col-12 d-flex flex-wrap gap-2">
                                                <button class="btn btn-primary">Save Question</button>
                                                <a class="btn btn-outline-secondary" href="?page=cbt_admin&tab=questions&test_id=<?php echo (int)$test_id; ?>">Clear Form</a>
                                            </div>
                                        </form>
                                    <?php else: ?>
                                        <div class="text-muted">Select a CBT exam first, then add advanced questions to the bank.</div>
                                    <?php endif; ?>
                                </div>
                            </div>
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4">
                                    <div class="d-flex justify-content-between align-items-center mb-3">
                                        <h5 class="mb-0">Question Bank</h5>
                                        <span class="badge text-bg-light border"><?php echo (int)count($questions); ?> question(s)</span>
                                    </div>
                                    <div class="table-responsive">
                                        <table class="table table-striped align-middle cbt-enterprise-table mb-0">
                                            <thead><tr><th>#</th><th>Question</th><th>Type</th><th>Difficulty</th><th>Marks</th><th>Status</th><th>Actions</th></tr></thead>
                                            <tbody>
                                            <?php foreach ($questions as $row): ?>
                                                <tr>
                                                    <td><?php echo (int)($row['id'] ?? 0); ?></td>
                                                    <td>
                                                        <div class="fw-semibold"><?php echo e(mb_strimwidth((string)($row['question'] ?? ''), 0, 100, '…')); ?></div>
                                                        <div class="small text-muted"><?php echo e((string)($row['section_name'] ?? '')); ?> <?php echo ($row['topic_tag'] ?? '') ? '• ' . e((string)$row['topic_tag']) : ''; ?></div>
                                                    </td>
                                                    <td><?php echo e(ucwords(str_replace('_', ' ', (string)($row['question_type'] ?? 'single_choice')))); ?></td>
                                                    <td><?php echo e(ucfirst((string)($row['difficulty_level'] ?? 'medium'))); ?></td>
                                                    <td><?php echo e((string)($row['marks'] ?? '1')); ?></td>
                                                    <td><span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['status'] ?? 'active')); ?>"><?php echo e(ucfirst((string)($row['status'] ?? 'active'))); ?></span></td>
                                                    <td>
                                                        <div class="d-flex flex-wrap gap-1">
                                                            <a class="btn btn-sm btn-outline-primary" href="?page=cbt_admin&tab=questions&test_id=<?php echo (int)$test_id; ?>&edit_question_id=<?php echo (int)($row['id'] ?? 0); ?>">Edit</a>
                                                            <a class="btn btn-sm btn-outline-danger" href="?page=cbt_admin&tab=questions&test_id=<?php echo (int)$test_id; ?>&act=delete_question&id=<?php echo (int)($row['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>" onclick="return confirm('Delete this question?');">Delete</a>
                                                        </div>
                                                    </td>
                                                </tr>
                                            <?php endforeach; ?>
                                            <?php if (!$questions): ?><tr><td colspan="7" class="text-muted">No questions have been added for the selected exam.</td></tr><?php endif; ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    <?php endif; ?>

                    <?php if ($tab === 'attempts'): ?>
                        <div class="cbt-enterprise-card p-3 p-lg-4">
                            <div class="d-flex justify-content-between align-items-center mb-3">
                                <h5 class="mb-0">Attempt Monitor</h5>
                                <span class="badge text-bg-light border"><?php echo (int)count($attempts); ?> attempt(s)</span>
                            </div>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle cbt-enterprise-table mb-0">
                                    <thead><tr><th>Exam</th><th>Student</th><th>Attempt</th><th>Status</th><th>Score</th><th>Incidents</th><th>Window</th><th>Actions</th></tr></thead>
                                    <tbody>
                                    <?php foreach ($attempts as $row): ?>
                                        <tr>
                                            <td><div class="fw-semibold"><?php echo e((string)($row['test_title'] ?? '')); ?></div><div class="small text-muted"><?php echo e((string)($row['course_code'] ?? '')); ?></div></td>
                                            <td><div class="fw-semibold"><?php echo e((string)($row['student_name'] ?? 'Student')); ?></div><div class="small text-muted"><?php echo e((string)($row['matric_no'] ?? ($row['registration_no'] ?? $row['admission_no'] ?? ''))); ?></div></td>
                                            <td>#<?php echo (int)($row['attempt_number'] ?? 1); ?></td>
                                            <td>
                                                <span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['status'] ?? 'started')); ?>"><?php echo e(ucfirst((string)($row['status'] ?? 'started'))); ?></span>
                                                <span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['grading_status'] ?? 'pending')); ?>"><?php echo e(ucfirst(str_replace('_',' ', (string)($row['grading_status'] ?? 'pending')))); ?></span>
                                            </td>
                                            <td><?php echo ($row['score'] === null || $row['score'] === '') ? '<span class="text-muted">Pending</span>' : e((string)$row['score']) . ' <span class="small text-muted">(' . e((string)($row['percentage_score'] ?? '0')) . '%)</span>'; ?></td>
                                            <td><?php echo (int)($row['incident_count'] ?? 0); ?></td>
                                            <td class="small"><?php echo !empty($row['started_at']) ? e(date('d M Y h:i A', strtotime((string)$row['started_at']))) : '—'; ?><br><?php echo !empty($row['submitted_at']) ? e(date('d M Y h:i A', strtotime((string)$row['submitted_at']))) : 'Not submitted'; ?></td>
                                            <td>
                                                <div class="d-flex flex-wrap gap-1">
                                                    <a class="btn btn-sm btn-outline-primary" href="?page=cbt_admin&tab=results&attempt_id=<?php echo (int)($row['id'] ?? 0); ?>">Review</a>
                                                    <a class="btn btn-sm btn-outline-danger" href="?page=cbt_admin&tab=attempts&act=reset_attempt&attempt_id=<?php echo (int)($row['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>" onclick="return confirm('Reset this attempt for a fresh re-entry?');">Reset</a>
                                                </div>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$attempts): ?><tr><td colspan="8" class="text-muted">No CBT attempts have been recorded yet.</td></tr><?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    <?php endif; ?>

                    <?php if ($tab === 'results'): ?>
                        <div class="row g-4">
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4">
                                    <h5 class="mb-3">Results & Manual Marking</h5>
                                    <div class="table-responsive">
                                        <table class="table table-striped align-middle cbt-enterprise-table mb-0">
                                            <thead><tr><th>Exam</th><th>Student</th><th>Grade State</th><th>Result Status</th><th>Score</th><th>Action</th></tr></thead>
                                            <tbody>
                                            <?php foreach ($attempts as $row): ?>
                                                <tr>
                                                    <td><?php echo e((string)($row['test_title'] ?? '')); ?></td>
                                                    <td><?php echo e((string)($row['student_name'] ?? 'Student')); ?></td>
                                                    <td><span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['grading_status'] ?? 'pending')); ?>"><?php echo e(ucfirst(str_replace('_',' ', (string)($row['grading_status'] ?? 'pending')))); ?></span></td>
                                                    <td><span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['result_status'] ?? 'pending')); ?>"><?php echo e(ucfirst(str_replace('_',' ', (string)($row['result_status'] ?? 'pending')))); ?></span></td>
                                                    <td><?php echo ($row['score'] === null || $row['score'] === '') ? '<span class="text-muted">Pending</span>' : e((string)$row['score']) . ' <span class="small text-muted">(' . e((string)($row['percentage_score'] ?? '0')) . '%)</span>'; ?></td>
                                                    <td><div class="d-flex flex-wrap gap-1"><a class="btn btn-sm btn-outline-primary" href="?page=cbt_admin&tab=results&attempt_id=<?php echo (int)($row['id'] ?? 0); ?>">Open</a><?php if (strtolower((string)($row['grading_status'] ?? 'pending')) === 'graded'): ?><a class="btn btn-sm btn-outline-success" href="?page=cbt_admin&tab=results&act=set_attempt_result_status&mode=approve&attempt_id=<?php echo (int)($row['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>">Approve</a><a class="btn btn-sm btn-outline-warning" href="?page=cbt_admin&tab=results&act=set_attempt_result_status&mode=withhold&attempt_id=<?php echo (int)($row['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>">Withhold</a><?php endif; ?></div></td>
                                                </tr>
                                            <?php endforeach; ?>
                                            <?php if (!$attempts): ?><tr><td colspan="6" class="text-muted">No result records available.</td></tr><?php endif; ?>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4">
                                    <?php if ($selectedAttemptForGrade): ?>
                                        <?php
                                            $attemptQuestions = portalCbtFetchQuestionsByIds($pdo, portalCbtJsonDecode($selectedAttemptForGrade['question_order_json'] ?? '[]', []));
                                            $attemptAnswers = portalCbtJsonDecode($selectedAttemptForGrade['answers_json'] ?? '{}', []);
                                            $manualScores = [];
                                            try {
                                                $st = $pdo->prepare("SELECT * FROM cbt_manual_scores WHERE attempt_id = ?");
                                                $st->execute([(int)($selectedAttemptForGrade['id'] ?? 0)]);
                                                foreach (($st->fetchAll(PDO::FETCH_ASSOC) ?: []) as $manualScoreRow) {
                                                    $manualScores[(int)($manualScoreRow['question_id'] ?? 0)] = $manualScoreRow;
                                                }
                                            } catch (Throwable $e) {}
                                        ?>
                                        <div class="d-flex justify-content-between align-items-center mb-3">
                                            <div>
                                                <h5 class="mb-1"><?php echo e((string)($selectedAttemptForGrade['test_title'] ?? '')); ?></h5>
                                                <div class="small text-muted"><?php echo e((string)($selectedAttemptForGrade['student_name'] ?? 'Student')); ?> • Attempt #<?php echo (int)($selectedAttemptForGrade['attempt_number'] ?? 1); ?></div>
                                            </div>
                                            <div class="d-flex flex-wrap gap-2">
                                                <span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($selectedAttemptForGrade['grading_status'] ?? 'pending')); ?>"><?php echo e(ucfirst(str_replace('_', ' ', (string)($selectedAttemptForGrade['grading_status'] ?? 'pending')))); ?></span>
                                                <span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($selectedAttemptForGrade['result_status'] ?? 'pending')); ?>"><?php echo e(ucfirst(str_replace('_', ' ', (string)($selectedAttemptForGrade['result_status'] ?? 'pending')))); ?></span>
                                                <?php if (strtolower((string)($selectedAttemptForGrade['grading_status'] ?? 'pending')) === 'graded'): ?><a class="btn btn-sm btn-outline-success" href="?page=cbt_admin&tab=results&act=set_attempt_result_status&mode=approve&attempt_id=<?php echo (int)($selectedAttemptForGrade['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>">Approve</a><a class="btn btn-sm btn-outline-warning" href="?page=cbt_admin&tab=results&act=set_attempt_result_status&mode=withhold&attempt_id=<?php echo (int)($selectedAttemptForGrade['id'] ?? 0); ?>&cbt_csrf_token=<?php echo e($cbtCsrf); ?>">Withhold</a><?php endif; ?>
                                            </div>
                                        </div>
                                        <form method="post" class="row g-3">
                                            <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                            <input type="hidden" name="act" value="grade_attempt">
                                            <input type="hidden" name="attempt_id" value="<?php echo (int)($selectedAttemptForGrade['id'] ?? 0); ?>">
                                            <?php foreach ($attemptQuestions as $questionRow): ?>
                                                <?php
                                                    $qType = portalCbtQuestionType($questionRow['question_type'] ?? '');
                                                    $qid = (int)($questionRow['id'] ?? 0);
                                                    $savedManual = $manualScores[$qid] ?? [];
                                                    $submittedAnswer = $attemptAnswers[(string)$qid]['selected'] ?? '';
                                                ?>
                                                <div class="col-12">
                                                    <div class="cbt-enterprise-soft p-3">
                                                        <div class="d-flex justify-content-between align-items-start gap-2 mb-2">
                                                            <div>
                                                                <div class="fw-semibold">Q<?php echo (int)$qid; ?> • <?php echo e(ucwords(str_replace('_',' ', $qType))); ?></div>
                                                                <div class="small text-muted"><?php echo e((string)($questionRow['subject_name'] ?? '')); ?> <?php echo ($questionRow['section_name'] ?? '') ? '• ' . e((string)$questionRow['section_name']) : ''; ?></div>
                                                            </div>
                                                            <span class="badge text-bg-light border"><?php echo e((string)($questionRow['marks'] ?? '1')); ?> mark(s)</span>
                                                        </div>
                                                        <div class="mb-2 richtext-output"><?php echo portalRenderSafeRichText((string)($questionRow['question'] ?? '')); ?></div>
                                                        <?php if ($qType === 'essay'): ?>
                                                            <div class="mb-2"><strong>Student Answer:</strong><div class="small text-muted"><?php echo nl2br(e(is_array($submittedAnswer) ? implode(', ', $submittedAnswer) : (string)$submittedAnswer)); ?></div></div>
                                                            <div class="row g-2">
                                                                <div class="col-md-3"><label class="form-label">Marks Awarded</label><input type="number" step="0.01" min="0" class="form-control" name="manual_marks[<?php echo (int)$qid; ?>]" value="<?php echo e((string)($savedManual['marks_awarded'] ?? '0')); ?>"></div>
                                                                <div class="col-md-9"><label class="form-label">Marker Comment</label><input type="text" class="form-control" name="manual_comment[<?php echo (int)$qid; ?>]" value="<?php echo e((string)($savedManual['marker_comment'] ?? '')); ?>"></div>
                                                            </div>
                                                        <?php else: ?>
                                                            <div class="small text-muted">Auto-marked question. Submitted answer: <strong><?php echo e(is_array($submittedAnswer) ? implode(', ', $submittedAnswer) : (string)$submittedAnswer); ?></strong></div>
                                                        <?php endif; ?>
                                                    </div>
                                                </div>
                                            <?php endforeach; ?>
                                            <div class="col-12 d-flex flex-wrap gap-2">
                                                <button class="btn btn-primary">Save Manual Grading</button>
                                                <a class="btn btn-outline-secondary" href="?page=cbt_admin&tab=results">Close Review</a>
                                            </div>
                                        </form>
                                    <?php else: ?>
                                        <div class="text-muted">Pick an attempt from the list to review or manually grade theory questions.</div>
                                    <?php endif; ?>
                                </div>
                            </div>
                        </div>
                    <?php endif; ?>


                    <?php if ($tab === 'reports'): ?>
                        <div class="row g-4">
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4">
                                    <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
                                        <h5 class="mb-0">CBT Reports & Analytics</h5>
                                        <div class="d-flex flex-wrap gap-2">
                                            <a class="btn btn-outline-secondary btn-sm" href="?page=cbt_admin&tab=reports&report_test_id=<?php echo (int)$report_test_id; ?>&report_status=<?php echo e($report_status); ?>&report_from=<?php echo e($report_from); ?>&report_to=<?php echo e($report_to); ?>&print=attendance_sheet" target="_blank">Print Attendance</a>
                                            <a class="btn btn-primary btn-sm" href="?page=cbt_admin&tab=reports&report_test_id=<?php echo (int)$report_test_id; ?>&report_status=<?php echo e($report_status); ?>&report_from=<?php echo e($report_from); ?>&report_to=<?php echo e($report_to); ?>&download=cbt_reports_csv">Export CSV</a>
                                        </div>
                                    </div>
                                    <form method="get" class="row g-3 mb-4">
                                        <input type="hidden" name="page" value="cbt_admin"><input type="hidden" name="tab" value="reports">
                                        <div class="col-md-4"><label class="form-label">Exam</label><select class="form-select" name="report_test_id"><option value="0">All Exams</option><?php foreach ($tests as $testRow): ?><option value="<?php echo (int)($testRow['id'] ?? 0); ?>" <?php echo $report_test_id === (int)($testRow['id'] ?? 0) ? 'selected' : ''; ?>><?php echo e((string)($testRow['title'] ?? '')); ?></option><?php endforeach; ?></select></div>
                                        <div class="col-md-2"><label class="form-label">Status</label><select class="form-select" name="report_status"><?php foreach (['all' => 'All','submitted' => 'Submitted','graded' => 'Graded','published' => 'Published','withheld' => 'Withheld','pending' => 'Pending'] as $rk => $rv): ?><option value="<?php echo e($rk); ?>" <?php echo $report_status === $rk ? 'selected' : ''; ?>><?php echo e($rv); ?></option><?php endforeach; ?></select></div>
                                        <div class="col-md-2"><label class="form-label">From</label><input type="date" class="form-control" name="report_from" value="<?php echo e($report_from); ?>"></div>
                                        <div class="col-md-2"><label class="form-label">To</label><input type="date" class="form-control" name="report_to" value="<?php echo e($report_to); ?>"></div>
                                        <div class="col-md-2 d-flex align-items-end"><button class="btn btn-outline-primary w-100">Apply</button></div>
                                    </form>
                                    <div class="cbt-admin-grid mb-4">
                                        <div class="cbt-enterprise-soft p-3"><div class="small text-muted">Attempts</div><div class="fs-3 fw-bold"><?php echo (int)($reportBundle['summary']['total_attempts'] ?? 0); ?></div></div>
                                        <div class="cbt-enterprise-soft p-3"><div class="small text-muted">Submitted</div><div class="fs-3 fw-bold"><?php echo (int)($reportBundle['summary']['submitted'] ?? 0); ?></div></div>
                                        <div class="cbt-enterprise-soft p-3"><div class="small text-muted">Passed</div><div class="fs-3 fw-bold text-success"><?php echo (int)($reportBundle['summary']['passed'] ?? 0); ?></div></div>
                                        <div class="cbt-enterprise-soft p-3"><div class="small text-muted">Failed</div><div class="fs-3 fw-bold text-danger"><?php echo (int)($reportBundle['summary']['failed'] ?? 0); ?></div></div>
                                        <div class="cbt-enterprise-soft p-3"><div class="small text-muted">Average Score</div><div class="fs-3 fw-bold"><?php echo number_format((float)($reportBundle['summary']['average_percent'] ?? 0), 1); ?>%</div></div>
                                        <div class="cbt-enterprise-soft p-3"><div class="small text-muted">Attendance Rate</div><div class="fs-3 fw-bold"><?php echo number_format((float)($reportBundle['summary']['attendance_rate'] ?? 0), 1); ?>%</div></div>
                                    </div>
                                    <div class="row g-3 mb-4"><div class="col-md-6"><div class="cbt-enterprise-soft p-3 h-100"><div class="small text-muted">Highest Score</div><div class="fs-3 fw-bold text-success"><?php echo number_format((float)($reportBundle['summary']['highest_percent'] ?? 0), 1); ?>%</div></div></div><div class="col-md-6"><div class="cbt-enterprise-soft p-3 h-100"><div class="small text-muted">Lowest Score</div><div class="fs-3 fw-bold text-danger"><?php echo number_format((float)($reportBundle['summary']['lowest_percent'] ?? 0), 1); ?>%</div></div></div></div>
                                    <div class="table-responsive"><table class="table table-striped align-middle cbt-enterprise-table mb-0"><thead><tr><th>Exam</th><th>Student</th><th>Status</th><th>Score</th><th>Grade</th><th>Submitted</th></tr></thead><tbody><?php foreach (($reportBundle['rows'] ?? []) as $reportRow): ?><tr><td><div class="fw-semibold"><?php echo e((string)($reportRow['test_title'] ?? '')); ?></div><div class="small text-muted"><?php echo e((string)($reportRow['exam_code'] ?? '')); ?> • <?php echo e((string)($reportRow['course_code'] ?? '')); ?></div></td><td><div class="fw-semibold"><?php echo e((string)($reportRow['student_name'] ?? 'Student')); ?></div><div class="small text-muted"><?php echo e((string)($reportRow['matric_no'] ?? ($reportRow['registration_no'] ?? $reportRow['admission_no'] ?? ''))); ?></div></td><td><span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($reportRow['grading_status'] ?? 'pending')); ?>"><?php echo e(ucfirst(str_replace('_',' ', (string)($reportRow['grading_status'] ?? 'pending')))); ?></span> <span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($reportRow['result_status'] ?? 'pending')); ?>"><?php echo e(ucfirst(str_replace('_',' ', (string)($reportRow['result_status'] ?? 'pending')))); ?></span></td><td><?php echo ($reportRow['score'] === null || $reportRow['score'] === '') ? '<span class="text-muted">Pending</span>' : e((string)$reportRow['score']) . ' <span class="small text-muted">(' . e((string)($reportRow['percentage_score'] ?? '0')) . '%)</span>'; ?></td><td><?php echo e((string)($reportRow['grade_label'] ?? '')); ?> <span class="small text-muted"><?php echo e((string)($reportRow['pass_fail_status'] ?? '')); ?></span></td><td><?php echo !empty($reportRow['submitted_at']) ? e(date('d M Y h:i A', strtotime((string)$reportRow['submitted_at']))) : '<span class="text-muted">Not submitted</span>'; ?></td></tr><?php endforeach; ?><?php if (empty($reportBundle['rows'])): ?><tr><td colspan="6" class="text-muted">No CBT report rows matched the selected filters.</td></tr><?php endif; ?></tbody></table></div>
                                </div>
                            </div>
                        </div>
                    <?php endif; ?>

                    <?php if ($tab === 'students'): ?>
                        <div class="cbt-enterprise-card p-3 p-lg-4">
                            <form method="get" class="row g-3 mb-3">
                                <input type="hidden" name="page" value="cbt_admin">
                                <input type="hidden" name="tab" value="students">
                                <div class="col-md-6"><label class="form-label">Search Student</label><input type="text" class="form-control" name="student_q" value="<?php echo e($student_search); ?>" placeholder="Name, matric number, registration number, email"></div>
                                <div class="col-md-3"><label class="form-label">Access Status</label><select class="form-select" name="student_status"><option value="all">All</option><?php foreach (['active','suspended','deleted'] as $statusValue): ?><option value="<?php echo e($statusValue); ?>" <?php echo $student_status_filter === $statusValue ? 'selected' : ''; ?>><?php echo e(ucfirst($statusValue)); ?></option><?php endforeach; ?></select></div>
                                <div class="col-md-3 d-flex align-items-end"><button class="btn btn-outline-primary w-100">Filter</button></div>
                            </form>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle cbt-enterprise-table mb-0">
                                    <thead><tr><th>Student</th><th>Academic Scope</th><th>Contact</th><th>CBT Access</th><th>Update</th></tr></thead>
                                    <tbody>
                                    <?php foreach ($studentRows as $row): ?>
                                        <tr>
                                            <td><div class="fw-semibold"><?php echo e((string)($row['student_name'] ?? 'Student')); ?></div><div class="small text-muted"><?php echo e((string)($row['matric_no'] ?? ($row['registration_no'] ?? $row['admission_no'] ?? ''))); ?></div></td>
                                            <td><div class="small"><?php echo e((string)($row['department'] ?? '')); ?> • <?php echo e((string)($row['programme'] ?? '')); ?> • <?php echo e((string)($row['mode_of_study'] ?? portalInferModeOfStudy((array)$row))); ?></div><div class="small text-muted"><?php echo e((string)($row['level'] ?? '')); ?> • <?php echo e((string)($row['session'] ?? '')); ?></div></td>
                                            <td><div class="small"><?php echo e((string)($row['email'] ?? '')); ?></div><div class="small text-muted"><?php echo e((string)($row['phone'] ?? ($row['mobile_phone'] ?? ''))); ?></div></td>
                                            <td><span class="badge text-bg-<?php echo portalCbtStatusBadgeClass((string)($row['access_status'] ?? 'active')); ?>"><?php echo e(ucfirst((string)($row['access_status'] ?? 'active'))); ?></span><?php if (!empty($row['cbt_access_note'])): ?><div class="small text-muted mt-1"><?php echo e((string)$row['cbt_access_note']); ?></div><?php endif; ?></td>
                                            <td>
                                                <form method="post" class="row g-2">
                                                    <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                                    <input type="hidden" name="act" value="update_student_access">
                                                    <input type="hidden" name="student_id" value="<?php echo (int)($row['id'] ?? 0); ?>">
                                                    <div class="col-12"><select class="form-select form-select-sm" name="access_status"><?php foreach (['active','suspended','deleted'] as $statusValue): ?><option value="<?php echo e($statusValue); ?>" <?php echo strtolower((string)($row['access_status'] ?? 'active')) === $statusValue ? 'selected' : ''; ?>><?php echo e(ucfirst($statusValue)); ?></option><?php endforeach; ?></select></div>
                                                    <div class="col-12"><input type="text" class="form-control form-control-sm" name="access_note" value="<?php echo e((string)($row['cbt_access_note'] ?? '')); ?>" placeholder="Reason / note"></div>
                                                    <div class="col-12"><button class="btn btn-sm btn-outline-primary w-100">Save</button></div>
                                                </form>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$studentRows): ?><tr><td colspan="5" class="text-muted">No students matched the current filter.</td></tr><?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    <?php endif; ?>

                    <?php if ($tab === 'settings'): ?>
                        <div class="row g-4">
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4">
                                    <h5 class="mb-3">CBT Settings</h5>
                                    <form method="post" class="row g-3">
                                        <input type="hidden" name="cbt_csrf_token" value="<?php echo e($cbtCsrf); ?>">
                                        <input type="hidden" name="act" value="save_settings">
                                        <div class="col-md-6"><label class="form-label">Site Name</label><input type="text" class="form-control" name="site_name" value="<?php echo e((string)($settings['site_name'] ?? 'HontTech CBT Manager')); ?>"></div>
                                        <div class="col-md-6"><label class="form-label">Timezone</label><input type="text" class="form-control" name="timezone_name" value="<?php echo e((string)($settings['timezone_name'] ?? 'Africa/Lagos')); ?>"></div>
                                        <div class="col-md-4"><label class="form-label">Default Browser Tolerance</label><input type="number" min="0" class="form-control" name="default_browser_tolerance" value="<?php echo (int)($settings['default_browser_tolerance'] ?? 3); ?>"></div>
                                        <div class="col-md-4"><label class="form-label">Default UI</label><input type="text" class="form-control" name="default_exam_ui" value="<?php echo e((string)($settings['default_exam_ui'] ?? 'modern')); ?>"></div>
                                        <div class="col-md-4">
                                            <div class="form-check mt-4"><input class="form-check-input" type="checkbox" name="email_notifications_enabled" id="cbt_email_notifications_enabled" <?php echo !empty($settings['email_notifications_enabled']) ? 'checked' : ''; ?>><label class="form-check-label" for="cbt_email_notifications_enabled">Email Notifications</label></div>
                                            <div class="form-check"><input class="form-check-input" type="checkbox" name="sms_placeholder_enabled" id="cbt_sms_placeholder_enabled" <?php echo !empty($settings['sms_placeholder_enabled']) ? 'checked' : ''; ?>><label class="form-check-label" for="cbt_sms_placeholder_enabled">SMS-ready Placeholder</label></div>
                                        </div>
                                        <div class="col-12"><label class="form-label">Default Exam Instructions</label><textarea class="form-control" rows="4" name="default_instructions"><?php echo e((string)($settings['default_instructions'] ?? '')); ?></textarea></div>
                                        <div class="col-12"><label class="form-label">Email Template: Assignment</label><textarea class="form-control" rows="3" name="email_template_assignment"><?php echo e((string)($settings['email_template_assignment'] ?? '')); ?></textarea></div>
                                        <div class="col-12"><label class="form-label">Email Template: Result Published</label><textarea class="form-control" rows="3" name="email_template_result"><?php echo e((string)($settings['email_template_result'] ?? '')); ?></textarea></div>
                                        <div class="col-12"><button class="btn btn-primary">Save Settings</button></div>
                                    </form>
                                </div>
                            </div>
                            <div class="col-12">
                                <div class="cbt-enterprise-card p-3 p-lg-4 h-100">
                                    <h5 class="mb-3">Enterprise Notes</h5>
                                    <div class="cbt-enterprise-soft p-3 mb-2">Scope-aware exam visibility remains tied to existing faculty, department, programme, level, session, and semester filters.</div>
                                    <div class="cbt-enterprise-soft p-3 mb-2">Manual grading activates automatically when essay/theory questions exist inside a live attempt.</div>
                                    <div class="cbt-enterprise-soft p-3">Anti-cheat event logging uses incidents, device signature, browser trail, and tolerance counters without breaking your current single-file flow.</div>
                                </div>
                            </div>
                        </div>
                    <?php endif; ?>

                    <?php if ($tab === 'logs'): ?>
                        <div class="cbt-enterprise-card p-3 p-lg-4">
                            <h5 class="mb-3">Activity Log</h5>
                            <div class="table-responsive">
                                <table class="table table-striped align-middle cbt-enterprise-table mb-0">
                                    <thead><tr><th>Time</th><th>Actor</th><th>Entity</th><th>Action</th><th>Message</th></tr></thead>
                                    <tbody>
                                    <?php foreach ($logs as $row): ?>
                                        <tr>
                                            <td><?php echo e(date('d M Y h:i A', strtotime((string)($row['created_at'] ?? 'now')))); ?></td>
                                            <td><?php echo e((string)($row['actor_user_type'] ?? 'admin')); ?> #<?php echo (int)($row['actor_id'] ?? 0); ?></td>
                                            <td><?php echo e((string)($row['entity_type'] ?? '')); ?> #<?php echo (int)($row['entity_id'] ?? 0); ?></td>
                                            <td><?php echo e((string)($row['action_key'] ?? '')); ?></td>
                                            <td><?php echo e((string)($row['log_message'] ?? '')); ?></td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <?php if (!$logs): ?><tr><td colspan="5" class="text-muted">No CBT activity logs recorded yet.</td></tr><?php endif; ?>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    <?php endif; ?>
                </div>
                <?php
                break;


default:
                redirect('?page=home');
                break;
        }
        ?>

        <!-- Scripts -->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
        <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
        <script>
        // Sidebar toggle for mobile
        function toggleSidebar() {
            const sidebar = document.getElementById('sidebar');
            const mainContent = document.getElementById('main-content');

            if (sidebar.classList.contains('show')) {
                sidebar.classList.remove('show');
            } else {
                sidebar.classList.add('show');
            }
        }

        // Signature pad functionality
        function initSignaturePad(canvasId, hiddenInputId) {
            const canvas = document.getElementById(canvasId);
            if (!canvas) return;

            const ctx = canvas.getContext('2d');
            let isDrawing = false;

            const hiddenId = hiddenInputId || (canvasId + '_data');
            const hiddenInput = document.getElementById(hiddenId);

            // Set canvas size
            function resizeCanvas() {
                const rect = canvas.getBoundingClientRect();
                const snapshot = canvas.toDataURL('image/png');
                canvas.width = rect.width;
                canvas.height = rect.height;
                ctx.lineWidth = 2;
                ctx.lineCap = 'round';
                ctx.strokeStyle = '#000';

                if (snapshot && snapshot.indexOf('data:image') === 0) {
                    const img = new Image();
                    img.onload = () => ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                    img.src = snapshot;
                }
            }

            function saveToHidden() {
                if (!hiddenInput) return;
                try {
                    const blank = document.createElement('canvas');
                    blank.width = canvas.width;
                    blank.height = canvas.height;
                    const isBlank = canvas.toDataURL() === blank.toDataURL();
                    hiddenInput.value = isBlank ? '' : canvas.toDataURL('image/png');
                } catch (e) {
                    hiddenInput.value = canvas.toDataURL('image/png');
                }
            }

            function loadFromHidden() {
                if (!hiddenInput || !hiddenInput.value) return;
                const v = hiddenInput.value.trim();
                if (!v || v.indexOf('data:image') !== 0) return;
                const img = new Image();
                img.onload = () => {
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                };
                img.src = v;
            }

            resizeCanvas();
            loadFromHidden();

            window.addEventListener('resize', () => {
                resizeCanvas();
                loadFromHidden();
            });

            canvas.addEventListener('mousedown', (e) => {
                isDrawing = true;
                const rect = canvas.getBoundingClientRect();
                ctx.beginPath();
                ctx.moveTo(e.clientX - rect.left, e.clientY - rect.top);
            });

            canvas.addEventListener('mousemove', (e) => {
                if (!isDrawing) return;
                const rect = canvas.getBoundingClientRect();
                ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top);
                ctx.stroke();
            });

            function stopDrawing() {
                if (!isDrawing) return;
                isDrawing = false;
                saveToHidden();
            }

            canvas.addEventListener('mouseup', stopDrawing);
            canvas.addEventListener('mouseout', stopDrawing);

            canvas.addEventListener('touchstart', (e) => {
                e.preventDefault();
                const touch = e.touches[0];
                const rect = canvas.getBoundingClientRect();
                isDrawing = true;
                ctx.beginPath();
                ctx.moveTo(touch.clientX - rect.left, touch.clientY - rect.top);
            }, { passive: false });

            canvas.addEventListener('touchmove', (e) => {
                e.preventDefault();
                if (!isDrawing) return;
                const touch = e.touches[0];
                const rect = canvas.getBoundingClientRect();
                ctx.lineTo(touch.clientX - rect.left, touch.clientY - rect.top);
                ctx.stroke();
            }, { passive: false });

            canvas.addEventListener('touchend', stopDrawing);

            const clearBtn = document.getElementById(canvasId + '_clear');
            if (clearBtn) {
                clearBtn.addEventListener('click', () => {
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                    if (hiddenInput) hiddenInput.value = '';
                });
            }

            const form = canvas.closest('form');
            if (form) {
                form.addEventListener('submit', () => saveToHidden());
            }
        }


        function initTinyMceEditors() {
            if (typeof window.tinymce === 'undefined') return;
            const richAreas = Array.from(document.querySelectorAll('textarea[data-richtext="tinymce"]'));
            if (!richAreas.length) return;

            richAreas.forEach(function(node){
                if (!node.id) {
                    node.id = 'tiny_' + Math.random().toString(36).slice(2, 10);
                }
                if (window.tinymce.get(node.id)) {
                    window.tinymce.get(node.id).remove();
                }
                const profile = node.getAttribute('data-richtext-profile') || 'default';
                const isEmail = profile === 'html_email';
                const isQuestion = profile === 'cbt_question';
                window.tinymce.init({
                    selector: '#' + node.id,
                    promotion: false,
                    branding: false,
                    menubar: 'file edit view insert format table tools help',
                    plugins: 'advlist autolink lists link charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime table help wordcount autoresize',
                    toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link table blockquote code preview removeformat | fullscreen help',
                    toolbar_sticky: true,
                    min_height: isEmail ? 420 : (isQuestion ? 300 : 260),
                    autoresize_bottom_margin: 18,
                    browser_spellcheck: true,
                    contextmenu: 'link table',
                    convert_urls: false,
                    relative_urls: false,
                    link_default_target: '_blank',
                    link_assume_external_targets: true,
                    object_resizing: false,
                    content_style: 'body{font-family:Segoe UI,Tahoma,Verdana,sans-serif;font-size:14px;line-height:1.6;padding:12px;} p{margin:0 0 .85rem;} table{border-collapse:collapse;width:100%;} table td,table th{border:1px solid #dbe5f1;padding:6px 8px;}',
                    setup: function(editor) {
                        editor.on('change input undo redo keyup setcontent', function() {
                            editor.save();
                        });
                    }
                });
            });

            document.querySelectorAll('form').forEach(function(form){
                if (form.dataset.tinyBound === '1') return;
                form.dataset.tinyBound = '1';
                form.addEventListener('submit', function(){
                    if (typeof window.tinymce !== 'undefined') {
                        window.tinymce.triggerSave();
                    }
                });
            });
        }


        // Initialize signature pads when DOM is loaded
        document.addEventListener('DOMContentLoaded', () => {
            initTinyMceEditors();

            // Initialize signature pads found in the DOM
            const canvases = document.querySelectorAll('canvas.signature-canvas');
            canvases.forEach(canvas => {
                if (!canvas.id) return;

                let hiddenId = canvas.id + '_data';
                if (canvas.id === 'applicant_signature') hiddenId = 'applicant_signature_data';
                if (canvas.id === 'referee_signature') hiddenId = 'ref_signature_data';

                initSignaturePad(canvas.id, hiddenId);
            });

            // -------- Multi-select UX improvement (no CTRL/CMD required) --------
            // Allows users to click options to toggle selection on/off while keeping other selections.
            // Applies to the School Certificate "Subjects" multi-select.
            function enableClickToggleMultiSelect(selectEl){
                if (!selectEl || !selectEl.multiple) return;

                // Prevent attaching twice
                if (selectEl.dataset.clickToggleBound === '1') return;
                selectEl.dataset.clickToggleBound = '1';

                // Desktop: clicking an option normally clears others unless CTRL/CMD is held.
                // Intercept option mousedown and toggle selection manually.
                selectEl.addEventListener('mousedown', function(e){
                    const opt = e.target;
                    if (!opt || opt.tagName !== 'OPTION') return;
                    // Allow normal behavior if disabled
                    if (selectEl.disabled) return;

                    e.preventDefault();
                    e.stopPropagation();

                    const prevScroll = selectEl.scrollTop;
                    opt.selected = !opt.selected;

                    // Keep focus for keyboard accessibility
                    try { selectEl.focus({ preventScroll: true }); } catch(e) { try { selectEl.focus(); } catch(_) {} }

                    // Restore scroll position (some browsers jump)
                    setTimeout(() => { selectEl.scrollTop = prevScroll; }, 0);

                    // Trigger change so the grades UI updates
                    selectEl.dispatchEvent(new Event('change', { bubbles: true }));
                });

                // Mobile/Safari fallback: toggle last selectedIndex on click
                selectEl.addEventListener('click', function(){
                    // Some mobile browsers won't fire mousedown on OPTION; this keeps it usable.
                    if (selectEl.disabled) return;
                    const idx = selectEl.selectedIndex;
                    if (idx < 0) return;
                    const opt = selectEl.options[idx];
                    if (!opt) return;
                    // If user clicked the same option, toggle it.
                    // We can't know the exact clicked option reliably on some mobile UIs,
                    // but this still provides consistent behavior.
                    // (No preventDefault here; the UI picker may be native.)
                    // Ensure change event fires after possible native update.
                    setTimeout(() => {
                        // Keep other selections; just ensure UI change triggers downstream logic.
                        selectEl.dispatchEvent(new Event('change', { bubbles: true }));
                    }, 0);
                });
            }


            // -------- Examination Results UI (Subjects + Grades, Custom Exam Type) --------
            const EXAM_SUBJECTS = <?php echo json_encode([
                "English Language","Mathematics","Biology","Chemistry","Physics","Agricultural Science","Economics","Government","Geography",
                "Literature in English","Civic Education","Christian Religious Studies","Islamic Religious Studies","Commerce","Financial Accounting",
                "Further Mathematics","Data Processing","Computer Studies","Technical Drawing","Home Economics","Fine Arts","French","History"
            ]); ?>;
            const EXAM_GRADES = ["A1","B2","B3","C4","C5","C6","D7","E8","F9"];

            function parseSubjectsGrades(str){
                const map = {};
                (str || '').split(',').map(s => s.trim()).filter(Boolean).forEach(pair => {
                    // Expect formats like "Mathematics A1" or "Mathematics: A1"
                    let p = pair.replace(':', ' ').trim();
                    const parts = p.split(/\s+/);
                    if (parts.length < 2) return;
                    const grade = parts.pop();
                    const subject = parts.join(' ');
                    map[subject] = grade;
                });
                return map;
            }

            function buildSubjectOptions(){
                return EXAM_SUBJECTS.map(s => `<option value="${s}">${s}</option>`).join('');
            }
            function buildGradeOptions(selected){
                return EXAM_GRADES.map(g => `<option value="${g}" ${selected===g?'selected':''}>${g}</option>`).join('');
            }

            function initExamRow(tr){
                if (!tr) return;

                const typeSel = tr.querySelector('.exam-type-select');
                const otherIn = tr.querySelector('.exam-type-other');
                if (typeSel && otherIn) {
                    const syncOther = () => {
                        if (typeSel.value === 'OTHER') {
                            otherIn.style.display = '';
                        } else {
                            otherIn.style.display = 'none';
                            otherIn.value = '';
                        }
                    };
                    typeSel.addEventListener('change', syncOther);
                    syncOther();
                }

                const subjectsSel = tr.querySelector('.exam-subjects');
                const rowsDiv = tr.querySelector('.exam-grade-rows');
                const hidden = tr.querySelector('.exam-grades-hidden');

                if (!subjectsSel || !rowsDiv || !hidden) return;

                // Enable click-to-toggle multi-select (no CTRL/CMD required)
                enableClickToggleMultiSelect(subjectsSel);

                // Enable click-to-toggle multi-select behavior
                enableClickToggleMultiSelect(subjectsSel);

                // Ensure options exist (for dynamically added rows)
                if (subjectsSel.options.length === 0) {
                    subjectsSel.innerHTML = buildSubjectOptions();
                }

                const initialMap = parseSubjectsGrades(hidden.value);
                // Preselect any subjects from stored value
                Array.from(subjectsSel.options).forEach(opt => {
                    if (initialMap[opt.value]) opt.selected = true;
                });

                const render = () => {
                    const selectedSubjects = Array.from(subjectsSel.selectedOptions).map(o => o.value);
                    const currentMap = parseSubjectsGrades(hidden.value);
                    rowsDiv.innerHTML = '';

                    selectedSubjects.forEach(sub => {
                        const gradeSel = currentMap[sub] || initialMap[sub] || 'A1';
                        const row = document.createElement('div');
                        row.className = 'sg-row';
                        row.innerHTML = `
                            <div class="sg-subject">${sub}</div>
                            <select class="form-select form-select-sm sg-grade" data-subject="${sub}">
                                ${buildGradeOptions(gradeSel)}
                            </select>
                        `;
                        rowsDiv.appendChild(row);
                    });

                    // Update hidden from current UI
                    const updateHidden = () => {
                        const pairs = [];
                        rowsDiv.querySelectorAll('select.sg-grade').forEach(sel => {
                            const sub = sel.getAttribute('data-subject') || '';
                            const gr = sel.value || '';
                            if (sub && gr) pairs.push(`${sub} ${gr}`);
                        });
                        hidden.value = pairs.join(', ');
                    };

                    rowsDiv.querySelectorAll('select.sg-grade').forEach(sel => {
                        sel.addEventListener('change', updateHidden);
                    });

                    updateHidden();
                };

                subjectsSel.addEventListener('change', render);
                render();
            }

            // Initialize existing rows
            document.querySelectorAll('#resultsTable tbody tr').forEach(tr => initExamRow(tr));

            // Dynamic form rows
            window.addSchoolRow = function() {
                const tbody = document.querySelector('#schoolsTable tbody');
                if(!tbody) return;
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td><input type="text" class="form-control form-control-sm" name="school_name[]"></td>
                    <td><input type="month" class="form-control form-control-sm" name="school_from[]"></td>
                    <td><input type="month" class="form-control form-control-sm" name="school_to[]"></td>
                    <td><input type="text" class="form-control form-control-sm" name="school_qual[]"></td>
                    <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()">X</button></td>
                `;
                tbody.appendChild(row);
            };

            window.addResultRow = function() {
                const tbody = document.querySelector('#resultsTable tbody');
                if(!tbody) return;
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td>
                        <select class="form-select form-select-sm exam-type-select" name="exam_type[]">
                            <option value="WAEC">WAEC</option>
                            <option value="NECO">NECO</option>
                            <option value="NABTEB">NABTEB/NAPTEB</option>
                            <option value="GCE">GCE</option>
                            <option value="OTHER">OTHER</option>
                        </select>
                        <input type="text" class="form-control form-control-sm mt-1 exam-type-other" name="exam_type_other[]" placeholder="Enter examination name" style="display:none;">
                    </td>
                    <td><input type="month" class="form-control form-control-sm" name="exam_date[]"></td>
                    <td><input type="text" class="form-control form-control-sm" name="exam_no[]"></td>
                    <td>
                        <div class="exam-sg">
                            <select class="form-select form-select-sm exam-subjects" multiple></select>
                            <div class="exam-grade-rows mt-2"></div>
                            <input type="hidden" name="exam_grades[]" class="exam-grades-hidden" value="">
                            <div class="form-text">Select subjects then choose grades.</div>
                        </div>
                    </td>
                    <td><div class="form-check"><input class="form-check-input" type="checkbox" name="exam_attached[]" checked></div></td>
                    <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()">X</button></td>
                `;
                tbody.appendChild(row);
                initExamRow(row);
            };

            window.addSubjectRow = function() {
                const tbody = document.querySelector('#subjectsTable tbody');
                if(!tbody) return;
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td>
                        <select class="form-select form-select-sm" name="subject_name[]">
                            <option value="Mathematics">Mathematics</option>
                            <option value="English Language">English Language</option>
                            <option value="Biology">Biology</option>
                            <option value="Chemistry">Chemistry</option>
                            <option value="Physics">Physics</option>
                            <option value="Economics">Economics</option>
                            <option value="Geography">Geography</option>
                            <option value="Government">Government</option>
                            <option value="Literature in English">Literature in English</option>
                            <option value="History">History</option>
                            <option value="Christian Religious Studies">Christian Religious Studies</option>
                            <option value="Islamic Religious Studies">Islamic Religious Studies</option>
                            <option value="Accounting">Accounting</option>
                            <option value="Commerce">Commerce</option>
                            <option value="Agricultural Science">Agricultural Science</option>
                            <option value="Further Mathematics">Further Mathematics</option>
                        </select>
                    </td>
                    <td>
                        <select class="form-select form-select-sm" name="subject_grade[]">
                            <option value="A1">A1</option>
                            <option value="B2">B2</option>
                            <option value="B3">B3</option>
                            <option value="C4">C4</option>
                            <option value="C5">C5</option>
                            <option value="C6">C6</option>
                            <option value="D7">D7</option>
                            <option value="E8">E8</option>
                            <option value="F9">F9</option>
                        </select>
                    </td>
                    <td><button type="button" class="btn btn-sm btn-danger" onclick="this.closest('tr').remove()">X</button></td>
                `;
                tbody.appendChild(row);
            };

            // Update declarant name (for application_form)
            const surnameInput = document.querySelector('input[name="surname"]');
            const firstNameInput = document.querySelector('input[name="firstname"]');
            const declarantSpan = document.getElementById('declarantName');

            if (surnameInput && firstNameInput && declarantSpan) {
                function updateDeclarantName() {
                    const s = surnameInput.value.toUpperCase() || "";
                    const f = firstNameInput.value.toUpperCase() || "";
                    declarantSpan.textContent = `${f} ${s}`;
                }

                surnameInput.addEventListener('input', updateDeclarantName);
                firstNameInput.addEventListener('input', updateDeclarantName);
            }

            // Admin: delete application (AJAX)
            window.deleteApplication = function(id){
                if(!id) return;
                if(!confirm('Delete this application? This cannot be undone.')) return;
                const fd = new FormData();
                fd.append('action','delete_application');
                fd.append('id', String(id));
                fetch('?page=admin_applications', {method:'POST', body:fd})
                    .then(r=>r.text())
                    .then(t=>{
                        if(String(t).trim()==='success'){
                            const btn = document.querySelector('button[onclick="deleteApplication(' + id + ')"]');
                            if(btn){ const tr = btn.closest('tr'); if(tr) tr.remove(); }
                            alert('Application deleted.');
                        } else {
                            alert('Delete failed: ' + t);
                        }
                    })
                    .catch(()=>alert('Delete failed. Please try again.'));
            };
});
        </script>
    </div>


<script>
document.addEventListener('DOMContentLoaded', function () {
  const walletForms = Array.from(document.querySelectorAll('form')).filter(function (form) {
    const actionInput = form.querySelector('input[name="action"]');
    return actionInput && actionInput.value === 'pay_with_wallet';
  });
  if (!walletForms.length) return;

  const typeLabels = {
    registration: 'Registration Fee',
    acceptance: 'Acceptance Fee',
    hostel: 'Hostel Fee',
    school: 'School Fee',
    assigned: 'Assigned Payment'
  };

  function formatNaira(value) {
    const n = Number(value || 0);
    try {
      return new Intl.NumberFormat('en-NG', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(n);
    } catch (e) {
      return (Math.round(n * 100) / 100).toFixed(2);
    }
  }

  function escapeHtml(value) {
    return String(value == null ? '' : value)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#039;');
  }

  function readBalance(form) {
    const hidden = form.querySelector('input[name="wallet_balance_snapshot"]');
    if (hidden && hidden.value !== '') return hidden.value;
    const block = form.closest('.border, .card, .card-body, .mt-3, .p-3') || form.parentElement;
    if (!block) return '0';
    const textNode = Array.from(block.querySelectorAll('*')).find(function (node) {
      return /Wallet Balance:/i.test(node.textContent || '');
    });
    if (!textNode) return '0';
    const match = (textNode.textContent || '').match(/₦\s*([\d,]+(?:\.\d+)?)/i);
    return match ? match[1].replace(/,/g, '') : '0';
  }

  function readAmount(form) {
    const hidden = form.querySelector('input[name="wallet_fee_amount"]');
    if (hidden && hidden.value !== '') return hidden.value;
    const btn = form.querySelector('button[type="submit"], input[type="submit"]');
    const btnText = btn ? (btn.textContent || btn.value || '') : '';
    const match = btnText.match(/₦\s*([\d,]+(?:\.\d+)?)/i);
    if (match) return match[1].replace(/,/g, '');
    const card = form.closest('.card, .card-body, .border') || form.parentElement;
    if (!card) return '0';
    const amountNode = Array.from(card.querySelectorAll('.fw-bold, strong')).find(function (node) {
      return /₦/.test(node.textContent || '');
    });
    if (!amountNode) return '0';
    const match2 = (amountNode.textContent || '').match(/₦\s*([\d,]+(?:\.\d+)?)/i);
    return match2 ? match2[1].replace(/,/g, '') : '0';
  }

  function readFeeName(form) {
    const hidden = form.querySelector('input[name="wallet_fee_name"]');
    if (hidden && hidden.value.trim() !== '') return hidden.value.trim();
    const type = (form.querySelector('input[name="payment_type"]') || {}).value || '';
    if (type === 'assigned') {
      const card = form.closest('.card');
      const titleNode = card ? card.querySelector('.fw-bold') : null;
      if (titleNode && titleNode.textContent.trim() !== '') return titleNode.textContent.trim();
    }
    return typeLabels[type] || 'Selected Fee';
  }

  let walletModalWrap = null;
  let walletModalKeyHandler = null;
  let walletModalPreviousOverflow = '';

  function closeWalletModal() {
    if (walletModalKeyHandler) {
      document.removeEventListener('keydown', walletModalKeyHandler);
      walletModalKeyHandler = null;
    }
    const stale = document.getElementById('wallet-payment-confirm-overlay');
    if (stale && stale.parentNode) {
      stale.parentNode.removeChild(stale);
    }
    if (walletModalWrap && walletModalWrap.parentNode) {
      walletModalWrap.parentNode.removeChild(walletModalWrap);
    }
    walletModalWrap = null;
    if (walletModalPreviousOverflow !== '') {
      document.body.style.overflow = walletModalPreviousOverflow;
      walletModalPreviousOverflow = '';
    }
  }

  function showWalletConfirm(form) {
    closeWalletModal();
    const amount = readAmount(form);
    const feeName = readFeeName(form);
    const balance = readBalance(form);

    const wrap = document.createElement('div');
    wrap.id = 'wallet-payment-confirm-overlay';
    wrap.setAttribute('role', 'presentation');
    wrap.style.position = 'fixed';
    wrap.style.top = '0';
    wrap.style.right = '0';
    wrap.style.bottom = '0';
    wrap.style.left = '0';
    wrap.style.width = '100vw';
    wrap.style.height = '100vh';
    wrap.style.display = 'flex';
    wrap.style.alignItems = 'center';
    wrap.style.justifyContent = 'center';
    wrap.style.padding = '16px';
    wrap.style.background = 'rgba(10, 22, 40, 0.55)';
    wrap.style.zIndex = '2147483000';
    wrap.style.boxSizing = 'border-box';

    const dialog = document.createElement('div');
    dialog.id = 'wallet-payment-confirm-dialog';
    dialog.setAttribute('role', 'dialog');
    dialog.setAttribute('aria-modal', 'true');
    dialog.setAttribute('aria-labelledby', 'walletConfirmTitle');
    dialog.style.width = '100%';
    dialog.style.maxWidth = '520px';
    dialog.style.background = '#ffffff';
    dialog.style.borderRadius = '18px';
    dialog.style.boxShadow = '0 20px 50px rgba(15, 23, 42, 0.28)';
    dialog.style.overflow = 'hidden';
    dialog.style.margin = 'auto';
    dialog.style.position = 'relative';
    dialog.style.transform = 'translateZ(0)';

    dialog.innerHTML = '' +
      '<div style="background:#0d47a1;color:#fff;padding:16px 20px;display:flex;align-items:center;justify-content:space-between;gap:12px;">' +
        '<div style="display:flex;align-items:center;gap:10px;min-width:0;">' +
          '<i class="bi bi-wallet2"></i>' +
          '<strong id="walletConfirmTitle" style="font-size:1.05rem;line-height:1.2;">Confirm Wallet Payment</strong>' +
        '</div>' +
        '<button type="button" aria-label="Close" data-wallet-close="1" style="background:transparent;border:none;color:#fff;font-size:28px;line-height:1;cursor:pointer;padding:0 4px;">&times;</button>' +
      '</div>' +
      '<div style="padding:22px 20px 20px;">' +
        '<div style="margin-bottom:14px;color:#475569;font-size:1rem;line-height:1.6;">You are about to pay <strong style="color:#0f172a;">₦' + escapeHtml(formatNaira(amount)) + '</strong> for <strong style="color:#0f172a;">' + escapeHtml(feeName) + '</strong> using your wallet.</div>' +
        '<div style="border:1px solid #dbe4f0;border-radius:14px;background:#f8fafc;padding:14px 16px;margin-bottom:16px;">' +
          '<div style="font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#64748b;margin-bottom:4px;">Current Wallet Balance</div>' +
          '<div style="font-weight:700;font-size:1.25rem;color:#0f172a;">₦' + escapeHtml(formatNaira(balance)) + '</div>' +
        '</div>' +
        '<div style="font-weight:600;color:#0f172a;margin-bottom:20px;">Proceed with payment?</div>' +
        '<div style="display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end;">' +
          '<button type="button" data-wallet-cancel="1" class="btn btn-outline-secondary">Cancel</button>' +
          '<button type="button" data-wallet-confirm="1" class="btn btn-primary">Proceed</button>' +
        '</div>' +
      '</div>';

    wrap.appendChild(dialog);
    document.body.appendChild(wrap);
    walletModalWrap = wrap;
    walletModalPreviousOverflow = document.body.style.overflow || '';
    document.body.style.overflow = 'hidden';

    const cancelBtn = wrap.querySelector('[data-wallet-cancel="1"]');
    const confirmBtn = wrap.querySelector('[data-wallet-confirm="1"]');
    const closeBtn = wrap.querySelector('[data-wallet-close="1"]');

    [cancelBtn, closeBtn].forEach(function (btn) {
      if (btn) btn.addEventListener('click', closeWalletModal);
    });

    wrap.addEventListener('click', function (ev) {
      if (ev.target === wrap) closeWalletModal();
    });

    walletModalKeyHandler = function (ev) {
      if (ev.key === 'Escape') {
        ev.preventDefault();
        closeWalletModal();
      }
    };
    document.addEventListener('keydown', walletModalKeyHandler);

    if (confirmBtn) {
      confirmBtn.addEventListener('click', function () {
        const flag = form.querySelector('input[name="wallet_payment_confirmed"]');
        if (flag) flag.value = '1';
        closeWalletModal();
        if (typeof form.requestSubmit === 'function') {
          form.requestSubmit();
        } else {
          HTMLFormElement.prototype.submit.call(form);
        }
      });
      setTimeout(function () { confirmBtn.focus(); }, 0);
    } else if (dialog) {
      dialog.setAttribute('tabindex', '-1');
      dialog.focus();
    }
  }

  walletForms.forEach(function (form) {
    form.addEventListener('submit', function (ev) {
      const flag = form.querySelector('input[name="wallet_payment_confirmed"]');
      if (flag && flag.value === '1') return;
      ev.preventDefault();
      if (flag) flag.value = '0';
      showWalletConfirm(form);
    });
  });
});
</script>

<!-- Floating Notifications (chat icon) -->
<?php if (isLoggedIn()):
    $role = currentUserRole();
    $uid  = (int)currentUserId();
    $unreadFloat = getUnreadNotificationCount($pdo, $role, $uid);
?>
<a href="?page=notifications" class="btn btn-primary floating-chat-btn no-print" title="Notifications">
    <i class="bi bi-chat-dots-fill"></i>
    <?php if ($unreadFloat > 0): ?>
        <span class="badge bg-danger"><?php echo (int)$unreadFloat; ?></span>
    <?php endif; ?>
</a>
<?php endif; ?>

<script>
document.addEventListener('DOMContentLoaded', function () {
  function setupTableFilters(scope) {
    const search = scope.querySelector('#search');
    const filterBtn = scope.querySelector('#filter_btn');
    const resetBtn = scope.querySelector('#reset_btn');
    const statusFilter = scope.querySelector('#status_filter');
    const typeFilter = scope.querySelector('#type_filter');
    const table = scope.querySelector('table');
    if (!table) return;

    const tbody = table.tBodies && table.tBodies[0] ? table.tBodies[0] : null;
    if (!tbody) return;

    function norm(s){ return (s||'').toString().toLowerCase().trim(); }

    function apply() {
      const q = norm(search ? search.value : '');
      const sf = norm(statusFilter ? statusFilter.value : '');
      const tf = norm(typeFilter ? typeFilter.value : '');
      Array.from(tbody.rows).forEach(function (tr) {
        const rowText = norm(tr.innerText);
        let ok = true;
        if (q && rowText.indexOf(q) === -1) ok = false;
        if (sf && rowText.indexOf(sf) === -1) ok = false;
        if (tf && rowText.indexOf(tf) === -1) ok = false;
        tr.style.display = ok ? '' : 'none';
      });
    }

    if (filterBtn) filterBtn.addEventListener('click', function(e){ e.preventDefault(); apply(); });
    if (resetBtn) resetBtn.addEventListener('click', function(e){
      e.preventDefault();
      if (search) search.value = '';
      if (statusFilter) statusFilter.value = '';
      if (typeFilter) typeFilter.value = '';
      apply();
    });

    if (search) search.addEventListener('keyup', apply);
    if (statusFilter) statusFilter.addEventListener('change', apply);
    if (typeFilter) typeFilter.addEventListener('change', apply);

    // initial
    apply();
  }

  document.querySelectorAll('.card-body').forEach(setupTableFilters);
});
</script>



<?php
$__cascadeDepartments = [];
$__cascadeProgrammes = [];
try {
    $__cascadeDepartments = $pdo->query("SELECT id, faculty_id, name FROM departments WHERE is_active = 1 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
} catch (Throwable $e) { $__cascadeDepartments = []; }
try {
    $__cascadeProgrammes = portalFetchDistinctProgrammeOptions($pdo);
} catch (Throwable $e) { $__cascadeProgrammes = []; }
?>
<script>
(function(){
  const academicCascade = {
    departments: <?php echo json_encode($__cascadeDepartments, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>,
    programmes: <?php echo json_encode($__cascadeProgrammes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>
  };

  function firstPlaceholder(selectEl, fallbackText) {
    if (!selectEl) return { value: '', text: fallbackText || 'Select option' };
    const opts = Array.from(selectEl.options || []);
    const match = opts.find(opt => String(opt.value) === '' || String(opt.value) === '0');
    if (match) return { value: match.value, text: match.textContent || fallbackText || 'Select option' };
    return { value: '', text: fallbackText || 'Select option' };
  }

  function refillSelect(selectEl, items, selectedValue, placeholderText, valueKey, labelKey, selectedLabel) {
    if (!selectEl) return;
    const placeholder = firstPlaceholder(selectEl, placeholderText);
    const wanted = String(selectedValue ?? '');
    const wantedLabel = normalizeAcademicOptionLabel(selectedLabel || '');
    selectEl.innerHTML = '';

    const ph = document.createElement('option');
    ph.value = placeholder.value;
    ph.textContent = placeholder.text;
    selectEl.appendChild(ph);

    let found = false;
    items.forEach(item => {
      const opt = document.createElement('option');
      opt.value = String(item[valueKey]);
      opt.textContent = item[labelKey] ?? '';
      opt.dataset.scopeName = normalizeAcademicOptionLabel(item && item[labelKey]);
      if (item && item.mode_of_study) {
        opt.dataset.modeOfStudy = String(item.mode_of_study || '');
      }
      if (String(item[valueKey]) === wanted || (!found && wantedLabel !== '' && opt.dataset.scopeName === wantedLabel)) {
        opt.selected = true;
        found = true;
      }
      selectEl.appendChild(opt);
    });

    if (!found) {
      selectEl.value = placeholder.value;
    }
  }

  function resolveScope(selectEl) {
    if (!selectEl) return null;
    return selectEl.closest('form') || selectEl.closest('tr') || selectEl.closest('.modal-content') || selectEl.closest('.card') || selectEl.parentElement;
  }

  function filterDepartments(scope, preserveSelection) {
    if (!scope) return;
    const facultySelect = scope.querySelector('select[name="faculty_id"]');
    const departmentSelect = scope.querySelector('select[name="department_id"]');
    if (!departmentSelect) return;

    const facultyId = parseInt((facultySelect && facultySelect.value) ? facultySelect.value : '0', 10) || 0;
    const selectedDepartment = preserveSelection ?? departmentSelect.value;
    const selectedDepartmentLabel = ((departmentSelect.options || [])[departmentSelect.selectedIndex] || {}).textContent || '';
    const filtered = academicCascade.departments.filter(dep => !facultyId || parseInt(dep.faculty_id || 0, 10) === facultyId);
    refillSelect(departmentSelect, filtered, selectedDepartment, 'Select Department', 'id', 'name', selectedDepartmentLabel);
    filterProgrammes(scope);
  }

  function normalizeAcademicOptionLabel(value) {
    return String(value || '').trim().replace(/\s+/g, ' ').toLowerCase();
  }

  function uniqueProgrammeItems(items) {
    const seen = new Set();
    const unique = [];
    (items || []).forEach(function(item) {
      const norm = normalizeAcademicOptionLabel(item && item.name);
      if (!norm || seen.has(norm)) return;
      seen.add(norm);
      unique.push(item);
    });
    return unique;
  }

  function appendNoProgrammeState(selectEl, message) {
    if (!selectEl) return;
    const opt = document.createElement('option');
    opt.value = '';
    opt.textContent = message || 'No matching programmes';
    opt.disabled = true;
    selectEl.appendChild(opt);
  }

  function ensureModeOption(selectEl, value) {
    if (!selectEl) return;
    const normalized = String(value || '').trim();
    if (!normalized) return;
    const existing = Array.from(selectEl.options || []).find(function(opt){
      return String(opt.value || '').trim() === normalized;
    });
    if (existing) return;
    const opt = document.createElement('option');
    opt.value = normalized;
    opt.textContent = normalized;
    selectEl.appendChild(opt);
  }

  function syncModeOfStudy(scope) {
    if (!scope) return;
    const programmeSelect = scope.querySelector('select[name="programme_id"]');
    const modeInput = scope.querySelector('[data-mode-of-study-preview="1"]');
    if (!modeInput) return;
    const selectedOption = programmeSelect ? programmeSelect.options[programmeSelect.selectedIndex] : null;
    const modeValue = selectedOption ? String(selectedOption.dataset.modeOfStudy || '') : '';
    const fallbackValue = String(modeInput.value || '').trim() || 'Full-Time';
    const nextValue = String(modeValue || fallbackValue || 'Full-Time').trim();
    ensureModeOption(modeInput, nextValue);
    modeInput.value = nextValue;
  }

  function filterProgrammes(scope, preserveSelection) {
    if (!scope) return;
    const facultySelect = scope.querySelector('select[name="faculty_id"]');
    const departmentSelect = scope.querySelector('select[name="department_id"]');
    const programmeSelect = scope.querySelector('select[name="programme_id"]');
    if (!programmeSelect) return;

    const facultyId = parseInt((facultySelect && facultySelect.value) ? facultySelect.value : '0', 10) || 0;
    const departmentId = parseInt((departmentSelect && departmentSelect.value) ? departmentSelect.value : '0', 10) || 0;
    const selectedProgramme = preserveSelection ?? programmeSelect.value;
    const selectedProgrammeLabel = ((programmeSelect.options || [])[programmeSelect.selectedIndex] || {}).textContent || '';
    const filtered = uniqueProgrammeItems(academicCascade.programmes.filter(prog => {
      const progFaculty = parseInt(prog.faculty_id || 0, 10) || 0;
      const progDepartment = parseInt(prog.department_id || 0, 10) || 0;
      if (departmentId && progDepartment !== departmentId) return false;
      if (!departmentId && facultyId && progFaculty !== facultyId) return false;
      return true;
    }));
    refillSelect(programmeSelect, filtered, selectedProgramme, 'Select Programme', 'id', 'name', selectedProgrammeLabel);
    if (!filtered.length && (facultyId || departmentId)) {
      appendNoProgrammeState(programmeSelect, 'No matching programmes');
    }
    syncModeOfStudy(scope);
  }

  function initAcademicCascades() {
    const scopes = new Set();
    document.querySelectorAll('select[name="faculty_id"], select[name="department_id"], select[name="programme_id"]').forEach(selectEl => {
      const scope = resolveScope(selectEl);
      if (scope) scopes.add(scope);
    });

    scopes.forEach(scope => {
      const facultySelect = scope.querySelector('select[name="faculty_id"]');
      const departmentSelect = scope.querySelector('select[name="department_id"]');
      const programmeSelect = scope.querySelector('select[name="programme_id"]');

      if (facultySelect && !facultySelect.dataset.cascadeBound) {
        facultySelect.dataset.cascadeBound = '1';
        facultySelect.addEventListener('change', function(){
          filterDepartments(scope);
        });
      }
      if (departmentSelect && !departmentSelect.dataset.cascadeBound) {
        departmentSelect.dataset.cascadeBound = '1';
        departmentSelect.addEventListener('change', function(){
          filterProgrammes(scope);
        });
      }
      if (programmeSelect && !programmeSelect.dataset.cascadeModeBound) {
        programmeSelect.dataset.cascadeModeBound = '1';
        programmeSelect.addEventListener('change', function(){
          syncModeOfStudy(scope);
        });
      }

      if (facultySelect || departmentSelect) {
        filterDepartments(scope, departmentSelect ? departmentSelect.value : '');
      } else {
        filterProgrammes(scope);
      }
      syncModeOfStudy(scope);
    });
  }

  document.addEventListener('DOMContentLoaded', initAcademicCascades);
})();
</script>

<!-- PWA Install UI -->
<div id="pwa-install-wrap" class="no-print" style="position:fixed; right:16px; bottom:140px; z-index:9999; display:none;">
    <button id="pwa-install-btn" class="btn btn-success btn-sm">
        <i class="bi bi-download"></i> Install App
    </button>
</div>

<div id="pwa-ios-tip" class="no-print" style="position:fixed; left:16px; right:16px; bottom:90px; z-index:9999; display:none;">
    <div class="alert alert-info d-flex align-items-center justify-content-between mb-0" style="box-shadow:0 10px 30px rgba(0,0,0,.15);">
        <div style="padding-right:10px;">
            Install this app: tap <strong>Share</strong> then <strong>Add to Home Screen</strong>.
        </div>
        <button type="button" class="btn btn-sm btn-outline-secondary" id="pwa-ios-close">Close</button>
    </div>
</div>

<script>
(function(){
    if ('serviceWorker' in navigator) {
        window.addEventListener('load', function () {
            navigator.serviceWorker.register('?pwa=sw').catch(function(){});
        });
    }

    let deferredPrompt = null;
    const wrap = document.getElementById('pwa-install-wrap');
    const btn = document.getElementById('pwa-install-btn');

    window.addEventListener('beforeinstallprompt', function (e) {
        e.preventDefault();
        deferredPrompt = e;
        if (wrap) wrap.style.display = 'block';
    });

    if (btn) {
        btn.addEventListener('click', async function () {
            if (!deferredPrompt) return;
            deferredPrompt.prompt();
            try { await deferredPrompt.userChoice; } catch(e) {}
            deferredPrompt = null;
            if (wrap) wrap.style.display = 'none';
        });
    }

    const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent);
    const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) || window.navigator.standalone;
    const iosTip = document.getElementById('pwa-ios-tip');
    const iosClose = document.getElementById('pwa-ios-close');

    if (isIOS && !isStandalone) {
        const show = !location.search.includes('page=admin_') && !location.search.includes('page=student_');
        if (show && iosTip) iosTip.style.display = 'block';
    }
    if (iosClose && iosTip) iosClose.addEventListener('click', function(){ iosTip.style.display = 'none'; });

    window.addEventListener('appinstalled', function () {
        deferredPrompt = null;
        if (wrap) wrap.style.display = 'none';
    });
})();
</script>


<!-- Payment Proof + Receipt Modal (professional, single modal) -->
<div class="modal fade" id="paymentDocModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered modal-xl modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="paymentDocModalTitle"><i class="bi bi-receipt"></i> Payment Documents</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <ul class="nav nav-tabs" id="paymentDocTabs" role="tablist" style="display:none;">
          <li class="nav-item" role="presentation">
            <button class="nav-link active" id="tab-receipt" data-bs-toggle="tab" data-bs-target="#pane-receipt" type="button" role="tab">
              <i class="bi bi-file-earmark-text"></i> Receipt
            </button>
          </li>
          <li class="nav-item" role="presentation">
            <button class="nav-link" id="tab-proof" data-bs-toggle="tab" data-bs-target="#pane-proof" type="button" role="tab">
              <i class="bi bi-image"></i> Proof of Payment
            </button>
          </li>
        </ul>
        <div class="tab-content pt-3" id="paymentDocTabContent">
          <div class="tab-pane fade show active" id="pane-receipt" role="tabpanel">
            <div id="paymentDocReceiptBody" class="payment-doc-body"></div>
          </div>
          <div class="tab-pane fade" id="pane-proof" role="tabpanel">
            <div id="paymentDocProofBody" class="payment-doc-body"></div>
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <a class="btn btn-outline-secondary" id="paymentDocOpenNewTab" href="#" target="_blank" rel="noopener">Open current tab in new tab</a>
        <button type="button" class="btn btn-primary" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

<script>
(function(){
  function isImageUrl(u){
    return /\.(png|jpe?g|gif|webp)$/i.test((u||'').split('?')[0].split('#')[0]);
  }
  function isPdfUrl(u){
    return /\.pdf$/i.test((u||'').split('?')[0].split('#')[0]);
  }
  function addEmbedIfReceipt(u){
    try {
      if (u && u.indexOf('page=') !== -1 && /_receipt\b/.test(u)) {
        if (!/[?&]embed=1\b/.test(u)) {
          u += (u.indexOf('?') !== -1 ? '&' : '?') + 'embed=1';
        }
      }
    } catch(e){}
    return u;
  }
  function renderDocInto(container, url){
    if (!container) return;
    if (!url) {
      container.innerHTML = '<div class="alert alert-secondary mb-0">Not available.</div>';
      return;
    }
    var u = url;
    var html = '';
    if (isImageUrl(u)) {
      html = '<div class="text-center"><img src="'+encodeURI(u)+'" alt="Document" style="max-width:100%;height:auto;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 10px 30px rgba(0,0,0,.08);"></div>';
    } else {
      html = '<iframe src="'+encodeURI(u)+'" style="width:100%;height:75vh;border:1px solid #e5e7eb;border-radius:12px;background:#fff;"></iframe>';
    }
    container.innerHTML = html;
  }

  function openPaymentDocs(opts){
    var modalEl = document.getElementById('paymentDocModal');
    if (!modalEl) return;

    var titleEl = document.getElementById('paymentDocModalTitle');
    var tabs = document.getElementById('paymentDocTabs');
    var receiptBody = document.getElementById('paymentDocReceiptBody');
    var proofBody = document.getElementById('paymentDocProofBody');
    var openBtn = document.getElementById('paymentDocOpenNewTab');

    var receiptUrl = opts.receiptUrl ? addEmbedIfReceipt(opts.receiptUrl) : '';
    var proofUrl   = opts.proofUrl || '';
    var hasReceipt = !!receiptUrl;
    var hasProof   = !!proofUrl;

    if (titleEl) titleEl.innerHTML = '<i class="bi bi-receipt"></i> ' + (opts.title || 'Payment Documents');

    // Loading placeholders
    if (receiptBody) receiptBody.innerHTML = '<div class="text-center py-5"><div class="spinner-border" role="status" aria-hidden="true"></div><div class="mt-2 text-muted">Loading receipt…</div></div>';
    if (proofBody) proofBody.innerHTML = '<div class="text-center py-5"><div class="spinner-border" role="status" aria-hidden="true"></div><div class="mt-2 text-muted">Loading proof…</div></div>';

    // Decide UI
    if (tabs) tabs.style.display = (hasReceipt && hasProof) ? '' : 'none';

    // Activate correct tab
    var receiptTabBtn = document.getElementById('tab-receipt');
    var proofTabBtn = document.getElementById('tab-proof');

    if (hasReceipt && (!hasProof || opts.prefer === 'receipt')) {
      if (receiptTabBtn) bootstrap.Tab.getOrCreateInstance(receiptTabBtn).show();
      if (openBtn) openBtn.href = opts.receiptUrl;
    } else if (hasProof) {
      if (proofTabBtn) bootstrap.Tab.getOrCreateInstance(proofTabBtn).show();
      if (openBtn) openBtn.href = opts.proofUrl;
    }

    // Keep open-new-tab linked to the active tab
    function syncOpenBtn(){
      if (!openBtn) return;
      var active = document.querySelector('#paymentDocTabs .nav-link.active');
      if (active && active.id === 'tab-proof') openBtn.href = opts.proofUrl || '#';
      else openBtn.href = opts.receiptUrl || '#';
    }
    modalEl.addEventListener('shown.bs.modal', syncOpenBtn, { once: true });
    document.getElementById('paymentDocTabs')?.addEventListener('shown.bs.tab', syncOpenBtn);

    // Render
    renderDocInto(receiptBody, receiptUrl);
    renderDocInto(proofBody, proofUrl);

    bootstrap.Modal.getOrCreateInstance(modalEl).show();
  }

  // Delegate click
  document.addEventListener('click', function(e){
    var el = e.target.closest('[data-proof-url],[data-receipt-url],a.js-payment-doc,button.js-payment-doc,[data-doc-url]');
    if(!el) return;

    // Combined docs button (preferred)
    var proofUrl = el.getAttribute('data-proof-url');
    var receiptUrl = el.getAttribute('data-receipt-url');
    if (proofUrl || receiptUrl) {
      e.preventDefault();
      openPaymentDocs({
        title: el.getAttribute('data-doc-title') || 'Payment Documents',
        proofUrl: proofUrl || '',
        receiptUrl: receiptUrl || '',
        prefer: el.getAttribute('data-prefer') || ''
      });
      return;
    }

    // Single document legacy
    var url = el.getAttribute('data-doc-url') || el.getAttribute('href');
    if(!url || url === '#') return;
    e.preventDefault();
    var title = el.getAttribute('data-doc-title') || el.getAttribute('title') || (el.textContent || 'Document').trim();
    openPaymentDocs({
      title: title,
      receiptUrl: /_receipt\b/.test(url) ? url : '',
      proofUrl: /_receipt\b/.test(url) ? '' : url,
      prefer: /_receipt\b/.test(url) ? 'receipt' : 'proof'
    });
  });
})();
</script>



<?php $portalResumeJsEnabled = false; try { $portalResumeJsEnabled = isLoggedIn() && portalLazyLoaderEnabled($pdo); } catch (Throwable $e) { $portalResumeJsEnabled = false; } ?>
<script>
window.portalThemeMode = {
  enabled: <?php echo (!empty($portalThemeFeatureEnabled) && isLoggedIn()) ? 'true' : 'false'; ?>,
  currentMode: <?php echo json_encode((string)$portalThemeCurrentMode, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); ?>,
  token: <?php echo json_encode((!empty($portalThemeFeatureEnabled) && isLoggedIn()) ? portalThemePreferenceToken() : '', JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); ?>,
  endpoint: ''
};
(function(){
  if (!window.portalThemeMode || !window.portalThemeMode.enabled || !window.portalThemeMode.token) return;
  var body = document.body;
  var switcher = document.getElementById('portalThemeSwitcher');
  function applyMode(mode){
    mode = mode === 'dark' ? 'dark' : 'light';
    window.portalThemeMode.currentMode = mode;
    body.classList.remove('theme-light','theme-dark');
    body.classList.add('theme-' + mode);
    body.setAttribute('data-theme-mode', mode);
    var metaTheme = document.querySelector('meta[name="theme-color"]');
    if (metaTheme) metaTheme.setAttribute('content', mode === 'dark' ? '#0f172a' : <?php echo json_encode((string)getSystemSettingValue($pdo,'theme_color_1','#003366'), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); ?>);
    if (!switcher) return;
    switcher.querySelectorAll('[data-theme-mode]').forEach(function(btn){
      var active = (btn.getAttribute('data-theme-mode') || 'light') === mode;
      btn.classList.toggle('active', active);
      btn.setAttribute('aria-pressed', active ? 'true' : 'false');
    });
  }
  function persistMode(mode){
    try {
      var fd = new FormData();
      fd.append('action', 'save_theme_preference');
      fd.append('theme_preference_token', window.portalThemeMode.token);
      fd.append('theme_mode', mode);
      fd.append('current_url', (window.location.search || '?page=' + encodeURIComponent(<?php echo json_encode((string)($page ?? ''), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); ?>)) + (window.location.hash || ''));
      fetch(window.location.pathname || '', { method: 'POST', body: fd, credentials: 'same-origin', keepalive: true }).catch(function(){});
    } catch (e) {}
  }
  document.addEventListener('click', function(e){
    var btn = e.target.closest('#portalThemeSwitcher [data-theme-mode]');
    if (!btn) return;
    e.preventDefault();
    var mode = btn.getAttribute('data-theme-mode') || 'light';
    applyMode(mode);
    persistMode(mode);
  });
  document.addEventListener('DOMContentLoaded', function(){ applyMode(window.portalThemeMode.currentMode || 'light'); });
})();

window.portalResumeProgress = {
  enabled: <?php echo $portalResumeJsEnabled ? 'true' : 'false'; ?>,
  token: <?php echo json_encode($portalResumeJsEnabled ? portalResumeProgressToken() : '', JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); ?>,
  endpoint: '',
  currentPage: <?php echo json_encode((string)($page ?? ''), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); ?>
};
(function(){
  if (!window.portalResumeProgress || !window.portalResumeProgress.enabled || !window.portalResumeProgress.token) return;
  var lastSent = '';
  var timer = null;

  function buildUrl(){
    try {
      var qs = window.location.search || ('?page=' + encodeURIComponent(window.portalResumeProgress.currentPage || ''));
      return qs + (window.location.hash || '');
    } catch (e) {
      return '?page=' + encodeURIComponent(window.portalResumeProgress.currentPage || '');
    }
  }

  function send(force){
    var currentUrl = buildUrl();
    if (!currentUrl || (!force && currentUrl === lastSent)) return;
    lastSent = currentUrl;
    try {
      var fd = new FormData();
      fd.append('action', 'save_resume_progress');
      fd.append('resume_state_token', window.portalResumeProgress.token);
      fd.append('current_url', currentUrl);
      if (window.portalThemeMode && window.portalThemeMode.currentMode) {
        fd.append('current_theme_mode', window.portalThemeMode.currentMode);
      }
      if (navigator.sendBeacon) {
        navigator.sendBeacon(window.location.pathname || '', fd);
      } else {
        fetch(window.location.pathname || '', { method: 'POST', body: fd, credentials: 'same-origin', keepalive: true }).catch(function(){});
      }
    } catch (e) {}
  }

  function queueSave(force){
    if (force) { send(true); return; }
    window.clearTimeout(timer);
    timer = window.setTimeout(function(){ send(false); }, 180);
  }

  document.addEventListener('DOMContentLoaded', function(){ queueSave(true); });
  window.addEventListener('hashchange', function(){ queueSave(true); });
  window.addEventListener('beforeunload', function(){ send(true); });
  document.addEventListener('visibilitychange', function(){ if (document.visibilityState === 'hidden') send(true); });
  document.addEventListener('shown.bs.tab', function(){ queueSave(true); });
  document.addEventListener('click', function(e){
    var el = e.target.closest('a[href], button[data-bs-toggle], [data-bs-toggle="tab"], [data-bs-toggle="pill"]');
    if (!el) return;
    queueSave(false);
  }, true);
})();
</script>

<?php
$__portalSessionDropdownOptions = [];
$__portalLevelDropdownOptions = [];
try { $__portalSessionDropdownOptions = portalAvailableSessionOptions($pdo); } catch (Throwable $e) {}
try { $__portalLevelDropdownOptions = portalAcademicLevelOptions($pdo); } catch (Throwable $e) {}
?>
<script>
window.portalSessionDropdownOptions = <?php echo json_encode(array_values($__portalSessionDropdownOptions), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); ?>;
window.portalLevelDropdownOptions = <?php echo json_encode(array_values($__portalLevelDropdownOptions), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); ?>;
window.portalCurrentSessionOption = <?php echo json_encode((string)portalCurrentAcademicSessionName($pdo), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); ?>;
(function(){
  function copyAttributes(from, to){
    Array.from(from.attributes || []).forEach(function(attr){
      if (attr.name === 'type' || attr.name === 'list') return;
      try { to.setAttribute(attr.name, attr.value); } catch (e) {}
    });
    to.className = from.className;
    if (from.required) to.required = true;
    if (from.disabled) to.disabled = true;
    if (from.autofocus) to.autofocus = true;
    if (from.dataset) {
      Object.keys(from.dataset).forEach(function(key){
        try { to.dataset[key] = from.dataset[key]; } catch (e) {}
      });
    }
  }
  function buildSelectFromInput(input, options, blankLabel){
    if (!input || input.tagName !== 'INPUT') return;
    var type = (input.getAttribute('type') || 'text').toLowerCase();
    if (type === 'hidden' || type === 'password' || type === 'number' || type === 'date' || type === 'email' || input.dataset.sessionLevelUpgraded === '1') return;
    if (input.closest('[data-no-auto-dropdown="1"]')) return;
    var select = document.createElement('select');
    copyAttributes(input, select);
    select.dataset.sessionLevelUpgraded = '1';
    var currentValue = (input.value || '').trim();
    var fieldName = String(input.getAttribute('name') || '').toLowerCase();
    var currentSession = String(window.portalCurrentSessionOption || '').trim();
    if ((currentValue === '' || /^session\s*not\s*set$/i.test(currentValue) || /^not\s*set$/i.test(currentValue)) && (fieldName === 'session_name' || fieldName === 'session' || fieldName === 'academic_session') && currentSession) {
      currentValue = currentSession;
      input.value = currentValue;
    }
    var allowBlank = !input.required;
    if (allowBlank) {
      var blank = document.createElement('option');
      blank.value = '';
      blank.textContent = blankLabel;
      select.appendChild(blank);
    }
    var seen = {};
    (options || []).forEach(function(opt){
      opt = String(opt || '').trim();
      if (!opt || seen[opt]) return;
      seen[opt] = true;
      var option = document.createElement('option');
      option.value = opt;
      option.textContent = opt;
      if (opt === currentValue) option.selected = true;
      select.appendChild(option);
    });
    if (currentValue && !seen[currentValue]) {
      var current = document.createElement('option');
      current.value = currentValue;
      current.textContent = currentValue;
      current.selected = true;
      select.appendChild(current);
    }
    input.replaceWith(select);
  }
  function upgradeSessionLevelInputs(root){
    root = root || document;
    var sessionOptions = Array.isArray(window.portalSessionDropdownOptions) ? window.portalSessionDropdownOptions : [];
    var levelOptions = Array.isArray(window.portalLevelDropdownOptions) ? window.portalLevelDropdownOptions : [];
    var currentSession = String(window.portalCurrentSessionOption || '').trim();
    root.querySelectorAll('input[name="session_name"], input[name="session"], input[name="academic_session"]').forEach(function(input){
      buildSelectFromInput(input, sessionOptions, 'Select Session');
    });
    root.querySelectorAll('select[name="session_name"], select[name="session"], select[name="academic_session"]').forEach(function(select){
      if (!select) return;
      var value = String(select.value || '').trim();
      if ((!value || /^session\s*not\s*set$/i.test(value) || /^not\s*set$/i.test(value)) && currentSession) {
        var preferred = Array.from(select.options || []).find(function(opt){ return String(opt.value || '').trim() === currentSession; });
        if (!preferred) {
          var option = document.createElement('option');
          option.value = currentSession;
          option.textContent = currentSession;
          select.appendChild(option);
        }
        select.value = currentSession;
      }
    });
    root.querySelectorAll('input[name="level"]').forEach(function(input){
      buildSelectFromInput(input, levelOptions, 'All Levels');
    });

    if (currentSession) {
      root.querySelectorAll('td, th, div, span, small, strong, option').forEach(function(node){
        if (!node || node.children.length > 0) return;
        var text = String(node.textContent || '').trim();
        if (/^session\s*not\s*set$/i.test(text)) {
          node.textContent = currentSession;
        }
      });
    }
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', function(){ upgradeSessionLevelInputs(document); });
  } else {
    upgradeSessionLevelInputs(document);
  }
})();
</script>

</body>
</html>
<?php

// Handle final submit (lock)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'final_submit_application') {
    $studentId = function_exists('portalResolveStudentViewerId')
        ? portalResolveStudentViewerId($pdo, false)
        : (int)($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0);
    if (!$studentId) { redirect('index.php'); exit; }

    $stmt = $pdo->prepare("SELECT email, surname, first_name, other_name, application_submitted, application_status, offer_status, admission_no FROM students WHERE id = ?");
    $stmt->execute([(int)$studentId]);
    $stu = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$stu) {
        $_SESSION['upload_error'] = "Student record not found.";
        redirect('?page=application_form');
        exit;
    }

    portalEnsureApplicationGraceSupport($pdo);
    if (portalStudentAdmissionFormLocked($stu)) {
        $_SESSION['upload_error'] = portalStudentAdmissionFormLockMessage($stu);
        redirect('?page=application_form');
        exit;
    }
    $submitState = portalApplicationResubmissionState($pdo, $stu);
    if ((int)($stu['application_submitted'] ?? 0) === 1 && empty($submitState['can_edit'])) {
        $_SESSION['upload_success'] = "Application already finally submitted.";
        redirect('?page=application_form');
        exit;
    }


    // Enforce required supporting documents ONLY on final submission
    $stmt = $pdo->prepare("SELECT document_type FROM student_documents WHERE student_id = ? ORDER BY id DESC");
    $stmt->execute([(int)$studentId]);
    $types = $stmt->fetchAll(PDO::FETCH_COLUMN) ?: [];
    $set = array_fill_keys($types, true);

    $missing = [];
    if (empty($set['nin_slip'])) { $missing[] = "NIN Slip"; }
    if (empty($set['birth_certificate'])) { $missing[] = "Birth Certificate/Affidavit"; }

    if (!empty($missing)) {
        $_SESSION['upload_error'] = "You must upload the required document(s) before final submission: " . implode(", ", $missing) . ".";
        redirect('?page=application_form');
        exit;
    }

    $pdo->prepare("UPDATE students SET application_submitted = 1, application_submitted_at = NOW() WHERE id = ?")->execute([(int)$studentId]);

    $fullName = trim(($stu['surname'] ?? '') . ', ' . ($stu['first_name'] ?? '') . ' ' . ($stu['other_name'] ?? ''));
    try {
        createNotification($pdo, 'student', (int)$studentId, 'Application finally submitted', 'Your application has been finally submitted and locked. You can no longer edit it.', '?page=application_form');
    } catch (Throwable $e) { /* ignore */ }

    if (!empty($stu['email'])) {
        $subject = APP_NAME . ": Application Locked (Final Submission)";
        $link = appBaseUrl() . "/index.php?page=application_form";
        $name = htmlspecialchars($fullName, ENT_QUOTES, 'UTF-8');
        $body = "<p>Dear {$name},</p>" .
                "<p>Your application has been <strong>finally submitted</strong> and locked. You can no longer edit it.</p>" .
                "<p>You can login to your portal to view/print your submitted application form:</p>" .
                "<p><a href=\"" . htmlspecialchars($link, ENT_QUOTES, 'UTF-8') . "\">Open Application Form</a></p>" .
                "<p>Regards,<br>" . htmlspecialchars(APP_NAME) . "</p>";
        sendLoggedEmail($pdo, 'student', (int)$studentId, (string)$stu['email'], 'application_form_final_submit', $subject, $body);
    }

    $_SESSION['upload_success'] = !empty($submitState['reopened'])
        ? "Application resubmission recorded successfully."
        : "Application finally submitted and locked.";
    redirect('?page=application_form');
    exit;
}

// Handle update application form
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_application') {
    $studentId = function_exists('portalResolveStudentViewerId')
        ? portalResolveStudentViewerId($pdo, false)
        : (int)($_SESSION['student_id'] ?? $_SESSION['user_id'] ?? 0);

    portalEnsureApplicationGraceSupport($pdo);
    $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
    $stmt->execute([$studentId]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
    if (portalStudentAdmissionFormLocked($row)) {
        $_SESSION['upload_error'] = portalStudentAdmissionFormLockMessage($row);
        redirect('?page=application_form');
        exit;
    }
    $applicationState = portalApplicationResubmissionState($pdo, $row);
    $editCount = (int)($applicationState['used_count'] ?? 0);
    $isSubmitted = !empty($applicationState['submitted']);
    $canEdit = !empty($applicationState['can_edit']);
    $graceUsedBefore = (int)($applicationState['grace_used'] ?? 0);

if (!$canEdit) {
    $_SESSION['upload_error'] = (string)($applicationState['message'] ?: "Application is locked (final submitted) or you have used up your allowed edits.");
    redirect('?page=application_form');
    exit;
}
if ($canEdit) {

        try {
            portalEnsureApplicationAcademicFieldSupport($pdo);
            $applicationAcademic = portalValidateApplicationAcademicSelection(
                $pdo,
                (int)($_POST['faculty_id'] ?? 0),
                (int)($_POST['department_id'] ?? 0),
                (int)($_POST['programme_id'] ?? 0)
            );
        } catch (Throwable $e) {
            $_SESSION['upload_error'] = $e->getMessage();
            redirect('?page=application_form');
            exit;
        }

        // NOTE:
        // Students are allowed to SAVE / UPDATE their application without uploading required documents.
        // Required documents (NIN Slip + Birth Certificate/Affidavit) are enforced ONLY when they click FINAL SUBMIT.
        // (See the final submit handler below.)

        // Update student data

        // Capture dynamic O'Level subjects (Choice of Course/Subject)
        $subjectNames  = $_POST['subject_name'] ?? [];
        $subjectGrades = $_POST['subject_grade'] ?? [];

        $sub1 = isset($subjectNames[0]) ? trim((string)$subjectNames[0]) : '';
        $sub2 = isset($subjectNames[1]) ? trim((string)$subjectNames[1]) : '';
        $sub3 = isset($subjectNames[2]) ? trim((string)$subjectNames[2]) : '';

        $stmt = $pdo->prepare("UPDATE students SET
        surname = ?, first_name = ?, other_name = ?, maiden_name = ?, sex = ?,
        date_of_birth = ?, nationality = ?, state_of_origin = ?, lga = ?, mobile_phone = ?,
        residential_phone = ?, email = ?, permanent_address = ?, extra_curricular = ?,
        faculty_id = ?, department_id = ?, programme_id = ?,
        faculty = ?, department = ?, programme = ?, program = ?,
        school_name = ?, school = ?, course_in_view = ?, course_of_study = ?, programme_name_cache = ?,
        nok_name = ?, nok_phone = ?, sponsor_name = ?, sponsor_phone = ?, sponsor_occupation = ?,
        subject1 = ?, subject2 = ?, subject3 = ?, signature_data = ?,
        application_edit_count = application_edit_count + 1,
        application_grace_used_count = GREATEST((application_edit_count + 1) - COALESCE(NULLIF(application_max_edit_count, 0), 3), 0),
        application_last_grace_used_at = CASE WHEN ((application_edit_count + 1) > COALESCE(NULLIF(application_max_edit_count, 0), 3)) THEN NOW() ELSE application_last_grace_used_at END
        WHERE id = ?");

        $stmt->execute([
            $_POST['surname'] ?? '', $_POST['first_name'] ?? '', $_POST['other_name'] ?? '', $_POST['maiden_name'] ?? '', $_POST['sex'] ?? '',
            $_POST['date_of_birth'] ?? null, $_POST['nationality'] ?? '', $_POST['state_of_origin'] ?? '', $_POST['lga'] ?? '', $_POST['mobile_phone'] ?? '',
            $_POST['residential_phone'] ?? '', $_POST['email'] ?? '', $_POST['permanent_address'] ?? '', $_POST['extra_curricular'] ?? '',
            (int)($applicationAcademic['faculty_id'] ?? 0), (int)($applicationAcademic['department_id'] ?? 0), (int)($applicationAcademic['programme_id'] ?? 0),
            $applicationAcademic['school'] ?? '', $applicationAcademic['course_of_study'] ?? '', $applicationAcademic['programme'] ?? '', $applicationAcademic['programme'] ?? '',
            $applicationAcademic['school'] ?? '', $applicationAcademic['school'] ?? '', $applicationAcademic['course_of_study'] ?? '', $applicationAcademic['course_of_study'] ?? '', $applicationAcademic['programme'] ?? '',
            $_POST['nok_name'] ?? '', $_POST['nok_phone'] ?? '', $_POST['sponsor_name'] ?? '', $_POST['sponsor_phone'] ?? '', $_POST['sponsor_occupation'] ?? '',
            $sub1, $sub2, $sub3, $_POST['applicant_signature_data'] ?? '',
            $studentId
        ]);

        $afterApplicationState = portalApplicationResubmissionState($pdo, $studentId);
        if ((int)($afterApplicationState['grace_used'] ?? 0) > $graceUsedBefore) {
            portalLogApplicationResubmissionAudit(
                $pdo,
                (int)$studentId,
                0,
                'use',
                (int)($applicationState['extra_grace_count'] ?? 0),
                (int)($applicationState['extra_grace_count'] ?? 0),
                (int)($afterApplicationState['grace_used'] ?? 0),
                'Student used a grace resubmission attempt.'
            );
        }

        // Persist dynamic O'Level subjects/grades
        try {
            $pdo->beginTransaction();
            $del = $pdo->prepare("DELETE FROM student_olevel_subjects WHERE student_id = ?");
            $del->execute([$studentId]);

            if (is_array($subjectNames) && is_array($subjectGrades)) {
                $ins = $pdo->prepare("INSERT INTO student_olevel_subjects (student_id, subject_name, grade) VALUES (?, ?, ?)");
                foreach ($subjectNames as $i => $subName) {
                    $sn = trim((string)$subName);
                    $gr = trim((string)($subjectGrades[$i] ?? ''));
                    if ($sn !== '' && $gr !== '') {
                        $ins->execute([$studentId, $sn, $gr]);
                    }
                }
            }
            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) { $pdo->rollBack(); }
        }

        // Handle profile image upload
        if (isset($_FILES['profile_image']) && $_FILES['profile_image']['error'] === UPLOAD_ERR_OK) {
            $uploadResult = handleFileUpload($_FILES['profile_image'], 'profile_image');
            if ($uploadResult['success']) {
                $stmt = $pdo->prepare("UPDATE students SET profile_image = ? WHERE id = ?");
                $stmt->execute([$uploadResult['filepath'], $studentId]);
            }
        }

        // Handle passport photo upload
        if (isset($_FILES['passport_photo']) && $_FILES['passport_photo']['error'] === UPLOAD_ERR_OK) {
            $uploadResult = handleFileUpload($_FILES['passport_photo'], 'passport_photo');
            if ($uploadResult['success']) {
                $stmt = $pdo->prepare("UPDATE students SET passport_photo = ? WHERE id = ?");
                $stmt->execute([$uploadResult['filepath'], $studentId]);
            }
        }

        // Handle document uploads
        if (isset($_FILES['documents']) && is_array($_FILES['documents']['name'])) {
            foreach ($_FILES['documents']['name'] as $key => $name) {
                if ($_FILES['documents']['error'][$key] === UPLOAD_ERR_OK) {
                    $file = [
                        'name' => $_FILES['documents']['name'][$key],
                        'type' => $_FILES['documents']['type'][$key],
                        'tmp_name' => $_FILES['documents']['tmp_name'][$key],
                        'error' => $_FILES['documents']['error'][$key],
                        'size' => $_FILES['documents']['size'][$key]
                    ];

                    $documentType = $_POST['document_type'][$key] ?? 'other';
                    $uploadResult = handleFileUpload($file, $documentType);

                    if ($uploadResult['success']) {
                        $stmt = $pdo->prepare("INSERT INTO student_documents
                            (student_id, document_name, document_type, file_path, file_size)
                            VALUES (?, ?, ?, ?, ?)");
                        $stmt->execute([
                            $studentId,
                            $name,
                            $uploadResult['document_type'],
                            $uploadResult['filepath'],
                            $file['size']
                        ]);
                    }
                }
            }
        }

        // Delete existing education history
        $stmt = $pdo->prepare("DELETE FROM education_history WHERE student_id = ?");
        $stmt->execute([$studentId]);

        // Add education history
        if (isset($_POST['school_name']) && is_array($_POST['school_name'])) {
            foreach ($_POST['school_name'] as $index => $schoolName) {
                if (!empty($schoolName)) {
                    $fromDate = $_POST['school_from'][$index] ?? '';
                    $toDate = $_POST['school_to'][$index] ?? '';
                    $qualification = $_POST['school_qual'][$index] ?? '';

                    $stmt = $pdo->prepare("INSERT INTO education_history
                        (student_id, institution_name, from_date, to_date, qualification)
                        VALUES (?, ?, ?, ?, ?)");
                    $stmt->execute([$studentId, $schoolName, $fromDate, $toDate, $qualification]);
                }
            }
        }

        // Delete existing examination results
        $stmt = $pdo->prepare("DELETE FROM examination_results WHERE student_id = ?");
        $stmt->execute([$studentId]);

        // Add examination results
        if (isset($_POST['exam_type']) && is_array($_POST['exam_type'])) {
            foreach ($_POST['exam_type'] as $index => $examType) {
                $examDate = $_POST['exam_date'][$index] ?? '';
                $examNo = $_POST['exam_no'][$index] ?? '';
                $examGrades = $_POST['exam_grades'][$index] ?? '';
                $examAttached = isset($_POST['exam_attached'][$index]) ? 1 : 0;

                $stmt = $pdo->prepare("INSERT INTO examination_results
                    (student_id, exam_type, exam_date, exam_number, subjects_grades, credentials_attached)
                    VALUES (?, ?, ?, ?, ?, ?)");
                $stmt->execute([$studentId, $examType, $examDate, $examNo, $examGrades, $examAttached]);
            }
        }

        // Update referee information
        $stmt = $pdo->prepare("UPDATE referees SET
            full_name = ?, occupation = ?, post_held = ?, address = ?, date = ?, signature_data = ?
            WHERE student_id = ?");
        $stmt->execute([
            $_POST['ref_name'], $_POST['ref_occ'], $_POST['ref_post'], $_POST['ref_address'],
            $_POST['ref_date'], $_POST['ref_signature_data'], $studentId
        ]);

        // Store applicant signature
        $applicantSignature = $_POST['applicant_signature_data'] ?? '';
        $stmt = $pdo->prepare("UPDATE students SET signature_data = ? WHERE id = ?");
        $stmt->execute([$applicantSignature, $studentId]);

        if ($isSubmitted && (int)($afterApplicationState['grace_remaining'] ?? 0) >= 0) {
            $_SESSION['upload_success'] = 'Your application has been reopened for resubmission. You have ' . (int)($afterApplicationState['grace_remaining'] ?? 0) . ' grace resubmission remaining.';
        } else {
            $_SESSION['upload_success'] = "Application saved successfully.";
        }
        redirect('?page=application_form&saved=1');

        // Self-healing: extend CBT tests/attempts columns (non-breaking)
        try {
            if (!portalColumnExists($pdo, 'cbt_tests', 'pass_mark')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN pass_mark INT NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'randomize_questions')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN randomize_questions TINYINT(1) NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'published')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN published TINYINT(1) NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'results_published')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN results_published TINYINT(1) NOT NULL DEFAULT 0"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'question_per_page')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN question_per_page INT NOT NULL DEFAULT 1"); }
            if (!portalColumnExists($pdo, 'cbt_tests', 'negative_mark_per_wrong')) { $pdo->exec("ALTER TABLE cbt_tests ADD COLUMN negative_mark_per_wrong DECIMAL(6,2) NOT NULL DEFAULT 0"); }
        } catch (Throwable $e) {}

        try {
            if (!portalColumnExists($pdo, 'cbt_attempts', 'answers_json')) { $pdo->exec("ALTER TABLE cbt_attempts ADD COLUMN answers_json LONGTEXT NULL"); }
            if (!portalColumnExists($pdo, 'cbt_attempts', 'question_order_json')) { $pdo->exec("ALTER TABLE cbt_attempts ADD COLUMN question_order_json LONGTEXT NULL"); }
        } catch (Throwable $e) {}
    }
}

// Handle update profile
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_profile') {
    if (isAdmin()) {
        $userId = $_SESSION['user_id'];
        $username = $_POST['username'] ?? '';
        $email = $_POST['email'] ?? '';
        $fullName = $_POST['full_name'] ?? '';
        $password = $_POST['password'] ?? '';

        // Update admin profile
        if (!empty($password)) {
            $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
            $stmt = $pdo->prepare("UPDATE admins SET username = ?, email = ?, full_name = ?, password = ? WHERE id = ?");
            $stmt->execute([$username, $email, $fullName, $hashedPassword, $userId]);
        } else {
            $stmt = $pdo->prepare("UPDATE admins SET username = ?, email = ?, full_name = ? WHERE id = ?");
            $stmt->execute([$username, $email, $fullName, $userId]);
        }

        $_SESSION['username'] = $username;
        $_SESSION['full_name'] = $fullName;
    } else {
        $userId = $_SESSION['user_id'];
        $surname = $_POST['surname'] ?? '';
        $firstName = $_POST['first_name'] ?? '';
        $otherName = $_POST['other_name'] ?? '';
        $email = $_POST['email'] ?? '';
        $mobilePhone = $_POST['mobile_phone'] ?? '';
        $residentialPhone = $_POST['residential_phone'] ?? '';
        $permanentAddress = $_POST['permanent_address'] ?? '';
        $password = $_POST['password'] ?? '';

        // Update student profile
        if (!empty($password)) {
            $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
            $stmt = $pdo->prepare("UPDATE students SET
                surname = ?, first_name = ?, other_name = ?, email = ?,
                mobile_phone = ?, residential_phone = ?, permanent_address = ?, password = ?
                WHERE id = ?");
            $stmt->execute([$surname, $firstName, $otherName, $email, $mobilePhone, $residentialPhone, $permanentAddress, $hashedPassword, $userId]);
        } else {
            $stmt = $pdo->prepare("UPDATE students SET
                surname = ?, first_name = ?, other_name = ?, email = ?,
                mobile_phone = ?, residential_phone = ?, permanent_address = ?
                WHERE id = ?");
            $stmt->execute([$surname, $firstName, $otherName, $email, $mobilePhone, $residentialPhone, $permanentAddress, $userId]);
        }

        $_SESSION['full_name'] = $surname . ', ' . $firstName . (!empty($otherName) ? ' ' . $otherName : '');
    }

    $_SESSION['profile_updated'] = true;
    redirect('?page=profile');
}

// Handle delete document
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_document') {
    $docId = $_POST['doc_id'] ?? 0;

    if ($docId > 0) {
        // Get document path
        $stmt = $pdo->prepare("SELECT file_path FROM student_documents WHERE id = ?");
        $stmt->execute([$docId]);
        $document = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($document) {
            // Delete file from server
            if (file_exists($document['file_path'])) {
                unlink($document['file_path']);
            }

            // Delete from database
            $stmt = $pdo->prepare("DELETE FROM student_documents WHERE id = ?");
            $stmt->execute([$docId]);

            echo "success";
        } else {
            echo "Document not found";
        }
    } else {
        echo "Invalid document ID";
    }

    exit;
}

// Handle receipt view for admin
if (isset($_GET['page']) && $_GET['page'] === 'payment_receipt' && isset($_GET['transaction_id']) && isAdmin()) {
    $transactionId = $_GET['transaction_id'];

    // Get transaction details
    $stmt = $pdo->prepare("SELECT pt.*, s.first_name, s.surname, s.registration_no, s.application_no, s.admission_no, s.matric_no FROM payment_transactions pt
    JOIN students s ON pt.student_id = s.id
    WHERE pt.id = ?");
    $stmt->execute([$transactionId]);
    $transaction = $stmt->fetch(PDO::FETCH_ASSOC);

    if ($transaction) {

                        $embed = (isset($_GET['embed']) && (string)$_GET['embed'] === '1');
                        if ($embed) {
                            header('Content-Type: text/html; charset=utf-8');
                            ?><!doctype html>
                            <html>
                            <head>
                                <meta charset="utf-8">
                                <meta name="viewport" content="width=device-width, initial-scale=1">
                                <title>Payment Receipt</title>
                                <style><?php echo portalReceiptEmbeddedStyleBlock(); ?></style>
                            </head>
                            <body>
                                <div class="wrap">
                                    <div class="receipt-container doc-stamp-wrap">
                                        <?php if (!empty($stampPath)): ?>
                                            <img src="<?php echo htmlspecialchars($stampPath); ?>" class="doc-stamp" alt="Official Stamp">
                                        <?php endif; ?>
                                        <div class="receipt-header">
                                            <div class="logo-area mb-3">
                                                <?php if ($leftLogoPath): ?>
                                                <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">HONTTECH Logo</div>
                                                <?php endif; ?>
                                                <?php if ($rightLogoPath): ?>
                                                <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                                <?php else: ?>
                                                <div class="logo-placeholder">DAHIFT Logo</div>
                                                <?php endif; ?>
                                            </div>
                                            <h2>Honest Technologies University</h2>
                                            <h3>Ahmadu Bello University, Zaria</h3>
                                            <p>In affiliation with Dahift Global Ltd</p>
                                            <h4>Payment Receipt</h4>
                                        </div>

                                        <div class="receipt-body">
                                            <div class="receipt-meta">
                                                <div class="receipt-meta-col">
                                                    <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                                    <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                                    <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                                    <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                                </div>
                                                <div class="receipt-meta-col">
                                                    <p><strong>Student Name:</strong> <?php echo e(full_name($transaction)); ?></p>
                                                    <p><strong>Admission No.:</strong> <?php echo e(portalReceiptAdmissionNumber($transaction)); ?></p>
                                                    <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                                    <p><strong>Application No.:</strong> <?php echo e(portalReceiptApplicationNumber($transaction)); ?></p>
                                                    <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                    <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                    <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                                </div>
                                            </div>

                                            <div class="table-responsive mb-3">
                                                <table class="table">
                                                    <thead>
                                                        <tr>
                                                            <th>Description</th>
                                                            <th>Amount</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <tr>
                                                            <td><?php echo $transaction['transaction_type']; ?></td>
                                                            <td>₦<?php echo number_format($transaction['amount']); ?></td>
                                                        </tr>
                                                        <tr>
                                                            <th>Total</th>
                                                            <th>₦<?php echo number_format($transaction['amount']); ?></th>
                                                        </tr>
                                                    </tbody>
                                                </table>
                                            </div>

                                            <div class="qr-code">
                                                <p><strong>QR Code:</strong></p>
                                                <div class="qr-pack">
                                                    <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                                <?php if (!empty($stampPath)): ?>
                                                    <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                                    <?php endif; ?>
                                                </div>
                                            </div>
                                        </div>

                                        <div class="receipt-footer">
                                            <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                            <p>For inquiries, please contact the admissions office.</p>
                                            <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                                        </div>
                                    </div>
                                </div>
                            </body>
                            </html><?php
                            exit;
                        }
        ?>
        <div class="row">
            <div class="col-md-12">
                <div class="card">
                    <div class="card-header d-flex justify-content-between align-items-center">
                        <h4 class="mb-0">Payment Receipt</h4>
                        <div class="no-print">
                            <button class="btn btn-secondary" onclick="window.print()">
                                <i class="bi bi-printer"></i> Print
                            </button>
                            <a href="?page=admin_payments" class="btn btn-primary">
                                <i class="bi bi-arrow-left"></i> Back to Payments
                            </a>
                        </div>
                    </div>
                    <div class="card-body">
                        <div class="receipt-container doc-stamp-wrap">
                            <div class="receipt-header">
                                <div class="logo-area mb-3">
                                    <?php if ($leftLogoPath): ?>
                                    <img src="<?php echo $leftLogoPath; ?>" alt="HONTTECH Logo" class="logo-display">
                                    <?php else: ?>
                                    <div class="logo-placeholder">HONTTECH Logo</div>
                                    <?php endif; ?>
                                    <?php if ($rightLogoPath): ?>
                                    <img src="<?php echo $rightLogoPath; ?>" alt="DAHIFT Logo" class="logo-display">
                                    <?php else: ?>
                                    <div class="logo-placeholder">DAHIFT Logo</div>
                                    <?php endif; ?>
                                </div>
                                <h2>Honest Technologies University</h2>
                                <h3>Ahmadu Bello University, Zaria</h3>
                                <p>In affiliation with Dahift Global Ltd</p>
                                <h4>Payment Receipt</h4>
                            </div>

                            <div class="receipt-body">
                                <div class="receipt-meta">
                                    <div class="receipt-meta-col">
                                        <p><strong>Receipt No:</strong> <?php echo e(portalReceiptNumber($transaction)); ?></p>
                                        <p><strong>Transaction ID:</strong> <?php echo $transaction['transaction_id']; ?></p>
                                        <p><strong>Transaction Ref:</strong> <?php echo $transaction['reference']; ?></p>
                                        <p><strong>Payment Date:</strong> <?php echo formatPaymentDate($pdo, $transaction); ?></p>
                                    </div>
                                    <div class="receipt-meta-col">
                                        <p><strong>Student Name:</strong> <?php echo $transaction['first_name'] . ' ' . $transaction['surname']; ?></p>
                                        <p><strong>Matric No.:</strong> <?php echo e(portalReceiptMatricNumber($transaction)); ?></p>
                                        <p><strong>Payment Type:</strong> <?php echo e(bestPaymentType($transaction)); ?></p>
                                                    <p><strong>Session:</strong> <?php echo e(portalPaymentAcademicSession($transaction, $transaction)); ?></p>
                                                    <p><strong>Semester:</strong> <?php echo e(portalPaymentSemester($transaction, $transaction)); ?></p>
                                    </div>
                                </div>

                                <div class="table-responsive mb-3">
                                    <table class="table table-bordered">
                                        <thead>
                                            <tr>
                                                <th>Description</th>
                                                <th>Amount</th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <tr>
                                                <td><?php echo $transaction['transaction_type']; ?></td>
                                                <td>₦<?php echo number_format($transaction['amount']); ?></td>
                                            </tr>
                                            <tr>
                                                <th>Total</th>
                                                <th>₦<?php echo number_format($transaction['amount']); ?></th>
                                            </tr>
                                        </tbody>
                                    </table>
                                </div>

                                <div class="qr-code">
                                    <p><strong>QR Code:</strong></p>
                                    <div class="qr-pack">
                                        <img src="<?php echo $transaction['qr_code_data']; ?>" alt="Payment QR Code">
                                        <?php if (!empty($stampPath)): ?>
                                            <img src="<?php echo htmlspecialchars($stampPath); ?>" class="qr-stamp" alt="Official Stamp">
                                        <?php endif; ?>
                                    </div>
                                </div>
                            </div>

                            <div class="receipt-footer">
                                <p>This receipt serves as proof of payment. Please keep it for your records.</p>
                                <p>For inquiries, please contact admissions office.</p>
                                <p>&copy; <?php echo date('Y'); ?> <?php echo htmlspecialchars(setting('portal_title','Honttech Registration')); ?> System. All rights reserved.</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <?php
    } else {
        echo '<div class="alert alert-danger">Transaction not found.</div>';
    }
    exit; // Exit to prevent HTML output below from rendering again
}

// Handle generate ID card
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'generate_id_card') {
    $studentId = $_POST['student_id'] ?? 0;

    if ($studentId > 0) {
        // Generate ID card
        $stmt = $pdo->prepare("UPDATE students SET id_card_generated = TRUE, id_card_generated_date = NOW() WHERE id = ?");
        $stmt->execute([$studentId]);

        // Get student details for email
        $stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
        $stmt->execute([$studentId]);
        $student = $stmt->fetch(PDO::FETCH_ASSOC);

        $studentDear = htmlspecialchars(studentFullName($student), ENT_QUOTES, 'UTF-8');

        // Send ID card notification email
        $subject = "Student ID Card Generated - HontTech Registration";
        $body = "
        <h2>Student ID Card Generated</h2>
            <p>Dear {$studentDear},</p>
        <p>Your student ID card has been generated and is now available in your dashboard.</p>
        <p>You can access and print your ID card from your student dashboard.</p>
        <p>Best regards,<br>HontTech Administration</p>
        ";

        sendLoggedEmail($pdo, 'student', (int)$student['id'], (string)$student['email'], 'id_card_generated:' . (int)$student['id'], $subject, $body);

        echo "success";
    } else {
        echo "Invalid student ID";
    }

    exit;
}
?>
