// ── Authentication database (Settings → Database) ───────────────────────────
// The page is built from the module's own .secdb-* blocks (styles.css) rather
// than raw Bootstrap cards, so it stays compact and follows the light/dark
// theme like the rest of the console.

// The six identity fields PPPHP itself understands. Anything beyond these is a
// user-defined mapping, carried through as `extra_fields`.
const SECDB_CORE_FIELDS = [
    { key: 'username_field', label: 'Username', icon: 'bi-person-badge', required: true, hint: 'Sign-in identifier' },
    { key: 'email_field', label: 'Email', icon: 'bi-envelope', required: true, hint: 'Sign-in + password reset' },
    { key: 'password_field', label: 'Password', icon: 'bi-key', required: true, hint: 'Stores the hash' },
    { key: 'fullname_field', label: 'Full name', icon: 'bi-person-vcard', required: false, hint: 'Shown in the app' },
    { key: 'role_field', label: 'Role', icon: 'bi-shield-check', required: false, hint: 'Drives permissions' },
    { key: 'status_field', label: 'Status', icon: 'bi-toggle-on', required: false, hint: 'Active / blocked' }
];

// Column-name guesses for Auto-map, best match first.
const SECDB_GUESSES = {
    username_field: ['username', 'user_name', 'login', 'user', 'uname'],
    email_field: ['email', 'email_address', 'mail', 'e_mail'],
    password_field: ['password', 'passwd', 'pass', 'password_hash', 'pwd'],
    fullname_field: ['fullname', 'full_name', 'name', 'display_name', 'realname'],
    role_field: ['role', 'user_role', 'usertype', 'user_type', 'level', 'group'],
    status_field: ['status', 'active', 'is_active', 'enabled', 'state']
};

