// World pre-generation controls + live status display.
//
// We use the canonical config-then-start sequence rather than the all-in-one
// `chunky start <world> <shape> <cx> <cz> <r>` form because the all-in-one
// form's argument order varies between Chunky versions, and Brigadier silently
// prints the usage hint instead of erroring when it doesn't match.
//
// Status is parsed from Chunky's own log lines (re-broadcast by console.js as
// the `serverlog` custom event) -- no separate polling endpoint is needed.
//
// Chunky is intentionally only invoked from this panel -- it can punch holes
// in chunks if it crashes mid-run, so we don't want it ticking on its own.
"use strict";

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

async function send(cmd) {
    await apiJson("/api/command", { command: cmd });
}

async function startPregen(radius) {
    await send("chunky world minecraft:overworld");
    await send("chunky shape square");
    await send("chunky center 0 0");
    await send(`chunky radius ${radius}`);
    await send("chunky start");
}

// ─────────── status display ───────────

const els = {};

function setState(label, cssClass) {
    if (!els.state) return;
    els.state.textContent = label;
    els.state.className = "val " + cssClass;
    applyButtonStates(cssClass);
}

/// Enable/disable the Start/Pause/Resume/Cancel buttons based on the current
/// pregen state. Called whenever setState changes the displayed status.
function applyButtonStates(cssClass) {
    if (!els.btnStart) return;
    // Map the state CSS class to a logical state name. Default = idle.
    let s = "idle";
    if (cssClass === "pg-state-running")    s = "running";
    else if (cssClass === "pg-state-paused") s = "paused";
    else if (cssClass === "pg-state-cancelling") s = "cancelling";

    els.btnStart.disabled    = s !== "idle";
    els.btnPause.disabled    = s !== "running";
    els.btnContinue.disabled = s !== "paused";
    els.btnCancel.disabled   = !(s === "running" || s === "paused");
}

function resetMetrics() {
    if (!els.progressFill) return;
    els.progressFill.style.width = "0%";
    els.progressText.textContent = "--";
    els.chunks.textContent = "--";
    els.rate.textContent = "--";
    els.eta.textContent = "--";
}

// Parse a Chunky log line. Returns an object describing what changed, or null
// if this line isn't a Chunky message we recognise (or is for a different world).
//
// Chunky supports one task per world running concurrently, so we narrow our
// display to the overworld -- that's the only world the Start button targets,
// and it keeps the UI sane if someone kicks off other worlds via raw command.
//
// Real Chunky lines look roughly like:
//   "[Chunky] Task running for minecraft:overworld at 0,0. Progress: 12.50% (1234/9876 chunks), 45.20 cps, ETA: 0h 1m 30s"
//   "[Chunky] Task started for minecraft:overworld."
//   "[Chunky] Task stopped for minecraft:overworld."
//   "[Chunky] Task paused for minecraft:overworld."
//   "[Chunky] No task running."
const TARGET_WORLD = "minecraft:overworld";

function parseChunky(text) {
    if (!/Chunky|chunky/.test(text)) return null;

    // If a world is named, only react when it's the one we're tracking.
    // Lines without a world (e.g. "No task running.") fall through.
    const worldMatch = text.match(/(minecraft:[a-z_]+|the_nether|the_end|overworld)/i);
    if (worldMatch) {
        const w = worldMatch[1].toLowerCase();
        const normalised = w.startsWith("minecraft:") ? w : `minecraft:${w}`;
        if (normalised !== TARGET_WORLD) return null;
    }

    // State transitions
    if (/Task started/i.test(text))   return { state: "running" };
    if (/Task paused/i.test(text))    return { state: "paused" };
    if (/Task (stopped|cancelled|canceled|completed|finished)/i.test(text))
                                       return { state: "idle", clear: true };
    if (/No task running/i.test(text)) return { state: "idle", clear: true };

    // Progress line: try to extract whatever pieces are present
    const out = {};
    let matched = false;

    const pct = text.match(/(\d+(?:\.\d+)?)\s*%/);
    if (pct) { out.percent = parseFloat(pct[1]); matched = true; }

    const chunks = text.match(/(\d[\d,]*)\s*\/\s*(\d[\d,]*)\s*chunks?/i);
    if (chunks) {
        out.done  = chunks[1].replace(/,/g, "");
        out.total = chunks[2].replace(/,/g, "");
        matched = true;
    }

    const cps = text.match(/(\d+(?:\.\d+)?)\s*cps/i);
    if (cps) { out.cps = parseFloat(cps[1]); matched = true; }

    const eta = text.match(/ETA[:\s]+([^,)\n]+?)(?=[,)\n]|$)/i);
    if (eta) { out.eta = eta[1].trim(); matched = true; }

    if (matched) { out.state = "running"; return out; }
    return null;
}

