<?php
/**
 * cPanel Import — Select Site Groups
 *
 * Stage 2 of the two-phase import flow. The analyze pass (run-cpanel-import.php
 * mode=analyze) extracted the cpmove tarball, parsed userdata, and wrote
 * /var/lib/whp/cpanel-imports/<import_id>.manifest.json. cpanel_imports.status
 * is now 'awaiting_selection'. cpanel-import.php (the live progress page)
 * detected the transition and redirected the operator here.
 *
 * This page:
 *   - reads the manifest via api/cpanel-import-ajax.php?action=get_manifest
 *   - renders a per-site-group checklist (primary + additional domains +
 *     docroot + PHP version + DB list + mailbox count)
 *   - lets the operator select/deselect groups, with bulk affordances:
 *       * select all / select none
 *       * typeahead substring filter via TypeaheadMultiselect
 *   - posts to api/cpanel-import-ajax.php?action=confirm_selection with the
 *     chosen docroot keys; that endpoint persists the pick and kicks off the
 *     resume pass.
 *   - redirects back to cpanel-import.php so the live progress UI takes
 *     over once status flips from awaiting_selection to running.
 *
 * Defensive page gate (per feedback_whp_admin_page_checklist).
 * No inner <h1> (per feedback_whp_page_title_dup).
 * Inline script wrapped in DOMContentLoaded (per feedback_whp_inline_scripts_need_dom_ready).
 */

if (!defined('AUTH_USER')) {
    header('Location: login.php');
    exit();
}

// Defensive gate: this page is root-only. index.php already enforces this
// via $root_only_pages, but a future refactor of that list could regress
// access; double-check here.
if (AUTH_USER !== 'root') {
    echo '<div class="alert alert-danger">Access denied.</div>';
    return;
}

$pageTitle = 'cPanel Import — Select Site Groups';
$currentPage = 'cpanel-import-selector';

// The cpanel-import.php progress page passes the import_id in the URL when
// it redirects here. Validate format — uniqid('import_') only produces
// [A-Za-z0-9.] (period from microsecond precision when ENT-style).
$importId = $_GET['import_id'] ?? '';
if (!preg_match('/^import_[A-Za-z0-9.]+$/', $importId)) {
    echo '<div class="alert alert-danger">Invalid or missing import_id.</div>';
    return;
}
?>
<div class="container-fluid" data-import-id="<?= htmlspecialchars($importId) ?>">

    <div class="alert alert-info" id="intro-alert">
        <i class="bi bi-funnel"></i>
        The cPanel backup has been parsed. Choose which site groups to import,
        then click <strong>Import Selected</strong> to continue. All sites are
        selected by default. Sites you uncheck will be left out of this import.
    </div>

    <div id="loading-state" class="alert alert-secondary">
        <i class="fa fa-spinner fa-spin"></i> Loading parsed backup&hellip;
    </div>

    <div id="error-state" class="alert alert-danger d-none"></div>

    <div id="selector-body" class="d-none">

        <div class="row mb-3 g-2 align-items-center">
            <div class="col-md-6">
                <!-- Typeahead component renders here.
                     Search a substring of any primary or additional domain. -->
                <div id="domain-typeahead"></div>
            </div>
            <div class="col-md-6 text-md-end">
                <div class="btn-group" role="group" aria-label="Bulk selection">
                    <button type="button" class="btn btn-outline-primary btn-sm" id="select-all-btn">
                        <i class="bi bi-check2-all"></i> Select all
                    </button>
                    <button type="button" class="btn btn-outline-secondary btn-sm" id="select-none-btn">
                        <i class="bi bi-x-circle"></i> Select none
                    </button>
                </div>
                <div class="ms-2 d-inline-block text-muted small" id="selection-counter">0 of 0 selected</div>
            </div>
        </div>

        <div class="card mb-3">
            <div class="card-header d-flex justify-content-between align-items-center">
                <h5 class="mb-0">Site groups discovered in backup</h5>
                <span class="badge bg-secondary" id="totals-badge">&mdash;</span>
            </div>
            <div class="card-body p-0">
                <table class="table table-sm mb-0" id="site-groups-table">
                    <thead class="table-light">
                        <tr>
                            <th style="width: 40px;"></th>
                            <th>Primary domain</th>
                            <th>Doc root</th>
                            <th>Additional domains</th>
                            <th class="text-end">DBs</th>
                            <th class="text-end">Mailboxes</th>
                            <th style="width: 80px;"></th>
                        </tr>
                    </thead>
                    <tbody id="site-groups-tbody"></tbody>
                </table>
            </div>
        </div>

        <div class="card mb-3" id="orphan-dbs-card" hidden>
            <div class="card-header">
                <h6 class="mb-0">Databases not matched to any site group</h6>
            </div>
            <div class="card-body">
                <p class="text-muted small mb-2">
                    These databases were found in the cpmove but couldn't be
                    tied to a primary domain by the heuristic name match. They
                    will be imported alongside any selected site group. (Per-DB
                    selection is a planned v1.1 enhancement.)
                </p>
                <ul class="small mb-0" id="orphan-dbs-list"></ul>
            </div>
        </div>

        <div class="d-flex justify-content-between">
            <a href="?page=cpanel-import" class="btn btn-outline-secondary">
                <i class="bi bi-arrow-left"></i> Cancel
            </a>
            <button type="button" class="btn btn-primary" id="confirm-btn" disabled>
                <i class="bi bi-cloud-upload"></i> Import Selected
                <span id="confirm-btn-count" class="badge bg-light text-dark ms-1"></span>
            </button>
        </div>

    </div>
