// ── Audit Trail & Record Locks (console viewers) ────────────────────────────
// Both screens READ what Misc → Advanced configured (the single source of truth)
// and what the built app populated. They never create tables. The endpoints return
// { configured, enabled, table } so we can guide the admin when nothing is set up.

// One self-contained stylesheet, injected once. Mirrors the compact look of the
// Row-Level Security / Field Encryption pages (theme-aware via Bootstrap vars).
function ensureAuditStyles() {
    if (document.getElementById('auditStyles')) return;
    const s = document.createElement('style');
    s.id = 'auditStyles';
    s.textContent = `
    .aud-wrap{max-width:100%}
    .aud-bar{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;padding:.6rem .75rem;border:1px solid var(--bs-border-color);border-radius:.6rem;background:var(--bs-tertiary-bg);margin-bottom:.75rem}
    .aud-bar .aud-title{font-weight:600;display:flex;align-items:center;gap:.4rem;margin-right:auto}
    .aud-bar .aud-title .badge{font-weight:600}
    .aud-filters{display:flex;gap:.4rem;flex-wrap:wrap;align-items:end;margin-bottom:.75rem}
    .aud-filters .fg{display:flex;flex-direction:column;gap:.15rem}
    .aud-filters label{font-size:.68rem;text-transform:uppercase;letter-spacing:.03em;color:var(--bs-secondary-color);font-weight:600}
    .aud-filters .form-select-sm,.aud-filters .form-control-sm{min-width:9rem}
    .aud-table{width:100%;border-collapse:separate;border-spacing:0;font-size:.82rem}
    .aud-table thead th{position:sticky;top:0;background:var(--bs-tertiary-bg);color:var(--bs-secondary-color);font-size:.68rem;text-transform:uppercase;letter-spacing:.03em;font-weight:600;padding:.45rem .6rem;border-bottom:1px solid var(--bs-border-color);text-align:left;white-space:nowrap}
    .aud-table tbody td{padding:.4rem .6rem;border-bottom:1px solid var(--bs-border-color);vertical-align:middle}
    .aud-table tbody tr:hover{background:var(--bs-tertiary-bg)}
    .aud-scroll{max-height:60vh;overflow:auto;border:1px solid var(--bs-border-color);border-radius:.6rem}
    .aud-act{display:inline-block;font-size:.7rem;font-weight:600;padding:.12rem .5rem;border-radius:1rem;text-transform:capitalize}
    .aud-act.g{background:rgba(25,135,84,.14);color:#198754}
    .aud-act.r{background:rgba(220,53,69,.14);color:#dc3545}
    .aud-act.b{background:rgba(13,110,253,.14);color:#0d6efd}
    .aud-act.p{background:rgba(111,66,193,.16);color:#6f42c1}
    .aud-act.m{background:var(--bs-secondary-bg);color:var(--bs-secondary-color)}
    .aud-target code{font-size:.76rem;background:var(--bs-secondary-bg);padding:.05rem .35rem;border-radius:.3rem}
    .aud-mono{font-variant-numeric:tabular-nums;color:var(--bs-secondary-color);white-space:nowrap}
    .aud-empty{padding:2.5rem 1rem;text-align:center;color:var(--bs-secondary-color)}
    .aud-setup{border:1px dashed var(--bs-border-color);border-radius:.75rem;padding:2rem 1.5rem;text-align:center;background:var(--bs-tertiary-bg)}
    .aud-setup i.big{font-size:2.2rem;color:var(--bs-secondary-color)}
    .aud-diff{font-size:.78rem}
    .aud-diff table{width:100%;border-collapse:collapse}
    .aud-diff th,.aud-diff td{padding:.3rem .5rem;border-bottom:1px solid var(--bs-border-color);text-align:left;vertical-align:top}
    .aud-diff .del{color:#dc3545;text-decoration:line-through;opacity:.8}
    .aud-diff .add{color:#198754}
    .aud-modebar{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;padding:.55rem .7rem;border:1px solid var(--bs-border-color);border-radius:.6rem;background:var(--bs-body-bg);margin-bottom:.75rem}
    .aud-modebar .lbl{font-weight:600;display:flex;align-items:center;gap:.4rem;margin-right:auto}
    .aud-seg{display:inline-flex;border:1px solid var(--bs-border-color);border-radius:.5rem;overflow:hidden}
    .aud-seg button{border:0;background:transparent;color:var(--bs-secondary-color);font-size:.8rem;font-weight:600;padding:.3rem .8rem;cursor:pointer;display:inline-flex;align-items:center;gap:.35rem}
    .aud-seg button.on{background:var(--bs-primary,#0d6efd);color:#fff}
    .aud-seg button:not(.on):hover{background:var(--bs-tertiary-bg)}
    .aud-dyn{border:1px dashed var(--bs-border-color);border-radius:.75rem;padding:1.75rem 1.5rem;background:var(--bs-tertiary-bg)}
    .aud-dyn h5{display:flex;align-items:center;gap:.5rem;margin:0 0 .4rem}
    .aud-dyn .path{display:inline-flex;align-items:center;gap:.35rem;background:var(--bs-secondary-bg);border-radius:.4rem;padding:.15rem .5rem;font-size:.82rem}
    `;
    document.head.appendChild(s);
}