function applyParsed(p) {
    if (!els.state) return;
    if (p.state === "running")  setState("Running",  "pg-state-running");
    if (p.state === "paused")   setState("Paused",   "pg-state-paused");
    if (p.state === "idle")     setState("Idle",     "pg-state-idle");
    if (p.clear) resetMetrics();

    if (p.percent != null) {
        els.progressFill.style.width  = `${Math.min(100, p.percent)}%`;
        els.progressText.textContent  = `${p.percent.toFixed(2)}%`;
    }
    if (p.done && p.total) {
        const fmt = n => Number(n).toLocaleString();
        els.chunks.textContent = `${fmt(p.done)} / ${fmt(p.total)}`;
    }
    if (p.cps  != null) els.rate.textContent = `${p.cps.toFixed(1)} chunks/s`;
    if (p.eta)          els.eta.textContent  = p.eta;
}

function setupCollapsible() {
    // Persist collapsed state per card across reloads via localStorage.
    document.querySelectorAll(".card.collapsible").forEach(card => {
        const id = card.id || "";
        const storageKey = id ? `bs-collapsed:${id}` : null;
        const startCollapsed = storageKey && localStorage.getItem(storageKey) === "1";
        if (!startCollapsed) card.classList.add("expanded");
        const toggle = card.querySelector(".collapsible-toggle");
        if (!toggle) return;
        toggle.addEventListener("click", () => {
            card.classList.toggle("expanded");
            if (storageKey) {
                localStorage.setItem(storageKey,
                    card.classList.contains("expanded") ? "0" : "1");
            }
        });
    });
}

export function setupPregen() {
    setupCollapsible();
    els.state        = document.getElementById("pgState");
    els.progressFill = document.getElementById("pgProgressFill");
    els.progressText = document.getElementById("pgProgressText");
    els.chunks       = document.getElementById("pgChunks");
    els.rate         = document.getElementById("pgRate");
    els.eta          = document.getElementById("pgEta");
    els.btnStart     = document.getElementById("pgStart");
    els.btnPause     = document.getElementById("pgPause");
    els.btnContinue  = document.getElementById("pgContinue");
    els.btnCancel    = document.getElementById("pgCancel");

    // Idle by default -- disable everything except Start.
    applyButtonStates("pg-state-idle");

    const radiusInput = document.getElementById("pgRadius");

    document.getElementById("pgStart").addEventListener("click", async () => {
        const r = parseInt(radiusInput.value, 10);
        if (!Number.isFinite(r) || r < 100) {
            alert("Enter a radius of at least 100 blocks.");
            return;
        }
        if (r > 20000 && !confirm(`Radius ${r} is large and may take hours. Continue?`)) {
            return;
        }
        try {
            setState("Starting…", "pg-state-running");
            resetMetrics();
            await startPregen(r);
        } catch (e) {
            setState("Idle", "pg-state-idle");
            alert(e.message);
        }
    });

    document.getElementById("pgPause").addEventListener("click", async () => {
        try { await send("chunky pause"); } catch (e) { alert(e.message); }
    });
    document.getElementById("pgContinue").addEventListener("click", async () => {
        try {
            await send("chunky continue");
            setState("Running", "pg-state-running");
        } catch (e) { alert(e.message); }
    });
    document.getElementById("pgCancel").addEventListener("click", async () => {
        if (!confirm("Cancel the current pre-generation run?")) return;
        try {
            setState("Cancelling…", "pg-state-cancelling");
            await send("chunky cancel");
        } catch (e) { alert(e.message); }
    });

    // Subscribe to the shared SSE re-broadcast from console.js
    document.addEventListener("serverlog", e => {
        const msg = e.detail?.m;
        if (typeof msg !== "string") return;
        const parsed = parseChunky(msg);
        if (parsed) applyParsed(parsed);
    });
}