async function renderDatabaseSettings() {
    const container = document.getElementById('securityPageContainer');
    let settings = {
        db_auth_enabled: false,
        users_table: 'users',
        username_field: 'username',
        email_field: 'email',
        password_field: 'password',
        fullname_field: 'fullname',
        role_field: 'role',
        status_field: 'status',
        create_new_table: false,
        custom_fields: [],
        extra_fields: []
    };
    try {
        const res = await fetch(`${baseUrl}/project/${projectSlug}/security/settings`);
        const data = await res.json();
        if (data.success) settings = { ...settings, ...data.settings };
    } catch (e) { console.error(e); }
    window.securitySettings = settings;
    window.updateSecurityChrome?.('settings-database');

    const isEnabled = !!settings.db_auth_enabled;
    const useNew = !!settings.create_new_table;
    let columns = [];                       // columns of the selected table

    const adminReady = Boolean(isEnabled && settings.users_table && settings.username_field
        && settings.email_field && settings.password_field && settings.role_field);

    container.innerHTML = `
        <div class="secdb">
            <div class="secdb-master ${isEnabled ? 'is-on' : ''}" id="secdbMaster">
                <span class="secdb-master-icon"><i class="bi bi-shield-lock"></i></span>
                <div class="secdb-master-copy">
                    <strong>Database authentication</strong>
                    <p>Store user accounts in a database table instead of the built-in admin account.</p>
                </div>
                <label class="secdb-switch form-switch" for="enableDbAuth">
                    <span class="secdb-switch-state" id="secdbState">${isEnabled ? 'Enabled' : 'Disabled'}</span>
                    <input class="form-check-input" type="checkbox" role="switch" id="enableDbAuth" ${isEnabled ? 'checked' : ''}>
                </label>
            </div>

            <div id="authDisabledMsg" class="secdb-empty" style="${isEnabled ? 'display:none' : ''}">
                <i class="bi bi-person-lock"></i>
                <strong>Using the built-in admin account</strong>
                <span>Turn on database authentication to manage real users, roles and permissions.</span>
            </div>

            <div id="authSettings" style="${isEnabled ? '' : 'display:none'}">
                <div class="secdb">
                    <div class="secdb-panel">
                        <div class="secdb-panel-head">
                            <i class="bi bi-table"></i>
                            <div><h6>User table</h6><small>Where accounts live</small></div>
                        </div>
                        <div class="secdb-panel-body secdb" style="gap:11px">
                            <div class="secdb-source">
                                <label>
                                    <input type="radio" name="tableChoice" id="useExisting" value="existing" ${!useNew ? 'checked' : ''}>
                                    <span class="secdb-source-icon"><i class="bi bi-database-check"></i></span>
                                    <span class="secdb-source-copy"><strong>Use existing table</strong><span>Map columns you already have</span></span>
                                </label>
                                <label>
                                    <input type="radio" name="tableChoice" id="createNew" value="new" ${useNew ? 'checked' : ''}>
                                    <span class="secdb-source-icon"><i class="bi bi-database-add"></i></span>
                                    <span class="secdb-source-copy"><strong>Create new table</strong><span>PPPHP builds it for you</span></span>
                                </label>
                            </div>

                            <div id="existingTableDiv" style="${!useNew ? '' : 'display:none'}">
                                <div class="secdb-field">
                                    <label class="secdb-label" for="existingTableSelect">Select table</label>
                                    <select class="form-select form-select-sm" id="existingTableSelect">
                                        <option value="">Loading tables…</option>
                                    </select>
                                </div>
                            </div>

                            <div id="newTableDiv" class="secdb" style="${useNew ? 'gap:11px' : 'display:none'}">
                                <div class="secdb-field">
                                    <label class="secdb-label" for="newTableName">New table name</label>
                                    <input type="text" class="form-control form-control-sm" id="newTableName"
                                           value="${escapeHtml(settings.users_table || 'users')}" spellcheck="false">
                                </div>
                                <div class="secdb-field">
                                    <span class="secdb-label">Created automatically</span>
                                    <div class="secdb-defaults">
                                        ${['id', 'username', 'email', 'password', 'fullname', 'role', 'status', 'created_at', 'updated_at']
                                            .map(c => `<span class="secdb-chip">${c}</span>`).join('')}
                                    </div>
                                </div>
                                <div class="secdb-field">
                                    <span class="secdb-label">Extra columns (optional)</span>
                                    <div class="secdb-cols" id="customFieldsList"></div>
                                    <div>
                                        <button type="button" class="btn btn-sm btn-outline-secondary" id="addCustomField">
                                            <i class="bi bi-plus-lg"></i> Add column
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="secdb-panel" id="columnMapping" style="display:none">
                        <div class="secdb-panel-head">
                            <i class="bi bi-diagram-3"></i>
                            <div><h6>Field mapping</h6><small>Which column holds each identity field</small></div>
                            <div class="secdb-panel-head-actions">
                                <button type="button" class="btn btn-sm btn-outline-secondary" id="secdbAutoMap"
                                        title="Guess each mapping from the column names">
                                    <i class="bi bi-magic"></i> Auto-map
                                </button>
                            </div>
                        </div>
                        <div class="secdb-panel-body secdb" style="gap:11px">
                            <div class="secdb-map" id="secdbMapGrid"></div>
                            <div class="secdb-summary" id="secdbMapSummary"></div>
                        </div>
                    </div>

                    <section class="security-admin-provision ${adminReady ? 'is-ready' : ''}" aria-labelledby="adminProvisionTitle">
                        <span class="security-admin-provision-icon"><i class="bi bi-person-lock"></i></span>
                        <div class="security-admin-provision-copy">
                            <div><span class="security-page-kicker">Generated application access</span><h6 id="adminProvisionTitle">Administrator account</h6></div>
                            <p>Create a privileged user after saving the table and Username, Email, Password, and Role mappings.</p>
                            <small><i class="bi bi-box-arrow-in-right"></i> Generated app login: <code>/generated/auth/login.php</code> · use the mapped Username or Email and the password you set.</small>
                        </div>
                        <button type="button" class="btn btn-outline-primary" id="btnCreateAdminUser" ${adminReady ? '' : 'disabled'}
                                title="${adminReady ? 'Add an administrator to the configured generated-app user table' : 'Save a user table and required field mappings first'}">
                            <i class="bi bi-person-plus me-1"></i> Add admin user
                        </button>
                    </section>

                    <div class="secdb-foot">
                        <div class="secdb-summary" id="secdbFootSummary"></div>
                        <button class="btn btn-primary btn-sm" id="btnSaveSettings"><i class="bi bi-save me-1"></i> Save settings</button>
                    </div>
                </div>
            </div>
        </div>
    `;

    // ── element handles ─────────────────────────────────────────────────
    const master = document.getElementById('secdbMaster');
    const toggle = document.getElementById('enableDbAuth');
    const stateLbl = document.getElementById('secdbState');
    const authDiv = document.getElementById('authSettings');
    const disabledMsg = document.getElementById('authDisabledMsg');
    const radioExisting = document.getElementById('useExisting');
    const radioNew = document.getElementById('createNew');
    const existingDiv = document.getElementById('existingTableDiv');
    const newDiv = document.getElementById('newTableDiv');
    const existingSelect = document.getElementById('existingTableSelect');
    const mappingPanel = document.getElementById('columnMapping');
    const mapGrid = document.getElementById('secdbMapGrid');

    // ── field mapping ───────────────────────────────────────────────────
    function optionsFor(selected) {
        return '<option value="">— not mapped —</option>' + columns.map(c =>
            `<option value="${escapeHtml(c)}"${c === selected ? ' selected' : ''}>${escapeHtml(c)}</option>`).join('');
    }

    function coreItem(f) {
        const val = settings[f.key] || '';
        return `<div class="secdb-map-item" data-map-key="${f.key}">
            <div class="secdb-map-head">
                <i class="bi ${f.icon}"></i><strong>${f.label}</strong>
                <span class="secdb-tag ${f.required ? 'is-req' : 'is-opt'}">${f.required ? 'Required' : 'Optional'}</span>
            </div>
            <select class="form-select form-select-sm" data-map-for="${f.key}" title="${escapeHtml(f.hint)}">${optionsFor(val)}</select>
        </div>`;
    }

    // A user-added mapping: free-text field name + a column, both editable.
    function customItem(key = '', column = '') {
        return `<div class="secdb-map-item is-custom" data-map-custom="1">
            <div class="secdb-map-head">
                <i class="bi bi-plus-square-dotted"></i>
                <input class="secdb-map-key" placeholder="field name" value="${escapeHtml(key)}"
                       spellcheck="false" aria-label="Custom field name">
                <button type="button" class="secdb-map-x" title="Remove this mapping" aria-label="Remove"><i class="bi bi-x-lg"></i></button>
            </div>
            <select class="form-select form-select-sm">${optionsFor(column)}</select>
        </div>`;
    }

    function renderMapGrid() {
        // keep anything the user already typed when the column list reloads
        const keep = mapGrid.innerHTML ? readCustomRows() : (settings.extra_fields || []);
        mapGrid.innerHTML = SECDB_CORE_FIELDS.map(coreItem).join('')
            + keep.map(r => customItem(r.key, r.column)).join('')
            + `<button type="button" class="secdb-map-add" id="secdbAddMapping">
                   <i class="bi bi-plus-circle"></i><span>Add field</span>
               </button>`;
        wireMapGrid();
        refreshMapState();
    }

    function readCustomRows() {
        return [...mapGrid.querySelectorAll('[data-map-custom]')].map(el => ({
            key: el.querySelector('.secdb-map-key').value.trim(),
            column: el.querySelector('select').value
        })).filter(r => r.key || r.column);
    }

    function wireMapGrid() {
        mapGrid.querySelector('#secdbAddMapping')?.addEventListener('click', () => {
            const add = mapGrid.querySelector('#secdbAddMapping');
            add.insertAdjacentHTML('beforebegin', customItem());
            const item = add.previousElementSibling;
            wireItem(item);
            item.querySelector('.secdb-map-key').focus();
            refreshMapState();
        });
        mapGrid.querySelectorAll('.secdb-map-item').forEach(wireItem);
    }

    function wireItem(item) {
        item.querySelector('select')?.addEventListener('change', refreshMapState);
        item.querySelector('.secdb-map-key')?.addEventListener('input', refreshMapState);
        item.querySelector('.secdb-map-x')?.addEventListener('click', () => { item.remove(); refreshMapState(); });
    }

    // Visual state + the summary strips. Also surfaces duplicate mappings,
    // which silently break sign-in when two fields point at one column.
    function refreshMapState() {
        let mapped = 0, missing = 0;
        const used = {};
        SECDB_CORE_FIELDS.forEach(f => {
            const item = mapGrid.querySelector(`[data-map-key="${f.key}"]`);
            if (!item) return;
            const val = item.querySelector('select').value;
            item.classList.toggle('is-set', !!val);
            item.classList.toggle('is-missing', f.required && !val);
            if (val) { mapped++; used[val] = (used[val] || 0) + 1; }
            if (f.required && !val) missing++;
        });
        const customs = readCustomRows();
        customs.forEach(c => { if (c.column) used[c.column] = (used[c.column] || 0) + 1; });
        const dupes = Object.keys(used).filter(c => used[c] > 1);

        const summary = document.getElementById('secdbMapSummary');
        if (summary) {
            summary.innerHTML =
                `<span class="secdb-pill ${missing ? 'is-warn' : 'is-ok'}"><i class="bi ${missing ? 'bi-exclamation-triangle' : 'bi-check-circle'}"></i>
                   ${mapped} of ${SECDB_CORE_FIELDS.length} identity fields mapped</span>`
                + (customs.length ? `<span class="secdb-pill"><i class="bi bi-plus-square-dotted"></i> ${customs.length} custom</span>` : '')
                + (missing ? `<span class="secdb-pill is-warn"><i class="bi bi-asterisk"></i> ${missing} required field${missing > 1 ? 's' : ''} still unmapped</span>` : '')
                + (dupes.length ? `<span class="secdb-pill is-warn"><i class="bi bi-files"></i> column reused: ${dupes.map(escapeHtml).join(', ')}</span>` : '');
        }
        const foot = document.getElementById('secdbFootSummary');
        if (foot) {
            foot.innerHTML = radioNew.checked
                ? `<span class="secdb-pill"><i class="bi bi-database-add"></i> A new table will be created on save</span>`
                : (existingSelect.value
                    ? `<span class="secdb-pill ${missing ? 'is-warn' : 'is-ok'}"><i class="bi bi-table"></i> ${escapeHtml(existingSelect.value)}</span>`
                    : `<span class="secdb-pill is-warn"><i class="bi bi-table"></i> No table selected</span>`);
        }
    }

    function autoMap() {
        let hits = 0;
        const lower = columns.map(c => c.toLowerCase());
        SECDB_CORE_FIELDS.forEach(f => {
            const sel = mapGrid.querySelector(`[data-map-key="${f.key}"] select`);
            if (!sel || sel.value) return;                        // never overwrite a choice
            for (const guess of (SECDB_GUESSES[f.key] || [])) {
                const i = lower.indexOf(guess);
                if (i !== -1) { sel.value = columns[i]; hits++; break; }
            }
        });
        refreshMapState();
        pp.dialog.toast(hits ? 'success' : 'info',
            hits ? `Matched ${hits} field${hits > 1 ? 's' : ''} by column name.` : 'No obvious matches — pick the columns manually.');
    }

    // ── data loading ────────────────────────────────────────────────────
    async function loadTables() {
        try {
            const res = await fetch(`${baseUrl}/project/${projectSlug}/security/tables`);
            const data = await res.json();
            if (!data.success) return;
            existingSelect.innerHTML = '<option value="">— select a table —</option>'
                + data.tables.map(t => `<option value="${escapeHtml(t)}">${escapeHtml(t)}</option>`).join('');
            // Preselect the known table even if the project was last saved on
            // the "create new" branch — the developer is on this branch now, and
            // an empty picker with a table already configured just looks broken.
            if (settings.users_table) {
                existingSelect.value = settings.users_table;
                if (existingSelect.value) await loadColumns(existingSelect.value);
            }
            refreshMapState();
        } catch (e) { console.error(e); }
    }

    async function loadColumns(table) {
        if (!table) { mappingPanel.style.display = 'none'; refreshMapState(); return; }
        try {
            const res = await fetch(`${baseUrl}/project/${projectSlug}/security/columns`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ table })
            });
            const data = await res.json();
            if (!data.success) return;
            columns = data.columns || [];
            mappingPanel.style.display = '';
            renderMapGrid();
        } catch (e) { console.error(e); }
    }

    // ── new-table extra columns ─────────────────────────────────────────
    const customFieldsDiv = document.getElementById('customFieldsList');
    function addCustomField(name = '', type = 'VARCHAR(255)') {
        const row = document.createElement('div');
        row.className = 'secdb-col-row';
        row.innerHTML = `
            <input type="text" class="form-control form-control-sm" placeholder="column name" value="${escapeHtml(name)}" spellcheck="false">
            <input type="text" class="form-control form-control-sm" placeholder="VARCHAR(255)" value="${escapeHtml(type)}" spellcheck="false">
            <button class="secdb-map-x remove-custom-field" type="button" title="Remove column" aria-label="Remove"><i class="bi bi-x-lg"></i></button>`;
        row.querySelector('.remove-custom-field').addEventListener('click', () => row.remove());
        customFieldsDiv.appendChild(row);
        return row;
    }
    document.getElementById('addCustomField')?.addEventListener('click', () => addCustomField().querySelector('input').focus());
    (settings.custom_fields || []).forEach(f => addCustomField(f.name, f.type));

    // ── interactions ────────────────────────────────────────────────────
    toggle.addEventListener('change', async function () {
        const on = this.checked;
        master.classList.toggle('is-on', on);
        stateLbl.textContent = on ? 'Enabled' : 'Disabled';
        if (on) {
            authDiv.style.display = '';
            disabledMsg.style.display = 'none';
            await loadTables();
            return;
        }
        const result = await Swal.fire({
            title: 'Disable database authentication?',
            text: 'Do you want to delete all security tables (users, groups, permissions, etc.)? This action is irreversible.',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'Yes, delete tables',
            cancelButtonText: 'Keep tables'
        });
        if (result.isConfirmed) {
            await fetch(`${baseUrl}/project/${projectSlug}/security/drop-tables`, { method: 'POST' });
            pp.dialog.toast('success', 'Security tables deleted.');
        }
        await saveSettings({ db_auth_enabled: false });
        authDiv.style.display = 'none';
        disabledMsg.style.display = '';
    });

    radioExisting.addEventListener('change', () => {
        if (!radioExisting.checked) return;
        existingDiv.style.display = '';
        newDiv.style.display = 'none';
        loadTables();
        refreshMapState();
    });
    radioNew.addEventListener('change', () => {
        if (!radioNew.checked) return;
        existingDiv.style.display = 'none';
        newDiv.style.display = '';
        mappingPanel.style.display = 'none';       // a new table is mapped 1:1 by definition
        refreshMapState();
    });

    existingSelect.addEventListener('change', () => loadColumns(existingSelect.value));
    document.getElementById('secdbAutoMap')?.addEventListener('click', autoMap);

    document.getElementById('btnCreateAdminUser')?.addEventListener('click', async () => {
        const { value: admin } = await Swal.fire({
            title: 'Add generated-app administrator',
            width: 560,
            html: `<div class="security-admin-dialog">
                <p>This account is created in <strong>${escapeHtml(settings.users_table || 'the mapped user table')}</strong> with the privileged <code>superadmin</code> role.</p>
                <div class="security-admin-dialog-grid">
                    <label>Username<input id="newAdminUsername" class="swal2-input" autocomplete="off" placeholder="admin"></label>
                    <label>Email<input id="newAdminEmail" type="email" class="swal2-input" autocomplete="off" placeholder="admin@example.com"></label>
                    <label class="is-wide">Full name<input id="newAdminFullname" class="swal2-input" autocomplete="off" placeholder="Administrator"></label>
                    <label>Password<input id="newAdminPassword" type="password" class="swal2-input" autocomplete="new-password" placeholder="At least 8 characters"></label>
                    <label>Confirm password<input id="newAdminConfirm" type="password" class="swal2-input" autocomplete="new-password" placeholder="Repeat password"></label>
                </div>
                <div class="security-admin-dialog-note"><i class="bi bi-shield-check"></i> The password is hashed before storage and is never returned by the server.</div>
            </div>`,
            showCancelButton: true,
            confirmButtonText: 'Create administrator',
            focusConfirm: false,
            preConfirm: () => {
                const username = document.getElementById('newAdminUsername').value.trim();
                const email = document.getElementById('newAdminEmail').value.trim();
                const fullname = document.getElementById('newAdminFullname').value.trim();
                const password = document.getElementById('newAdminPassword').value;
                const confirm = document.getElementById('newAdminConfirm').value;
                if (!username || !email || !password) return Swal.showValidationMessage('Username, email, and password are required.');
                if (password.length < 8) return Swal.showValidationMessage('Use a password with at least 8 characters.');
                if (password !== confirm) return Swal.showValidationMessage('Passwords do not match.');
                return { username, email, fullname, password };
            }
        });
        if (!admin) return;
        try {
            const response = await fetch(`${baseUrl}/project/${projectSlug}/security/admin-user`, {
                method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(admin)
            });
            const result = await response.json();
            if (!response.ok || !result.success) throw new Error(result.message || 'Unable to create administrator.');
            await Swal.fire({
                icon: 'success', title: 'Administrator ready',
                html: `<div class="security-admin-success"><p>Use either identifier on the generated Login page:</p><dl><dt>Username</dt><dd>${escapeHtml(result.login?.username || admin.username)}</dd><dt>Email</dt><dd>${escapeHtml(result.login?.email || admin.email)}</dd><dt>Login page</dt><dd><code>/generated/auth/login.php</code></dd></dl><small>Keep the password in your password manager; it is not displayed again.</small></div>`,
                confirmButtonText: 'Done'
            });
        } catch (error) {
            pp.dialog.alert('Administrator not created', error.message || 'Unable to create administrator.', 'error');
        }
    });

    // ── save ────────────────────────────────────────────────────────────
    document.getElementById('btnSaveSettings').addEventListener('click', async () => {
        const enabled = toggle.checked;
        const useExisting = radioExisting.checked;
        let usersTable = '';
        let fieldMapping = {};
        let customFields = [];
        let extraFields = [];

        if (enabled) {
            if (useExisting) {
                usersTable = existingSelect.value;
                if (!usersTable) return pp.dialog.alert('Select a table', 'Choose the table that holds your user accounts.', 'error');

                SECDB_CORE_FIELDS.forEach(f => {
                    const sel = mapGrid.querySelector(`[data-map-key="${f.key}"] select`);
                    fieldMapping[f.key] = (sel && sel.value) ? sel.value : (f.required ? '' : null);
                });
                const missing = SECDB_CORE_FIELDS.filter(f => f.required && !fieldMapping[f.key]).map(f => f.label);
                if (missing.length) {
                    return pp.dialog.alert('Mapping incomplete', `Map a column for: ${missing.join(', ')}.`, 'error');
                }

                // user-added mappings — a name is required, blank rows are dropped
                const rows = readCustomRows();
                const seen = new Set();
                for (const r of rows) {
                    if (!r.key) return pp.dialog.alert('Name the field', 'Every custom mapping needs a field name.', 'error');
                    if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(r.key)) {
                        return pp.dialog.alert('Invalid field name', `"${r.key}" must start with a letter or underscore and contain only letters, numbers and underscores.`, 'error');
                    }
                    if (SECDB_CORE_FIELDS.some(f => f.label.toLowerCase() === r.key.toLowerCase() || f.key === r.key + '_field')) {
                        return pp.dialog.alert('Reserved name', `"${r.key}" is already a built-in identity field.`, 'error');
                    }
                    if (seen.has(r.key.toLowerCase())) return pp.dialog.alert('Duplicate field', `"${r.key}" is listed twice.`, 'error');
                    seen.add(r.key.toLowerCase());
                    if (r.column) extraFields.push({ key: r.key, column: r.column });
                }
            } else {
                usersTable = document.getElementById('newTableName').value.trim();
                if (!usersTable) return pp.dialog.alert('Name the table', 'Enter a name for the new user table.', 'error');
                if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(usersTable)) {
                    return pp.dialog.alert('Invalid table name', 'Use letters, numbers and underscores only.', 'error');
                }
                customFieldsDiv.querySelectorAll('.secdb-col-row').forEach(row => {
                    const inputs = row.querySelectorAll('input');
                    const name = inputs[0]?.value.trim();
                    if (name) customFields.push({ name, type: inputs[1]?.value.trim() || 'VARCHAR(255)' });
                });
                fieldMapping = {
                    username_field: 'username', email_field: 'email', password_field: 'password',
                    fullname_field: 'fullname', role_field: 'role', status_field: 'status'
                };
            }
        }

        const newSettings = {
            db_auth_enabled: enabled,
            users_table: usersTable,
            create_new_table: enabled ? !useExisting : false,
            custom_fields: customFields,
            extra_fields: extraFields,
            ...fieldMapping
        };
        try {
            const res = await fetch(`${baseUrl}/project/${projectSlug}/security/settings`, {
                method: 'POST', headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ settings: newSettings })
            });
            const data = await res.json();
            if (!data.success) return pp.dialog.alert('Error', data.message, 'error');

            // Existing-table auth reports what it did to bridge the table to the
            // runtime (added columns / a blocker that needs the developer's action).
            const bridge = data.bridge;
            if (bridge && bridge.ok === false) {
                await pp.dialog.alert('Action needed',
                    bridge.message || 'The existing table could not be linked to authentication.', 'warning');
            } else if (bridge && Array.isArray(bridge.added) && bridge.added.length) {
                await pp.dialog.alert('Existing table linked',
                    'Authentication is now linked to your table.\n\nThese missing column(s) were created automatically so sign-in, registration and password reset work:\n\n• '
                    + bridge.added.join('\n• '), 'success');
            } else if (bridge && bridge.view) {
                pp.dialog.toast('success', 'Authentication linked to your table.');
            } else {
                pp.dialog.toast('success', 'Settings saved.');
            }
            if (enabled && !useExisting) {
                const initRes = await fetch(`${baseUrl}/project/${projectSlug}/security/init`, { method: 'POST' });
                const initData = await initRes.json();
                pp.dialog.toast(initData.success ? 'success' : 'error',
                    initData.success ? 'Security tables created successfully.' : (initData.message || 'Failed to create tables'));
            }
            location.reload();
        } catch (e) { pp.dialog.toast('error', 'Network error'); }
    });

    // ── initial state ───────────────────────────────────────────────────
    if (isEnabled) await loadTables();
    refreshMapState();
    // NO auto-save here, deliberately. attachAutoSaveListeners() clicks Save
    // 600ms after any keystroke, and saving this page creates database tables
    // and reloads — which wiped half-typed field names and fired validation
    // dialogs mid-edit. Saving is explicit: the button below, or the toolbar
    // Save (both run the same handler via saveCurrentSecurityPage()).
}

async function saveSettings(newSettings) {
    const settings = { ...window.securitySettings, ...newSettings };
    try {
        await fetch(`${baseUrl}/project/${projectSlug}/security/settings`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ settings })
        });
        window.securitySettings = settings;
        window.updateSecurityChrome?.('settings-database');
    } catch (e) { console.error(e); }
}

window.renderDatabaseSettings = renderDatabaseSettings;