function audParseJson(v) {
    if (v == null) return null;
    if (typeof v === 'object') return v;
    try { return JSON.parse(v); } catch (e) { return null; }
}

// Colour class for an action badge.
function audActClass(a) {
    a = String(a || '').toLowerCase();
    if (a === 'login' || a === 'create') return 'g';
    if (a === 'login_failed' || a === 'account_locked' || a === 'delete') return 'r';
    if (a === 'update') return 'b';
    if (a === 'export') return 'p';
    return 'm';
}

// Short human summary of what changed / happened for the table's Details cell.
function audSummary(log) {
    const nd = audParseJson(log.new_data), od = audParseJson(log.old_data);
    if (nd && nd._details) return escapeHtml(String(nd._details));
    if (String(log.action).toLowerCase() === 'update' && od && nd) {
        const changed = Object.keys(nd).filter(k => k[0] !== '_' && String(od[k] ?? '') !== String(nd[k] ?? ''));
        if (changed.length) return `${changed.length} field${changed.length > 1 ? 's' : ''}: ` + escapeHtml(changed.slice(0, 4).join(', ')) + (changed.length > 4 ? '…' : '');
    }
    const keys = nd ? Object.keys(nd).filter(k => k[0] !== '_') : (od ? Object.keys(od).filter(k => k[0] !== '_') : []);
    if (keys.length) return `${keys.length} field${keys.length > 1 ? 's' : ''}`;
    return '<span class="text-muted">—</span>';
}

// ── Static/Dynamic mode selector (shared by both screens) ───────────────────
// Static  = review here in the console (default). Dynamic = the build ships an
// admin-only page inside the generated app (like the Admin Area). Persisted in
// Misc → Advanced via the security/audit-mode | lock-mode endpoints.
const AUD_MODE_META = {
    audit: { label: 'Audit Log', icon: 'bi-journal-text', endpoint: 'audit-mode', page: 'Audit Log' },
    locks: { label: 'Record Locks', icon: 'bi-lock-fill', endpoint: 'lock-mode', page: 'Record Locks' },
};

function auditModeBar(kind, mode) {
    return `
    <div class="aud-modebar">
        <span class="lbl"><i class="bi bi-toggles"></i> Where to manage this</span>
        <span class="text-muted small d-none d-md-inline">${mode === 'dynamic' ? 'Built into the generated app' : 'Reviewed here in the console'}</span>
        <div class="aud-seg" role="group" data-kind="${kind}">
            <button data-mode="static"  class="${mode === 'static' ? 'on' : ''}"><i class="bi bi-window-desktop"></i> Static</button>
            <button data-mode="dynamic" class="${mode === 'dynamic' ? 'on' : ''}"><i class="bi bi-box-arrow-up-right"></i> Dynamic</button>
        </div>
    </div>`;
}

