<?php
require_once __DIR__ . '/header.php';
perms_require('view_live_dashboard');
$user = current_user();
$isSuper = perms_is_super($user);

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['dashboard_action'])) {
    csrf_require();
    $action = $_POST['dashboard_action'];

    if ($action === 'reset_sessions') {
        $pdo->exec("UPDATE osce_sessions SET status = 'timeout', last_action = 'Cleared by admin'");
        audit_log($pdo, 'live_dashboard_reset', []);
        header('Location: live_dashboard.php?msg=reset');
        exit;
    }
    if ($action === 'force_timeout') {
        $pdo->exec("UPDATE osce_sessions SET status = 'timeout', last_action = 'Force timeout by admin' WHERE status IN ('checked_in','in_progress')");
        audit_log($pdo, 'live_dashboard_timeout', []);
        header('Location: live_dashboard.php?msg=timeout');
        exit;
    }
    if ($action === 'clear_finished') {
        // Remove finished rows from the live view to reduce clutter. This only
        // deletes the live-tracking session rows — the actual grades live in
        // the `attempts` table and are untouched.
        $pdo->exec("DELETE FROM osce_sessions WHERE status IN ('submitted','timeout')");
        audit_log($pdo, 'live_dashboard_clear_finished', []);
        header('Location: live_dashboard.php?msg=cleared');
        exit;
    }
}
?>
<h2><span style="color:var(--c-danger);">●</span> Live Control Dashboard</h2>
<p class="muted">Real-time view of every station. Updates silently every 3 s without reloading the page.</p>

<?php if (isset($_GET['msg']) && $_GET['msg'] === 'reset'):  ?><div class="alert alert-success">Live dashboard reset.</div><?php endif; ?>
<?php if (isset($_GET['msg']) && $_GET['msg'] === 'timeout'):?><div class="alert alert-success">All active sessions marked timeout.</div><?php endif; ?>
<?php if (isset($_GET['msg']) && $_GET['msg'] === 'cleared'):?><div class="alert alert-success">Finished candidates cleared from the dashboard (grades kept).</div><?php endif; ?>

<!-- ====== CENTRALISED ROUND CONTROL ====== -->
<div id="round-card" style="background: linear-gradient(135deg,#0f172a 0%,#1e293b 100%); color:#f1f5f9; border-radius:12px; padding:18px 22px; margin-bottom:16px; box-shadow:var(--shadow-lg);">
    <div style="display:flex; align-items:center; gap:16px; flex-wrap:wrap;">
        <div style="font-size:13px; opacity:.7; text-transform:uppercase; letter-spacing:.06em;">Centralised Round</div>
        <div id="round-status" class="badge" style="background:#334155; color:#cbd5e1;">IDLE</div>
        <div style="flex:1;"></div>
        <button id="muteToggle" class="btn" style="background:transparent; color:#f1f5f9; border-color:#475569;">🔇 Muted</button>
    </div>

    <!-- IDLE state: start a round -->
    <div id="round-idle" class="mt-2">
        <p style="opacity:.8; font-size:14px;">When you start a round, every connected dashboard and grading screen syncs to the same clock and plays the same bells: <strong>1 bell</strong> at start · <strong>2 bells</strong> at 2-minute warning · <strong>3 bells</strong> at round end.</p>
        <div class="form-row">
            <div class="field" style="flex:1;">
                <label style="color:#cbd5e1;">Label (optional)</label>
                <input type="text" id="round-label" placeholder="e.g. Round 1" style="background:#1e293b; color:#f1f5f9; border-color:#475569;">
            </div>
            <div class="field">
                <label style="color:#cbd5e1;">Duration (minutes)</label>
                <input type="number" id="round-duration" min="1" max="60" value="8" style="background:#1e293b; color:#f1f5f9; border-color:#475569;">
            </div>
            <div class="field">
                <label style="color:#cbd5e1;">2-min warning at</label>
                <input type="number" id="round-warn" min="1" max="30" value="2" style="background:#1e293b; color:#f1f5f9; border-color:#475569;">
            </div>
        </div>
        <button id="start-round" class="btn btn-primary btn-lg">🔔 Start Round</button>
    </div>

    <!-- RUNNING state: huge countdown + controls -->
    <div id="round-running" class="mt-2" style="display:none;">
        <div style="text-align:center; padding:14px 0;">
            <div id="round-countdown" style="font-size:64px; font-weight:800; font-variant-numeric:tabular-nums; line-height:1;">00:00</div>
            <div id="round-meta" style="opacity:.8; margin-top:6px; font-size:14px;"></div>
        </div>
        <div class="flex" style="justify-content:center;">
            <button id="end-round"   class="btn btn-warning">⏹ End Round Now</button>
            <button id="abort-round" class="btn btn-danger">✕ Abort (no bell)</button>
            <button class="btn manual-bell" data-pattern="1" style="background:#334155; color:#f1f5f9; border-color:#475569;">🔔 Ring 1</button>
            <button class="btn manual-bell" data-pattern="2" style="background:#334155; color:#f1f5f9; border-color:#475569;">🔔🔔 Ring 2 (warn)</button>
            <button class="btn manual-bell" data-pattern="3" style="background:#334155; color:#f1f5f9; border-color:#475569;">🔔🔔🔔 Ring 3 (end)</button>
        </div>
    </div>
