"""JavaScript for dashboard, setup wizard, and WebAuthn auth."""

DASHBOARD_JS = """
<script>
(function () {
    'use strict';

    window.doAction = function (endpoint) {
        fetch(endpoint, { method: 'POST' }).then(function () {
            window.location.reload();
        });
    };

    /* ── Element refs ─────────────────────────────────────────── */
    var summaryEl    = document.getElementById('orch-summary');
    var subagentsEl  = document.getElementById('orch-subagents');
    var jobsEl       = document.getElementById('orch-jobs');
    var eventsEl     = document.getElementById('orch-events');
    var evCountEl    = document.getElementById('orch-events-count');
    var evPagEl      = document.getElementById('orch-events-pagination');
    var evInfoEl     = document.getElementById('orch-events-info');
    var evPrevEl     = document.getElementById('orch-events-prev');
    var evNextEl     = document.getElementById('orch-events-next');

    var schedulesEl  = document.getElementById('orch-schedules');
    var schedulesCountEl = document.getElementById('orch-schedules-count');

    if (!summaryEl || !jobsEl || !eventsEl) return;

    /* ── State ────────────────────────────────────────────────── */
    var eventsState = { cursor: null, next: null, prev: null };
    var agentFilter = null; /* null = all */
    var selectedJobId = null;
    var selectedSchedId = null;
    var jobLogAutoTail = true;

    /* ── Helpers ──────────────────────────────────────────────── */
    function esc(v) {
        var d = document.createElement('div');
        d.textContent = String(v == null ? '' : v);
        return d.innerHTML;
    }

    function timeAgo(isoOrEpoch) {
        var ts;
        if (typeof isoOrEpoch === 'number') {
            ts = isoOrEpoch * 1000;
        } else {
            ts = new Date(String(isoOrEpoch || '')).getTime();
        }
        if (!ts || isNaN(ts)) return '—';
        var diff = Math.max(0, Math.floor((Date.now() - ts) / 1000));
        if (diff < 60)   return diff + 's ago';
        if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
        if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
        return Math.floor(diff / 86400) + 'd ago';
    }

    function shortTime(isoOrEpoch) {
        var ts;
        if (typeof isoOrEpoch === 'number') {
            ts = new Date(isoOrEpoch * 1000);
        } else {
            ts = new Date(String(isoOrEpoch || ''));
        }
        if (!ts.getTime() || isNaN(ts.getTime())) return '';
        var h = ts.getHours(), m = ts.getMinutes();
        return (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
    }

    function trimText(v, maxLen) {
        var text = String(v == null ? '' : v);
        if (text.length <= maxLen) return text;
        return text.slice(0, Math.max(0, maxLen - 1)) + '…';
    }

    function statusClass(s) {
        s = String(s || '').toLowerCase();
        if (s === 'in_progress' || s === 'delivered') return 'running';
        if (s === 'reworking') return 'reworking';
        if (s === 'ready') return 'ready';
        if (s === 'failed') return 'failed';
        return 'done';
    }

    function statusLabel(s) {
        s = String(s || '').toLowerCase();
        if (s === 'in_progress') return 'running';
        if (s === 'delivered') return 'running';
        if (s === 'reworking') return 'reworking';
        return s || 'unknown';
    }

    function dotClass(eventType) {
        var t = String(eventType || '').toLowerCase();
        if (t.indexOf('rework') >= 0) return 'dot-reworking';
        if (t.indexOf('fail') >= 0 || t.indexOf('error') >= 0) return 'dot-failed';
        if (t.indexOf('start') >= 0 || t.indexOf('progress') >= 0 || t.indexOf('pick') >= 0)
            return 'dot-active';
        if (t.indexOf('done') >= 0 || t.indexOf('complete') >= 0) return 'dot-done';
        return '';
    }

    function eventSentence(ev) {
        var parts = [];
        var job = ev.job_id || ev.task_id || '';
        var event = ev.event || ev.type || 'event';
        var status = ev.status || '';
        var assignee = ev.assignee || ev.subagent || '';

        parts.push('<strong>' + esc(event) + '</strong>');
        if (job) parts.push('<span class="ev-job">' + esc(job) + '</span>');
        if (assignee) parts.push('→ ' + esc(assignee));
        if (status && status !== event) parts.push('(' + esc(status) + ')');

        return parts.join(' ');
    }

    /* ── Render: Summary Strip ────────────────────────────────── */
    function renderSummary(payload) {
        var s = payload.summary || {};
        var stats = [
            { count: s.running || 0, label: 'active', cls: 'running' },
            { count: s.reworking || 0, label: 'reworking', cls: 'reworking' },
            { count: s.ready || 0, label: 'queued', cls: 'ready' },
            { count: s.failed || 0, label: 'failed', cls: 'failed' },
            { count: s.done_recent || 0, label: 'done', cls: 'done' },
        ];

        var html = '';
        for (var i = 0; i < stats.length; i++) {
            if (i > 0) html += '<span class="orch-summary-sep"></span>';
            var st = stats[i];
            html += '<span class="orch-summary-stat" style="color: var(--status-' + st.cls + ');">'
                  + '<span class="stat-count">' + esc(st.count) + '</span>'
                  + '<span class="stat-label">' + esc(st.label) + '</span>'
                  + '</span>';
        }
        html += '<span class="orch-summary-updated">' + timeAgo(payload.updated_at) + '</span>';
        summaryEl.innerHTML = html;

        /* Subagent pills */
        var agents = payload.subagents || [];
        var pillsHtml = '<button class="orch-agent-pill' + (agentFilter === null ? ' active' : '')
                       + '" data-agent="">'
                       + '<span class="pill-dot" style="background:var(--text-muted)"></span>'
                       + 'All'
                       + '</button>';
        for (var j = 0; j < agents.length; j++) {
            var a = agents[j];
            var isActive = agentFilter === a.name;
            var running = a.running || 0;
            var pillColor = running > 0 ? 'var(--status-running)'
                          : (a.failed || 0) > 0 ? 'var(--status-failed)'
                          : 'var(--text-muted)';
            pillsHtml += '<button class="orch-agent-pill' + (isActive ? ' active' : '')
                       + '" data-agent="' + esc(a.name) + '">'
                       + '<span class="pill-dot" style="background:' + pillColor + '"></span>'
                       + esc(a.name);
            if (running > 0) {
                pillsHtml += ' <span class="orch-agent-count">' + running + '</span>';
            }
            pillsHtml += '</button>';
        }
        subagentsEl.innerHTML = pillsHtml;

        /* Bind pill clicks */
        subagentsEl.querySelectorAll('.orch-agent-pill').forEach(function (btn) {
            btn.addEventListener('click', function () {
                var val = btn.getAttribute('data-agent');
                agentFilter = val || null;
                subagentsEl.querySelectorAll('.orch-agent-pill').forEach(function (b) {
                    b.classList.remove('active');
                });
                btn.classList.add('active');
                loadJobs();
            });
        });
    }

    /* ── Render: Grouped Jobs ─────────────────────────────────── */
    function renderJobs(payload) {
        var rows = payload.rows || [];

        /* Split into groups */
        var active = [], failed = [], done = [];
        for (var i = 0; i < rows.length; i++) {
            var r = rows[i];
            var sc = statusClass(r.status);
            if (sc === 'running' || sc === 'ready' || sc === 'reworking') active.push(r);
            else if (sc === 'failed') failed.push(r);
            else done.push(r);
        }

        if (rows.length === 0) {
            jobsEl.innerHTML = '<div class="orch-section"><div class="orch-jobs-empty">'
                             + 'No jobs to display.</div></div>';
            return;
        }

        var html = '';
        html += renderJobGroup('Active', active, false);
        html += renderJobGroup('Failed', failed, false);
        html += renderJobGroup('Completed', done, done.length > 5);

        /* Pagination at the bottom if applicable */
        var paginationHtml = '';
        if (payload.total > (payload.rows || []).length
            || payload.prev_cursor != null || payload.next_cursor != null) {
            paginationHtml = '<div class="orch-pagination">'
                + '<span>' + rows.length + ' of ' + (payload.total || rows.length) + '</span>'
                + '<div class="orch-pagination-btns">'
                + '<button class="btn btn-sm" id="orch-jobs-prev"'
                + (payload.prev_cursor == null ? ' disabled' : '') + '>← Prev</button>'
                + '<button class="btn btn-sm" id="orch-jobs-next"'
                + (payload.next_cursor == null ? ' disabled' : '') + '>Next →</button>'
                + '</div></div>';
        }
        html += paginationHtml;
        jobsEl.innerHTML = html;

        /* Wire section toggles */
        jobsEl.querySelectorAll('.orch-section-header').forEach(function (hdr) {
            hdr.addEventListener('click', function () {
                hdr.parentElement.classList.toggle('collapsed');
            });
        });

        /* Wire job row clicks */
        jobsEl.querySelectorAll('.orch-job-row[data-job-id]').forEach(function (row) {
            row.addEventListener('click', function () {
                var jobId = row.getAttribute('data-job-id');
                openJobLog(jobId);
            });
        });

        /* Wire pagination */
        var prevBtn = document.getElementById('orch-jobs-prev');
        var nextBtn = document.getElementById('orch-jobs-next');
        if (prevBtn) prevBtn.addEventListener('click', function () {
            if (payload.prev_cursor != null) { jobsCursor = payload.prev_cursor; loadJobs(); }
        });
        if (nextBtn) nextBtn.addEventListener('click', function () {
            if (payload.next_cursor != null) { jobsCursor = payload.next_cursor; loadJobs(); }
        });

        /* Restore expanded detail after DOM rebuild */
        if (selectedJobId) {
            var detail = document.getElementById('orch-detail-' + selectedJobId);
            if (detail) {
                detail.hidden = false;
                loadJobLog();
            }
        }
    }

    function renderJobGroup(title, items, collapsed) {
        if (items.length === 0) return '';
        var cls = collapsed ? ' collapsed' : '';
        var html = '<div class="orch-section' + cls + '">'
            + '<div class="orch-section-header">'
            + '<span class="orch-section-title">' + esc(title)
            + ' <span class="section-count">' + items.length + '</span></span>'
            + '<span class="orch-section-chevron">▾</span>'
            + '</div>'
            + '<div class="orch-section-body">';

        for (var i = 0; i < items.length; i++) {
            html += renderJobRow(items[i]);
        }

        html += '</div></div>';
        return html;
    }

    function renderJobRow(row) {
        var sc = statusClass(row.status);
        var indent = (row.depth || 0) * 20;
        var title = row.title || row.description || row.id || '—';
        var assignee = row.assignee || 'main';
        var jobId = row.id || '';
        var isExpanded = selectedJobId === jobId;

        var html = '<div class="orch-job-row' + (isExpanded ? ' expanded' : '')
            + '" data-job-id="' + esc(jobId) + '" style="padding-left:'
            + (20 + indent) + 'px">'
            + '<div class="orch-job-status">'
            + '<span class="status-pill status-pill--' + sc + '">'
            + '<span class="pill-dot"></span>' + esc(statusLabel(row.status))
            + '</span></div>'
            + '<div class="orch-job-body">'
            + '<div class="orch-job-title">' + esc(title)
            + '<span class="job-id">' + esc(jobId) + '</span>'
            + '</div>'
            + '<div class="orch-job-meta">';

        html += '<span>' + esc(assignee) + '</span>';
        if (row.attempts != null && row.attempts > 0) {
            var attemptText = 'attempt ' + row.attempts;
            if (row.maxAttempts) attemptText += '/' + row.maxAttempts;
            html += '<span>' + esc(attemptText) + '</span>';
        }
        if (row.children && row.children.length) {
            html += '<span>' + row.children.length + ' children</span>';
        }
        if (row.blockedBy && row.blockedBy.length) {
            html += '<span>blocked by ' + row.blockedBy.join(', ') + '</span>';
        }
        if (row.updatedAt) {
            html += '<span>' + timeAgo(row.updatedAt) + '</span>';
        }

        html += '</div>';

        var st = String(row.status || '').toLowerCase();
        if (row.error && (st === 'failed' || st === 'cancelled')) {
            html += '<div class="orch-job-error">⚠ '
                  + esc(String(row.error).slice(0, 200)) + '</div>';
        }

        html += '</div></div>';

        /* Expandable detail container (populated when opened) */
        html += '<div class="orch-job-detail" id="orch-detail-' + esc(jobId) + '"'
              + (isExpanded ? '' : ' hidden') + '></div>';

        return html;
    }

    function collapseJobDetail(jobId) {
        var detail = document.getElementById('orch-detail-' + jobId);
        if (detail) detail.hidden = true;
        var row = jobsEl.querySelector('.orch-job-row[data-job-id="' + jobId + '"]');
        if (row) row.classList.remove('expanded');
    }

    function expandJobDetail(jobId) {
        var detail = document.getElementById('orch-detail-' + jobId);
        if (detail) detail.hidden = false;
        var row = jobsEl.querySelector('.orch-job-row[data-job-id="' + jobId + '"]');
        if (row) row.classList.add('expanded');
    }

    function openJobLog(jobId) {
        if (!jobId) return;
        /* Accordion: collapse previous if different */
        if (selectedJobId && selectedJobId !== jobId) {
            collapseJobDetail(selectedJobId);
        }
        /* Toggle: clicking the same row collapses it */
        if (selectedJobId === jobId) {
            collapseJobDetail(jobId);
            selectedJobId = null;
            return;
        }
        selectedJobId = jobId;
        expandJobDetail(jobId);
        loadJobLog();
        /* Scroll the row into view */
        var row = jobsEl.querySelector('.orch-job-row[data-job-id="' + jobId + '"]');
        if (row) row.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }

    function renderJobLog(payload) {
        if (!selectedJobId) return;
        var detail = document.getElementById('orch-detail-' + selectedJobId);
        if (!detail) return;

        var job = payload.job || {};
        var meta = '';
        if (job.assignee) meta += esc(job.assignee);
        if (job.status) meta += (meta ? ' · ' : '') + esc(String(job.status));
        if (job.attempts != null) {
            meta += (meta ? ' · ' : '') + 'Attempt ' + esc(String(job.attempts));
            if (job.maxAttempts) meta += '/' + esc(String(job.maxAttempts));
        }
        if (job.updatedAt) meta += (meta ? ' · ' : '') + 'Updated ' + timeAgo(job.updatedAt);

        var errText = job.error || '';
        if (!errText && String(job.status || '').toLowerCase() === 'failed') {
            errText = 'No failure details were captured for this failure.';
        }
        var fbText = job.feedback || '';

        var lines = payload.lines || [];
        var logText = '';
        if (!lines.length) {
            logText = 'No live session logs yet.';
        } else {
            for (var i = 0; i < lines.length; i++) {
                var line = lines[i];
                var ts = shortTime(line.ts || line.timestamp || '');
                var src = line.source || 'log';
                var evt = line.event || line.message || 'log';
                var msg = line.messageText || line.text || '';
                logText += ('[' + ts + '] ' + src + '/' + evt + ': ' + trimText(msg, 320)) + '\\n';
            }
            logText = logText.trim();
        }

        var html = '<div class="orch-job-detail-inner">'
            + '<div class="orch-job-detail-meta">' + meta + '</div>';
        if (errText) {
            html += '<div class="orch-job-detail-error">Why: ' + esc(errText) + '</div>';
        }
        if (fbText) {
            html += '<div class="orch-job-detail-feedback">Feedback: ' + esc(fbText) + '</div>';
        }
        var st = String(job.status || '').toLowerCase();
        var isActive = (st === 'in_progress' || st === 'ready'
            || st === 'delivered' || st === 'reworking');
        var cancelHtml = '';
        if (isActive) {
            cancelHtml = '<button class="btn btn-sm btn-danger orch-job-cancel" '
                + 'data-job-id="' + esc(selectedJobId) + '">Cancel Routine</button>';
        }

        html += '<pre class="orch-job-detail-text">' + esc(logText) + '</pre>'
            + '<div class="orch-job-detail-controls">'
            + '<label><input type="checkbox" class="orch-detail-autotail"'
            + (jobLogAutoTail ? ' checked' : '') + '> Tail</label>'
            + cancelHtml
            + '</div></div>';

        detail.innerHTML = html;

        /* Scroll log to bottom */
        var logEl = detail.querySelector('.orch-job-detail-text');
        if (logEl) logEl.scrollTop = logEl.scrollHeight;

        /* Bind autotail checkbox */
        var tailCb = detail.querySelector('.orch-detail-autotail');
        if (tailCb) {
            tailCb.addEventListener('change', function () {
                jobLogAutoTail = tailCb.checked;
            });
        }

        /* Bind cancel routine button */
        var cancelBtn = detail.querySelector('.orch-job-cancel');
        if (cancelBtn) {
            cancelBtn.addEventListener('click', function (e) {
                e.stopPropagation();
                cancelBtn.disabled = true;
                cancelBtn.textContent = 'Cancelling\u2026';
                fetch('/orchestration/jobs/'
                    + encodeURIComponent(cancelBtn.getAttribute('data-job-id'))
                    + '/cancel', { method: 'POST' })
                .then(function (res) {
                    cancelBtn.textContent = res.ok ? 'Cancelled' : 'Failed';
                    setTimeout(function () { loadJobs(); }, 1500);
                }).catch(function () {
                    cancelBtn.textContent = 'Failed';
                    cancelBtn.disabled = false;
                });
            });
        }
    }

    async function loadJobLog() {
        if (!selectedJobId) return;
        var detail = document.getElementById('orch-detail-' + selectedJobId);
        if (!detail || detail.hidden) return;
        try {
            var q = new URLSearchParams();
            q.set('limit', '300');
            var url = '/orchestration/jobs/' + encodeURIComponent(selectedJobId) + '/session?'
                    + q.toString();
            var res = await fetch(url);
            if (!res.ok) {
                detail.innerHTML = '<div class="orch-job-detail-inner">'
                    + '<pre class="orch-job-detail-text">Failed to load session logs.</pre></div>';
                return;
            }
            renderJobLog(await res.json());
        } catch (e) {
            detail.innerHTML = '<div class="orch-job-detail-inner">'
                + '<pre class="orch-job-detail-text">Failed to load session logs.</pre></div>';
        }
    }

    /* ── Render: Events Timeline ──────────────────────────────── */
    function renderEvents(payload) {
        eventsState.next = payload.next_cursor;
        eventsState.prev = payload.prev_cursor;

        var events = payload.events || [];

        if (evCountEl) {
            evCountEl.textContent = events.length + ' of ' + (payload.total || events.length);
        }

        if (events.length === 0) {
            eventsEl.innerHTML = '<div class="orch-jobs-empty">No events recorded.</div>';
        } else {
            /* Show newest first */
            var reversed = events.slice().reverse();
            var html = '';
            for (var i = 0; i < reversed.length; i++) {
                var ev = reversed[i];
                var dc = dotClass(ev.event || ev.type || '');
                var ts = ev.ts || ev.timestamp || '';

                html += '<div class="orch-event">'
                    + '<div class="orch-event-time">' + esc(shortTime(ts)) + '</div>'
                    + '<div class="orch-event-dot-col">'
                    + '<div class="orch-event-dot ' + dc + '"></div>'
                    + '</div>'
                    + '<div class="orch-event-text">' + eventSentence(ev) + '</div>'
                    + '</div>';
            }
            eventsEl.innerHTML = html;
        }

        /* Pagination */
        var showPag = payload.prev_cursor != null || payload.next_cursor != null;
        evPagEl.style.display = showPag ? 'flex' : 'none';
        if (evInfoEl) {
            evInfoEl.textContent = events.length + ' events';
        }
        evPrevEl.disabled = payload.prev_cursor == null;
        evNextEl.disabled = payload.next_cursor == null;
    }

    /* ── Data loading ─────────────────────────────────────────── */
    var jobsCursor = 0;

    async function loadSummary() {
        try {
            var res = await fetch('/orchestration/summary');
            if (!res.ok) return;
            renderSummary(await res.json());
        } catch (e) { /* noop */ }
    }

    async function loadJobs() {
        try {
            var q = new URLSearchParams();
            q.set('scope', 'all');
            q.set('limit', '100');
            q.set('cursor', String(jobsCursor || 0));
            if (agentFilter) q.set('assignee', agentFilter);
            var res = await fetch('/orchestration/jobs?' + q.toString());
            if (!res.ok) return;
            renderJobs(await res.json());
        } catch (e) { /* noop */ }
    }

    async function loadEvents() {
        try {
            var q = new URLSearchParams();
            q.set('limit', '200');
            if (eventsState.cursor != null) {
                q.set('cursor', String(eventsState.cursor));
            }
            var res = await fetch('/orchestration/events?' + q.toString());
            if (!res.ok) return;
            renderEvents(await res.json());
        } catch (e) { /* noop */ }
    }

    var schedHeader = document.querySelector('#orch-schedules-section > .orch-section-header');
    if (schedHeader) {
        schedHeader.addEventListener('click', function () {
            schedHeader.parentElement.classList.toggle('collapsed');
        });
    }

    evPrevEl.addEventListener('click', function () {
        if (eventsState.prev == null) return;
        eventsState.cursor = eventsState.prev;
        loadEvents();
    });

    evNextEl.addEventListener('click', function () {
        if (eventsState.next == null) return;
        eventsState.cursor = eventsState.next;
        loadEvents();
    });

    /* ── Render: Schedules & Reminders ───────────────────────── */
    function renderSchedules(items) {
        if (!schedulesEl) return;
        if (schedulesCountEl) schedulesCountEl.textContent = items.length;
        if (items.length === 0) {
            schedulesEl.innerHTML = '<div class="orch-jobs-empty">No schedules or reminders.</div>';
            return;
        }
        var html = '';
        for (var i = 0; i < items.length; i++) {
            var s = items[i];
            var typeCls = s.type === 'cron' ? 'running' : 'ready';
            var typeLabel = s.type === 'cron' ? 'cron' : 'one-shot';
            var nextRun = s.next_run ? timeAgo(s.next_run) : '—';
            var nextRunFull = s.next_run || '';
            var isExpanded = selectedSchedId === s.id;
            html += '<div class="orch-job-row' + (isExpanded ? ' sched-expanded' : '')
                + '" data-sched-id="' + esc(s.id) + '" style="padding-left:20px">'
                + '<div class="orch-job-status">'
                + '<span class="status-pill status-pill--' + typeCls + '">'
                + '<span class="pill-dot"></span>' + esc(typeLabel)
                + '</span></div>'
                + '<div class="orch-job-body">'
                + '<div class="orch-job-title">' + esc(s.title || s.id)
                + '<span class="job-id">' + esc(s.id) + '</span>'
                + '</div>'
                + '<div class="orch-job-meta">'
                + '<span title="' + esc(nextRunFull) + '">next: ' + esc(nextRun) + '</span>';
            if (s.repeat) {
                html += '<span>' + esc(s.repeat) + '</span>';
            }
            html += '</div></div></div>';
            /* Expandable detail for full prompt */
            var actionsHtml = '<div class="orch-detail-actions">';
            actionsHtml += '<button class="btn btn-sm btn-primary orch-sched-run" '
                + 'data-id="' + esc(s.id) + '" data-skip="false">Run Now</button>';
            if (s.type === 'cron') {
                actionsHtml += '<button class="btn btn-sm orch-sched-run" '
                    + 'data-id="' + esc(s.id) + '" data-skip="true">Run Now & Skip Next</button>';
            }
            actionsHtml += '</div>';
            html += '<div class="orch-sched-detail" id="orch-sched-' + esc(s.id) + '"'
                + (isExpanded ? '' : ' hidden') + '>'
                + '<div class="orch-sched-detail-inner">'
                + '<pre class="orch-sched-detail-text">' + esc(s.prompt || s.title || '') + '</pre>'
                + actionsHtml
                + '</div></div>';
        }
        schedulesEl.innerHTML = html;

        /* Wire schedule row clicks */
        schedulesEl.querySelectorAll('.orch-job-row[data-sched-id]').forEach(function (row) {
            row.addEventListener('click', function () {
                var schedId = row.getAttribute('data-sched-id');
                if (!schedId) return;
                /* Collapse previous if different */
                if (selectedSchedId && selectedSchedId !== schedId) {
                    var prevDetail = document.getElementById('orch-sched-' + selectedSchedId);
                    if (prevDetail) prevDetail.hidden = true;
                    var prevRow = schedulesEl.querySelector(
                        '.orch-job-row[data-sched-id="' + selectedSchedId + '"]');
                    if (prevRow) prevRow.classList.remove('sched-expanded');
                }
                /* Toggle current */
                if (selectedSchedId === schedId) {
                    var detail = document.getElementById('orch-sched-' + schedId);
                    if (detail) detail.hidden = true;
                    row.classList.remove('sched-expanded');
                    selectedSchedId = null;
                } else {
                    var detail = document.getElementById('orch-sched-' + schedId);
                    if (detail) detail.hidden = false;
                    row.classList.add('sched-expanded');
                    selectedSchedId = schedId;
                }
            });
        });

        /* Wire Run Now buttons */
        schedulesEl.querySelectorAll('.orch-sched-run').forEach(function (btn) {
            btn.addEventListener('click', function (e) {
                e.stopPropagation();
                var id = btn.getAttribute('data-id');
                var skip = btn.getAttribute('data-skip') === 'true';
                btn.disabled = true;
                btn.textContent = 'Triggering\u2026';
                fetch('/schedules/' + encodeURIComponent(id) + '/run', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ skip_next: skip })
                }).then(function (res) {
                    btn.textContent = res.ok ? 'Triggered' : 'Failed';
                    setTimeout(function () { loadSchedules(); }, 1500);
                }).catch(function () {
                    btn.textContent = 'Failed';
                    btn.disabled = false;
                });
            });
        });
    }

    async function loadSchedules() {
        if (!schedulesEl) return;
        try {
            var res = await fetch('/schedules/list');
            if (!res.ok) return;
            renderSchedules(await res.json());
        } catch (e) { /* noop */ }
    }

    /* ── Polling ──────────────────────────────────────────────── */
    function tick() {
        if (document.visibilityState === 'hidden') return;
        loadSummary();
        loadJobs();
        loadSchedules();
        /* Only auto-refresh events when viewing latest (no manual cursor) */
        if (eventsState.cursor == null) loadEvents();
        if (selectedJobId && jobLogAutoTail) {
            loadJobLog();
        }
    }
    tick();
    setInterval(tick, 4000);

    document.addEventListener('visibilitychange', function () {
        if (document.visibilityState === 'visible') tick();
    });
})();
</script>
"""