function wireModeBar(kind) {
    const meta = AUD_MODE_META[kind];
    document.querySelectorAll(`.aud-seg[data-kind="${kind}"] button`).forEach(btn => {
        btn.addEventListener('click', async () => {
            const next = btn.dataset.mode;
            if (btn.classList.contains('on')) return;
            try {
                const res = await fetch(`${baseUrl}/project/${projectSlug}/security/${meta.endpoint}`, {
                    method: 'POST', headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ mode: next }),
                });
                const d = await res.json();
                if (!d.success) return pp.dialog.alert('Error', d.message || 'Could not change mode', 'error');
                pp.dialog.toast('success', d.message);
                (kind === 'audit' ? renderAudit : renderLocks)();
            } catch (e) { pp.dialog.toast('error', 'Network error'); }
        });
    });
}

// The "managed in the app" panel shown when a screen is set to Dynamic.
function renderModeDynamic(kind, meta) {
    const container = document.getElementById('securityPageContainer');
    if (!container) return;
    const m = AUD_MODE_META[kind];
    const enabled = !!meta.enabled;
    container.innerHTML = `
        <div class="aud-wrap">
            ${auditModeBar(kind, 'dynamic')}
            <div class="aud-dyn">
                <h5><i class="bi ${m.icon}"></i> ${m.page} is built into the app</h5>
                <p class="text-muted mb-2">
                    In <strong>Dynamic</strong> mode the generated app ships its own admin-only
                    ${m.page} page — your customer&rsquo;s administrators open it from the
                    <em>user menu → ${m.page}</em>, exactly like the Admin Area. There is nothing to review here.
                </p>
                <p class="mb-2"><span class="path"><i class="bi bi-signpost-2"></i> user menu → ${m.page}</span>
                    &nbsp;→&nbsp; <span class="path"><i class="bi bi-file-earmark-code"></i> /generated/admin/${kind === 'audit' ? 'audit' : 'locks'}.php</span></p>
                ${enabled
                    ? `<div class="alert alert-info py-2 mb-0"><i class="bi bi-hammer me-1"></i> <strong>Rebuild the app</strong> to publish this page. It reads the live <code>${escapeHtml(meta.table || '')}</code> table.</div>`
                    : `<div class="alert alert-warning py-2 mb-0"><i class="bi bi-exclamation-triangle me-1"></i> ${m.page} isn&rsquo;t enabled yet — turn it on in <strong>Misc → Advanced</strong> first, then rebuild.</div>`}
            </div>
        </div>`;
    wireModeBar(kind);
}

// ======================== AUDIT TRAIL ================================
let auditUsers = [];
let currentFilters = { user_id: '', action: '', start_date: '', end_date: '', limit: 50, offset: 0 };
let totalLogs = 0;
let lastAuditLogs = [];

async function renderAudit() {
    ensureAuditStyles();
    const container = document.getElementById('securityPageContainer');
    if (!container) return;
    container.innerHTML = '<div class="text-center py-5"><div class="spinner-border"></div></div>';
    await fetchUsersForAudit();
    try {
        await fetchAuditLogs();
    } catch (e) {
        console.error(e);
        container.innerHTML = `<div class="alert alert-danger">Failed to load the audit trail: ${escapeHtml(e.message)}</div>`;
    }
}

async function fetchUsersForAudit() {
    try {
        const res = await fetch(`${baseUrl}/project/${projectSlug}/security/users`);
        const data = await res.json();
        auditUsers = (data.success && Array.isArray(data.users)) ? data.users : [];
    } catch (e) { auditUsers = []; }
}

