// World backup management -- list, create, restore, delete.
//
// Backups are server-online (no downtime) -- the daemon issues `save-all flush`
// + `save-off`, archives the world, then `save-on`. Restore *does* stop the
// server (it has to), and snapshots the current world to a `-prerestore-*` dir
// before extracting so a wrong restore is recoverable.
"use strict";

import { api } from "./api.js";

const els = {};
let lastSchedule = null;
let lastKeep = null;

function fmtSize(bytes) {
    if (bytes < 1024) return `${bytes} B`;
    if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
    if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
    return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}

function fmtDate(iso) {
    try { return new Date(iso).toLocaleString(); } catch { return iso; }
}

function fmtRelativeFuture(iso) {
    if (!iso) return "--";
    const target = new Date(iso).getTime();
    const ms = target - Date.now();
    if (ms <= 0) return "imminent";
    const sec = Math.round(ms / 1000);
    if (sec < 60)   return `in ${sec}s`;
    const min = Math.round(sec / 60);
    if (min < 60)   return `in ${min}m`;
    const hr  = Math.floor(min / 60);
    const rem = min % 60;
    if (hr < 24)    return rem ? `in ${hr}h ${rem}m` : `in ${hr}h`;
    const days = Math.floor(hr / 24);
    return `in ${days}d ${hr % 24}h`;
}

async function refresh() {
    let data;
    try { data = await api("/api/backup/list"); }
    catch { return; }

    els.dir.textContent      = data.dir || "--";
    // Server returns a human description ("Daily at 04:00", "Every 6 hours", "Disabled").
    els.schedule.textContent = data.description || (data.schedule ? `Daily at ${data.schedule}` : "Disabled");
    els.next.textContent     = data.nextRun ? fmtRelativeFuture(data.nextRun) : "--";
    els.keep.textContent     = data.keep != null ? `${data.keep} latest` : "--";
    lastSchedule = data.schedule || "";
    lastKeep = data.keep ?? 14;

    // Right-sidebar badge: count of backups
    const badge = document.getElementById("bkpBadge");
    if (badge) badge.textContent = data.backups?.length ? `${data.backups.length}` : "0";

    if (!data.backups || data.backups.length === 0) {
        els.list.innerHTML = '<li class="empty-state">No backups yet</li>';
        return;
    }

    els.list.innerHTML = data.backups.map(b => `
        <li class="backup-item">
            <div class="backup-meta">
                <div class="backup-name">${escape(b.name)}</div>
                <div class="backup-sub">${fmtSize(b.sizeBytes)} · ${fmtDate(b.createdAt)}</div>
            </div>
            <div class="backup-actions">
                <button class="ghost-btn bkp-restore" data-name="${escape(b.name)}">Restore</button>
                <button class="ghost-btn bkp-delete"  data-name="${escape(b.name)}">Delete</button>
            </div>
        </li>
    `).join("");
}

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

function showMsg(text, ok = false) {
    els.msg.className = ok ? "acct-msg ok" : "acct-msg";
    els.msg.textContent = text;
}

async function postJson(path, body) {
    const res = await fetch(path, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
    });
    return { ok: res.ok, status: res.status, body: await res.json().catch(() => ({})) };
}

export function setupBackup() {
    els.dir       = document.getElementById("backupDir");
    els.list      = document.getElementById("bkpList");
    els.create    = document.getElementById("bkpCreate");
    els.msg       = document.getElementById("bkpMsg");
    els.schedule  = document.getElementById("backupSchedule");
    els.next      = document.getElementById("backupNext");
    els.keep      = document.getElementById("backupKeep");
    els.editBtn   = document.getElementById("bkpEditSchedule");
    els.form      = document.getElementById("bkpScheduleForm");
    els.input     = document.getElementById("bkpScheduleInput");
    els.keepInput = document.getElementById("bkpKeepInput");
    els.saveBtn   = document.getElementById("bkpScheduleSave");
    els.cancelBtn = document.getElementById("bkpScheduleCancel");
    if (!els.create) return;

    els.editBtn?.addEventListener("click", () => {
        els.form.hidden = !els.form.hidden;
        if (!els.form.hidden) {
            els.input.value     = lastSchedule || "";
            els.keepInput.value = lastKeep ?? 14;
            els.input.focus();
        }
    });

    els.cancelBtn?.addEventListener("click", () => {
        els.form.hidden = true;
        showMsg("");
    });

    els.saveBtn?.addEventListener("click", async () => {
        const sched = els.input.value.trim();
        const keep  = parseInt(els.keepInput.value, 10);
        const r = await postJson("/api/backup/schedule", {
            schedule: sched,
            keep:     Number.isFinite(keep) ? keep : undefined,
        });
        if (!r.ok || r.body.ok === false) {
            showMsg(r.body.error || `Error ${r.status}`);
        } else {
            showMsg(sched ? `Schedule saved: daily at ${sched}` : "Schedule disabled.", true);
            els.form.hidden = true;
            refresh();
        }
    });

    els.create.addEventListener("click", async () => {
        const reason = prompt("Optional reason / label for this backup (e.g. 'pre-update'). Leave blank for none:");
        if (reason === null) return; // user cancelled
        showMsg("Creating backup -- this may take a minute on a large world...");
        els.create.disabled = true;
        const r = await postJson("/api/backup/create", { reason: reason.trim() || null });
        els.create.disabled = false;
        if (!r.ok || r.body.ok === false) {
            showMsg(r.body.error || `Error ${r.status}`);
        } else {
            showMsg(`Backup created: ${r.body.name} (${fmtSize(r.body.sizeBytes)})`, true);
            refresh();
        }
    });

    els.list.addEventListener("click", async e => {
        const restore = e.target.closest(".bkp-restore");
        const del     = e.target.closest(".bkp-delete");
        if (restore) {
            const name = restore.dataset.name;
            if (!confirm(`Restore from ${name}?\n\nServer will stop, current world is moved to a "-prerestore" folder for safety, then the backup is extracted and server starts again.`))
                return;
            showMsg("Restoring -- this stops the server...");
            const r = await postJson("/api/backup/restore", { name });
            if (!r.ok || r.body.ok === false) showMsg(r.body.error || `Error ${r.status}`);
            else showMsg("Restore complete. Server is starting.", true);
        }
        if (del) {
            const name = del.dataset.name;
            if (!confirm(`Delete backup ${name}? This cannot be undone.`)) return;
            const r = await postJson("/api/backup/delete", { name });
            if (!r.ok || r.body.ok === false) showMsg(r.body.error || `Error ${r.status}`);
            else { showMsg("Deleted.", true); refresh(); }
        }
    });

    refresh();
    // Backups don't change often; light poll to pick up new ones if scheduled
    // backups are added later, or just to refresh after an external mv/rm.
    setInterval(refresh, 30000);
}
