<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>E-Ticket Scanner</title>
    <link rel="manifest" href="{{ path('app_scanner_manifest') }}">
    <meta name="theme-color" content="#111827">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="apple-mobile-web-app-title" content="Scanner">
    <link rel="apple-touch-icon" href="/logo.png">
    <script src="https://cdn.jsdelivr.net/npm/html5-qrcode@2.3.8/html5-qrcode.min.js" integrity="sha384-c9d8RFSL+u3exBOJ4Yp3HUJXS4znl9f+z66d1y54ig+ea249SpqR+w1wyvXz/lk+" crossorigin="anonymous"></script>
    <style>
        *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #111827; color: #f9fafb; min-height: 100dvh; }
        .screen { display: none; min-height: 100dvh; flex-direction: column; }
        .screen.active { display: flex; }
        .header { background: #1f2937; padding: 16px 20px; display: flex; align-items: center; justify-content: space-between; border-bottom: 2px solid #fabf04; }
        .header h1 { font-size: 16px; font-weight: 900; text-transform: uppercase; letter-spacing: 2px; }
        .header .badge { background: #fabf04; color: #111827; font-size: 10px; font-weight: 900; padding: 4px 10px; text-transform: uppercase; letter-spacing: 1px; }
        .content { flex: 1; padding: 20px; overflow-y: auto; }
        .btn { display: block; width: 100%; padding: 16px; border: 2px solid #374151; background: #1f2937; color: #f9fafb; font-size: 14px; font-weight: 800; text-transform: uppercase; letter-spacing: 1px; cursor: pointer; transition: all .15s; text-align: center; text-decoration: none; }
        .btn:active { transform: scale(0.98); }
        .btn-primary { background: #fabf04; color: #111827; border-color: #fabf04; }
        .btn-danger { background: #dc2626; border-color: #dc2626; }
        .btn-sm { padding: 10px 16px; font-size: 12px; width: auto; display: inline-block; }
        .input { width: 100%; padding: 14px 16px; background: #1f2937; border: 2px solid #374151; color: #f9fafb; font-size: 16px; font-weight: 600; outline: none; }
        .input:focus { border-color: #fabf04; }
        .input::placeholder { color: #6b7280; }
        .label { display: block; font-size: 10px; font-weight: 900; text-transform: uppercase; letter-spacing: 2px; color: #9ca3af; margin-bottom: 8px; }
        .card { background: #1f2937; border: 2px solid #374151; padding: 16px; margin-bottom: 12px; cursor: pointer; transition: all .15s; }
        .card:active { border-color: #fabf04; }
        .card-title { font-size: 15px; font-weight: 800; margin-bottom: 4px; }
        .card-sub { font-size: 12px; color: #9ca3af; }
        .result-box { padding: 24px; text-align: center; border: 3px solid; margin-bottom: 16px; }
        .result-accepted { border-color: #22c55e; background: #14532d33; }
        .result-refused { border-color: #dc2626; background: #7f1d1d33; }
        .result-icon { font-size: 48px; margin-bottom: 8px; }
        .result-state { font-size: 20px; font-weight: 900; text-transform: uppercase; letter-spacing: 2px; }
        .result-accepted .result-state { color: #22c55e; }
        .result-refused .result-state { color: #dc2626; }
        .result-warning { border-color: #eab308; background: #713f1233; }
        .result-warning .result-state { color: #eab308; }
        .result-detail { margin-top: 16px; text-align: left; }
        .result-detail .row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #374151; font-size: 13px; }
        .result-detail .row .key { color: #9ca3af; font-weight: 700; text-transform: uppercase; font-size: 10px; letter-spacing: 1px; }
        .result-detail .row .val { font-weight: 800; }
        .spinner { width: 32px; height: 32px; border: 3px solid #374151; border-top-color: #fabf04; border-radius: 50%; animation: spin .6s linear infinite; margin: 40px auto; }
        @keyframes spin { to { transform: rotate(360deg); } }
        .error-msg { background: #7f1d1d; border: 2px solid #dc2626; padding: 12px 16px; font-size: 13px; font-weight: 700; margin-bottom: 16px; }
        .scan-count { text-align: center; margin-top: 16px; font-size: 12px; color: #9ca3af; }
        .scan-count strong { color: #fabf04; font-size: 20px; }
        #qr-reader { width: 100%; }
        #qr-reader video { border: 2px solid #374151 !important; }
        .back-btn { background: none; border: none; color: #9ca3af; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: 1px; cursor: pointer; padding: 8px 0; display: flex; align-items: center; gap: 6px; }
        .mt-4 { margin-top: 16px; }
        .mb-4 { margin-bottom: 16px; }
        .mb-6 { margin-bottom: 24px; }
        .gap-3 { display: flex; flex-direction: column; gap: 12px; }
        .text-center { text-align: center; }
        .text-muted { color: #6b7280; font-size: 13px; }
        .install-banner { background: #1f2937; border: 2px solid #fabf04; padding: 12px 16px; margin-bottom: 16px; display: flex; align-items: center; justify-content: space-between; }
        .install-banner p { font-size: 12px; font-weight: 700; }
        .hidden { display: none !important; }
    </style>
</head>
<body>

<!-- LOGIN -->
<div id="screen-login" class="screen active">
    <div class="header">
        <h1>E-Ticket Scanner</h1>
    </div>
    <div class="content" style="display:flex;flex-direction:column;justify-content:center;">
        <div id="install-banner" class="install-banner hidden">
            <p>Installer l'app sur votre telephone</p>
            <button class="btn btn-primary btn-sm" id="install-btn">Installer</button>
        </div>
        <p class="label" style="text-align:center;margin-bottom:24px;font-size:12px;color:#9ca3af;">Connectez-vous avec votre compte organisateur</p>
        <div id="login-error"></div>
        <div class="gap-3">
            <div>
                <label class="label" for="login-email">Email</label>
                <input type="email" id="login-email" class="input" placeholder="organisateur@email.fr" autocomplete="email">
            </div>
            <div>
                <label class="label" for="login-password">Mot de passe</label>
                <input type="password" id="login-password" class="input" placeholder="Mot de passe" autocomplete="current-password">
            </div>
            <button class="btn btn-primary mt-4" id="login-btn">Se connecter</button>
            <div style="display:flex;align-items:center;gap:12px;margin-top:20px;">
                <div style="flex:1;height:1px;background:#374151;"></div>
                <span style="font-size:10px;font-weight:900;text-transform:uppercase;letter-spacing:2px;color:#6b7280;">ou</span>
                <div style="flex:1;height:1px;background:#374151;"></div>
            </div>
            <a href="/api/auth/login/sso?from=scanner" class="btn mt-4" id="sso-btn" style="background:#2563eb;border-color:#2563eb;color:#fff;">Se connecter avec SSO E-Cosplay</a>
        </div>
    </div>
</div>

<!-- EVENTS -->
<div id="screen-events" class="screen">
    <div class="header">
        <h1>Evenements</h1>
        <button class="btn btn-sm btn-danger" id="logout-btn">Deconnexion</button>
    </div>
    <div class="content">
        <div id="events-list"></div>
        <div id="events-loading" class="hidden"><div class="spinner"></div></div>
        <div id="events-error"></div>
    </div>
</div>

<!-- SCANNER -->
<div id="screen-scanner" class="screen">
    <div class="header">
        <h1 id="scanner-title">Scanner</h1>
        <span class="badge" id="scanner-event-badge"></span>
    </div>
    <div class="content">
        <button class="back-btn mb-4" id="back-events">&#8592; Evenements</button>

        <!-- Mode selection -->
        <div id="scan-modes" class="gap-3 mb-4">
            <button class="btn btn-primary" id="btn-mode-camera">&#128247; Scanner un QR code</button>
            <div style="display:flex;align-items:center;gap:12px;">
                <div style="flex:1;height:1px;background:#374151;"></div>
                <span style="font-size:10px;font-weight:900;text-transform:uppercase;letter-spacing:2px;color:#6b7280;">ou</span>
                <div style="flex:1;height:1px;background:#374151;"></div>
            </div>
            <div>
                <label class="label" for="manual-reference">Cle de securite du billet</label>
                <div style="display:flex;gap:8px;">
                    <input type="text" id="manual-reference" class="input" placeholder="Ex: A1B2C3D4E5F6G7H8" style="flex:1;text-transform:uppercase;font-family:monospace;letter-spacing:2px;" autocomplete="off" spellcheck="false" maxlength="16">
                    <button class="btn btn-primary" id="btn-mode-manual" style="width:auto;padding:14px 20px;">Valider</button>
                </div>
            </div>
        </div>

        <!-- Camera zone (hidden by default) -->
        <div id="camera-zone" class="hidden">
            <div id="qr-reader" class="mb-4"></div>
            <button class="btn btn-sm btn-danger mb-4" id="btn-stop-camera">Arreter la camera</button>
        </div>

        <div id="scan-result"></div>
        <div class="scan-count">
            Scans effectues : <strong id="scan-counter">0</strong>
        </div>
    </div>
</div>

<script>
(function() {
    const API_BASE = window.location.origin;
    let auth = JSON.parse(localStorage.getItem('scanner_auth') || 'null');
    let currentEvent = null;
    let scanner = null;
    let scannedRefs = JSON.parse(localStorage.getItem('scanner_refs') || '[]');
    let scanCount = scannedRefs.length;
    let deferredPrompt = null;

    // Audio feedback
    const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    function playSound(type) {
        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();
        osc.connect(gain);
        gain.connect(audioCtx.destination);
        gain.gain.value = 0.3;
        if (type === 'good') {
            osc.frequency.value = 880;
            osc.type = 'sine';
            osc.start();
            osc.stop(audioCtx.currentTime + 0.15);
        } else if (type === 'warning') {
            osc.frequency.value = 440;
            osc.type = 'triangle';
            osc.start();
            setTimeout(() => { osc.frequency.value = 520; }, 150);
            osc.stop(audioCtx.currentTime + 0.35);
        } else {
            osc.frequency.value = 200;
            osc.type = 'square';
            osc.start();
            osc.stop(audioCtx.currentTime + 0.4);
        }
    }


    // PWA Install
    window.addEventListener('beforeinstallprompt', (e) => {
        e.preventDefault();
        deferredPrompt = e;
        document.getElementById('install-banner').classList.remove('hidden');
    });
    document.getElementById('install-btn').addEventListener('click', async () => {
        if (!deferredPrompt) return;
        deferredPrompt.prompt();
        await deferredPrompt.userChoice;
        deferredPrompt = null;
        document.getElementById('install-banner').classList.add('hidden');
    });

    // Screens
    function showScreen(id) {
        document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
        document.getElementById('screen-' + id).classList.add('active');
    }

    // API
    async function api(method, path, body) {
        const headers = { 'Content-Type': 'application/json' };
        if (auth) {
            headers['ETicket-Email'] = auth.email;
            headers['ETicket-JWT'] = auth.token;
        }
        const opts = { method, headers };
        if (body) opts.body = JSON.stringify(body);
        const res = await fetch(API_BASE + path, opts);
        const json = await res.json();
        if (!json.success && res.status === 401) {
            const refreshed = await tryRefresh();
            if (refreshed) return api(method, path, body);
            logout();
            throw new Error('Session expiree');
        }
        return json;
    }

    async function tryRefresh() {
        if (!auth) return false;
        try {
            const res = await fetch(API_BASE + '/api/auth/refresh', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'ETicket-Email': auth.email,
                    'ETicket-JWT': auth.token,
                },
            });
            const json = await res.json();
            if (json.success) {
                auth.token = json.data.token;
                auth.expiresAt = json.data.expiresAt;
                localStorage.setItem('scanner_auth', JSON.stringify(auth));
                return true;
            }
        } catch {}
        return false;
    }

    // Login
    document.getElementById('login-btn').addEventListener('click', doLogin);
    document.getElementById('login-password').addEventListener('keydown', (e) => { if (e.key === 'Enter') doLogin(); });

    async function doLogin() {
        const email = document.getElementById('login-email').value.trim();
        const password = document.getElementById('login-password').value;
        const errEl = document.getElementById('login-error');
        errEl.innerHTML = '';

        if (!email || !password) {
            errEl.innerHTML = '<div class="error-msg">Veuillez remplir tous les champs.</div>';
            return;
        }

        document.getElementById('login-btn').disabled = true;
        document.getElementById('login-btn').textContent = 'Connexion...';

        try {
            const res = await fetch(API_BASE + '/api/auth/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ email, password }),
            });
            const json = await res.json();
            if (!json.success) {
                errEl.innerHTML = '<div class="error-msg">' + (json.error || 'Erreur de connexion.') + '</div>';
                return;
            }
            auth = { email, token: json.data.token, expiresAt: json.data.expiresAt };
            localStorage.setItem('scanner_auth', JSON.stringify(auth));
            loadEvents();
        } catch (e) {
            errEl.innerHTML = '<div class="error-msg">Impossible de se connecter au serveur.</div>';
        } finally {
            document.getElementById('login-btn').disabled = false;
            document.getElementById('login-btn').textContent = 'Se connecter';
        }
    }

    // Logout
    document.getElementById('logout-btn').addEventListener('click', logout);
    function logout() {
        auth = null;
        localStorage.removeItem('scanner_auth');
        stopCamera();
        showScreen('login');
    }

    // Events
    async function loadEvents() {
        showScreen('events');
        const listEl = document.getElementById('events-list');
        const loadEl = document.getElementById('events-loading');
        const errEl = document.getElementById('events-error');
        listEl.innerHTML = '';
        errEl.innerHTML = '';
        loadEl.classList.remove('hidden');

        try {
            const json = await api('GET', '/api/live/events');
            loadEl.classList.add('hidden');
            if (!json.success) {
                errEl.innerHTML = '<div class="error-msg">' + json.error + '</div>';
                return;
            }
            if (!json.data.length) {
                listEl.innerHTML = '<p class="text-center text-muted mt-4">Aucun evenement.</p>';
                return;
            }
            json.data.forEach(ev => {
                const d = new Date(ev.startAt);
                const dateStr = d.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' });
                const timeStr = d.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
                const card = document.createElement('div');
                card.className = 'card';
                card.innerHTML = '<div class="card-title">' + escHtml(ev.title) + '</div>'
                    + '<div class="card-sub">' + dateStr + ' a ' + timeStr + ' - ' + escHtml(ev.city || '') + '</div>'
                    + '<div class="card-sub" style="margin-top:4px;">'
                    + (ev.isOnline ? '<span style="color:#22c55e;">En ligne</span>' : '<span style="color:#dc2626;">Hors ligne</span>')
                    + (ev.isSecret ? ' &middot; <span style="color:#eab308;">Secret</span>' : '')
                    + '</div>';
                card.addEventListener('click', () => openScanScreen(ev));
                listEl.appendChild(card);
            });
        } catch (e) {
            loadEl.classList.add('hidden');
            errEl.innerHTML = '<div class="error-msg">Erreur de chargement.</div>';
        }
    }

    // Scanner
    document.getElementById('back-events').addEventListener('click', () => { stopCamera(); loadEvents(); });

    function openScanScreen(event) {
        currentEvent = event;
        document.getElementById('scanner-title').textContent = 'Scanner';
        document.getElementById('scanner-event-badge').textContent = event.title;
        document.getElementById('scan-result').innerHTML = '';
        document.getElementById('scan-counter').textContent = scanCount;
        document.getElementById('manual-reference').value = '';
        document.getElementById('scan-modes').classList.remove('hidden');
        document.getElementById('camera-zone').classList.add('hidden');
        showScreen('scanner');
    }

    // Camera mode
    document.getElementById('btn-mode-camera').addEventListener('click', startCamera);

    function startCamera() {
        document.getElementById('scan-modes').classList.add('hidden');
        document.getElementById('camera-zone').classList.remove('hidden');
        document.getElementById('scan-result').innerHTML = '';

        scanner = new Html5Qrcode('qr-reader');
        scanner.start(
            { facingMode: 'environment' },
            { fps: 10, qrbox: { width: 250, height: 250 }, aspectRatio: 1 },
            onScanSuccess,
            () => {}
        ).catch(err => {
            document.getElementById('scan-result').innerHTML = '<div class="error-msg">Impossible d\'acceder a la camera: ' + escHtml(err.toString()) + '</div>';
        });
    }

    document.getElementById('btn-stop-camera').addEventListener('click', () => {
        stopCamera();
        document.getElementById('scan-modes').classList.remove('hidden');
        document.getElementById('camera-zone').classList.add('hidden');
    });

    // Manual mode
    document.getElementById('btn-mode-manual').addEventListener('click', doManualScan);
    document.getElementById('manual-reference').addEventListener('keydown', (e) => { if (e.key === 'Enter') doManualScan(); });

    function doManualScan() {
        const key = document.getElementById('manual-reference').value.trim().toUpperCase();
        if (!key) return;
        submitScan(null, key);
    }

    // Shared scan logic
    let scanning = false;
    async function onScanSuccess(decodedText) {
        if (scanning) return;
        scanning = true;

        try { await scanner.pause(true); } catch {}
        if (navigator.vibrate) navigator.vibrate(100);

        let reference = decodedText;
        try {
            const decoded = globalThis.atob(decodedText);
            if (decoded.startsWith('ETICKET-')) reference = decoded;
        } catch {}

        await submitScan(reference, null);

        setTimeout(() => {
            scanning = false;
            try { scanner.resume(); } catch {}
        }, 2000);
    }

    async function submitScan(reference, securityKey) {
        const resultEl = document.getElementById('scan-result');
        resultEl.innerHTML = '<div class="spinner"></div>';

        const body = reference ? { reference } : { securityKey };
        try {
            const json = await api('POST', '/api/live/scan', body);
            if (!json.success) {
                playSound('refused');
                resultEl.innerHTML = '<div class="result-box result-refused">'
                    + '<div class="result-icon">&#10007;</div>'
                    + '<div class="result-state">' + escHtml(json.error || 'Erreur') + '</div></div>';
            } else {
                const d = json.data;
                const isOk = d.state === 'accepted';
                const alreadyScanned = d.reason === 'already_scanned';

                if (isOk && !alreadyScanned) {
                    playSound('good');
                } else if (isOk && alreadyScanned) {
                    playSound('warning');
                } else {
                    playSound('refused');
                }

                // Unique scan count per reference
                if (isOk && d.reference && !scannedRefs.includes(d.reference)) {
                    scannedRefs.push(d.reference);
                    localStorage.setItem('scanner_refs', JSON.stringify(scannedRefs));
                    scanCount = scannedRefs.length;
                    document.getElementById('scan-counter').textContent = scanCount;
                }

                let reasonText = '';
                if (d.reason === 'invalid') reasonText = 'Billet invalide';
                else if (d.reason === 'expired') reasonText = 'Billet expire';
                else if (d.reason === 'exit_definitive') reasonText = 'Deja scanne (sortie definitive)';
                else if (d.reason === 'already_scanned') reasonText = 'Billet deja scanne';

                const boxClass = !isOk ? 'result-refused' : (alreadyScanned ? 'result-warning' : 'result-accepted');
                const icon = !isOk ? '&#10007;' : (alreadyScanned ? '&#9888;' : '&#10003;');
                const stateText = !isOk ? 'Refuse' : (alreadyScanned ? 'Accepte' : 'Accepte');

                let orderHtml = '';
                if (d.order) {
                    const o = d.order;
                    orderHtml = '<div style="margin-top:12px;padding-top:12px;border-top:2px solid #374151;">'
                        + row('Commande', '<span style="font-family:monospace;">' + escHtml(o.orderNumber || '') + '</span>')
                        + row('Email', escHtml(d.buyerEmail || ''))
                        + row('Total HT', o.totalHT.toFixed(2).replace('.', ',') + ' \u20ac')
                        + (o.paidAt ? row('Payee le', new Date(o.paidAt).toLocaleString('fr-FR')) : '')
                        + '<div style="margin-top:8px;">';
                    (o.items || []).forEach(function(item) {
                        orderHtml += '<div style="display:flex;justify-content:space-between;padding:4px 0;font-size:12px;">'
                            + '<span style="color:#9ca3af;">' + escHtml(item.billetName) + ' x' + item.quantity + '</span>'
                            + '<span style="font-weight:800;">' + item.unitPriceHT.toFixed(2).replace('.', ',') + ' \u20ac</span>'
                            + '</div>';
                    });
                    orderHtml += '</div></div>';
                }

                let forceHtml = '';
                if (!isOk && d.canForce) {
                    forceHtml = '<div style="margin-top:12px;"><button class="btn btn-primary" id="btn-force-validate" data-ref="' + escHtml(d.reference) + '">Forcer la validation</button></div>';
                }

                resultEl.innerHTML = '<div class="result-box ' + boxClass + '">'
                    + '<div class="result-icon">' + icon + '</div>'
                    + '<div class="result-state">' + stateText + '</div>'
                    + (reasonText ? '<div style="color:' + (alreadyScanned ? '#eab308' : '#f87171') + ';font-weight:700;margin-top:8px;">' + reasonText + '</div>' : '')
                    + forceHtml
                    + '</div>'
                    + '<div class="result-detail">'
                    + row('Nom', escHtml((d.buyerFirstName || '') + ' ' + (d.buyerLastName || '')))
                    + row('Billet', escHtml(d.billetName || ''))
                    + row('Reference', '<span style="font-size:11px;font-family:monospace;">' + escHtml(d.reference || '') + '</span>')
                    + (d.billetType === 'staff' ? row('Type', '<span style="display:inline-block;padding:2px 8px;background:#111827;color:#fff;font-size:10px;font-weight:900;text-transform:uppercase;letter-spacing:2px;">STAFF</span>') : '')
                    + (d.billetType === 'exposant' ? row('Type', '<span style="display:inline-block;padding:2px 8px;background:#7c3aed;color:#fff;font-size:10px;font-weight:900;text-transform:uppercase;letter-spacing:2px;">EXPOSANT</span>') : '')
                    + (d.isInvitation && d.billetType === 'billet' ? row('Type', '<span style="color:#eab308;">Invitation</span>') : '')
                    + (d.firstScannedAt ? row('Premier scan', new Date(d.firstScannedAt).toLocaleString('fr-FR')) : '')
                    + orderHtml
                    + '</div>';

                const forceBtn = document.getElementById('btn-force-validate');
                if (forceBtn) {
                    forceBtn.addEventListener('click', async function() {
                        forceBtn.disabled = true;
                        forceBtn.textContent = 'Validation...';
                        try {
                            const forceJson = await api('POST', '/api/live/scan/force', { reference: forceBtn.dataset.ref });
                            if (forceJson.success) {
                                playSound('good');
                                const fd = forceJson.data;
                                resultEl.innerHTML = '<div class="result-box result-accepted">'
                                    + '<div class="result-icon">&#10003;</div>'
                                    + '<div class="result-state">Force</div>'
                                    + '<div style="color:#22c55e;font-weight:700;margin-top:8px;">Validation forcee</div>'
                                    + '</div>'
                                    + '<div class="result-detail">'
                                    + row('Nom', escHtml((fd.buyerFirstName || '') + ' ' + (fd.buyerLastName || '')))
                                    + row('Billet', escHtml(fd.billetName || ''))
                                    + row('Reference', '<span style="font-size:11px;font-family:monospace;">' + escHtml(fd.reference || '') + '</span>')
                                    + '</div>';
                                if (fd.reference && !scannedRefs.includes(fd.reference)) {
                                    scannedRefs.push(fd.reference);
                                    localStorage.setItem('scanner_refs', JSON.stringify(scannedRefs));
                                    scanCount = scannedRefs.length;
                                    document.getElementById('scan-counter').textContent = scanCount;
                                }
                            } else {
                                playSound('refused');
                                forceBtn.textContent = forceJson.error || 'Erreur';
                            }
                        } catch (e) {
                            playSound('refused');
                            forceBtn.textContent = 'Erreur';
                        }
                    });
                }
            }
            document.getElementById('manual-reference').value = '';
        } catch (e) {
            playSound('refused');
            resultEl.innerHTML = '<div class="error-msg">Erreur: ' + escHtml(e.message) + '</div>';
        }
    }

    function stopCamera() {
        if (scanner) {
            try { scanner.stop(); } catch {}
            scanner = null;
        }
        document.getElementById('qr-reader').innerHTML = '';
    }

    // Helpers
    function row(key, val) {
        return '<div class="row"><span class="key">' + key + '</span><span class="val">' + val + '</span></div>';
    }
    function escHtml(s) {
        const d = document.createElement('div');
        d.textContent = s;
        return d.innerHTML;
    }

    // SSO callback handling
    function handleSsoCallback() {
        const hash = window.location.hash.substring(1);
        if (!hash) return false;

        const params = new URLSearchParams(hash);

        if (params.has('sso_error')) {
            const errorMap = {
                'auth_failed': 'Echec de l\'authentification SSO.',
                'no_account': 'Aucun compte associe a ce SSO.',
                'no_access': 'Acces reserve aux organisateurs.',
            };
            const errEl = document.getElementById('login-error');
            errEl.innerHTML = '<div class="error-msg">' + (errorMap[params.get('sso_error')] || 'Erreur SSO.') + '</div>';
            window.history.replaceState(null, '', '/scanner/');
            return true;
        }

        if (params.has('sso_token')) {
            auth = {
                email: params.get('sso_email'),
                token: params.get('sso_token'),
                expiresAt: params.get('sso_expires'),
            };
            localStorage.setItem('scanner_auth', JSON.stringify(auth));
            window.history.replaceState(null, '', '/scanner/');
            loadEvents();
            return true;
        }

        return false;
    }

    // Init
    if (!handleSsoCallback() && auth) {
        loadEvents();
    }

    // Service worker for PWA
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('/scanner/sw.js', { scope: '/scanner/' }).catch(() => {});
    }
})();
</script>
</body>
</html>