async function fetchAuditLogs() {
    const params = new URLSearchParams();
    if (currentFilters.user_id) params.append('user_id', currentFilters.user_id);
    if (currentFilters.action) params.append('action', currentFilters.action);
    if (currentFilters.start_date) params.append('start_date', currentFilters.start_date);
    if (currentFilters.end_date) params.append('end_date', currentFilters.end_date);
    params.append('limit', currentFilters.limit);
    params.append('offset', currentFilters.offset);

    const res = await fetch(`${baseUrl}/project/${projectSlug}/security/audit-logs?${params.toString()}`);
    const data = await res.json();
    if (!data.success) throw new Error(data.message || 'Request failed');

    // Dynamic → the page lives in the generated app; nothing to review here.
    if (data.mode === 'dynamic') { renderModeDynamic('audit', data); return; }
    // Not set up yet → guide the admin to Misc → Advanced instead of an empty grid.
    if (data.configured === false) { renderAuditSetup(data); return; }

    lastAuditLogs = Array.isArray(data.logs) ? data.logs : [];
    totalLogs = data.total || 0;
    renderAuditUI(lastAuditLogs, data);
}

function renderAuditSetup(data) {
    const container = document.getElementById('securityPageContainer');
    if (!container) return;
    const enabled = !!data.enabled;
    container.innerHTML = `
        <div class="aud-wrap">
            ${auditModeBar('audit', data.mode || 'static')}
            <div class="aud-setup">
                <i class="bi bi-journal-text big"></i>
                <h5 class="mt-2 mb-1">Audit Trail is ${enabled ? 'enabled but has no records yet' : 'not enabled yet'}</h5>
                <p class="text-muted mb-3">
                    The Audit Trail is governed by <strong>Misc → Advanced</strong> (the source of truth).
                    ${enabled
                        ? `It is enabled (table <code>${escapeHtml(data.table || 'audit_log')}</code>) — events will appear here as soon as users act in the built app.`
                        : `Enable <em>“Enable Audit Trail”</em> there and rebuild the app to start recording logins, record changes, exports and more.`}
                </p>
                <a href="${baseUrl}/project/${projectSlug}/misc#advanced" class="btn btn-sm btn-primary"><i class="bi bi-sliders me-1"></i> Open Misc → Advanced</a>
            </div>
        </div>`;
    wireModeBar('audit');
}