WIZARD_JS = """
<script>
(function () {
    var step = 1, total = 4;
    var oauthState = window.ottoWizardOAuthState || {};

    var oauthProviders = [
        { key: 'anthropic', display: 'Anthropic', prefixes: ['anthropic/', 'claude-code/'] },
        { key: 'openai', display: 'OpenAI', prefixes: ['openai/', 'codex/'] },
        { key: 'google', display: 'Google', prefixes: ['google/', 'gemini-cli/', 'vertex_ai/'] },
        { key: 'copilot', display: 'GitHub Copilot', prefixes: ['copilot/', 'github_copilot/'] }
    ];

    function detectOAuthProvider(model) {
        var normalized = (model || '').trim().toLowerCase();
        if (!normalized) return null;
        for (var i = 0; i < oauthProviders.length; i++) {
            var provider = oauthProviders[i];
            for (var j = 0; j < provider.prefixes.length; j++) {
                if (normalized.indexOf(provider.prefixes[j]) === 0) return provider;
            }
        }
        return null;
    }

    function updateAuthField() {
        var modelInput = document.getElementById('setup_model');
        var apiInput = document.getElementById('setup_api_key');
        var authMode = document.getElementById('setup_auth_mode');
        var label = document.getElementById('setup_api_key_label');
        var hint = document.getElementById('setup_api_key_hint');
        if (!modelInput || !apiInput || !authMode || !label || !hint) return;

        var provider = detectOAuthProvider(modelInput.value);
        if (!provider) {
            authMode.value = 'api_key';
            label.innerHTML = 'API key <span class="hint" style="display:inline; text-transform: none; font-weight: normal;">(optional)</span>';
            apiInput.placeholder = 'sk-... or leave blank to use env var';
            apiInput.disabled = false;
            hint.textContent = 'Used for API-key providers.';
            return;
        }

        authMode.value = 'oauth';
        apiInput.value = '';
        apiInput.disabled = true;

        if (oauthState[provider.key]) {
            label.textContent = provider.display + ' OAuth';
            apiInput.placeholder = 'Using saved OAuth credentials';
            hint.textContent = 'Stored OAuth credentials detected. Continue to use them.';
            return;
        }

        label.textContent = 'Authenticate with ' + provider.display;
        apiInput.placeholder = 'Run: otto auth login ' + provider.key;
        hint.textContent = 'OAuth credentials are required. Run: otto auth login ' + provider.key;
    }

    function show(n) {
        for (var i = 1; i <= total; i++) {
            var el = document.getElementById('sw-step-' + i);
            if (el) el.classList.toggle('active', i === n);
            var dot = document.getElementById('sw-fill-' + i);
            if (dot) dot.classList.toggle('active', i <= n);
        }
        document.getElementById('sw-back').hidden   = (n === 1);
        document.getElementById('sw-next').hidden   = (n === total);
        document.getElementById('sw-submit').hidden = (n !== total);
        if (n === total) buildReview();
    }

    function buildReview() {
        var model = (document.getElementById('setup_model') || {}).value || '\u2014';
        var tgSel = document.querySelector('input[name="_add_telegram"]:checked');
        var tg    = tgSel && tgSel.value === 'yes' ? 'Yes' : 'No';
        var name  = (document.getElementById('setup_owner_name') || {}).value || 'owner';
        var rows  = [['Model', model], ['Telegram', tg], ['Owner', name]];
        document.getElementById('sw-review').innerHTML = rows.map(function (r) {
            return '<div class="sw-review-row"><span class="sw-review-key">' + r[0] +
                   '</span><span class="sw-review-val">' + r[1] + '</span></div>';
        }).join('');
    }

    window.swNext = function () { if (step < total) { step++; show(step); } };
    window.swBack = function () { if (step > 1)     { step--; show(step); } };

    var modelInput = document.getElementById('setup_model');
    if (modelInput) {
        modelInput.addEventListener('input', updateAuthField);
        modelInput.addEventListener('change', updateAuthField);
    }
    updateAuthField();

    document.querySelectorAll('input[name="_add_telegram"]').forEach(function (r) {
        r.addEventListener('change', function () {
            var add = r.value === 'yes' && r.checked;
            var tgf = document.getElementById('sw-tg-fields');
            if (tgf) tgf.hidden = !add;
            var tw = document.getElementById('sw-tid-wrap');
            if (tw) tw.hidden = !add;
        });
    });
})();
</script>
"""

