// ── State ─────────────────────────────────────────────────────────
let buildHistory = [];
let lastBuild = null;
let isRunning = false;
const $ = (id) => document.getElementById(id);

// ── Build lock ────────────────────────────────────────────────────
// A full-screen, click-blocking overlay shown while an app builds on the server
// and lands in this client's output/. It stops accidental double-clicks and
// navigation, and narrates the pipeline in plain words. There is no server-sent
// progress stream (the build is one request), so the steps advance on a gentle
// timer and HOLD at "verifying" until the real result arrives — then resolve.
const buildLock = (() => {
    const STEPS = [
        { k: 'collect',  t: 'Collecting your design' },
        { k: 'send',     t: 'Sending it to the build server' },
        { k: 'assemble', t: 'Assembling your app on the server' },
        { k: 'generate', t: 'Generating pages, forms & logic' },
        { k: 'verify',   t: 'Verifying every file' },
        { k: 'place',    t: 'Placing the finished app in your project' },
    ];
    const MSGS = [
        'Reading your pages, tables and events…',
        'Turning your design into real code…',
        'Wiring up forms, lists and dashboards…',
        'Applying your theme and settings…',
        'Almost there — putting it all together…',
    ];
    let el = null, stepTimer = null, msgTimer = null, cur = 0;
    const beforeUnload = (e) => { e.preventDefault(); e.returnValue = 'A build is in progress. Leaving now will cancel it.'; return e.returnValue; };

    function ensure() {
        if (el) return el;
        el = document.createElement('div');
        el.className = 'fx-buildlock';
        el.setAttribute('role', 'alertdialog');
        el.setAttribute('aria-live', 'assertive');
        el.innerHTML =
            '<div class="fx-bl-card">' +
              '<div class="fx-bl-orb"><i class="bi bi-hammer"></i></div>' +
              '<h2 class="fx-bl-title">Building your app</h2>' +
              '<p class="fx-bl-msg" id="fxBlMsg">Starting…</p>' +
              '<ul class="fx-bl-steps" id="fxBlSteps">' +
                STEPS.map((s) => `<li class="fx-bl-step" data-k="${s.k}"><span class="dot"></span><span>${s.t}</span></li>`).join('') +
              '</ul>' +
              '<div class="fx-bl-bar" id="fxBlBar"></div>' +
              '<p class="fx-bl-foot"><i class="bi bi-shield-lock me-1"></i>Please keep this tab open — your existing app stays live until the new one is ready.</p>' +
              '<button type="button" class="btn btn-outline-secondary btn-sm fx-bl-close" id="fxBlClose">Close</button>' +
            '</div>';
        document.body.appendChild(el);
        el.querySelector('#fxBlClose').addEventListener('click', () => hide());
        // Swallow keys that could dismiss/navigate while locked.
        el.addEventListener('keydown', (e) => { if (e.key === 'Escape') e.stopPropagation(); }, true);
        return el;
    }
    function paint(i) {
        cur = i;
        const steps = el.querySelectorAll('.fx-bl-step');
        steps.forEach((li, n) => {
            li.classList.toggle('done', n < i);
            li.classList.toggle('active', n === i);
        });
    }
    function setMsg(txt) {
        const m = el.querySelector('#fxBlMsg');
        m.classList.add('swap');
        setTimeout(() => { m.textContent = txt; m.classList.remove('swap'); }, 160);
    }
    function show() {
        ensure();
        // Full reset — a previous build may have left a done/failed visual state.
        el.classList.remove('finished');
        el.querySelector('#fxBlBar').className = 'fx-bl-bar';
        el.querySelector('.fx-bl-title').textContent = 'Building your app';
        el.querySelector('.fx-bl-orb').innerHTML = '<i class="bi bi-hammer"></i>';
        el.querySelectorAll('.fx-bl-step').forEach((li) => li.classList.remove('fail', 'done', 'active'));
        el.classList.add('on');
        document.body.style.overflow = 'hidden';
        window.addEventListener('beforeunload', beforeUnload);
        paint(0);
        setMsg(MSGS[0]);
        // Advance to step 0→1→2→3 then hold at 4 (verify) until done() lands.
        const seq = [700, 900, 1400, 2600, 3200]; // ms between steps up to 'verify'
        let i = 0;
        const tick = () => {
            i++;
            if (i >= STEPS.length - 1) { paint(STEPS.length - 2); return; } // hold at 'verify'
            paint(i);
            stepTimer = setTimeout(tick, seq[Math.min(i, seq.length - 1)]);
        };
        stepTimer = setTimeout(tick, seq[0]);
        let mi = 0;
        msgTimer = setInterval(() => { mi = (mi + 1) % MSGS.length; setMsg(MSGS[mi]); }, 2600);
    }
    function stopTimers() { clearTimeout(stepTimer); clearInterval(msgTimer); stepTimer = msgTimer = null; }
    function done(ok, msg) {
        if (!el) return;
        stopTimers();
        window.removeEventListener('beforeunload', beforeUnload);
        if (ok) {
            el.querySelectorAll('.fx-bl-step').forEach((li) => { li.classList.remove('active'); li.classList.add('done'); });
            el.querySelector('#fxBlBar').className = 'fx-bl-bar done';
            el.querySelector('.fx-bl-title').textContent = 'Build complete';
            el.querySelector('.fx-bl-orb').innerHTML = '<i class="bi bi-check-circle-fill" style="color:#16a34a"></i>';
            setMsg(msg || 'Your app is ready.');
            setTimeout(() => hide(), 950);   // fade out to reveal the "what's next" panel
        } else {
            const steps = el.querySelectorAll('.fx-bl-step');
            (steps[cur] || steps[steps.length - 1]).classList.add('fail');
            el.querySelector('#fxBlBar').className = 'fx-bl-bar fail';
            el.querySelector('.fx-bl-title').textContent = 'Build stopped';
            el.querySelector('.fx-bl-orb').innerHTML = '<i class="bi bi-exclamation-octagon-fill" style="color:#dc2626"></i>';
            setMsg(msg || 'The build could not be completed.');
            el.classList.add('finished');   // reveal the Close button
        }
    }
    function hide() {
        if (!el) return;
        stopTimers();
        window.removeEventListener('beforeunload', beforeUnload);
        el.classList.remove('on', 'finished');
        document.body.style.overflow = '';
    }
    return { show, done, hide };
})();