function renderAuditUI(logs, meta) {
    const container = document.getElementById('securityPageContainer');
    if (!container) return;

    let userOptions = '<option value="">All users</option>';
    for (const u of auditUsers) {
        userOptions += `<option value="${u.id}" ${currentFilters.user_id == u.id ? 'selected' : ''}>${escapeHtml(u.username)}</option>`;
    }

    const rows = logs.map((log, i) => {
        const uname = log.user_id ? (auditUsers.find(u => u.id == log.user_id)?.username || ('#' + log.user_id)) : 'System';
        const target = log.table_name
            ? `<span class="aud-target"><code>${escapeHtml(log.table_name)}</code>${log.record_id ? ' #' + escapeHtml(String(log.record_id)) : ''}</span>`
            : '<span class="text-muted">—</span>';
        return `
            <tr>
                <td class="aud-mono">${escapeHtml(String(log.id))}</td>
                <td>${escapeHtml(uname)}</td>
                <td><span class="aud-act ${audActClass(log.action)}">${escapeHtml(String(log.action).replace(/_/g, ' '))}</span></td>
                <td>${target}</td>
                <td>${audSummary(log)}</td>
                <td class="aud-mono">${escapeHtml(log.ip_address || '')}</td>
                <td class="aud-mono">${escapeHtml(log.created_at || '')}</td>
                <td><button class="btn btn-sm btn-outline-secondary py-0 px-1 aud-view" data-i="${i}" title="View details"><i class="bi bi-eye"></i></button></td>
            </tr>`;
    }).join('');

    container.innerHTML = `
        <div class="aud-wrap">
            ${auditModeBar('audit', (meta && meta.mode) || 'static')}
            <div class="aud-bar">
                <span class="aud-title"><i class="bi bi-journal-text"></i> Audit Trail
                    <span class="badge ${meta && meta.enabled ? 'text-bg-success' : 'text-bg-secondary'}">${meta && meta.enabled ? 'Recording' : 'Paused'}</span>
                    <span class="badge text-bg-light border">${escapeHtml((meta && meta.table) || 'audit_log')}</span>
                </span>
                <button class="btn btn-sm btn-outline-secondary" id="refreshAudit"><i class="bi bi-arrow-clockwise"></i></button>
                <button class="btn btn-sm btn-outline-secondary" id="showAuditTips"><i class="bi bi-question-circle me-1"></i> Tips</button>
            </div>

            <div class="aud-filters">
                <div class="fg"><label>User</label><select id="auditUserFilter" class="form-select form-select-sm">${userOptions}</select></div>
                <div class="fg"><label>Action</label><input type="text" id="auditActionFilter" class="form-control form-control-sm" placeholder="login, update, export…" value="${escapeHtml(currentFilters.action)}"></div>
                <div class="fg"><label>From</label><input type="date" id="auditStartDate" class="form-control form-control-sm" value="${escapeHtml(currentFilters.start_date)}"></div>
                <div class="fg"><label>To</label><input type="date" id="auditEndDate" class="form-control form-control-sm" value="${escapeHtml(currentFilters.end_date)}"></div>
                <div class="fg"><label>Rows</label><select id="auditLimit" class="form-select form-select-sm" style="min-width:5rem">
                    <option value="20" ${currentFilters.limit == 20 ? 'selected' : ''}>20</option>
                    <option value="50" ${currentFilters.limit == 50 ? 'selected' : ''}>50</option>
                    <option value="100" ${currentFilters.limit == 100 ? 'selected' : ''}>100</option>
                </select></div>
                <div class="fg"><label>&nbsp;</label><button class="btn btn-sm btn-primary" id="applyAuditFilters"><i class="bi bi-funnel me-1"></i>Apply</button></div>
            </div>

            <div class="aud-scroll">
                <table class="aud-table">
                    <thead><tr><th>ID</th><th>User</th><th>Action</th><th>Target</th><th>Details</th><th>IP</th><th>When</th><th></th></tr></thead>
                    <tbody>${rows || `<tr><td colspan="8" class="aud-empty"><i class="bi bi-inbox d-block mb-2" style="font-size:1.6rem"></i>No events match these filters.</td></tr>`}</tbody>
                </table>
            </div>

            <div class="d-flex justify-content-between align-items-center mt-2 small">
                <span class="text-muted">Total: ${totalLogs} event${totalLogs === 1 ? '' : 's'}</span>
                <div>
                    <button class="btn btn-sm btn-outline-secondary" id="prevPage" ${currentFilters.offset === 0 ? 'disabled' : ''}><i class="bi bi-chevron-left"></i></button>
                    <span class="mx-2 text-muted">${Math.floor(currentFilters.offset / currentFilters.limit) + 1} / ${Math.max(1, Math.ceil(totalLogs / currentFilters.limit))}</span>
                    <button class="btn btn-sm btn-outline-secondary" id="nextPage" ${currentFilters.offset + currentFilters.limit >= totalLogs ? 'disabled' : ''}><i class="bi bi-chevron-right"></i></button>
                </div>
            </div>
        </div>

        <div class="modal fade" id="auditTipsModal" tabindex="-1"><div class="modal-dialog modal-lg"><div class="modal-content">
            <div class="modal-header"><h5 class="modal-title"><i class="bi bi-lightbulb me-2"></i>About the Audit Trail</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
            <div class="modal-body">
                <p><strong>Misc → Advanced is the source of truth.</strong> Turn <em>“Enable Audit Trail”</em> on there (optionally naming the table) and rebuild — the app then records events into that table and this screen reads them.</p>
                <p><strong>Static or Dynamic</strong> (top-right switch): <em>Static</em> reviews the trail here; <em>Dynamic</em> builds an admin-only Audit Log page into the generated app (opened from the app&rsquo;s user menu). Rebuild after changing the mode.</p>
                <p class="mb-1">Recorded automatically in the built app:</p>
                <ul class="mb-2">
                    <li><span class="aud-act g">create</span> / <span class="aud-act b">update</span> / <span class="aud-act r">delete</span> — every record change (with a before/after snapshot).</li>
                    <li><span class="aud-act g">login</span> / <span class="aud-act m">logout</span> / <span class="aud-act r">login failed</span> / <span class="aud-act r">account locked</span> — authentication events.</li>
                    <li><span class="aud-act p">export</span> — CSV/Excel/etc. exports (data leaving the app).</li>
                </ul>
                <ul class="mb-0">
                    <li>Click the <i class="bi bi-eye"></i> on any row to see the full before/after values.</li>
                    <li>Sensitive fields (password, secret, token, api_key) are redacted to <code>***</code> before storage.</li>
                    <li>Logs are never purged automatically — archive or trim them with SQL when needed.</li>
                </ul>
            </div>
            <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal">Close</button></div>
        </div></div></div>

        <div class="modal fade" id="auditDetailModal" tabindex="-1"><div class="modal-dialog modal-lg"><div class="modal-content">
            <div class="modal-header"><h5 class="modal-title"><i class="bi bi-file-earmark-text me-2"></i>Event details</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
            <div class="modal-body aud-diff" id="auditDetailBody"></div>
            <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal">Close</button></div>
        </div></div></div>
    `;

    const apply = () => {
        currentFilters.user_id = document.getElementById('auditUserFilter').value;
        currentFilters.action = document.getElementById('auditActionFilter').value.trim();
        currentFilters.start_date = document.getElementById('auditStartDate').value;
        currentFilters.end_date = document.getElementById('auditEndDate').value;
        currentFilters.limit = parseInt(document.getElementById('auditLimit').value, 10) || 50;
        currentFilters.offset = 0;
        fetchAuditLogs();
    };
    document.getElementById('applyAuditFilters')?.addEventListener('click', apply);
    document.getElementById('auditActionFilter')?.addEventListener('keydown', e => { if (e.key === 'Enter') apply(); });
    document.getElementById('refreshAudit')?.addEventListener('click', () => fetchAuditLogs());
    document.getElementById('prevPage')?.addEventListener('click', () => { if (currentFilters.offset >= currentFilters.limit) { currentFilters.offset -= currentFilters.limit; fetchAuditLogs(); } });
    document.getElementById('nextPage')?.addEventListener('click', () => { if (currentFilters.offset + currentFilters.limit < totalLogs) { currentFilters.offset += currentFilters.limit; fetchAuditLogs(); } });
    document.getElementById('showAuditTips')?.addEventListener('click', () => new bootstrap.Modal(document.getElementById('auditTipsModal')).show());
    document.querySelectorAll('.aud-view').forEach(btn => btn.addEventListener('click', () => showAuditDetail(lastAuditLogs[parseInt(btn.dataset.i, 10)])));
    wireModeBar('audit');
}