_AUTH_HELPERS_JS = """
    function base64urlToBuffer(b64url) {
        var b64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
        while (b64.length % 4) b64 += '=';
        var raw = atob(b64);
        var arr = new Uint8Array(raw.length);
        for (var i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
        return arr.buffer;
    }
    function bufferToBase64url(buf) {
        var arr = new Uint8Array(buf);
        var raw = '';
        for (var i = 0; i < arr.length; i++) raw += String.fromCharCode(arr[i]);
        return btoa(raw).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');
    }
    function showError(msg) {
        var el = document.getElementById('errorMsg');
        el.textContent = msg; el.style.display = 'block';
    }
"""

AUTH_LOGIN_JS = (
    """
<script>
(function () {
    'use strict';
"""
    + _AUTH_HELPERS_JS
    + """
    var btn = document.getElementById('loginBtn');
    if (!btn) return;
    btn.addEventListener('click', async function () {
        btn.disabled = true;
        try {
            var optRes = await fetch('/auth/login/begin', { method: 'POST' });
            if (!optRes.ok) { showError('Failed to get authentication options.'); btn.disabled = false; return; }
            var options = await optRes.json();
            if (options.challenge) options.challenge = base64urlToBuffer(options.challenge);
            if (options.allowCredentials) {
                options.allowCredentials = options.allowCredentials.map(function (c) {
                    return Object.assign({}, c, { id: base64urlToBuffer(c.id) });
                });
            }
            var credential = await navigator.credentials.get({ publicKey: options });
            var body = JSON.stringify({
                id: credential.id,
                rawId: bufferToBase64url(credential.rawId),
                type: credential.type,
                response: {
                    authenticatorData: bufferToBase64url(credential.response.authenticatorData),
                    clientDataJSON: bufferToBase64url(credential.response.clientDataJSON),
                    signature: bufferToBase64url(credential.response.signature),
                    userHandle: credential.response.userHandle
                        ? bufferToBase64url(credential.response.userHandle) : null
                }
            });
            var verRes = await fetch('/auth/login/complete', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: body
            });
            if (verRes.ok) { window.location.href = '/'; }
            else { showError('Authentication failed. Please try again.'); }
        } catch (e) {
            if (e.name === 'NotAllowedError') showError('Authentication was cancelled.');
            else showError('Error: ' + e.message);
        }
        btn.disabled = false;
    });
})();
</script>
"""
)