// ── Download helper ───────────────────────────────────────────────
// The server builds the file, streams it once, then deletes it.
function triggerDownload(url) {
    const a = document.createElement('a');
    a.href = url;
    a.rel = 'noopener';
    document.body.appendChild(a);
    a.click();
    a.remove();
}

// ── Action runner ─────────────────────────────────────────────────
// payload     — extra data sent with the action (e.g. demo credentials)
// previewWin  — a tab opened synchronously on click, so preview isn't blocked
async function runAction(action, payload = null, previewWin = null) {
    if (isRunning) {
        showToast('An action is already running. Please wait.', 'warning');
        if (previewWin) previewWin.close();
        return;
    }

    const card = document.querySelector(`[data-action="${action}"]`);
    if (card) card.classList.add('running');

    isRunning = true;
    $('actionIndicator').textContent = 'Running ' + action + '…';
    if (action === 'build') buildLock.show();   // lock the screen for the whole build

    try {
        const res = await fetch(`${baseUrl}/project/${projectSlug}/finished/action/${action}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload || {}),
        });
        const json = await res.json();

        // Pre-build gate: the project has event errors. Show the report instead
        // of a plain failure toast, and let the user fix or force the build.
        if (json.gated) {
            if (action === 'build') buildLock.hide();
            showBuildGate(json.report);
            return json;
        }

        // Resolve the build lock from the authoritative result before anything else.
        if (action === 'build') {
            if (json.success) buildLock.done(true, json.message || 'Your app is ready.');
            else buildLock.done(false, json.message || 'The build did not complete.');
        }

        if (json.success) {
            if (json.message) showToast(json.message, 'success');
        } else {
            showToast(json.message || 'Action failed.', 'danger');
        }

        // Generated file ready (ZIP / SQL) — start the download straight away.
        if (json.success && json.download) {
            triggerDownload(json.download);
        }

        // Preview — load the site into the tab we opened on click.
        if (action === 'preview') {
            if (json.success && json.url) {
                if (previewWin) previewWin.location.href = json.url;
                else window.open(json.url, '_blank', 'noopener');
            } else if (previewWin) {
                previewWin.close();
            }
        }

        // If health check, render results inline
        if (action === 'health-check' && json.checks) {
            renderHealthChecks(json.checks, json.status);
        }

        // If build or deploy, re-render from the authoritative server state
        if (['build', 'deploy-ftp', 'deploy-demo', 'deploy-git'].includes(action) && json.success && json.state) {
            lastBuild = json.state.lastBuild || lastBuild;
            buildHistory = json.state.history || buildHistory;
            renderHistory(buildHistory);
            renderStats(lastBuild);
            renderStatusBanner(lastBuild, buildHistory);
        }

        // After a successful build, show where it went and what to do next
        if (action === 'build' && json.success) {
            showBuildComplete(json);
        }

        return json;
    } catch (err) {
        if (previewWin) previewWin.close();
        if (action === 'build') buildLock.done(false, 'Network error — ' + err.message + '. Your existing app is unchanged.');
        showToast('Network error: ' + err.message, 'danger');
        return { success: false };
    } finally {
        isRunning = false;
        if (card) card.classList.remove('running');
        $('actionIndicator').textContent = '';
    }
}

// ── Toast ─────────────────────────────────────────────────────────
function showToast(message, type = 'success') {
    const container = $('actionFeedback');
    const icons = { success: 'bi-check-circle-fill', danger: 'bi-x-circle-fill', warning: 'bi-exclamation-triangle-fill', info: 'bi-info-circle-fill' };
    const bg = { success: 'text-bg-success', danger: 'text-bg-danger', warning: 'text-bg-warning', info: 'text-bg-info' };

    const toast = document.createElement('div');
    toast.className = `toast align-items-center ${bg[type] || 'text-bg-secondary'} border-0 show mb-1`;
    toast.role = 'alert';
    toast.innerHTML = `
        <div class="d-flex">
            <div class="toast-body small">
                <i class="bi ${icons[type] || 'bi-info-circle-fill'} me-1"></i> ${message}
            </div>
            <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
        </div>
    `;
    container.appendChild(toast);
    setTimeout(() => { toast.remove(); }, 6000);
}

// ── Pre-build gate: event errors block the build ───────────────────
function showBuildGate(report) {
    document.getElementById('ppBuildGate')?.remove();
    const esc = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
    const totals = (report && report.totals) || { error: 0, warning: 0, info: 0 };
    const events = ((report && report.events) || []).filter(e => e.counts && e.counts.error > 0);

    const cards = events.map(ev => {
        const rows = (ev.diagnostics || []).filter(d => d.severity === 'error').map(d =>
            `<div class="pbg-diag"><i class="bi bi-x-circle-fill"></i>
                <span class="pbg-loc">${d.line ? 'Line ' + d.line : ''}</span>
                <span>${esc(d.message)}</span></div>`).join('');
        return `<div class="pbg-card">
            <div class="pbg-card-head"><span class="pbg-lang ${esc(ev.lang)}">${esc((ev.lang || '').toUpperCase())}</span>
                <b>${esc(ev.label)}</b></div>${rows}</div>`;
    }).join('');

    const ov = document.createElement('div');
    ov.id = 'ppBuildGate';
    ov.className = 'pbg-ov';
    ov.innerHTML = `
        <div class="pbg-panel" role="dialog" aria-modal="true" aria-label="Build blocked">
            <div class="pbg-head">
                <span class="pbg-ic"><i class="bi bi-exclamation-triangle-fill"></i></span>
                <div>
                    <h2>Build blocked</h2>
                    <p>${totals.error} event ${totals.error === 1 ? 'error' : 'errors'} would ship broken code. Fix ${totals.error === 1 ? 'it' : 'them'} first, or build anyway.</p>
                </div>
                <button type="button" class="pbg-x" data-pbg-close aria-label="Close"><i class="bi bi-x-lg"></i></button>
            </div>
            <div class="pbg-body">${cards}</div>
            <div class="pbg-foot">
                <button type="button" class="btn btn-sm btn-outline-secondary" data-pbg-close>Cancel</button>
                <a class="btn btn-sm btn-primary" href="${baseUrl}/project/${projectSlug}/events"><i class="bi bi-pencil-square"></i> Fix in Events editor</a>
                <button type="button" class="btn btn-sm btn-outline-danger" data-pbg-force><i class="bi bi-exclamation-octagon"></i> Build anyway</button>
            </div>
        </div>`;
    document.body.appendChild(ov);

    const close = () => ov.remove();
    ov.addEventListener('click', (e) => {
        if (e.target === ov || e.target.closest('[data-pbg-close]')) { close(); return; }
        if (e.target.closest('[data-pbg-force]')) { close(); runAction('build', { force: true }); }
    });
    document.addEventListener('keydown', function esc2(e) {
        if (e.key === 'Escape') { close(); document.removeEventListener('keydown', esc2); }
    });
}

// ── Render health checks ──────────────────────────────────────────
function renderHealthChecks(checks, status) {
    const body = $('healthBody');
    const statusIcon = status === 'healthy' ? 'bi-check-circle-fill text-success' :
        status === 'warning' ? 'bi-exclamation-triangle-fill text-warning' :
        'bi-x-circle-fill text-danger';
    const statusLabel = status === 'healthy' ? 'All systems healthy' :
        status === 'warning' ? 'Some checks require attention' :
        'Critical issues found';

    body.innerHTML = `
        <div class="d-flex align-items-center gap-2 mb-2 pb-2 border-bottom">
            <i class="bi ${statusIcon} fs-5"></i>
            <span class="fw-semibold small">${statusLabel}</span>
        </div>
        ${checks.map(c => {
            const icon = c.status === 'pass' ? 'bi-check-circle-fill text-success' :
                c.status === 'warn' ? 'bi-exclamation-triangle-fill text-warning' :
                'bi-x-circle-fill text-danger';
            return `<div class="health-item">
                <i class="bi ${icon} health-icon"></i>
                <span class="health-name">${htmlEscape(c.name)}</span>
                <span class="health-value">${htmlEscape(c.value)}</span>
            </div>`;
        }).join('')}
    `;
}

// ── Render history table ──────────────────────────────────────────
function renderHistory(history) {
    const body = $('historyBody');
    const empty = $('historyEmpty');
    if (!body) return;

    if (!history || history.length === 0) {
        body.innerHTML = '';
        if (empty) empty.style.display = 'block';
        return;
    }
    if (empty) empty.style.display = 'none';

    const reversed = [...history].reverse();
    body.innerHTML = reversed.map((h, i) => {
        const idx = history.length - 1 - i;
        const statusIcon = h.status === 'success' ? 'bi-check-circle-fill text-success' :
            h.status === 'failed' ? 'bi-x-circle-fill text-danger' : 'bi-hourglass text-warning';
        const actionLabels = {
            'build': 'Build',
            'deploy-ftp': 'FTP Deploy',
            'deploy-demo': 'Demo Deploy',
            'deploy-git': 'Git Commit',
        };
        const label = actionLabels[h.action] || h.action;
        const time = h.time ? new Date(h.time).toLocaleString() : '—';
        const dur = h.duration ? h.duration + 's' : '—';
        return `<tr>
            <td><span class="badge bg-light text-dark">${htmlEscape(label)}</span></td>
            <td><i class="bi ${statusIcon}"></i></td>
            <td class="text-muted">${htmlEscape(time)}</td>
            <td class="text-muted">${htmlEscape(dur)}</td>
            <td><button class="btn btn-sm btn-outline-secondary py-0 px-1 history-detail" data-index="${idx}" title="Details"><i class="bi bi-info-circle"></i></button></td>
        </tr>`;
    }).join('');

    body.querySelectorAll('.history-detail').forEach(btn => {
        btn.addEventListener('click', () => {
            const entry = history[parseInt(btn.dataset.index)];
            if (!entry) return;
            Swal.fire({
                title: 'Build Detail',
                html: `<div class="text-start small">
                    <div><strong>Action:</strong> ${htmlEscape(entry.action)}</div>
                    <div><strong>Status:</strong> ${htmlEscape(entry.status)}</div>
                    <div><strong>Time:</strong> ${entry.time ? new Date(entry.time).toLocaleString() : '—'}</div>
                    <div><strong>Duration:</strong> ${entry.duration || '—'}s</div>
                    <div><strong>Message:</strong> ${entry.message || '—'}</div>
                </div>`,
                confirmButtonText: 'Close',
            });
        });
    });
}

// ── Render stats ──────────────────────────────────────────────────
function renderStats(build) {
    if (!build) {
        $('statLastBuild').textContent = '—';
        $('statFiles').textContent = '—';
        $('statDuration').textContent = '—';
        $('statSize').textContent = '—';
        return;
    }
    $('statLastBuild').textContent = build.time ? new Date(build.time).toLocaleDateString() : '—';
    $('statFiles').textContent = build.files ?? '—';
    $('statDuration').textContent = build.duration ? build.duration + 's' : '—';
    $('statSize').textContent = build.size || '—';
}

function renderStatusBanner(build, history) {
    const banner = $('statusBanner');
    const icon = $('statusIcon');
    const title = $('statusTitle');
    const detail = $('statusDetail');

    if (!build) {
        banner.className = 'status-banner never p-3 mb-3 d-flex align-items-center gap-3';
        icon.className = 'bi bi-info-circle-fill fs-4 text-secondary';
        title.textContent = 'Not Built Yet';
        detail.textContent = 'Configure your project in Tables, Pages and Output, then click Build Now.';
        return;
    }

    if (build.status === 'success') {
        banner.className = 'status-banner healthy p-3 mb-3 d-flex align-items-center gap-3';
        icon.className = 'bi bi-check-circle-fill fs-4 text-success';
        title.textContent = 'Build Successful';
        const loc = build.outputUrl || build.outputRelative || friendlyPath(build.outputDir);
        const dirLine = loc
            ? `<br><span class="text-muted">Location: ${build.outputUrl
                ? `<a href="${htmlEscape(build.outputUrl)}" target="_blank" rel="noopener">${htmlEscape(build.outputUrl)}</a>`
                : `<code style="word-break:break-all">${htmlEscape(loc)}</code>`}</span>`
            : '';
        detail.innerHTML = `Last built <strong>${new Date(build.time).toLocaleString()}</strong> &middot; ${build.files || 0} files generated in ${build.duration || 0}s${dirLine}`;
    } else {
        banner.className = 'status-banner failed p-3 mb-3 d-flex align-items-center gap-3';
        icon.className = 'bi bi-x-circle-fill fs-4 text-danger';
        title.textContent = 'Build Failed';
        detail.textContent = build.message || 'An error occurred during the last build.';
    }
}

// ── Utility ──────────────────────────────────────────────────────
function htmlEscape(str) {
    if (!str) return '';
    return String(str).replace(/&/g, '&amp;').replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

// Fallback for legacy build records that only stored an absolute path: show from
// the last `output/` segment onward (e.g. ".../public_html/output/app1" → "output/app1")
// so the server's absolute filesystem path is never surfaced. Prefer the
// server-provided outputRelative/outputUrl fields when present.
function friendlyPath(abs) {
    if (!abs) return '';
    const s = String(abs).replace(/\\/g, '/').replace(/\/+$/, '');
    const i = s.lastIndexOf('/output/');
    if (i !== -1) return s.slice(i + 1);
    const parts = s.split('/');
    return parts.slice(-2).join('/') || s;
}

// ── Build-complete dialog: output location + deployment next steps ──
function showBuildComplete(json) {
    const url = json.outputUrl || lastBuild?.outputUrl || '';
    const rel = json.outputRelative || lastBuild?.outputRelative || friendlyPath(json.outputDir || lastBuild?.outputDir || '');
    const es = json.eventScan || {};
    const reviewLink = `<a href="${baseUrl}/project/${projectSlug}/events">Review in Events →</a>`;
    let advisory = '';
    if (es.forcedErrors > 0) {
        advisory = `<div class="alert alert-danger py-2 px-2 small mb-2"><i class="bi bi-exclamation-octagon-fill me-1"></i>
            Built with <strong>${es.forcedErrors}</strong> event ${es.forcedErrors === 1 ? 'error' : 'errors'} you chose to override — the app may fail at runtime. ${reviewLink}</div>`;
    } else if (es.warnings > 0) {
        advisory = `<div class="alert alert-warning py-2 px-2 small mb-2"><i class="bi bi-exclamation-triangle-fill me-1"></i>
            Built successfully — but <strong>${es.warnings}</strong> ${es.warnings === 1 ? 'warning' : 'warnings'} worth a look in your events. ${reviewLink}</div>`;
    } else if (es.notes > 0) {
        advisory = `<div class="alert alert-info py-2 px-2 small mb-2"><i class="bi bi-info-circle-fill me-1"></i>
            <strong>${es.notes}</strong> ${es.notes === 1 ? 'note' : 'notes'} in your events (e.g. unused variables). ${reviewLink}</div>`;
    }
    Swal.fire({
        title: 'Build Complete',
        icon: 'success',
        width: 620,
        html: `<div class="text-start small">
            ${advisory}
            <div class="mb-2"><strong><i class="bi bi-folder2-open me-1"></i>Built to:</strong><br>
                ${url
                    ? `<a href="${htmlEscape(url)}" target="_blank" rel="noopener">${htmlEscape(url)}</a>`
                    : `<code style="word-break:break-all">${htmlEscape(rel)}</code>`}</div>
            <div class="mb-1"><strong>${json.files ?? '—'}</strong> files &middot; <strong>${json.duration ?? '—'}s</strong>
                &middot; standalone bundle (no CDN dependencies)</div>
            <hr class="my-2">
            <div class="fw-semibold mb-1">Deploy to your web server:</div>
            <ol class="ps-3 mb-2">
                <li>Upload the entire folder to your server's document root.</li>
                <li>Create the database: import <code>database.sql</code> (includes schema&nbsp;+&nbsp;seed data) via
                    <code>mysql -u USER -p &lt; database.sql</code> or phpMyAdmin&nbsp;→&nbsp;Import.</li>
                <li>Edit <code>config/database.php</code> with the remote MySQL credentials.</li>
                <li>Use the administrator created in <strong>Security → Database</strong> to sign in at <code>generated/auth/login.php</code>.</li>
            </ol>
            <div class="text-muted">Full instructions are in <code>INSTALL.md</code> inside the build folder.</div>
        </div>`,
        confirmButtonText: 'Got it',
        showDenyButton: true,
        denyButtonText: 'Open Folder',
        denyButtonColor: '#6366f1',
    }).then((r) => {
        if (r.isDenied) runAction('open-folder');
    });
}
