import { apiGet, apiPost, apiPut, apiDownloadFetch, redirectToLogin, apiUrl } from '../api.js';
import { getRole, pageHeaderHTML, showToast } from '../app.js';
import { confirmAction } from '../confirm-modal.js';
import { navigate } from '../router.js';

const REVOKE_ALL_CONFIRM = 'REVOKE ALL SESSIONS';
const DELETE_ALL_TRANSLATIONS_CONFIRM = 'DELETE ALL TRANSLATIONS';

const ACTIVITY_LABELS = {
    app_sync: 'App sync',
    app_change: 'App change',
    web_change: 'Website change',
    web_export: 'Website export',
    api_error: 'API error',
};

export function devPage() {
    if (getRole() !== 'admin') {
        navigate('#/');
        return;
    }

    const app = document.getElementById('app');
    const today = new Date().toISOString().slice(0, 10);
    app.innerHTML = `
        ${pageHeaderHTML('Admin Settings')}
        <div class="card security-settings-card" style="max-width:720px;margin-bottom:16px">
            <h2 style="margin:0 0 8px;font-size:1.1rem">Security &amp; sessions</h2>
            <p class="field-hint" style="margin:0 0 14px">
                Login rate limits apply per username and IP. Session length applies to new logins
                (website and mobile). Password-change tokens use the temporary session length.
            </p>
            <div id="securitySettingsForm" class="security-settings-form">
                <div class="spinner" style="margin:12px 0"></div>
            </div>
            <p id="securitySettingsMeta" class="field-hint" style="margin:12px 0 0"></p>
            <div class="security-revoke-all" style="margin-top:20px;padding-top:16px;border-top:1px solid var(--border)">
                <h3 style="margin:0 0 8px;font-size:1rem">Revoke all sessions</h3>
                <p class="field-hint callout-danger" style="margin:0 0 12px">
                    <strong>Signs out everyone</strong> — all admins, supervisors, and counselors on every device.
                    You will be logged out too and must sign in again. Use after a suspected compromise or policy change.
                </p>
                <label for="revokeAllConfirm" style="font-size:.85rem;font-weight:500;display:block;margin-bottom:6px">
                    Type <code>${REVOKE_ALL_CONFIRM}</code> to confirm
                </label>
                <input type="text" id="revokeAllConfirm" class="revoke-confirm-input" autocomplete="off" spellcheck="false"
                    placeholder="${REVOKE_ALL_CONFIRM}">
                <button type="button" class="btn btn-danger" id="revokeAllSessionsBtn" style="margin-top:10px" disabled>
                    Revoke all sessions
                </button>
            </div>
        </div>
        <div class="card" style="max-width:720px;margin-bottom:16px">
            <h2 style="margin:0 0 8px;font-size:1.1rem">Translations</h2>
            <p class="field-hint callout-danger" style="margin:0 0 12px">
                <strong>Removes all non-German translations</strong> from question, answer-option, and string tables,
                and deletes all languages except German (<code>de</code>). Questionnaire structure and German source
                text are kept. Re-import a bundle from the Translations page to restore other languages.
            </p>
            <label for="deleteAllTranslationsConfirm" style="font-size:.85rem;font-weight:500;display:block;margin-bottom:6px">
                Type <code>${DELETE_ALL_TRANSLATIONS_CONFIRM}</code> to confirm
            </label>
            <input type="text" id="deleteAllTranslationsConfirm" class="revoke-confirm-input" autocomplete="off" spellcheck="false"
                placeholder="${DELETE_ALL_TRANSLATIONS_CONFIRM}">
            <button type="button" class="btn btn-danger" id="deleteAllTranslationsBtn" style="margin-top:10px" disabled>
                Delete all translations
            </button>
            <pre id="deleteAllTranslationsResult" style="margin-top:12px;font-size:.8rem;max-height:160px;overflow:auto;display:none"></pre>
        </div>
        <div class="card" style="max-width:720px;margin-bottom:16px">
            <h2 style="margin:0 0 8px;font-size:1.1rem">Export all response data (ZIP)</h2>
            <p class="field-hint" style="margin:0 0 14px">
                Download one CSV per questionnaire in a single ZIP (full server data).
                <strong>Current data</strong> uses live answers;
                <strong>All versions</strong> includes every upload (one row per submission).
            </p>
            <div style="display:flex;flex-wrap:wrap;gap:10px">
                <button type="button" class="btn btn-primary" id="exportAllCurrentZipBtn">Export all — current data (ZIP)</button>
                <button type="button" class="btn" id="exportAllVersionsZipBtn">Export all — all versions (ZIP)</button>
            </div>
        </div>
        <div class="card" style="max-width:720px">
            <h2 style="margin:0 0 8px;font-size:1.1rem">Dev import</h2>
            <p class="field-hint callout-warn">
                <strong>Local / test only.</strong> Imports prefixed dev users (<code>dev_*</code>),
                clients (<code>DEV-CL-*</code>), and completed questionnaire runs.
                Existing real users are not modified. Conflicts are skipped (usernames, client codes, completions).
                Default password: <code>socialvrlab</code>. Questionnaires must already exist in the database.
            </p>
            <div class="form-group">
                <label for="devFixtureFile">Fixture JSON</label>
                <input type="file" id="devFixtureFile" accept=".json,application/json">
                <span class="field-hint">Use <code>dev-test-fixture.json</code> from the repo root (regenerate with <code>barometer-server/scripts/generate_dev_fixture.py</code> after importing the latest <code>questionnaires_bundle_*.json</code>; uneven counselor load, multi-version uploads, natural timestamps).</span>
            </div>
            <div id="devFixtureSummary" class="field-hint" style="margin:12px 0;display:none"></div>
            <div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px">
                <button type="button" class="btn btn-primary" id="devImportBtn" disabled>Import fixture</button>
                <button type="button" class="btn btn-danger" id="devRemoveBtn">Remove test data</button>
            </div>
            <pre id="devImportResult" style="margin-top:16px;font-size:.8rem;max-height:320px;overflow:auto;display:none"></pre>
        </div>
        <div class="card" style="max-width:720px;margin-top:16px">
            <h2 style="margin:0 0 8px;font-size:1.1rem">Reset database</h2>
            <p class="field-hint callout-danger">
                <strong>Destructive.</strong> Deletes all clients, completions, counselors, and supervisors
                (including non-dev accounts). Admin logins and questionnaire definitions are kept.
            </p>
            <button type="button" class="btn btn-danger" id="devWipeBtn">Remove all data (keep admins)</button>
        </div>
        <div class="card activity-log-card" style="margin-top:16px">
            <h2 style="margin:0 0 8px;font-size:1.1rem">API activity log</h2>
            <p class="field-hint" style="margin:0 0 14px">
                App syncs, website edits (translations, users, questionnaires, …),
                and exports/downloads. Routine page loads (lists, dashboards) are not logged.
            </p>
            <div class="filter-bar" style="margin-bottom:12px">
                <label style="font-size:.85rem;font-weight:500">Date</label>
                <input type="date" id="activityLogDate" value="${today}">
                <label style="font-size:.85rem;font-weight:500">Activity</label>
                <select id="activityLogKind">
                    <option value="">All kinds</option>
                    <option value="app_sync">App sync</option>
                    <option value="app_change">App change</option>
                    <option value="web_change">Website change</option>
                    <option value="web_export">Website export</option>
                    <option value="errors">Errors only (4xx/5xx)</option>
                </select>
                <button type="button" class="btn btn-sm" id="activityLogRefresh">Refresh</button>
            </div>
            <div id="activityLogStatus" class="field-hint" style="margin:0 0 10px;display:none"></div>
            <p id="activityLogMeta" class="field-hint" style="margin:0 0 10px"></p>
            <div class="table-wrapper">
                <table class="data-table activity-log-table">
                    <thead>
                        <tr>
                            <th>Time (UTC)</th>
                            <th>Kind</th>
                            <th>Subject</th>
                            <th>Method</th>
                            <th>Route</th>
                            <th>Status</th>
                            <th>Duration</th>
                            <th>Values</th>
                            <th>Performed by</th>
                        </tr>
                    </thead>
                    <tbody id="activityLogBody">
                        <tr><td colspan="9" style="text-align:center;color:var(--text-secondary);padding:20px">Loading…</td></tr>
                    </tbody>
                </table>
            </div>
        </div>
    `;

    let parsedFixture = null;

    document.getElementById('devFixtureFile').addEventListener('change', async (e) => {
        const file = e.target.files?.[0];
        const summary = document.getElementById('devFixtureSummary');
        const btn = document.getElementById('devImportBtn');
        parsedFixture = null;
        btn.disabled = true;
        summary.style.display = 'none';

        if (!file) return;

        try {
            const text = await file.text();
            parsedFixture = JSON.parse(text);
            const st = parsedFixture.stats || {};
            summary.innerHTML = `
                <strong>Preview:</strong>
                ${parsedFixture.admins?.length ?? st.admins ?? 0} admins,
                ${parsedFixture.supervisors?.length ?? st.supervisors ?? 0} supervisors,
                ${parsedFixture.coaches?.length ?? st.coaches ?? 0} counselors,
                ${parsedFixture.clients?.length ?? st.clients ?? 0} clients,
                ${parsedFixture.completions?.length ?? st.completionRecords ?? st.completions ?? 0} completions,
                ${st.totalUploads ?? '—'} uploads (${st.multiVersionPairs ?? '—'} re-uploaded)
            `;
            summary.style.display = '';
            btn.disabled = false;
        } catch (err) {
            showToast('Invalid JSON: ' + err.message, 'error');
        }
    });

    document.getElementById('devRemoveBtn').addEventListener('click', async () => {
        if (!(await confirmAction({
            title: 'Remove dev test data',
            message: 'Remove all dev test data?\n\nDeletes users matching dev_*, clients DEV-CL-*, and their answers/completions.\nReal accounts are not affected.',
            confirmLabel: 'Remove',
            variant: 'danger',
        }))) {
            return;
        }
        const btn = document.getElementById('devRemoveBtn');
        const pre = document.getElementById('devImportResult');
        btn.disabled = true;
        btn.textContent = 'Removing…';
        pre.style.display = 'none';

        try {
            const res = await apiPost('dev', {
                action: 'remove',
                usernamePrefix: 'dev_',
                clientCodePrefix: 'DEV-CL-',
            });
            if (!res.success) {
                showToast(res.error || res.message || 'Remove failed', 'error');
                return;
            }
            pre.textContent = JSON.stringify(res, null, 2);
            pre.style.display = '';
            const d = res.deleted || {};
            showToast(
                `Removed ${d.clients ?? 0} clients, ${d.users ?? 0} users, `
                + `${d.completed_questionnaires ?? 0} completions`,
                'success'
            );
        } catch (e) {
            showToast(e.message, 'error');
        } finally {
            btn.disabled = false;
            btn.textContent = 'Remove test data';
        }
    });

    document.getElementById('devWipeBtn').addEventListener('click', async () => {
        if (!(await confirmAction({
            title: 'Wipe operational data',
            message: 'Remove ALL clients, completions, counselors, and supervisors?\n\nAdmin accounts and questionnaires will NOT be deleted.\nThis cannot be undone.',
            confirmLabel: 'Remove all',
            variant: 'danger',
        }))) {
            return;
        }
        const btn = document.getElementById('devWipeBtn');
        const pre = document.getElementById('devImportResult');
        btn.disabled = true;
        btn.textContent = 'Removing…';
        pre.style.display = 'none';

        try {
            const res = await apiPost('dev', { action: 'wipeExceptAdmins' });
            if (!res.success) {
                showToast(res.error || res.message || 'Wipe failed', 'error');
                return;
            }
            pre.textContent = JSON.stringify(res, null, 2);
            pre.style.display = '';
            const d = res.deleted || {};
            const kept = res.kept?.admins ?? '?';
            showToast(
                `Wiped ${d.clients ?? 0} clients, ${d.users ?? 0} users, `
                + `${d.completed_questionnaires ?? 0} completions (${kept} admins kept)`,
                'success'
            );
        } catch (e) {
            showToast(e.message, 'error');
        } finally {
            btn.disabled = false;
            btn.textContent = 'Remove all data (keep admins)';
        }
    });

    wireAdminZipExport();
    wireActivityLog();
    wireSecuritySettings();
    wireDeleteAllTranslations();

    document.getElementById('devImportBtn').addEventListener('click', async () => {
        if (!parsedFixture) return;
        const btn = document.getElementById('devImportBtn');
        const pre = document.getElementById('devImportResult');
        btn.disabled = true;
        btn.textContent = 'Importing…';
        pre.style.display = 'none';

        try {
            const res = await apiPost('dev', { fixture: parsedFixture });
            if (!res.success) {
                showToast(res.error || 'Import failed', 'error');
                return;
            }
            pre.textContent = JSON.stringify(res, null, 2);
            pre.style.display = '';
            const imp = res.imported || {};
            const sk = res.skipped || {};
            showToast(
                `Imported: ${imp.completions ?? 0} completions, ${imp.submissions ?? 0} uploads, `
                + `${imp.clients ?? 0} clients, ${imp.coaches ?? 0} counselors `
                + `(skipped ${sk.completions ?? 0} existing)`,
                'success'
            );
            if (res.errors?.length) {
                showToast(`${res.errors.length} warning(s) — see log below`, 'error');
            }
        } catch (e) {
            showToast(e.message, 'error');
        } finally {
            btn.disabled = false;
            btn.textContent = 'Import fixture';
        }
    });
}

