<?php

if (session_status() === PHP_SESSION_NONE) session_start();

if (!isset($_SESSION['user_id'])) { header('Location: index.php'); exit; }

require_once 'config.php';

require_once 'includes/canteen_helpers.php';

canteen_require_active();

canteen_require_role(['admin', 'headteacher', 'accountant', 'cashier', 'registrar']);

$settings      = canteen_get_settings();

$fp_enabled   = !empty($settings['fingerprint_enabled']);

$face_enabled = !empty($settings['facial_recognition_enabled']);

?>

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">

    <title>Express Fee Collection — EduTrack</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

    <link rel="stylesheet" href="css/style.css">

    <style>

        /* ── Layout ── */

        #state-scan, #state-student, #state-confirm { display: none; }

        #state-scan.active, #state-student.active, #state-confirm.active { display: block; }



        /* ── Scanner viewfinder ── */

        #scanner-wrap {

            position: relative;

            width: 100%;

            max-width: 480px;

            margin: 0 auto;

            border-radius: 12px;

            overflow: hidden;

            background: #000;

            aspect-ratio: 1 / 1;

        }

        #scanner-video {

            width: 100%; height: 100%;

            object-fit: cover;

            display: block;

        }

        #scanner-canvas { display: none; }



        /* Green 4-corner bracket overlay (CSS only) */

        .corner { position: absolute; width: 28px; height: 28px; border-color: #22c55e; border-style: solid; }

        .corner-tl { top: 18px; left: 18px; border-width: 3px 0 0 3px; }

        .corner-tr { top: 18px; right: 18px; border-width: 3px 3px 0 0; }

        .corner-bl { bottom: 18px; left: 18px; border-width: 0 0 3px 3px; }

        .corner-br { bottom: 18px; right: 18px; border-width: 0 3px 3px 0; }



        /* scan line animation */

        @keyframes scan { 0%,100%{top:18px} 50%{top:calc(100% - 18px)} }

        .scan-line {

            position: absolute; left: 18px; right: 18px; height: 2px;

            background: linear-gradient(90deg, transparent, #22c55e, transparent);

            animation: scan 2s ease-in-out infinite;

        }



        /* ── Student card ── */

        .student-photo {

            width: 120px; height: 120px;

            border-radius: 50%; object-fit: cover;

            border: 3px solid #e2e8f0;

        }

        .fee-btn {

            padding: .6rem 1rem; border-radius: 8px;

            cursor: pointer; transition: transform .1s;

            text-align: left; width: 100%;

        }

        .fee-btn:active { transform: scale(.97); }

        .fee-btn.feeding { border: 2px solid #16a34a; background: #f0fdf4; }

        .fee-btn.feeding:hover { background: #dcfce7; }

        .fee-btn.regular { border: 2px solid #cbd5e1; background: #f8fafc; }

        .fee-btn.regular:hover { background: #f1f5f9; }

        .fee-btn.selected { outline: 3px solid #2563eb; }



        /* ── Payment method tabs ── */

        .method-btn {

            flex: 1; padding: .5rem; border-radius: 8px;

            border: 2px solid #e2e8f0; background: #fff;

            cursor: pointer; transition: all .15s;

            font-size: .85rem; text-align: center;

        }

        .method-btn.active { border-color: #2563eb; background: #eff6ff; color: #2563eb; font-weight: 600; }



        /* ── Confirmation overlay ── */

        #state-confirm {

            position: fixed; inset: 0; z-index: 9999;

            background: #16a34a;

            display: none; flex-direction: column;

            align-items: center; justify-content: center;

            color: #fff; text-align: center; padding: 2rem;

        }

        #state-confirm.active {

            display: flex;

        }

        #state-confirm .confirm-icon { font-size: 5rem; margin-bottom: 1rem; }



        /* ── Biometric buttons ── */

        .bio-row { display: flex; gap: .5rem; margin-top: .75rem; }

        .bio-action-btn {

            flex: 1; padding: .6rem .5rem; border-radius: 8px;

            border: 2px solid #e2e8f0; background: #fff;

            cursor: pointer; font-size: .8rem; text-align: center;

            transition: all .15s;

        }

        .bio-action-btn:hover { border-color: #6366f1; background: #f5f3ff; }

        .bio-action-btn i { display: block; font-size: 1.3rem; margin-bottom: 3px; }

        /* Face scan overlay */

        #face-wrap {

            display: none; position: relative;

            width: 100%; max-width: 480px; margin: 0 auto;

            border-radius: 12px; overflow: hidden; background: #000;

            aspect-ratio: 4/3;

        }

        #face-video { width: 100%; height: 100%; object-fit: cover; display: block; }

        #face-canvas { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }

        #face-status-banner {

            position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%);

            background: rgba(0,0,0,.7); color: #fff; border-radius: 20px;

            padding: 3px 12px; font-size: .75rem; white-space: nowrap;

        }

        #face-lowlight {

            position: absolute; top: 8px; left: 0; right: 0; text-align: center;

            display: none;

        }

        /* Face confirm panel */

        #face-confirm-panel {

            display: none; background: #f0fdf4; border: 2px solid #16a34a;

            border-radius: 12px; padding: 1rem; text-align: center; margin-top: .75rem;

        }

        /* ── html5-qrcode video override ── */

        #qr-reader { width: 100%; height: 100%; }

        #qr-reader video {

            width: 100% !important; height: 100% !important;

            object-fit: cover !important; display: block !important;

            border-radius: 0 !important;

        }

        /* ── Misc ── */

        .divider-label { font-size: .7rem; color: #94a3b8; text-transform: uppercase; letter-spacing: .05em; }

        .momo-opts { display: none; }

        .momo-opts.show { display: flex; }

        .momo-btn {

            flex: 1; padding: .4rem; border-radius: 6px;

            border: 2px solid #e2e8f0; background: #fff;

            cursor: pointer; font-size: .8rem; text-align: center;

        }

        .momo-btn.active { border-color: #f59e0b; background: #fffbeb; font-weight: 600; }

    </style>

</head>

<body>

<div class="wrapper">

    <nav id="sidebar" class="sidebar">

        <?php echo buildSidebar('fees_express.php'); ?>

    </nav>

    <div class="main-content">

        <header class="header">

            <button class="btn-toggle" id="sidebarToggle"><i class="fas fa-bars"></i></button>

            <div class="header-right">

                <span class="text-muted small me-3 d-none d-md-inline">

                    <i class="fas fa-bolt text-warning me-1"></i>Express Mode

                </span>

                <div class="dropdown">

                    <button class="btn btn-link dropdown-toggle" type="button" data-bs-toggle="dropdown">

                        <i class="fas fa-user-circle me-2"></i><?php echo htmlspecialchars((string)($_SESSION['full_name'] ?? '')); ?>

                    </button>

                    <ul class="dropdown-menu dropdown-menu-end">

                        <li><a class="dropdown-item" href="fees.php"><i class="fas fa-list me-2"></i>Standard Fees</a></li>

                        <li><hr class="dropdown-divider"></li>

                        <li><a class="dropdown-item" href="logout.php"><i class="fas fa-sign-out-alt me-2"></i>Logout</a></li>

                    </ul>

                </div>

            </div>

        </header>

        <main class="content">



            <!-- ══ STATE 1 — SCANNING ══════════════════════════════════════ -->

            <div id="state-scan" class="active">

                <div class="content-header pb-2">

                    <h2 class="mb-0"><i class="fas fa-bolt text-warning me-2"></i>Express Fee Collection</h2>

                    <p class="text-muted small mb-0">Scan student QR code or enter 5-digit ID</p>

                </div>



                <div class="row justify-content-center">

                    <div class="col-lg-6">



                        <!-- Camera viewfinder -->

                        <div id="scanner-wrap" class="mb-3">

                            <div id="qr-reader"></div>

                            <div class="corner corner-tl"></div>

                            <div class="corner corner-tr"></div>

                            <div class="corner corner-bl"></div>

                            <div class="corner corner-br"></div>

                            <div class="scan-line"></div>

                        </div>



                        <!-- Camera error / permission notice -->

                        <div id="camera-error" class="alert alert-warning d-none mb-3">

                            <i class="fas fa-video-slash me-2"></i>

                            Camera unavailable. Use the Student ID field below.

                        </div>



                        <!-- Manual fallback -->

                        <div class="card">

                            <div class="card-body py-2 px-3">

                                <label class="form-label mb-1 small fw-semibold">

                                    <i class="fas fa-keyboard me-1"></i>Or enter 5-digit Student ID:

                                </label>

                                <div class="input-group">

                                    <input type="text" id="manual-id-input"

                                           class="form-control form-control-lg text-center"

                                           placeholder="e.g. 00042"

                                           maxlength="20"

                                           inputmode="text"

                                           autocomplete="off">

                                    <button class="btn btn-primary" id="btn-lookup-id">

                                        <i class="fas fa-search"></i>

                                    </button>

                                </div>

                                <div id="manual-id-error" class="text-danger small mt-1 d-none"></div>

                            </div>

                        </div>



                        <?php if ($fp_enabled || $face_enabled): ?>

                        <!-- Biometric shortcuts -->

                        <div class="bio-row">

                            <?php if ($fp_enabled): ?>

                            <button class="bio-action-btn" id="btn-fingerprint">

                                <i class="fas fa-fingerprint text-primary"></i>

                                Scan Fingerprint

                            </button>

                            <?php endif; ?>

                            <?php if ($face_enabled): ?>

                            <button class="bio-action-btn" id="btn-face-scan">

                                <i class="fas fa-user-circle text-purple" style="color:#6366f1"></i>

                                Use Face Recognition

                            </button>

                            <?php endif; ?>

                        </div>



                        <?php if ($face_enabled): ?>

                        <!-- Face scan camera -->

                        <div id="face-wrap" class="mt-3">

                            <video id="face-video" autoplay playsinline muted></video>

                            <canvas id="face-canvas"></canvas>

                            <div id="face-status-banner">Looking for face…</div>

                            <div id="face-lowlight">

                                <span class="badge bg-warning text-dark">

                                    <i class="fas fa-exclamation-triangle me-1"></i>Low lighting

                                </span>

                            </div>

                            <button id="btn-close-face" style="position:absolute;top:8px;right:8px;background:rgba(0,0,0,.5);border:none;color:#fff;border-radius:20px;padding:3px 10px;font-size:.75rem;cursor:pointer">✕</button>

                        </div>



                        <!-- Face confirm -->

                        <div id="face-confirm-panel">

                            <img id="face-conf-photo" src="" alt="" style="width:80px;height:80px;border-radius:50%;object-fit:cover;border:3px solid #16a34a;display:none;margin-bottom:.5rem">

                            <div class="fw-bold" id="face-conf-name"></div>

                            <div class="text-muted small" id="face-conf-class"></div>

                            <div class="mt-2 d-flex gap-2 justify-content-center">

                                <button class="btn btn-success btn-sm" id="btn-face-confirm">

                                    <i class="fas fa-check me-1"></i>This is the student

                                </button>

                                <button class="btn btn-outline-secondary btn-sm" id="btn-face-cancel-confirm">

                                    Not them

                                </button>

                            </div>

                        </div>

                        <?php endif; ?>

                        <?php endif; ?>



                        <div id="scan-spinner" class="text-center mt-3 d-none">

                            <div class="spinner-border text-primary"></div>

                            <p class="text-muted small mt-1">Looking up student…</p>

                        </div>



                    </div>

                </div>

            </div>



            <!-- ══ STATE 2 — STUDENT FOUND ════════════════════════════════ -->

            <div id="state-student">

                <div class="content-header pb-2">

                    <div class="d-flex justify-content-between align-items-center">

                        <h2 class="mb-0"><i class="fas fa-user-check me-2"></i>Student Found</h2>

                        <button class="btn btn-outline-secondary btn-sm" id="btn-cancel">

                            <i class="fas fa-times me-1"></i>Cancel — Scan Next

                        </button>

                    </div>

                </div>



                <div class="row g-3">

                    <!-- Student info -->

                    <div class="col-12">

                        <div class="card">

                            <div class="card-body">

                                <div class="d-flex align-items-center gap-3">

                                    <img id="s-photo" src="" alt="" class="student-photo">

                                    <div>

                                        <div class="fw-bold" style="font-size:1.15rem;" id="s-name"></div>

                                        <div class="text-muted" id="s-class"></div>

                                        <div class="text-muted small" id="s-code"></div>

                                    </div>

                                </div>

                            </div>

                        </div>

                    </div>



                    <!-- Fee selection -->

                    <div class="col-12">

                        <div class="card">

                            <div class="card-body">

                                <div class="divider-label mb-2">Select fee to pay</div>

                                <div id="fee-list" class="d-flex flex-column gap-2"></div>



                                <!-- Custom amount -->

                                <div class="mt-2">

                                    <button class="btn btn-link btn-sm text-muted p-0" id="btn-toggle-custom">

                                        <i class="fas fa-plus me-1"></i>Enter different amount

                                    </button>

                                    <div id="custom-amount-wrap" class="mt-2 d-none">

                                        <input type="number" id="custom-amount" class="form-control"

                                               placeholder="Amount in GHS" min="0.01" step="0.01">

                                    </div>

                                </div>

                            </div>

                        </div>

                    </div>



                    <!-- Payment method -->

                    <div class="col-12">

                        <div class="card">

                            <div class="card-body">

                                <div class="divider-label mb-2">Payment method</div>

                                <div class="d-flex gap-2 mb-2">

                                    <button class="method-btn active" data-method="cash">

                                        <i class="fas fa-money-bill-wave d-block mb-1"></i>Cash

                                    </button>

                                    <button class="method-btn" data-method="mobile_money">

                                        <i class="fas fa-mobile-alt d-block mb-1"></i>Mobile Money

                                    </button>

                                    <button class="method-btn" data-method="bank_transfer">

                                        <i class="fas fa-university d-block mb-1"></i>Bank Transfer

                                    </button>

                                </div>

                                <!-- MoMo network sub-options -->

                                <div id="momo-opts" class="momo-opts gap-2">

                                    <button class="momo-btn active" data-network="MTN">MTN</button>

                                    <button class="momo-btn" data-network="Vodafone">Vodafone</button>

                                    <button class="momo-btn" data-network="AirtelTigo">AirtelTigo</button>

                                </div>

                            </div>

                        </div>

                    </div>



                    <!-- Record button -->

                    <div class="col-12">

                        <button id="btn-pay" class="btn btn-success btn-lg w-100" disabled>

                            <i class="fas fa-check me-2"></i>Record Payment

                        </button>

                        <div id="pay-error" class="text-danger small mt-1 d-none"></div>

                    </div>

                </div>

            </div>



            <!-- ══ STATE 3 — CONFIRMED ════════════════════════════════════ -->

            <div id="state-confirm">

                <div class="confirm-icon"><i class="fas fa-check-circle"></i></div>

                <h2 class="fw-bold mb-1">Payment Recorded</h2>

                <p class="mb-1 fs-5" id="conf-name"></p>

                <p class="mb-1" id="conf-fee"></p>

                <p class="mb-3 opacity-75 small" id="conf-sms"></p>

                <div class="spinner-border text-white opacity-50 mt-2" style="width:1.5rem;height:1.5rem;"></div>

                <p class="mt-1 opacity-50 small">Returning to scanner…</p>

            </div>



        </main>

    </div>

</div>



<?php if ($face_enabled): ?>

<script src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/face_mesh.js" crossorigin="anonymous"></script>

<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>

<?php endif; ?>

<!-- html5-qrcode (ZXing-based) — more reliable on printed QR codes -->

<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

<script>

'use strict';

(function () {



// ── State ──────────────────────────────────────────────────────────────────

let currentStudent   = null;   // response from identity_validate

let selectedFee      = null;   // { fee_structure_id, fee_type, amount, is_feeding }

let selectedMethod   = 'cash';

let selectedNetwork  = 'MTN';

let scanActive       = true;

let lastScannedToken = '';

let lastScanTime     = 0;



// ── DOM refs ───────────────────────────────────────────────────────────────

const stateScan    = document.getElementById('state-scan');

const stateStudent = document.getElementById('state-student');

const stateConfirm = document.getElementById('state-confirm');



// ── QR Scanner (html5-qrcode / ZXing) ─────────────────────────────────────

const qrScanner = new Html5Qrcode('qr-reader');

qrScanner.start(

    { facingMode: { ideal: 'environment' } },

    {

        fps: 15,

        videoConstraints: {

            facingMode: { ideal: 'environment' },

            width: { ideal: 1280 },

            height: { ideal: 720 }

        }

    },

    (decodedText) => {

        if (!scanActive) return;

        const token = decodedText.trim();

        const now   = Date.now();

        if (token === lastScannedToken && now - lastScanTime < 3000) return;

        lastScannedToken = token;

        lastScanTime     = now;

        validateToken(token);

    },

    () => {}

).catch(() => {

    document.getElementById('camera-error').classList.remove('d-none');

    document.getElementById('scanner-wrap').style.display = 'none';

});



// ── Manual ID lookup ───────────────────────────────────────────────────────

document.getElementById('btn-lookup-id').addEventListener('click', lookupById);

document.getElementById('manual-id-input').addEventListener('keydown', e => {

    if (e.key === 'Enter') lookupById();

});



function lookupById() {

    const val = document.getElementById('manual-id-input').value.trim();

    if (!val) return;

    // We look up by student_id field via the identity validate endpoint

    // passing a special prefix so the server can distinguish

    validateToken('__MANUAL__' + val);

}



// ── Auto-validate token passed via ?token= (e.g. from fees.php Scan QR modal) ─

(function () {

    const params = new URLSearchParams(window.location.search);

    const preToken = params.get('token');

    if (preToken && preToken.trim() !== '') {

        validateToken(preToken.trim());

        // Clean URL so a page refresh doesn't re-fire

        history.replaceState(null, '', window.location.pathname);

    }

})();



// ── Token validation ───────────────────────────────────────────────────────

function validateToken(token) {

    scanActive = false;

    document.getElementById('scan-spinner').classList.remove('d-none');

    document.getElementById('manual-id-error').classList.add('d-none');



    const fd = new FormData();

    fd.append('token', token);



    fetch('ajax/canteen_identity_validate.php', {

        method: 'POST', credentials: 'same-origin', body: fd

    })

    .then(r => r.json())

    .then(data => {

        document.getElementById('scan-spinner').classList.add('d-none');

        if (data.success) {

            showStudentState(data);

        } else {

            scanActive = true;

            const errEl = document.getElementById('manual-id-error');

            errEl.textContent = data.message || 'Student not found.';

            errEl.classList.remove('d-none');

            // Re-enable scan after 2s

            setTimeout(() => { lastScannedToken = ''; }, 2000);

        }

    })

    .catch(() => {

        document.getElementById('scan-spinner').classList.add('d-none');

        scanActive = true;

    });

}



// ── Show State 2 ───────────────────────────────────────────────────────────

function showStudentState(data) {

    currentStudent = data;

    selectedFee    = null;



    document.getElementById('s-photo').src  = data.photo_url || '';

    document.getElementById('s-name').textContent  = data.name;

    document.getElementById('s-class').textContent = data.class_name;

    document.getElementById('s-code').textContent  = 'ID: ' + data.student_code;



    // Build fee buttons

    const feeList = document.getElementById('fee-list');

    feeList.innerHTML = '';



    if (data.outstanding_fees.length === 0) {

        feeList.innerHTML = '<p class="text-muted small">No outstanding fees found for this student.</p>';

    } else {

        data.outstanding_fees.forEach(fee => {

            const btn = document.createElement('button');

            btn.className = 'fee-btn ' + (fee.is_feeding ? 'feeding' : 'regular');

            btn.dataset.feeId    = fee.fee_structure_id;

            btn.dataset.feeType  = fee.fee_type;

            btn.dataset.amount   = fee.remaining;

            btn.dataset.isFeeding = fee.is_feeding ? '1' : '0';



            const label = fee.is_feeding ? '<span class="badge bg-success me-1">Feeding</span>' : '';

            btn.innerHTML = `

                ${label}<strong>${escHtml(fee.fee_type)}</strong>

                <span class="float-end fw-bold">GHS ${fee.remaining.toFixed(2)}</span>`;

            btn.addEventListener('click', () => selectFee(btn, fee));

            feeList.appendChild(btn);

        });

    }



    document.getElementById('custom-amount').value = '';

    document.getElementById('custom-amount-wrap').classList.add('d-none');

    document.getElementById('btn-pay').disabled = true;



    switchState('student');

}



// ── Fee selection ──────────────────────────────────────────────────────────

function selectFee(btn, fee) {

    document.querySelectorAll('.fee-btn').forEach(b => b.classList.remove('selected'));

    btn.classList.add('selected');

    selectedFee = fee;

    document.getElementById('custom-amount').value = '';

    document.getElementById('custom-amount-wrap').classList.add('d-none');

    document.getElementById('btn-pay').disabled = false;

}



// Custom amount toggle

document.getElementById('btn-toggle-custom').addEventListener('click', () => {

    const wrap = document.getElementById('custom-amount-wrap');

    wrap.classList.toggle('d-none');

    if (!wrap.classList.contains('d-none')) {

        document.getElementById('custom-amount').focus();

    }

});



document.getElementById('custom-amount').addEventListener('input', () => {

    const val = parseFloat(document.getElementById('custom-amount').value);

    document.getElementById('btn-pay').disabled = !(selectedFee && val > 0);

});



// ── Payment method tabs ────────────────────────────────────────────────────

document.querySelectorAll('.method-btn').forEach(btn => {

    btn.addEventListener('click', () => {

        document.querySelectorAll('.method-btn').forEach(b => b.classList.remove('active'));

        btn.classList.add('active');

        selectedMethod = btn.dataset.method;

        const momoOpts = document.getElementById('momo-opts');

        if (selectedMethod === 'mobile_money') {

            momoOpts.classList.add('show');

        } else {

            momoOpts.classList.remove('show');

        }

    });

});



document.querySelectorAll('.momo-btn').forEach(btn => {

    btn.addEventListener('click', () => {

        document.querySelectorAll('.momo-btn').forEach(b => b.classList.remove('active'));

        btn.classList.add('active');

        selectedNetwork = btn.dataset.network;

    });

});



// ── Pay button ─────────────────────────────────────────────────────────────

document.getElementById('btn-pay').addEventListener('click', recordPayment);



function recordPayment() {

    if (!currentStudent || !selectedFee) return;



    const customAmt = parseFloat(document.getElementById('custom-amount').value);

    const amount    = customAmt > 0 ? customAmt : selectedFee.remaining;



    const errEl = document.getElementById('pay-error');

    errEl.classList.add('d-none');



    const btn = document.getElementById('btn-pay');

    btn.disabled = true;

    btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Recording…';



    const fd = new FormData();

    fd.append('student_id',       currentStudent.student_id);

    fd.append('fee_structure_id', selectedFee.fee_structure_id);

    fd.append('amount',           amount);

    fd.append('payment_method',   selectedMethod);

    if (selectedMethod === 'mobile_money') fd.append('momo_network', selectedNetwork);



    fetch('ajax/canteen_express_pay.php', {

        method: 'POST', credentials: 'same-origin', body: fd

    })

    .then(r => r.json())

    .then(data => {

        btn.disabled = false;

        btn.innerHTML = '<i class="fas fa-check me-2"></i>Record Payment';



        if (data.success) {

            showConfirm(data, amount);

        } else {

            errEl.textContent = data.message || 'Payment failed.';

            errEl.classList.remove('d-none');

        }

    })

    .catch(() => {

        btn.disabled = false;

        btn.innerHTML = '<i class="fas fa-check me-2"></i>Record Payment';

        errEl.textContent = 'Network error — please try again.';

        errEl.classList.remove('d-none');

    });

}



// ── Show State 3 — Confirmation ────────────────────────────────────────────

function showConfirm(data, amount) {

    document.getElementById('conf-name').textContent =

        data.student_name + ' · ' + currentStudent.class_name;

    document.getElementById('conf-fee').textContent =

        data.fee_type + ' — GHS ' + parseFloat(amount).toFixed(2);

    document.getElementById('conf-sms').textContent =

        data.parent_phone

            ? 'SMS sent to ' + data.parent_phone

            : 'No parent phone on record';



    switchState('confirm');



    setTimeout(() => {

        resetToScan();

    }, 3000);

}



// ── Cancel ─────────────────────────────────────────────────────────────────

document.getElementById('btn-cancel').addEventListener('click', resetToScan);



// ── Helpers ────────────────────────────────────────────────────────────────

function switchState(name) {

    stateScan.classList.remove('active');

    stateStudent.classList.remove('active');

    stateConfirm.classList.remove('active');

    if (name === 'scan')     { stateScan.classList.add('active');    }

    if (name === 'student')  { stateStudent.classList.add('active'); }

    if (name === 'confirm')  { stateConfirm.classList.add('active'); }

}



function resetToScan() {

    currentStudent   = null;

    selectedFee      = null;

    selectedMethod   = 'cash';

    lastScannedToken = '';

    document.querySelectorAll('.method-btn').forEach(b => b.classList.remove('active'));

    document.querySelector('.method-btn[data-method="cash"]').classList.add('active');

    document.getElementById('momo-opts').classList.remove('show');

    document.getElementById('manual-id-input').value = '';

    switchState('scan');

    scanActive = true;

}



function escHtml(str) {

    return str.replace(/[&<>"']/g, c =>

        ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' })[c]);

}



// ── Fingerprint (WebAuthn) ─────────────────────────────────────────────────

const btnFP = document.getElementById('btn-fingerprint');

if (btnFP) {

    btnFP.addEventListener('click', async function () {

        this.disabled = true;

        this.innerHTML = '<i class="fas fa-circle-notch fa-spin d-block mb-1"></i>Scanning…';



        let challenge;

        try {

            const resp = await fetch('ajax/canteen_webauthn_challenge.php',

                { method: 'POST', credentials: 'same-origin' });

            const data = await resp.json();

            if (!data.success) throw new Error(data.message);

            challenge = data.challenge;

        } catch (err) {

            this.disabled = false;

            this.innerHTML = '<i class="fas fa-fingerprint d-block mb-1"></i>Scan Fingerprint';

            showManualIdError('Challenge error: ' + err.message);

            return;

        }



        let credential;

        try {

            credential = await navigator.credentials.get({

                publicKey: {

                    challenge: b64uDecode(challenge),

                    rpId: window.location.hostname,

                    userVerification: 'required',

                    authenticatorSelection: { authenticatorAttachment: 'cross-platform' },

                    timeout: 30000,

                }

            });

        } catch (err) {

            this.disabled = false;

            this.innerHTML = '<i class="fas fa-fingerprint d-block mb-1"></i>Scan Fingerprint';

            if (err.name !== 'NotAllowedError') {

                showManualIdError('Fingerprint not recognised — please scan QR or enter ID.');

            }

            return;

        }



        // Verify on server

        const fd = new FormData();

        fd.append('credential_id',      b64uEncode(credential.rawId));

        fd.append('authenticator_data', b64uEncode(credential.response.authenticatorData));

        fd.append('client_data_json',   b64uEncode(credential.response.clientDataJSON));

        fd.append('signature',          b64uEncode(credential.response.signature));



        try {

            const resp = await fetch('ajax/canteen_webauthn_verify.php',

                { method: 'POST', credentials: 'same-origin', body: fd });

            const studentData = await resp.json();

            this.disabled = false;

            this.innerHTML = '<i class="fas fa-fingerprint d-block mb-1"></i>Scan Fingerprint';



            if (studentData.success) {

                showStudentState(studentData);

            } else {

                showManualIdError(studentData.message || 'Fingerprint not recognised.');

            }

        } catch {

            this.disabled = false;

            this.innerHTML = '<i class="fas fa-fingerprint d-block mb-1"></i>Scan Fingerprint';

            showManualIdError('Network error. Please try again.');

        }

    });

}



function showManualIdError(msg) {

    const el = document.getElementById('manual-id-error');

    if (el) { el.textContent = msg; el.classList.remove('d-none'); }

}



// ── Face recognition ───────────────────────────────────────────────────────

<?php if ($face_enabled): ?>

const btnFaceScan       = document.getElementById('btn-face-scan');

const faceWrap          = document.getElementById('face-wrap');

const btnCloseFace      = document.getElementById('btn-close-face');

const faceVideo         = document.getElementById('face-video');

const faceCanvas        = document.getElementById('face-canvas');

const faceStatusBanner  = document.getElementById('face-status-banner');

const faceLowLight      = document.getElementById('face-lowlight');

const faceConfPanel     = document.getElementById('face-confirm-panel');

const faceConfPhoto     = document.getElementById('face-conf-photo');

const faceConfName      = document.getElementById('face-conf-name');

const faceConfClass     = document.getElementById('face-conf-class');

const btnFaceConfirm    = document.getElementById('btn-face-confirm');

const btnFaceCancelConf = document.getElementById('btn-face-cancel-confirm');



let faceActive       = false;

let faceMeshInst     = null;

let faceCamInst      = null;

let facePendingMatch = null;

let faceDescriptors  = null;

let faceLightFrame   = 0;



if (btnFaceScan) {

    btnFaceScan.addEventListener('click', async function () {

        scanActive = false;

        this.style.display = 'none';

        if (document.getElementById('btn-fingerprint')) {

            document.getElementById('btn-fingerprint').style.display = 'none';

        }

        faceWrap.style.display = 'block';

        faceActive = true;



        // Load descriptors from prefetch cache

        faceDescriptors = await loadCachedFaceDescriptors();

        if (!faceDescriptors || faceDescriptors.length === 0) {

            stopFaceScan();

            showManualIdError('No facial data available offline. Ensure scanner was loaded online today.');

            return;

        }



        startFaceScan();

    });

}



if (btnCloseFace) btnCloseFace.addEventListener('click', stopFaceScan);



function startFaceScan() {

    if (typeof FaceMesh === 'undefined') {

        stopFaceScan();

        showManualIdError('MediaPipe not loaded.');

        return;

    }

    faceMeshInst = new FaceMesh({

        locateFile: f => `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${f}`

    });

    faceMeshInst.setOptions({

        maxNumFaces: 1, refineLandmarks: false,

        minDetectionConfidence: 0.5, minTrackingConfidence: 0.5,

    });

    faceMeshInst.onResults(onFaceResult);



    faceCamInst = new Camera(faceVideo, {

        onFrame: async () => {

            if (faceActive && faceVideo.readyState >= 2) {

                faceLightFrame++;

                if (faceLightFrame % 20 === 0) checkFaceLighting();

                await faceMeshInst.send({ image: faceVideo });

            }

        },

        width: 640, height: 480,

    });

    faceCamInst.start().catch(() => {

        stopFaceScan();

        showManualIdError('Camera failed. Please use QR or manual ID.');

    });

}



function onFaceResult(results) {

    if (!faceActive) return;

    if (!results.multiFaceLandmarks || !results.multiFaceLandmarks.length) {

        faceStatusBanner.textContent = 'Looking for face…';

        clearFaceCanvas();

        return;

    }

    const landmarks = results.multiFaceLandmarks[0];

    drawFaceDots(landmarks);



    const match = matchFaceLocally(landmarks);

    if (!match) {

        faceStatusBanner.textContent = 'No match — hold still…';

        return;

    }



    faceStatusBanner.textContent = '✓ Match found — confirm below';

    facePendingMatch = match;

    showFaceConfirmPanel(match);

}



function matchFaceLocally(landmarks) {

    if (!faceDescriptors) return null;

    const q = landmarks.map(lm => [lm.x, lm.y, lm.z]);

    let bestDist = Infinity, bestEntry = null;

    for (const entry of faceDescriptors) {

        const ref = entry.facial_descriptor;

        if (!Array.isArray(ref) || ref.length !== q.length) continue;

        let sum = 0;

        for (let i = 0; i < q.length; i++) {

            const dx = q[i][0] - (ref[i].x ?? ref[i][0] ?? 0);

            const dy = q[i][1] - (ref[i].y ?? ref[i][1] ?? 0);

            const dz = q[i][2] - (ref[i].z ?? ref[i][2] ?? 0);

            sum += dx*dx + dy*dy + dz*dz;

        }

        const dist = Math.sqrt(sum / q.length);

        if (dist < bestDist) { bestDist = dist; bestEntry = entry; }

    }

    if (!bestEntry || bestDist > 0.35) return null;

    return { ...bestEntry, confidence: Math.max(0, Math.min(1, 1 - bestDist / 0.35)) };

}



function showFaceConfirmPanel(match) {

    faceConfPhoto.src = match.photo_data_url || '';

    faceConfPhoto.style.display = match.photo_data_url ? 'block' : 'none';

    faceConfName.textContent  = match.student_name || '';

    faceConfClass.textContent = match.class_name   || '';

    faceConfPanel.style.display = 'block';

}



if (btnFaceConfirm) {

    btnFaceConfirm.addEventListener('click', function () {

        if (!facePendingMatch) return;

        stopFaceScan();

        // Show as identified student — load into State 2 via identity validate

        // We have all the data we need from the descriptor entry

        showStudentState({

            success:         true,

            student_id:      facePendingMatch.student_id,

            name:            facePendingMatch.student_name,

            class_name:      facePendingMatch.class_name,

            student_code:    facePendingMatch.student_code,

            photo_url:       facePendingMatch.photo_data_url

                                ? '' // data url already in cache but express pay uses server URL

                                : '',

            outstanding_fees: [], // will be loaded fresh

            _face_confidence: facePendingMatch.confidence,

        });

        // Reload outstanding fees via identity endpoint using student_id

        fetch('ajax/canteen_identity_validate.php', {

            method: 'POST', credentials: 'same-origin',

            body: (() => { const fd = new FormData(); fd.append('token', '__BYID__' + facePendingMatch.student_id); return fd; })()

        })

        .then(r => r.json())

        .then(data => {

            if (data.success) showStudentState(data);

        })

        .catch(() => {});

    });

}



if (btnFaceCancelConf) {

    btnFaceCancelConf.addEventListener('click', function () {

        faceConfPanel.style.display = 'none';

        facePendingMatch = null;

        faceStatusBanner.textContent = 'Continue scanning…';

    });

}



function stopFaceScan() {

    faceActive = false;

    if (faceCamInst)  { faceCamInst.stop();  faceCamInst  = null; }

    if (faceMeshInst) { faceMeshInst.close(); faceMeshInst = null; }

    if (faceVideo.srcObject) {

        faceVideo.srcObject.getTracks().forEach(t => t.stop());

        faceVideo.srcObject = null;

    }

    faceWrap.style.display = 'none';

    faceConfPanel.style.display = 'none';

    if (btnFaceScan) btnFaceScan.style.removeProperty('display');

    const fpBtn = document.getElementById('btn-fingerprint');

    if (fpBtn) fpBtn.style.removeProperty('display');

    scanActive = true;

}



function checkFaceLighting() {

    if (!faceVideo.videoWidth) return;

    const cvs = document.createElement('canvas');

    cvs.width  = Math.floor(faceVideo.videoWidth  / 4);

    cvs.height = Math.floor(faceVideo.videoHeight / 4);

    const ctx  = cvs.getContext('2d');

    ctx.drawImage(faceVideo, 0, 0, cvs.width, cvs.height);

    const data = ctx.getImageData(0, 0, cvs.width, cvs.height).data;

    let sum = 0;

    for (let i = 0; i < data.length; i += 4) {

        sum += 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];

    }

    faceLowLight.style.display = (sum / (data.length / 4)) < 60 ? 'block' : 'none';

}



function drawFaceDots(landmarks) {

    if (!faceCanvas || !faceVideo.videoWidth) return;

    faceCanvas.width  = faceVideo.videoWidth;

    faceCanvas.height = faceVideo.videoHeight;

    const ctx = faceCanvas.getContext('2d');

    ctx.clearRect(0, 0, faceCanvas.width, faceCanvas.height);

    ctx.fillStyle = 'rgba(99,102,241,0.7)';

    landmarks.forEach(lm => {

        ctx.beginPath();

        ctx.arc(lm.x * faceCanvas.width, lm.y * faceCanvas.height, 1.5, 0, Math.PI * 2);

        ctx.fill();

    });

}



function clearFaceCanvas() {

    if (!faceCanvas) return;

    faceCanvas.getContext('2d').clearRect(0, 0, faceCanvas.width, faceCanvas.height);

}



async function loadCachedFaceDescriptors() {

    if (!('caches' in window)) return null;

    try {

        const today = new Date().toISOString().slice(0, 10);

        const cache = await caches.open('canteen-entitlements-' + today);

        const resp  = await cache.match('../ajax/canteen_prefetch.php?date=' + today);

        if (!resp) return null;

        const data = await resp.json();

        if (!Array.isArray(data.entries)) return null;

        return data.entries.filter(e => e.facial_descriptor);

    } catch {

        return null;

    }

}

<?php endif; ?>



// ── Base64url helpers ──────────────────────────────────────────────────────

function b64uDecode(str) {

    const b64 = str.replace(/-/g, '+').replace(/_/g, '/') + '=='.slice(0, (4 - str.length % 4) % 4);

    const bin = atob(b64);

    return Uint8Array.from(bin, c => c.charCodeAt(0));

}

function b64uEncode(buf) {

    const bytes = new Uint8Array(buf instanceof ArrayBuffer ? buf : buf.buffer || buf);

    let bin = '';

    bytes.forEach(b => { bin += String.fromCharCode(b); });

    return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

}



})();

</script>

</body>

</html>