</div>

<!-- TypeaheadMultiselect is loaded globally by index.php (shipped 2026.05
     by the sites-page typeahead agent; also reused by mass-site-management).
     Defensive fallback handled in the script below if the class is missing. -->

<script>
document.addEventListener('DOMContentLoaded', function () {
    'use strict';

    const importId = document.querySelector('[data-import-id]').dataset.importId;
    const nonce = '<?= get_csrf_token() ?>';

    const loadingEl = document.getElementById('loading-state');
    const errorEl   = document.getElementById('error-state');
    const bodyEl    = document.getElementById('selector-body');
    const tbodyEl   = document.getElementById('site-groups-tbody');
    const totalsBadge = document.getElementById('totals-badge');
    const counterEl = document.getElementById('selection-counter');
    const confirmBtn = document.getElementById('confirm-btn');
    const confirmCount = document.getElementById('confirm-btn-count');
    const orphanCard = document.getElementById('orphan-dbs-card');
    const orphanList = document.getElementById('orphan-dbs-list');

    // The complete manifest as returned by the analyze pass. site_groups
    // entries are keyed by docroot which is also the value we POST back
    // in selected_docroots.
    let manifest = null;
    // docroot -> bool (selected). Default: every group selected.
    const selection = new Map();
    // Typeahead instance (initialized once we have site_groups).
    let typeahead = null;

    function showError(msg) {
        loadingEl.classList.add('d-none');
        bodyEl.classList.add('d-none');
        errorEl.classList.remove('d-none');
        errorEl.textContent = msg;
    }

    function selectedCount() {
        let n = 0;
        for (const v of selection.values()) if (v) n++;
        return n;
    }

    function refreshCounters() {
        const total = manifest && manifest.site_groups ? manifest.site_groups.length : 0;
        const sel = selectedCount();
        counterEl.textContent = sel + ' of ' + total + ' selected';
        confirmBtn.disabled = (sel === 0);
        confirmCount.textContent = sel > 0 ? '(' + sel + ')' : '';
    }

    function renderRows(visibleDocroots) {
        // visibleDocroots is a Set of docroot strings (typeahead filter
        // result). null means "show everything".
        tbodyEl.replaceChildren();
        const groups = (manifest && manifest.site_groups) ? manifest.site_groups : [];

        for (const g of groups) {
            if (visibleDocroots !== null && !visibleDocroots.has(g.docroot)) continue;

            const tr = document.createElement('tr');
            tr.dataset.docroot = g.docroot;
            if (!selection.get(g.docroot)) tr.classList.add('table-secondary');

            // Checkbox cell
            const cbCell = document.createElement('td');
            const cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.className = 'form-check-input';
            cb.checked = !!selection.get(g.docroot);
            cb.setAttribute('aria-label', 'Include ' + g.primary_domain);
            cb.addEventListener('change', function () {
                selection.set(g.docroot, cb.checked);
                tr.classList.toggle('table-secondary', !cb.checked);
                refreshCounters();
            });
            cbCell.appendChild(cb);
            tr.appendChild(cbCell);

            // Primary domain
            const primTd = document.createElement('td');
            const primStrong = document.createElement('strong');
            primStrong.textContent = g.primary_domain || '(unknown)';
            primTd.appendChild(primStrong);
            if (g.phpversion) {
                const phpBadge = document.createElement('span');
                phpBadge.className = 'badge bg-light text-dark ms-2';
                phpBadge.textContent = g.phpversion;
                primTd.appendChild(phpBadge);
            }
            tr.appendChild(primTd);

            // Docroot
            const drTd = document.createElement('td');
            const drCode = document.createElement('code');
            drCode.textContent = g.docroot;
            drTd.appendChild(drCode);
            tr.appendChild(drTd);

            // Additional domains
            const addTd = document.createElement('td');
            const adds = g.additional_domains || [];
            if (adds.length === 0) {
                const muted = document.createElement('span');
                muted.className = 'text-muted small';
                muted.textContent = '—';
                addTd.appendChild(muted);
            } else {
                addTd.appendChild(document.createTextNode(adds.slice(0, 3).join(', ')));
                if (adds.length > 3) {
                    const more = document.createElement('span');
                    more.className = 'text-muted small';
                    more.textContent = ' + ' + (adds.length - 3) + ' more';
                    addTd.appendChild(more);
                }
            }
            tr.appendChild(addTd);

            // DB count
            const dbTd = document.createElement('td');
            dbTd.className = 'text-end';
            const dbs = g.databases || [];
            dbTd.textContent = String(dbs.length);
            if (dbs.length > 0) {
                dbTd.title = dbs.join('\n');
            }
            tr.appendChild(dbTd);

            // Mailbox count
            const mbTd = document.createElement('td');
            mbTd.className = 'text-end';
            mbTd.textContent = String(g.mailbox_count || 0);
            tr.appendChild(mbTd);

            // Details toggle
            const detailsTd = document.createElement('td');
            const detailsBtn = document.createElement('button');
            detailsBtn.type = 'button';
            detailsBtn.className = 'btn btn-link btn-sm p-0';
            detailsBtn.textContent = 'Details';
            detailsBtn.addEventListener('click', function () {
                toggleDetails(g, tr);
            });
            detailsTd.appendChild(detailsBtn);
            tr.appendChild(detailsTd);

            tbodyEl.appendChild(tr);
        }
    }

    function toggleDetails(group, parentRow) {
        const existing = parentRow.nextElementSibling;
        if (existing && existing.classList.contains('details-row')) {
            existing.remove();
            return;
        }
        const dRow = document.createElement('tr');
        dRow.className = 'details-row table-light';
        const dCell = document.createElement('td');
        dCell.colSpan = 7;

        // Build details with DOM nodes only (no innerHTML of user data).
        const wrap = document.createElement('div');
        wrap.className = 'small';

        // All domains
        const allDoms = [group.primary_domain].concat(group.additional_domains || []);
        const domDl = document.createElement('div');
        domDl.appendChild(document.createTextNode('All domains: '));
        for (let i = 0; i < allDoms.length; i++) {
            const code = document.createElement('code');
            code.textContent = allDoms[i];
            domDl.appendChild(code);
            if (i < allDoms.length - 1) domDl.appendChild(document.createTextNode(', '));
        }
        wrap.appendChild(domDl);

        // Databases
        const dbDl = document.createElement('div');
        dbDl.appendChild(document.createTextNode('Databases (' + (group.databases || []).length + '): '));
        if (group.databases && group.databases.length > 0) {
            for (let i = 0; i < group.databases.length; i++) {
                const code = document.createElement('code');
                code.textContent = group.databases[i];
                dbDl.appendChild(code);
                if (i < group.databases.length - 1) dbDl.appendChild(document.createTextNode(', '));
            }
        } else {
            const muted = document.createElement('span');
            muted.className = 'text-muted';
            muted.textContent = '(none matched to this site group)';
            dbDl.appendChild(muted);
        }
        wrap.appendChild(dbDl);

        // Mailboxes
        const mbDl = document.createElement('div');
        mbDl.textContent = 'Mailboxes: ' + (group.mailbox_count || 0) + ' across all domains in this group.';
        wrap.appendChild(mbDl);

        dCell.appendChild(wrap);
        dRow.appendChild(dCell);
        parentRow.parentNode.insertBefore(dRow, parentRow.nextSibling);
    }

    async function loadManifest() {
        try {
            const resp = await fetch('/api/cpanel-import-ajax.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-CSRF-Token': nonce
                },
                body: new URLSearchParams({
                    action: 'get_manifest',
                    import_id: importId
                })
            });
            const result = await resp.json();
            if (!result.success) {
                showError(result.error || 'Failed to load manifest');
                return;
            }
            manifest = result.manifest;
            // Default selection: everything checked.
            selection.clear();
            for (const g of (manifest.site_groups || [])) {
                selection.set(g.docroot, true);
            }

            // Init typeahead. Items pool = every primary + additional domain
            // tagged with its docroot, so a substring match on a domain
            // surfaces (and unhides) the corresponding row.
            const items = [];
            const seenDr = new Set();
            for (const g of (manifest.site_groups || [])) {
                const allDoms = [g.primary_domain].concat(g.additional_domains || []);
                for (const d of allDoms) {
                    items.push({ value: d, label: d, meta: { docroot: g.docroot } });
                }
                seenDr.add(g.docroot);
            }
            // Render initial table (no filter)
            renderRows(null);
            refreshCounters();

            if (typeof TypeaheadMultiselect === 'function') {
                typeahead = new TypeaheadMultiselect(document.getElementById('domain-typeahead'), {
                    items: items,
                    placeholder: 'Filter by domain…',
                    emptyMessage: 'No domains match your filter.',
                    noItemsMessage: '(no domains discovered in backup)',
                    onChange: function (selectedValues) {
                        // selectedValues is the typeahead's own pick of items.
                        // We treat them as a SET of domain names — if any
                        // are checked, the selector restricts the table to
                        // rows whose group includes one of those domains.
                        // If none are checked, show all rows.
                        if (!selectedValues || selectedValues.length === 0) {
                            renderRows(null);
                            return;
                        }
                        const wantedDocroots = new Set();
                        const wantedDoms = new Set(selectedValues);
                        for (const g of (manifest.site_groups || [])) {
                            const allDoms = [g.primary_domain].concat(g.additional_domains || []);
                            for (const d of allDoms) {
                                if (wantedDoms.has(d)) { wantedDocroots.add(g.docroot); break; }
                            }
                        }
                        renderRows(wantedDocroots);
                    }
                });
            } else {
                // Component missing — degrade gracefully to a plain text filter.
                console.warn('TypeaheadMultiselect component not available; falling back to inert filter');
            }

            // Orphan DBs panel
            const orphans = manifest.orphan_databases || [];
            if (orphans.length > 0) {
                orphanCard.hidden = false;
                orphanList.replaceChildren();
                for (const dbName of orphans) {
                    const li = document.createElement('li');
                    const code = document.createElement('code');
                    code.textContent = dbName;
                    li.appendChild(code);
                    orphanList.appendChild(li);
                }
            }

            // Totals badge.
            const totalDb = manifest.total_db_count || 0;
            const totalMb = manifest.total_mailbox_count || 0;
            totalsBadge.textContent =
                (manifest.site_groups || []).length + ' sites · '
                + totalDb + ' DBs · '
                + totalMb + ' mailboxes';

            loadingEl.classList.add('d-none');
            bodyEl.classList.remove('d-none');
        } catch (err) {
            showError('Failed to load manifest: ' + err.message);
        }
    }

    document.getElementById('select-all-btn').addEventListener('click', function () {
        for (const g of (manifest.site_groups || [])) selection.set(g.docroot, true);
        // Re-render visible rows so checkboxes update.
        const visible = collectVisibleDocroots();
        renderRows(visible);
        refreshCounters();
    });

    document.getElementById('select-none-btn').addEventListener('click', function () {
        for (const g of (manifest.site_groups || [])) selection.set(g.docroot, false);
        const visible = collectVisibleDocroots();
        renderRows(visible);
        refreshCounters();
    });

    function collectVisibleDocroots() {
        // Recompute the current filter set from the live DOM rows. Returns
        // null if the table currently shows everything (typeahead is idle).
        if (!typeahead) return null;
        const sel = typeahead.getSelected ? typeahead.getSelected() : [];
        if (!sel || sel.length === 0) return null;
        const wantedDoms = new Set(sel);
        const wantedDr = new Set();
        for (const g of (manifest.site_groups || [])) {
            const allDoms = [g.primary_domain].concat(g.additional_domains || []);
            for (const d of allDoms) {
                if (wantedDoms.has(d)) { wantedDr.add(g.docroot); break; }
            }
        }
        return wantedDr;
    }

    document.getElementById('confirm-btn').addEventListener('click', async function () {
        const chosen = [];
        for (const [dr, on] of selection.entries()) if (on) chosen.push(dr);
        if (chosen.length === 0) {
            alert('Select at least one site group, or click Cancel to abort the import.');
            return;
        }
        confirmBtn.disabled = true;
        try {
            const resp = await fetch('/api/cpanel-import-ajax.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-CSRF-Token': nonce
                },
                body: new URLSearchParams({
                    action: 'confirm_selection',
                    import_id: importId,
                    selected_docroots: JSON.stringify(chosen)
                })
            });
            const result = await resp.json();
            if (!result.success) {
                alert('Failed to start import: ' + (result.error || 'unknown error'));
                confirmBtn.disabled = false;
                return;
            }
            // Redirect back to the live progress page; status is now 'running'
            // and the resume worker is in flight.
            window.location.href = '?page=cpanel-import&import_id=' + encodeURIComponent(importId);
        } catch (err) {
            alert('Failed to start import: ' + err.message);
            confirmBtn.disabled = false;
        }
    });

    loadManifest();
});
</script>