function showAuditDetail(log) {
    if (!log) return;
    const body = document.getElementById('auditDetailBody');
    const od = audParseJson(log.old_data) || {};
    const nd = audParseJson(log.new_data) || {};
    const keys = [...new Set([...Object.keys(od), ...Object.keys(nd)])].filter(k => k[0] !== '_').sort();
    let inner = `<div class="mb-2 small text-muted">
        <span class="aud-act ${audActClass(log.action)}">${escapeHtml(String(log.action).replace(/_/g, ' '))}</span>
        ${log.table_name ? ` on <code>${escapeHtml(log.table_name)}</code>${log.record_id ? ' #' + escapeHtml(String(log.record_id)) : ''}` : ''}
        &nbsp;·&nbsp; ${escapeHtml(log.created_at || '')} ${log.ip_address ? '· ' + escapeHtml(log.ip_address) : ''}
    </div>`;
    if (nd._details) inner += `<p class="mb-2">${escapeHtml(String(nd._details))}</p>`;
    if (keys.length) {
        inner += '<table><thead><tr><th>Field</th><th>Before</th><th>After</th></tr></thead><tbody>';
        for (const k of keys) {
            const before = od[k], after = nd[k];
            const changed = String(before ?? '') !== String(after ?? '');
            inner += `<tr>
                <td><code>${escapeHtml(k)}</code></td>
                <td class="${changed && before != null ? 'del' : ''}">${before == null ? '<span class="text-muted">—</span>' : escapeHtml(String(before))}</td>
                <td class="${changed && after != null ? 'add' : ''}">${after == null ? '<span class="text-muted">—</span>' : escapeHtml(String(after))}</td>
            </tr>`;
        }
        inner += '</tbody></table>';
    } else if (!nd._details) {
        inner += '<p class="text-muted mb-0">No field-level detail was recorded for this event.</p>';
    }
    body.innerHTML = inner;
    new bootstrap.Modal(document.getElementById('auditDetailModal')).show();
}