function renderActivityLogStatus(status) {
    const el = document.getElementById('activityLogStatus');
    if (!el || !status) {
        if (el) el.style.display = 'none';
        return;
    }
    if (!status.enabled) {
        el.className = 'field-hint callout-warn';
        el.innerHTML = 'Activity logging is disabled (<code>QDB_API_LOG=0</code> in .env).';
        el.style.display = '';
        return;
    }
    if (status.writable) {
        el.className = 'field-hint';
        el.innerHTML = `Logging to <code>${escHtml(status.dir)}</code>`
            + (status.php_user ? ` (PHP user: <code>${escHtml(status.php_user)}</code>)` : '');
        el.style.display = '';
        return;
    }
    el.className = 'field-hint callout-danger';
    el.innerHTML = `<strong>Cannot write activity log.</strong> ${escHtml(status.error || 'Permission problem.')}
        <br>Path: <code>${escHtml(status.dir)}</code>`
        + (status.php_user ? `<br>PHP runs as: <code>${escHtml(status.php_user)}</code>` : '')
        + (status.fix_hint ? `<br>${escHtml(status.fix_hint)}` : '');
    el.style.display = '';
}

function wireActivityLog() {
    const dateEl = document.getElementById('activityLogDate');
    const kindEl = document.getElementById('activityLogKind');
    document.getElementById('activityLogRefresh')?.addEventListener('click', loadActivityLog);
    dateEl?.addEventListener('change', loadActivityLog);
    kindEl?.addEventListener('change', loadActivityLog);
    loadActivityLog();
}