AUTH_REGISTER_JS = (
    """
<script>
(function () {
    'use strict';
"""
    + _AUTH_HELPERS_JS
    + """
    var btn = document.getElementById('registerBtn');
    if (!btn) return;
    btn.addEventListener('click', async function () {
        btn.disabled = true;
        try {
            var optRes = await fetch('/auth/register/begin', { method: 'POST' });
            if (!optRes.ok) { showError('Failed to get registration options.'); btn.disabled = false; return; }
            var options = await optRes.json();
            if (options.challenge) options.challenge = base64urlToBuffer(options.challenge);
            if (options.user && options.user.id) options.user.id = base64urlToBuffer(options.user.id);
            if (options.excludeCredentials) {
                options.excludeCredentials = options.excludeCredentials.map(function (c) {
                    return Object.assign({}, c, { id: base64urlToBuffer(c.id) });
                });
            }
            var credential = await navigator.credentials.create({ publicKey: options });
            var body = JSON.stringify({
                id: credential.id,
                rawId: bufferToBase64url(credential.rawId),
                type: credential.type,
                response: {
                    attestationObject: bufferToBase64url(credential.response.attestationObject),
                    clientDataJSON: bufferToBase64url(credential.response.clientDataJSON)
                }
            });
            var verRes = await fetch('/auth/register/complete', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: body
            });
            if (verRes.ok) { window.location.href = '/'; }
            else { showError('Registration failed. Please try again.'); }
        } catch (e) {
            if (e.name === 'NotAllowedError') showError('Registration was cancelled.');
            else showError('Error: ' + e.message);
        }
        btn.disabled = false;
    });
})();
</script>
"""
)