// ======================== RECORD LOCKS ================================
async function renderLocks() {
    ensureAuditStyles();
    const container = document.getElementById('securityPageContainer');
    if (!container) return;
    container.innerHTML = '<div class="text-center py-5"><div class="spinner-border"></div></div>';
    try {
        const res = await fetch(`${baseUrl}/project/${projectSlug}/security/record-locks`);
        const data = await res.json();
        if (!data.success) throw new Error(data.message || 'Request failed');
        if (data.mode === 'dynamic') { renderModeDynamic('locks', data); return; }
        if (data.configured === false) { renderLocksSetup(data); return; }
        renderLocksUI(Array.isArray(data.locks) ? data.locks : [], data);
    } catch (e) {
        console.error(e);
        container.innerHTML = `<div class="alert alert-danger">Failed to load record locks: ${escapeHtml(e.message)}</div>`;
    }
}

function renderLocksSetup(data) {
    const container = document.getElementById('securityPageContainer');
    if (!container) return;
    const enabled = !!data.enabled;
    container.innerHTML = `
        <div class="aud-wrap">
            ${auditModeBar('locks', data.mode || 'static')}
            <div class="aud-setup">
                <i class="bi bi-lock big"></i>
                <h5 class="mt-2 mb-1">Record Locking is ${enabled ? 'enabled — no active locks' : 'not enabled yet'}</h5>
                <p class="text-muted mb-3">
                    Record Locking is governed by <strong>Misc → Advanced</strong> (the source of truth).
                    ${enabled
                        ? `It is enabled (table <code>${escapeHtml(data.table || 'record_locks')}</code>). Locks appear here while users are editing records in the built app.`
                        : `Enable <em>“Record Locking”</em> there and rebuild the app so a record is held while one user edits it and others see a read-only notice.`}
                </p>
                <a href="${baseUrl}/project/${projectSlug}/misc#advanced" class="btn btn-sm btn-primary"><i class="bi bi-sliders me-1"></i> Open Misc → Advanced</a>
            </div>
        </div>`;
    wireModeBar('locks');
}