async function loadActivityLog() {
    const tbody = document.getElementById('activityLogBody');
    const meta = document.getElementById('activityLogMeta');
    if (!tbody) return;

    const date = document.getElementById('activityLogDate')?.value || new Date().toISOString().slice(0, 10);
    const activity = document.getElementById('activityLogKind')?.value || '';
    tbody.innerHTML = '<tr><td colspan="9" style="text-align:center;padding:20px">Loading…</td></tr>';

    try {
        const q = new URLSearchParams({ date, limit: '300' });
        if (activity) q.set('activity', activity);
        const data = await apiGet(`activity-log?${q}`);
        if (!data.success) throw new Error(data.error || 'Failed to load activity log');

        renderActivityLogStatus(data.logStatus);

        const entries = data.entries || [];
        const files = (data.files || []).join(', ') || 'none';
        const trunc = data.truncated ? ' (newest 300 shown)' : '';
        if (meta) {
            meta.textContent = entries.length
                ? `${entries.length} entries from ${files}${trunc}`
                : `No entries for this date/filter (files: ${files})`;
        }

        if (!entries.length) {
            tbody.innerHTML = '<tr><td colspan="9" style="text-align:center;color:var(--text-secondary);padding:24px">No activity recorded</td></tr>';
            return;
        }

        tbody.innerHTML = entries.map((e) => {
            const kind = e.activity || '';
            const label = ACTIVITY_LABELS[kind] || kind || '—';
            const badgeClass = kind ? `badge-activity-${kind}` : '';
            const time = formatActivityTime(e.ts);
            const statusNum = e.status != null ? Number(e.status) : null;
            const status = statusNum != null ? String(statusNum) : '—';
            const statusClass = statusNum != null && statusNum >= 400 ? 'error-text' : '';
            const dur = e.duration_ms != null ? `${e.duration_ms} ms` : '—';
            const subject = formatActivitySubject(e);
            const performedBy = formatActivityPerformer(e);
            const errPart = e.error
                ? `<div class="activity-log-error">${escHtml(e.error)}</div>`
                : '';
            return `<tr>
                <td class="activity-log-col-time">${escHtml(time)}</td>
                <td class="activity-log-col-kind"><span class="badge ${badgeClass}">${escHtml(label)}</span></td>
                <td class="activity-log-subject">${subject}</td>
                <td class="activity-log-col-meta"><code>${escHtml(e.method || '')}</code></td>
                <td class="activity-log-col-route"><code>${escHtml(e.route || '')}</code></td>
                <td class="activity-log-col-meta ${statusClass}">${escHtml(status)}</td>
                <td class="activity-log-col-meta">${escHtml(dur)}</td>
                <td class="activity-log-changes-cell">${renderActivityChanges(e.changes)}</td>
                <td class="activity-log-actor">${performedBy}${errPart}</td>
            </tr>`;
        }).join('');
    } catch (err) {
        if (meta) meta.textContent = '';
        tbody.innerHTML = `<tr><td colspan="9" class="error-text" style="padding:16px">${escHtml(err.message)}</td></tr>`;
    }
}

