// ══ Deploy client — state, helpers, rendering ══════════════════════
const $ = (id) => document.getElementById(id);

const D = {
    connections: [],
    dbConnections: [],
    options: { mode: 'changed', dbConnection: '__keep__', openInBrowser: true },
    history: [],
    stats: { builds: 0, uploads: 0 },
    lastBuild: null,
    outputDir: '',
    activeId: 0,
    connected: false,
    remotePath: '',
    localPath: '',
    lastOpenUrl: '',
    busy: false,
    connModal: null,
};

const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => (
    { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));

function humanBytes(n) {
    n = Number(n) || 0;
    if (n < 1024) return n + ' B';
    const u = ['KB', 'MB', 'GB', 'TB']; let i = -1;
    do { n /= 1024; i++; } while (n >= 1024 && i < u.length - 1);
    return (n < 10 ? n.toFixed(1) : Math.round(n)) + ' ' + u[i];
}
function fmtTime(t) { if (!t) return '—'; const d = new Date(t.replace(' ', 'T')); return isNaN(d) ? t : d.toLocaleString(); }

// ── API ────────────────────────────────────────────────────────────
// Every call is time-boxed so a stalled server can never hang the UI.
async function api(path, body, timeoutMs = 45000) {
    const ctrl = new AbortController();
    const timer = setTimeout(() => ctrl.abort(), timeoutMs);
    try {
        const res = await fetch(`${baseUrl}/project/${projectSlug}/deploy/${path}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(body || {}),
            signal: ctrl.signal,
        });
        return await res.json();
    } catch (e) {
        if (e.name === 'AbortError') {
            return { success: false, message: 'The server did not respond in time. It may be unreachable, or a firewall is blocking the data connection — try toggling Passive mode.' };
        }
        return { success: false, message: 'Network error: ' + e.message };
    } finally {
        clearTimeout(timer);
    }
}

// ── Toast ──────────────────────────────────────────────────────────
function toast(message, type = 'success') {
    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 el = document.createElement('div');
    el.className = `toast align-items-center ${bg[type] || 'text-bg-secondary'} border-0 show mb-1`;
    el.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>`;
    $('actionFeedback').appendChild(el);
    setTimeout(() => el.remove(), 6500);
}

function setStatus(main, right) {
    if (main !== undefined) $('statusMain').innerHTML = main;
    if (right !== undefined) $('statusRight').innerHTML = right;
}

// ── Connections dropdown ───────────────────────────────────────────
function activeConn() { return D.connections.find((c) => c.id === D.activeId) || null; }

function renderConnSelect() {
    const sel = $('connSelect');
    sel.innerHTML = '<option value="0">— No connection —</option>';
    D.connections.forEach((c) => {
        const o = document.createElement('option');
        o.value = c.id;
        o.textContent = `${c.name}  ·  ${c.protocol.toUpperCase()}  ·  ${c.host}`;
        sel.appendChild(o);
    });
    sel.value = D.activeId || 0;
    const has = !!activeConn();
    ['btnEditConn', 'btnDelConn', 'btnTestConn', 'btnConnect'].forEach((b) => { $(b).disabled = !has; });
}

function setConnected(state, text) {
    D.connected = state === true;
    const dot = $('connDot'), t = $('connStatusText');
    dot.className = 'dot ' + (state === 'busy' ? 'dot-busy' : state === true ? 'dot-on' : 'dot-off');
    t.textContent = text || (state === true ? 'Connected' : state === 'busy' ? 'Connecting…' : 'Disconnected');
    const btn = $('btnConnect');
    btn.innerHTML = D.connected
        ? '<i class="bi bi-x-lg"></i><span>Disconnect</span>'
        : '<i class="bi bi-lightning-charge-fill"></i><span>Connect</span>';
    btn.classList.toggle('btn-success', !D.connected);
    btn.classList.toggle('btn-outline-light', D.connected);
    ['btnRemoteUp', 'btnRemoteMkdir', 'btnRemoteSetTarget', 'btnRemoteRefresh'].forEach((b) => { $(b).disabled = !D.connected; });
}

// ── Live DB dropdown ───────────────────────────────────────────────
function renderDbSelect() {
    const sel = $('dbConnectionSelect');
    sel.innerHTML = '<option value="__keep__">Keep build settings</option>';
    D.dbConnections.forEach((c) => {
        const o = document.createElement('option');
        o.value = c.key;
        o.textContent = `${c.name} — ${c.dbname || '(no db)'} @ ${c.host || 'localhost'}`;
        sel.appendChild(o);
    });
    sel.value = D.options.dbConnection || '__keep__';
    if (sel.selectedIndex < 0) sel.value = '__keep__';
}

// ── Stats + status right ───────────────────────────────────────────
function renderStats() {
    $('dockStats').innerHTML =
        `<span>Builds <b>${D.stats.builds}</b></span><span>Uploads <b>${D.stats.uploads}</b></span>` +
        (D.lastBuild ? `<span>Last build <b>${D.lastBuild.files || 0}</b> files · ${esc(D.lastBuild.size || '—')}</span>` : '<span>No build yet</span>');
    const conn = activeConn();
    setStatus(undefined, conn
        ? `${esc(conn.protocol.toUpperCase())} · ${esc(conn.user || 'anonymous')}@${esc(conn.host)}:${conn.port}`
        : '');
}

// ── Transfer log ───────────────────────────────────────────────────
function clearLog() { $('transferLog').innerHTML = '<div class="dock-hint">Transfers will appear here.</div>'; }
function renderLog(lines) {
    const box = $('transferLog');
    if (!lines || !lines.length) { clearLog(); return; }
    const icon = { file: 'bi-arrow-up-circle-fill', error: 'bi-x-circle-fill', info: 'bi-info-circle-fill', plan: 'bi-dot' };
    box.innerHTML = lines.map((l) => {
        const cls = 'log-' + (l.type || 'info');
        const size = l.size ? `<span class="ls">${humanBytes(l.size)}</span>` : '';
        const text = l.path ? esc(l.path) : esc(l.message || '');
        return `<div class="log-line ${cls}"><span class="li"><i class="bi ${icon[l.type] || 'bi-dot'}"></i></span><span class="lp">${text}</span>${size}</div>`;
    }).join('');
    box.scrollTop = box.scrollHeight;
}

// ── History ────────────────────────────────────────────────────────
function renderHistory() {
    $('histBadge').textContent = D.history.length || '';
    const body = $('historyBody'), empty = $('historyEmpty');
    if (!D.history.length) { body.innerHTML = ''; empty.style.display = ''; return; }
    empty.style.display = 'none';
    body.innerHTML = D.history.map((h) => {
        const cls = h.status === 'success' ? 'h-success' : h.status === 'partial' ? 'h-partial' : 'h-failed';
        const mode = { 'changed': 'changed', 'all-project': 'project', 'all-output': 'output' }[h.mode] || h.mode;
        return `<tr>
            <td><span class="h-badge ${cls}">${esc(h.status)}</span></td>
            <td><strong>${esc(h.name || h.host)}</strong></td>
            <td class="h-host">${esc(h.protocol)}://${esc(h.host)}</td>
            <td>${h.uploaded} up${h.failed ? ' · ' + h.failed + ' failed' : ''}</td>
            <td>${humanBytes(h.bytes)}</td>
            <td>${mode}</td>
            <td>${h.db ? '<i class="bi bi-database"></i> ' + esc(h.db) : ''}</td>
            <td>${h.duration}s</td>
            <td class="text-muted">${fmtTime(h.time)}</td>
        </tr>`;
    }).join('');
}

// ── Diagnostics report ─────────────────────────────────────────────
function renderDiag(report, targetId = 'diagBody') {
    const box = $(targetId);
    if (!report) { box.innerHTML = '<div class="dock-hint">No diagnostics available.</div>'; return; }
    const stages = (report.stages || []).map((s) => `
        <div class="diag-stage">
            <span class="ds-ic ${s.ok ? 'ok' : 'bad'}"><i class="bi ${s.ok ? 'bi-check-circle-fill' : 'bi-x-circle-fill'}"></i></span>
            <span class="ds-name">${esc(s.stage)}</span>
            <span class="ds-detail">${esc(s.detail)}</span>
        </div>`).join('');
    box.innerHTML = `<div class="diag-report">
        <div class="diag-summary ${report.ok ? 'ok' : 'bad'}"><i class="bi ${report.ok ? 'bi-check-circle-fill' : 'bi-exclamation-triangle-fill'} me-1"></i>${esc(report.summary)}</div>
        ${stages}</div>`;
}

// ── Persist UI preferences ─────────────────────────────────────────
let saveTimer = null;
function persist() {
    clearTimeout(saveTimer);
    saveTimer = setTimeout(() => {
        api('save', { data: { options: D.options, activeId: D.activeId } }).catch(() => {});
    }, 300);
}