function renderLocksUI(locks, meta) {
    const container = document.getElementById('securityPageContainer');
    if (!container) return;

    const rows = locks.map(lock => `
        <tr>
            <td class="aud-mono">${escapeHtml(String(lock.id))}</td>
            <td><span class="aud-target"><code>${escapeHtml(lock.table_name)}</code> #${escapeHtml(String(lock.record_id))}</span></td>
            <td>${escapeHtml(lock.username || ('#' + lock.user_id))}</td>
            <td class="aud-mono">${escapeHtml(lock.locked_at || '')}</td>
            <td class="aud-mono">${lock.expires_at ? escapeHtml(lock.expires_at) : '<span class="text-muted">—</span>'}</td>
            <td><button class="btn btn-sm btn-outline-danger py-0 px-2 release-lock-btn" data-id="${lock.id}"><i class="bi bi-unlock me-1"></i>Release</button></td>
        </tr>`).join('');

    container.innerHTML = `
        <div class="aud-wrap">
            ${auditModeBar('locks', (meta && meta.mode) || 'static')}
            <div class="aud-bar">
                <span class="aud-title"><i class="bi bi-lock-fill"></i> Record Locks
                    <span class="badge ${meta && meta.enabled ? 'text-bg-success' : 'text-bg-secondary'}">${meta && meta.enabled ? 'Active' : 'Paused'}</span>
                    <span class="badge text-bg-light border">${escapeHtml((meta && meta.table) || 'record_locks')}</span>
                    <span class="badge text-bg-light border">${locks.length} held</span>
                </span>
                <button class="btn btn-sm btn-outline-secondary" id="refreshLocks"><i class="bi bi-arrow-clockwise"></i></button>
                <button class="btn btn-sm btn-danger" id="releaseAllLocks" ${locks.length === 0 ? 'disabled' : ''}><i class="bi bi-unlock me-1"></i>Release all</button>
                <button class="btn btn-sm btn-outline-secondary" id="showLockTips"><i class="bi bi-question-circle me-1"></i> Tips</button>
            </div>

            <div class="aud-scroll">
                <table class="aud-table">
                    <thead><tr><th>ID</th><th>Record</th><th>Held by</th><th>Since</th><th>Expires</th><th></th></tr></thead>
                    <tbody>${rows || `<tr><td colspan="6" class="aud-empty"><i class="bi bi-unlock d-block mb-2" style="font-size:1.6rem"></i>No records are currently locked.</td></tr>`}</tbody>
                </table>
            </div>
        </div>

        <div class="modal fade" id="lockTipsModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content">
            <div class="modal-header"><h5 class="modal-title"><i class="bi bi-lightbulb me-2"></i>About Record Locks</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
            <div class="modal-body">
                <p><strong>Misc → Advanced is the source of truth.</strong> With <em>“Record Locking”</em> enabled and the app rebuilt, opening a record for edit takes a lock; anyone else who opens it sees a read-only notice naming who holds it.</p>
                <p><strong>Static or Dynamic</strong> (top-right switch): <em>Static</em> manages locks here; <em>Dynamic</em> builds an admin-only Record Locks page into the generated app (opened from the app&rsquo;s user menu). Rebuild after changing the mode.</p>
                <ul class="mb-0">
                    <li>A lock is released on save, on leaving the edit page, or automatically after the configured timeout.</li>
                    <li>The edit page keeps its own lock alive with a heartbeat while it stays open.</li>
                    <li>As an administrator you can force-release a stuck lock here — the holder may lose unsaved edits.</li>
                </ul>
            </div>
            <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal">Close</button></div>
        </div></div></div>
    `;

    document.getElementById('refreshLocks')?.addEventListener('click', () => renderLocks());
    document.querySelectorAll('.release-lock-btn').forEach(btn => {
        btn.addEventListener('click', async () => {
            if (await pp.dialog.confirm('Release this lock?', 'The user holding it may lose unsaved changes.')) {
                const res = await fetch(`${baseUrl}/project/${projectSlug}/security/record-locks/${btn.dataset.id}`, { method: 'DELETE' });
                const data = await res.json();
                if (data.success) { pp.dialog.toast('success', data.message); renderLocks(); }
                else pp.dialog.alert('Error', data.message, 'error');
            }
        });
    });
    document.getElementById('releaseAllLocks')?.addEventListener('click', async () => {
        if (await pp.dialog.confirm('Release ALL locks?', 'Every active lock will be released. Use only for stuck locks.')) {
            const res = await fetch(`${baseUrl}/project/${projectSlug}/security/record-locks/all`, { method: 'DELETE' });
            const data = await res.json();
            if (data.success) { pp.dialog.toast('success', data.message); renderLocks(); }
            else pp.dialog.alert('Error', data.message, 'error');
        }
    });
    document.getElementById('showLockTips')?.addEventListener('click', () => new bootstrap.Modal(document.getElementById('lockTipsModal')).show());
    wireModeBar('locks');
}

window.renderAudit = renderAudit;
window.renderLocks = renderLocks;