function formatActivitySubject(entry) {
    if (entry.target_username) {
        return `<strong>${escHtml(entry.target_username)}</strong>`;
    }
    const changes = entry.changes || [];
    const userChange = changes.find((c) => (c.field || '').toLowerCase() === 'username');
    if (userChange) {
        const name = userChange.display || formatActivityChangeValue(userChange.value);
        return `<strong>${escHtml(name)}</strong>`;
    }
    const client = changes.find((c) => (c.field || '').toLowerCase().includes('clientcode'));
    if (client) {
        const code = client.display || formatActivityChangeValue(client.value);
        return `client <strong>${escHtml(code)}</strong>`;
    }
    const qClient = changes.find((c) => (c.field || '') === 'query.clientCode');
    if (qClient) {
        return `client <strong>${escHtml(formatActivityChangeValue(qClient.value))}</strong>`;
    }
    return '<span style="color:var(--text-secondary)">—</span>';
}

function formatActivityPerformer(entry) {
    if (entry.actor_username) {
        const role = entry.role ? ` <span class="activity-log-role">(${escHtml(entry.role)})</span>` : '';
        return `<strong>${escHtml(entry.actor_username)}</strong>${role}`;
    }
    if (entry.role) {
        return escHtml(entry.role);
    }
    return '—';
}