</div>

<div class="flex mb-2">
    <form method="post" style="display:inline" onsubmit="return confirm('Clear ALL live OSCE sessions?');">
        <?php echo csrf_field(); ?>
        <input type="hidden" name="dashboard_action" value="reset_sessions">
        <button type="submit" class="btn btn-danger">🔄 Reset Dashboard</button>
    </form>

    <form method="post" style="display:inline" onsubmit="return confirm('Force timeout for ALL active stations?');">
        <?php echo csrf_field(); ?>
        <input type="hidden" name="dashboard_action" value="force_timeout">
        <button type="submit" class="btn btn-warning">⏱ Force Timeout All</button>
    </form>

    <form method="post" style="display:inline" onsubmit="return confirm('Remove all finished (submitted/timeout) candidates from the dashboard? Their grades are kept.');">
        <?php echo csrf_field(); ?>
        <input type="hidden" name="dashboard_action" value="clear_finished">
        <button type="submit" class="btn">🧹 Clear Finished</button>
    </form>

    <span class="muted" id="last-updated">Loading…</span>
</div>

<div style="display:grid; grid-template-columns: 1fr 320px; gap:16px;" id="dashboard-grid">
    <div>
        <div class="table-wrap">
        <table class="data" id="live-tbl">
            <thead>
                <tr>
                    <th>Candidate</th><th>Station</th><th>Examiner</th>
                    <th>Status</th><th>Timer</th>
                    <th>Check-in</th><th>Grading</th><th>Submitted</th>
                    <th>Last Action</th>
                </tr>
            </thead>
            <tbody id="live-body">
                <tr><td colspan="9" class="muted">Loading…</td></tr>
            </tbody>
        </table>
        </div>
    </div>

    <div>
        <h3 style="margin-top:0; display:flex; align-items:center; justify-content:space-between;">
            Recent activity
            <button id="clear-activity" class="btn" style="font-size:11px; padding:3px 10px;">Clear</button>
        </h3>
        <p class="muted" style="font-size:12px;">Last 30 minutes of grading mutations.</p>
        <ul id="mods-list" style="list-style:none; padding:0; margin:0; max-height:600px; overflow:auto;"></ul>
    </div>
</div>

