(function () {
    const init = window.__TLS_INIT__;
    if (!init) return;

    const phaseEl = document.getElementById("tls-phase");
    const barEl = document.getElementById("tls-progress-bar");
    const logEl = document.getElementById("tls-log");
    const verbEl = document.getElementById("tls-status-verb");

    // Severity label tag. Matches SEVERITY_COLORS in schemas.py so the live
    // log and results page render identically.
    const SEV = {
        good:    { color: "bright-green",    label: "GOOD" },
        normal:  { color: "bright-blue",     label: "NORMAL" },
        notgood: { color: "bright-yellow",   label: "NOT GOOD" },
        bad:     { color: "bright-red",      label: "BAD" },
        serious: { color: "bright-purple",   label: "SERIOUS" },
        info:    { color: "light-grey-alt",  label: "INFO" },
    };

    let reconnectAttempts = 0;
    const MAX_RECONNECTS = 3;
    let ws = null;
    let closedByDone = false;

    function normalizeStep(s) {
        // "handshake_sim" → "handshake-sim" for display consistency with the
        // server-rendered results page.
        return String(s || "info").replace(/_/g, "-").toLowerCase();
    }

    function appendRow(category, body, detail, severity) {
        if (!logEl) return;
        const row = document.createElement("div");
        row.className = "tls-log-row";

        const cat = document.createElement("span");
        cat.className = "tls-log-cat text-light-grey-alt";
        cat.textContent = "[" + normalizeStep(category) + "]";
        row.appendChild(cat);

        const msg = document.createElement("span");
        msg.className = "tls-log-msg";
        if (severity && SEV[severity] && severity !== "info") {
            const sev = document.createElement("span");
            sev.className = "text-" + SEV[severity].color + " font-bold";
            sev.textContent = SEV[severity].label;
            msg.appendChild(sev);
            msg.appendChild(document.createTextNode(" "));
        }
        // Title is always in the default text color (white). Severity is
        // communicated by the coloured label tag, not by tinting the title.
        const msgText = document.createElement("span");
        msgText.className = "text-light-grey";
        msgText.textContent = body || "";
        msg.appendChild(msgText);
        row.appendChild(msg);

        const det = document.createElement("span");
        det.className = "tls-log-detail text-light-grey-alt";
        det.textContent = detail || "";
        row.appendChild(det);

        logEl.appendChild(row);
        logEl.scrollTop = logEl.scrollHeight;
    }

    function setProgress(value, phaseText) {
        if (barEl) barEl.style.width = `${Math.max(0, Math.min(1, value)) * 100}%`;
        if (phaseEl && phaseText) phaseEl.textContent = phaseText;
    }

    function markDone(rank, score) {
        if (verbEl) verbEl.textContent = "完了";
        if (barEl) barEl.style.width = "100%";
        if (phaseEl) phaseEl.textContent = `完了 (ランク ${rank}, スコア ${score})`;
    }

    function connect() {
        try {
            ws = new WebSocket(init.wsUrl);
        } catch (e) {
            scheduleReconnect();
            return;
        }
        ws.onmessage = (ev) => {
            let msg;
            try {
                msg = JSON.parse(ev.data);
            } catch (_) {
                return;
            }
            if (msg.type === "history") {
                (msg.entries || []).forEach((e) => appendRow(e.phase, e.detail, "", e.severity));
                if (msg.status === "done") {
                    closedByDone = true;
                    location.replace(init.resultsUrl);
                }
                return;
            }
            if (msg.type === "progress") {
                // Phase-level status: single body text with no detail
                appendRow(msg.phase, msg.detail, "", msg.severity);
                if (typeof msg.progress === "number") {
                    setProgress(msg.progress, msg.detail || msg.phase);
                }
                return;
            }
            if (msg.type === "finding") {
                const f = msg.finding || {};
                // Findings: show severity tag + title as the body, and detail in column 3.
                // The "[step]" prefix uses the phase slug (e.g. "handshake-sim")
                // rather than the coarser group ("vulnerabilities").
                const step = f.step || f.group || f.category || "info";
                appendRow(step, f.title || "", f.detail || "", f.severity || "info");
                return;
            }
            if (msg.type === "done") {
                closedByDone = true;
                markDone(msg.rank, msg.score);
                setTimeout(() => location.replace(msg.redirect || init.resultsUrl), 300);
                return;
            }
            if (msg.type === "error") {
                appendRow("error", msg.message || "engine failed", "", "serious");
                return;
            }
            if (msg.type === "started") {
                appendRow("init", msg.target || "", "", "info");
                return;
            }
        };
        ws.onclose = () => {
            if (!closedByDone) scheduleReconnect();
        };
        ws.onerror = () => {
            try { ws.close(); } catch (_) {}
        };
    }

    function scheduleReconnect() {
        if (closedByDone) return;
        if (reconnectAttempts >= MAX_RECONNECTS) {
            appendRow("ws", "WebSocket 接続が切断されました。ページをリロードしてください。", "", "bad");
            return;
        }
        const delay = Math.min(10_000, 1000 * Math.pow(2, reconnectAttempts));
        reconnectAttempts += 1;
        setTimeout(connect, delay);
    }

    window.addEventListener("beforeunload", () => {
        closedByDone = true;
        try { ws && ws.close(); } catch (_) {}
    });

    connect();
})();