function renderActivityChanges(changes) {
    const list = Array.isArray(changes) ? changes : [];
    if (!list.length) {
        return '<span style="color:var(--text-secondary)">—</span>';
    }
    const rows = list.map((c) => {
        const field = c.field ?? '';
        const raw = formatActivityChangeValue(c.value);
        const display = (c.display != null && String(c.display) !== '')
            ? String(c.display)
            : raw;
        const showRaw = display !== raw
            && !raw.startsWith('[REDACTED]')
            && !raw.startsWith('[ENCRYPTED');
        return `<tr>
            <th scope="row">${escHtml(field)}</th>
            <td>
                <strong>${escHtml(display)}</strong>
                ${showRaw ? `<div class="activity-change-raw">${escHtml(raw)}</div>` : ''}
            </td>
        </tr>`;
    }).join('');
    return `<table class="activity-changes-table"><tbody>${rows}</tbody></table>`;
}

function formatActivityChangeValue(value) {
    if (value === null || value === undefined) return 'null';
    if (typeof value === 'boolean') return value ? 'true' : 'false';
    return String(value);
}

function formatActivityTime(iso) {
    if (!iso) return '—';
    try {
        const d = new Date(iso);
        if (Number.isNaN(d.getTime())) return iso;
        return d.toISOString().replace('T', ' ').slice(0, 19);
    } catch {
        return iso;
    }
}

function escHtml(s) {
    const d = document.createElement('div');
    d.textContent = s ?? '';
    return d.innerHTML;
}

function filenameFromContentDisposition(res, fallback) {
    const disp = res.headers.get('Content-Disposition');
    if (!disp) return fallback;
    const m = /filename="([^"]+)"/i.exec(disp);
    return m ? m[1] : fallback;
}