<style>
@media (max-width: 980px) { #dashboard-grid { grid-template-columns: 1fr !important; } }
.mod-item { background:#fff; border:1px solid var(--c-border); border-radius:6px; padding:8px 10px; margin-bottom:6px; font-size:13px; }
.mod-item .when { color: var(--c-muted); font-size: 11px; display: block; margin-top: 2px; }
.mod-item.evt-grade_save        { border-left: 3px solid var(--c-success); }
.mod-item.evt-grade_undo        { border-left: 3px solid var(--c-warning); }
.mod-item.evt-attempt_edit      { border-left: 3px solid var(--c-info); }
.mod-item.evt-attempt_soft_delete { border-left: 3px solid var(--c-danger); }
.mod-item.evt-station_check_in  { border-left: 3px solid #94a3b8; }
#round-countdown.warn-2 { color: #facc15; }
#round-countdown.warn-1 { color: #fb923c; }
#round-countdown.done   { color: #f87171; }
</style>

<script>
(function () {
    const body    = document.getElementById('live-body');
    const mods    = document.getElementById('mods-list');
    const muteBtn = document.getElementById('muteToggle');
    const stamp   = document.getElementById('last-updated');

    let muted = localStorage.getItem('dashboardMuted') === 'true';
    function updateMute() { muteBtn.textContent = muted ? '🔇 Muted' : '🔊 Sound On'; }
    updateMute();
    muteBtn.addEventListener('click', () => { muted = !muted; localStorage.setItem('dashboardMuted', muted); updateMute(); });

    // Clear the Recent activity view (non-destructive: hides entries before now;
    // the audit log itself is untouched, full history stays in Audit Log).
    const clearActivityBtn = document.getElementById('clear-activity');
    if (clearActivityBtn) {
        clearActivityBtn.addEventListener('click', () => {
            localStorage.setItem('activityCutoffMs', String(Date.now()));
            mods.innerHTML = '<li class="muted">Activity cleared.</li>';
        });
    }

    // Audio
    const sBeep = new Audio('assets/sounds/beep-soft.mp3');
    const wBeep = new Audio('assets/sounds/beep-warning.mp3');
    const alarm = new Audio('assets/sounds/alarm.mp3');
    function play(a, vol=1) { if (muted) return; try { const x = a.cloneNode(); x.volume = vol; x.play().catch(()=>{}); } catch (_) {} }

    function chimes(n) {
        // Pattern: n short beeps spaced 300ms apart. For n=3 use the alarm sound for emphasis.
        const sound = n === 3 ? alarm : wBeep;
        for (let i = 0; i < n; i++) setTimeout(() => play(sound), i * 350);
    }

    function fmt(sec) {
        sec = Math.max(0, Math.floor(sec));
        const m = Math.floor(sec/60), s = sec%60;
        return String(m).padStart(2,'0')+':'+String(s).padStart(2,'0');
    }
    function fmtTime(t) {
        if (!t) return '—';
        const d = new Date(t.replace(' ', 'T'));
        if (isNaN(d)) return t;
        return d.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit',second:'2-digit'});
    }
    function escapeHtml(s) {
        return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
    }
    function statusBadge(s) {
        const map = {'waiting':'badge-waiting','checked_in':'badge-checked-in','in_progress':'badge-in-progress','submitted':'badge-submitted','timeout':'badge-timeout'};
        return `<span class="badge ${map[s] || 'badge'}">${(s || '').toUpperCase().replace('_',' ')}</span>`;
    }
    function describeEvent(evt, msg) {
        let ctx = {}; try { ctx = JSON.parse(msg).ctx || {}; } catch (_) {}
        switch (evt) {
            case 'grade_save':          return `Saved attempt #${ctx.attempt_id||'?'} (${ctx.percentage||'?'}%, ${ctx.grade||'?'})`;
            case 'grade_undo':          return `↩ Undid attempt #${ctx.attempt_id||'?'}`;
            case 'attempt_edit':        return `✎ Edited attempt #${ctx.attempt_id||'?'} (${ctx.percentage||'?'}%)`;
            case 'attempt_soft_delete': return `🗑 Deleted attempt #${ctx.id||'?'}`;
            case 'attempt_restore':     return `↺ Restored attempt #${ctx.id||'?'}`;
            case 'station_check_in':    return `→ Check-in candidate #${ctx.candidate_id||'?'} (${ctx.duration_min||'?'}m)`;
            default: return evt;
        }
    }

    // ============ Centralised round controls ============
    const elIdle    = document.getElementById('round-idle');
    const elRunning = document.getElementById('round-running');
    const elStatus  = document.getElementById('round-status');
    const elCount   = document.getElementById('round-countdown');
    const elMeta    = document.getElementById('round-meta');

    const csrf = () => document.querySelector('meta[name=csrf-token]').content;

    document.getElementById('start-round').addEventListener('click', async () => {
        const label    = document.getElementById('round-label').value.trim();
        const duration = parseInt(document.getElementById('round-duration').value, 10) || 8;
        const warn     = parseInt(document.getElementById('round-warn').value, 10) || 2;
        try {
            await OSCE.fetchJSON('bell_control.php', { method:'POST', body:{ action:'start', label, duration_minutes: duration, warn_minutes: warn } });
        } catch (e) { alert('Could not start round: ' + e.message); }
    });
    document.getElementById('end-round').addEventListener('click', async () => {
        if (!confirm('End the round now and ring the end bell?')) return;
        try { await OSCE.fetchJSON('bell_control.php', { method:'POST', body:{ action:'end' } }); }
        catch (e) { alert('Could not end round: ' + e.message); }
    });
    document.getElementById('abort-round').addEventListener('click', async () => {
        if (!confirm('Abort the round silently (no bell)?')) return;
        try { await OSCE.fetchJSON('bell_control.php', { method:'POST', body:{ action:'abort' } }); }
        catch (e) { alert('Could not abort: ' + e.message); }
    });
    document.querySelectorAll('.manual-bell').forEach(b => {
        b.addEventListener('click', async () => {
            try { await OSCE.fetchJSON('bell_control.php', { method:'POST', body:{ action:'manual_bell', pattern: b.dataset.pattern } }); }
            catch (e) { alert('Bell failed: ' + e.message); }
        });
    });

    // Round ticking (interpolate between polls)
    let serverOffset = 0, endTsMs = 0, lastWarn2 = false, lastWarn1 = false;

    function updateRoundDisplay(state) {
        if (state.running) {
            elIdle.style.display = 'none';
            elRunning.style.display = 'block';
            elStatus.textContent = 'RUNNING';
            elStatus.style.background = '#16a34a'; elStatus.style.color = '#fff';

            // Server time correction
            serverOffset = Date.now() - state.server_now_ms;
            endTsMs      = state.end_ts_ms;
            elMeta.textContent = `${state.label || 'Round'} · ${state.duration_minutes} min · started ${fmtTime(state.started_at)}`;
        } else {
            elIdle.style.display = 'block';
            elRunning.style.display = 'none';
            elStatus.textContent = 'IDLE';
            elStatus.style.background = '#334155'; elStatus.style.color = '#cbd5e1';
            elCount.textContent = '00:00';
            elCount.classList.remove('warn-2','warn-1','done');
        }
    }

    function tickRound() {
        if (elRunning.style.display === 'none') return;
        const nowServer = Date.now() - serverOffset;
        const rem = Math.max(0, Math.floor((endTsMs - nowServer) / 1000));
        elCount.textContent = fmt(rem);
        elCount.classList.toggle('done',   rem === 0);
        elCount.classList.toggle('warn-1', rem > 0 && rem <= 60);
        elCount.classList.toggle('warn-2', rem > 60 && rem <= 120);
    }
    setInterval(tickRound, 250);

    // ============ Bell playback (server-driven) ============
    let lastBellId = parseInt(localStorage.getItem('osceLastBellId') || '0', 10);

    function handleBells(events) {
        for (const e of events) {
            if (e.id <= lastBellId) continue;
            lastBellId = e.id;
            switch (e.event_type) {
                case 'start':     case 'manual_1': chimes(1); break;
                case 'warn_2min': case 'warn_1min': case 'manual_2': chimes(2); break;
                case 'end':       case 'manual_3': chimes(3); break;
            }
        }
        localStorage.setItem('osceLastBellId', String(lastBellId));
    }

    // ============ Live sessions table (per-session client-side ticking) ============
    // We keep one in-memory map of sessions, recomputed each tick from authoritative
    // end_ts received once per poll.
    let sessionsCache = []; // [{id, ..., endTsMs}]
    const localBeeped = { warn2: {}, warn1: {}, ended: {} };

    function renderSessions() {
        if (!sessionsCache.length) {
            body.innerHTML = '<tr><td colspan="9" class="muted">No live sessions.</td></tr>';
            return;
        }
        const serverNow = Date.now() - serverOffset;
        body.innerHTML = sessionsCache.map(r => {
            let timer = '—';
            if ((r.status === 'checked_in' || r.status === 'in_progress') && r.endTsMs > 0) {
                const rem = Math.max(0, Math.floor((r.endTsMs - serverNow) / 1000));
                timer = fmt(rem);
                const mins = Math.floor(rem/60);
                if (mins <= 2 && !localBeeped.warn2[r.id]) { localBeeped.warn2[r.id] = true; play(sBeep, 0.5); }
                if (mins <= 1 && !localBeeped.warn1[r.id]) { localBeeped.warn1[r.id] = true; play(wBeep, 0.5); }
                if (rem  <= 0 && !localBeeped.ended[r.id]) { localBeeped.ended[r.id] = true; play(alarm, 0.5); }
            }
            return `<tr>
                <td data-label="Candidate"><strong>${escapeHtml(r.candidate_number)}</strong><br><small class="muted">${escapeHtml(r.candidate_name)}</small></td>
                <td data-label="Station">${escapeHtml((r.station_name||'').toUpperCase())}</td>
                <td data-label="Examiner">${escapeHtml(r.examiner_name||'-')}</td>
                <td data-label="Status">${statusBadge(r.status)}</td>
                <td data-label="Timer" class="num">${timer}</td>
                <td data-label="Check-in">${fmtTime(r.start_time)}</td>
                <td data-label="Grading">${fmtTime(r.grading_started_at)}</td>
                <td data-label="Submitted">${fmtTime(r.submitted_at)}</td>
                <td data-label="Last Action">${escapeHtml(r.last_action||'')}</td>
            </tr>`;
        }).join('');
    }
    setInterval(renderSessions, 1000);

    // ============ Polling (decoupled so one failure doesn't kill the other) ============
    let lastDashErr = '', lastBellErr = '';

    async function pollDash() {
        try {
            const dash = await OSCE.fetchJSON('live_dashboard_data.php');
            if (dash.server_time) {
                const sNow = new Date(dash.server_time).getTime();
                if (!isNaN(sNow)) serverOffset = Date.now() - sNow;
            }
            sessionsCache = (dash.rows || []).map(r => {
                let endTsMs = 0;
                if (r.start_time && r.duration_minutes > 0) {
                    const s = new Date(r.start_time.replace(' ', 'T')).getTime();
                    if (!isNaN(s)) endTsMs = s + r.duration_minutes * 60 * 1000;
                }
                return Object.assign({}, r, { endTsMs });
            });
            renderSessions();

            const cutoff = parseInt(localStorage.getItem('activityCutoffMs') || '0', 10);
            const events = (dash.mods || []).filter(e => {
                if (!cutoff) return true;
                const t = new Date(String(e.created_at || '').replace(' ', 'T')).getTime();
                return isNaN(t) ? true : t >= cutoff;
            });
            mods.innerHTML = events.length
                ? events.map(e => `<li class="mod-item evt-${escapeHtml(e.event_type)}">
                        ${escapeHtml(describeEvent(e.event_type, e.message))}
                        <span class="when">${fmtTime(e.created_at)} · ${escapeHtml(e.user_name || ('user #' + (e.user_id||'?')))}</span>
                    </li>`).join('')
                : '<li class="muted">No recent activity.</li>';
            lastDashErr = '';
        } catch (e) {
            lastDashErr = e.message || 'fetch error';
            console.error('pollDash:', e);
        }
    }

    async function pollBell() {
        try {
            const bell = await OSCE.fetchJSON('bell_state.php?since=' + lastBellId);
            updateRoundDisplay(bell.round || {running:false});
            handleBells(bell.bells || []);
            lastBellErr = '';
        } catch (e) {
            lastBellErr = e.message || 'fetch error';
            console.error('pollBell:', e);
        }
    }

    function refreshStamp() {
        const now = new Date().toLocaleTimeString();
        if (!lastDashErr && !lastBellErr) {
            stamp.textContent = 'Updated ' + now;
            stamp.style.color = '';
        } else {
            const msgs = [];
            if (lastDashErr) msgs.push('sessions: ' + lastDashErr);
            if (lastBellErr) msgs.push('bells: '    + lastBellErr);
            stamp.textContent = 'Partial update at ' + now + ' (' + msgs.join('; ') + ')';
            stamp.style.color = 'var(--c-danger)';
        }
    }

    async function pollAll() {
        await Promise.allSettled([pollDash(), pollBell()]);
        refreshStamp();
    }
    pollAll();
    setInterval(pollAll, 3000);
})();
</script>

<?php require __DIR__ . '/footer.php'; ?>