async function downloadExportZip(url, defaultFilename) {
    const res = await apiDownloadFetch(url);
    if (!res.ok) {
        const err = await res.json().catch(() => ({ error: 'Download failed' }));
        throw new Error(err.error?.message || err.error || 'Download failed');
    }
    const blob = await res.blob();
    const blobUrl = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = blobUrl;
    a.download = filenameFromContentDisposition(res, defaultFilename);
    a.click();
    URL.revokeObjectURL(blobUrl);
}

function wireAdminZipExport() {
    document.getElementById('exportAllCurrentZipBtn')?.addEventListener('click', async () => {
        const btn = document.getElementById('exportAllCurrentZipBtn');
        btn.disabled = true;
        const prev = btn.textContent;
        btn.textContent = 'Preparing ZIP…';
        try {
            await downloadExportZip(apiUrl('export?exportAll=1'), 'responses_export_current.zip');
            showToast('ZIP download started (current data)', 'success');
        } catch (e) {
            showToast(e.message, 'error');
        } finally {
            btn.disabled = false;
            btn.textContent = prev;
        }
    });

    document.getElementById('exportAllVersionsZipBtn')?.addEventListener('click', async () => {
        const btn = document.getElementById('exportAllVersionsZipBtn');
        btn.disabled = true;
        const prev = btn.textContent;
        btn.textContent = 'Preparing ZIP…';
        try {
            await downloadExportZip(apiUrl('export?exportAll=1&allVersions=1'), 'responses_export_all_versions.zip');
            showToast('ZIP download started (all versions)', 'success');
        } catch (e) {
            showToast(e.message, 'error');
        } finally {
            btn.disabled = false;
            btn.textContent = prev;
        }
    });
}

function wireSecuritySettings() {
    const formEl = document.getElementById('securitySettingsForm');
    const metaEl = document.getElementById('securitySettingsMeta');
    const revokeInput = document.getElementById('revokeAllConfirm');
    const revokeBtn = document.getElementById('revokeAllSessionsBtn');
    if (!formEl) return;

    revokeInput?.addEventListener('input', () => {
        if (revokeBtn) {
            revokeBtn.disabled = revokeInput.value.trim() !== REVOKE_ALL_CONFIRM;
        }
    });

    revokeBtn?.addEventListener('click', async () => {
        if (revokeInput.value.trim() !== REVOKE_ALL_CONFIRM) return;
        if (!(await confirmAction({
            title: 'Revoke all sessions',
            message: 'Revoke every active session?\n\nAll users (including you) will be signed out on website and mobile.',
            confirmLabel: 'Revoke all',
            variant: 'danger',
        }))) {
            return;
        }
        revokeBtn.disabled = true;
        try {
            const data = await apiPost('settings', {
                action: 'revokeAllSessions',
                confirmPhrase: REVOKE_ALL_CONFIRM,
            });
            if (!data.success) throw new Error(data.error || 'Failed');
            showToast(`Revoked ${data.revokedSessions ?? 0} session(s). Signing you out…`, 'success');
            revokeInput.value = '';
            setTimeout(() => redirectToLogin(), 800);
        } catch (err) {
            showToast(err.message, 'error');
            revokeBtn.disabled = revokeInput.value.trim() !== REVOKE_ALL_CONFIRM;
        }
    });

    loadSecuritySettings(formEl, metaEl);
}

async function loadSecuritySettings(formEl, metaEl) {
    try {
        const data = await apiGet('settings');
        if (!data.success) throw new Error(data.error || 'Failed to load settings');
        const s = data.settings || {};
        const active = data.activeSessions ?? 0;
        if (metaEl) {
            metaEl.textContent = `${active} active session(s) in the database.`;
        }
        formEl.innerHTML = `
            <div class="security-settings-grid">
                <div class="form-group">
                    <label for="setLoginMax">Max failed logins</label>
                    <input type="number" id="setLoginMax" min="1" max="100" step="1"
                        value="${escAttr(s.login_max_attempts)}">
                    <span class="field-hint">Before lockout (per username + IP)</span>
                </div>
                <div class="form-group">
                    <label for="setLoginWindow">Login attempt window (minutes)</label>
                    <input type="number" id="setLoginWindow" min="1" max="1440" step="1"
                        value="${escAttr(Math.round((s.login_window_seconds || 900) / 60))}">
                </div>
                <div class="form-group">
                    <label for="setLoginLockout">Lockout duration (minutes)</label>
                    <input type="number" id="setLoginLockout" min="1" max="1440" step="1"
                        value="${escAttr(Math.round((s.login_lockout_seconds || 900) / 60))}">
                </div>
                <div class="form-group">
                    <label for="setSessionDays">Session length (days)</label>
                    <input type="number" id="setSessionDays" min="1" max="365" step="1"
                        value="${escAttr(Math.max(1, Math.round((s.session_ttl_seconds || 2592000) / 86400)))}">
                    <span class="field-hint">Website &amp; app after normal login</span>
                </div>
                <div class="form-group">
                    <label for="setTempSession">Temporary session (minutes)</label>
                    <input type="number" id="setTempSession" min="5" max="1440" step="1"
                        value="${escAttr(Math.round((s.temp_session_ttl_seconds || 600) / 60))}">
                    <span class="field-hint">Forced password change flow</span>
                </div>
            </div>
            <button type="button" class="btn btn-primary" id="saveSecuritySettingsBtn">Save security settings</button>
        `;
        document.getElementById('saveSecuritySettingsBtn')?.addEventListener('click', () => {
            saveSecuritySettings(formEl, metaEl);
        });
    } catch (err) {
        formEl.innerHTML = `<p class="error-text">${escHtml(err.message)}</p>`;
    }
}

async function saveSecuritySettings(formEl, metaEl) {
    const btn = document.getElementById('saveSecuritySettingsBtn');
    const payload = {
        login_max_attempts: parseInt(document.getElementById('setLoginMax')?.value || '10', 10),
        login_window_seconds: parseInt(document.getElementById('setLoginWindow')?.value || '15', 10) * 60,
        login_lockout_seconds: parseInt(document.getElementById('setLoginLockout')?.value || '15', 10) * 60,
        session_ttl_seconds: parseInt(document.getElementById('setSessionDays')?.value || '30', 10) * 86400,
        temp_session_ttl_seconds: parseInt(document.getElementById('setTempSession')?.value || '10', 10) * 60,
    };
    if (btn) btn.disabled = true;
    try {
        const data = await apiPut('settings', payload);
        if (!data.success) throw new Error(data.error || 'Save failed');
        showToast('Security settings saved', 'success');
        await loadSecuritySettings(formEl, metaEl);
    } catch (err) {
        showToast(err.message, 'error');
    } finally {
        if (btn) btn.disabled = false;
    }
}

function wireDeleteAllTranslations() {
    const input = document.getElementById('deleteAllTranslationsConfirm');
    const btn = document.getElementById('deleteAllTranslationsBtn');
    const result = document.getElementById('deleteAllTranslationsResult');
    if (!input || !btn) return;

    input.addEventListener('input', () => {
        btn.disabled = input.value.trim() !== DELETE_ALL_TRANSLATIONS_CONFIRM;
    });

    btn.addEventListener('click', async () => {
        if (input.value.trim() !== DELETE_ALL_TRANSLATIONS_CONFIRM) return;
        if (!(await confirmAction({
            title: 'Delete all translations',
            message: 'Remove every non-German translation and language from the database?\n\nGerman source text is kept.',
            confirmLabel: 'Delete all',
            variant: 'danger',
        }))) {
            return;
        }
        btn.disabled = true;
        if (result) result.style.display = 'none';
        try {
            const data = await apiPost('translations', {
                action: 'deleteAll',
                confirmPhrase: DELETE_ALL_TRANSLATIONS_CONFIRM,
            });
            if (!data.success) throw new Error(data.error || 'Failed');
            const d = data.data || {};
            const summary = [
                `Question translations removed: ${d.question ?? 0}`,
                `Answer-option translations removed: ${d.answer_option ?? 0}`,
                `String translations removed: ${d.string ?? 0}`,
                `Languages removed: ${d.languages ?? 0}`,
            ].join('\n');
            if (result) {
                result.textContent = summary;
                result.style.display = 'block';
            }
            showToast('All non-German translations deleted', 'success');
            input.value = '';
        } catch (err) {
            showToast(err.message, 'error');
            btn.disabled = input.value.trim() !== DELETE_ALL_TRANSLATIONS_CONFIRM;
        }
    });
}

function escAttr(v) {
    return String(v ?? '')
        .replace(/&/g, '&amp;')
        .replace(/"/g, '&quot;')
        .replace(/</g, '&lt;');
}
