"use strict";
var BrowserAgentSidePanel;
(function (BrowserAgentSidePanel) {
    // ── Config fields (used by save/load only — no UI in sidepanel) ──
    const _configFields = {
        serverUrl: hiddenInput("serverUrl"),
        authToken: hiddenInput("authToken"),
        model: hiddenInput("model"),
        requestTimeoutMs: hiddenInput("requestTimeoutMs"),
        maxSteps: hiddenInput("maxSteps"),
        maxDomTextChars: hiddenInput("maxDomTextChars"),
        maxScreenshotWidth: hiddenInput("maxScreenshotWidth"),
        captureDom: hiddenCheckbox("captureDom"),
        captureScreenshot: hiddenCheckbox("captureScreenshot"),
        autopilot: hiddenCheckbox("autopilot")
    };
    // Cached live config (loaded once, mutated on save)
    let _liveConfig = null;
    const btnGoogleLogin = byId("btnGoogleLogin");
    const loginStatus = byId("loginStatus");
    const loginRow = byId("loginRow");
    const welcomeCard = byId("welcomeCard");
    const serviceAccountEmailEl = byId("serviceAccountEmail");
    const userPill = byId("userPill");
    const userPillEmail = byId("userPillEmail");
    const btnSignOut = byId("btnSignOut");
    const sheetPill = byId("sheetPill");
    const sheetPillLink = byId("sheetPillLink");
    const btnClearSheet = byId("btnClearSheet");
    const sheetInputGroup = byId("sheetInputGroup");
    const runControls = byId("runControls");
    const panelOutput = byId("panel-output");
    const spreadsheetUrl = byId("spreadsheetUrl");
    const accessStatus = byId("accessStatus"); // hidden carrier
    const grabberConfigEditor = byId("grabberConfig");
    const configStatus = byId("configStatus");
    const btnConfigDefaults = byId("btnConfigDefaults");
    const btnConfigCheck = byId("btnConfigCheck");
    const accessCheckMark = byId("accessCheckMark");
    const sourceStats = byId("sourceStats");
    const accessErrorDialog = byId("accessErrorDialog");
    const accessErrorMessage = byId("accessErrorMessage");
    const accessErrorActions = byId("accessErrorActions");
    const accessErrorEmail = byId("accessErrorEmail");
    const btnAccessErrorCopy = byId("btnAccessErrorCopy");
    const btnAccessErrorRetry = byId("btnAccessErrorRetry");
    const btnConfigCopy = byId("btnConfigCopy");
    const btnStartAgent = byId("btnStartAgent");
    const btnStopAgent = byId("btnStopAgent");
    const agentState = byId("agentState");
    const logCount = byId("logCount");
    const tabSummary = byId("tabSummary");
    const screenshotEl = byId("screenshotPreview");
    const logContainer = byId("log");
    const outputBox = byId("output");
    const backendStatus = byId("backendStatus");
    const backendDetail = byId("backendDetail");
    const websocketStatus = byId("websocketStatus");
    const websocketDetail = byId("websocketDetail");
    const frontendStatus = byId("frontendStatus");
    const frontendDetail = byId("frontendDetail");
    const systemDetails = byId("systemDetails");
    const skillSelect = byId("skillSelect");
    const skillDescription = byId("skillDescription");
    const skillParamsInput = byId("skillParams");
    const skillStatus = byId("skillStatus");
    const skillOutput = byId("skillOutput");
    const skillHistoryList = byId("skillHistoryList");
    const skillHostDialog = byId("skillHostDialog");
    const skillHostTitle = byId("skillHostTitle");
    const skillHostMessage = byId("skillHostMessage");
    const skillHostPattern = byId("skillHostPattern");
    const btnSkillHostOpen = byId("btnSkillHostOpen");
    let loadedSkills = [];
    // ── Workflow state ─────────────────────────────────────────────────────────
    let signedIn = false;
    let sheetVerified = false;
    let hasOutput = false;
    let backendChecked = false;
    function applyWorkflowState() {
        const fullyReady = backendChecked && signedIn && sheetVerified;
        welcomeCard.hidden = fullyReady;
        loginRow.hidden = signedIn;
        userPill.hidden = !signedIn;
        sheetInputGroup.hidden = !signedIn || sheetVerified;
        sheetPill.hidden = !signedIn || !sheetVerified;
        runControls.hidden = !signedIn || !sheetVerified;
        panelOutput.hidden = !signedIn || !sheetVerified || !hasOutput;
        btnStartAgent.disabled = !(signedIn && sheetVerified);
    }
    async function loadServiceAccountEmail() {
        try {
            const response = await sendMessage({ type: "GET_SERVICE_ACCOUNT" });
            if (response.ok && response.email) {
                serviceAccountEmailEl.textContent = response.email;
                serviceAccountEmailEl.classList.remove("error");
                backendChecked = true;
            }
            else {
                serviceAccountEmailEl.textContent = response.error || "backend unavailable";
                serviceAccountEmailEl.classList.add("error");
                backendChecked = false;
            }
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            serviceAccountEmailEl.textContent = `backend unavailable (${message})`;
            serviceAccountEmailEl.classList.add("error");
            backendChecked = false;
        }
        applyWorkflowState();
    }
    // All log events kept for "Copy as JSON"
    const allLogEvents = [];
    let systemSocket = null;
    let systemSocketUrl = "";
    let reconnectTimer;
    let latestHealth = null;
    let lastSocketMessageAt;
    let systemClientId;
    let statusReportTimer;
    let currentBackendRunId;
    document.addEventListener("DOMContentLoaded", () => {
        wireCollapsible();
        wireEvents();
        wireDeveloperMode();
        renderAppVersion();
        initializeSystem();
    });
    function renderAppVersion() {
        const el = document.getElementById("appVersion");
        if (!el)
            return;
        const version = chrome.runtime.getManifest?.()?.version;
        el.textContent = version ? `v${version}` : "";
    }
    // ── Developer mode ──────────────────────────────────────────────────────────
    function applyDeveloperMode(enabled) {
        document.body.classList.toggle("dev-mode", enabled);
    }
    async function wireDeveloperMode() {
        const stored = await chrome.storage.local.get("developerMode");
        applyDeveloperMode(Boolean(stored.developerMode));
        chrome.storage.onChanged.addListener((changes, area) => {
            if (area === "local" && changes.developerMode) {
                applyDeveloperMode(Boolean(changes.developerMode.newValue));
            }
        });
    }
    chrome.runtime.onMessage.addListener((message) => {
        if (message.type === "RUN_EVENT" && message.event) {
            appendLog(message.event);
            handleRunEventOutput(message.event);
            if (message.event.title === "Run idle") {
                agentState.textContent = "Idle";
            }
            else if (message.event.title === "Run started") {
                agentState.textContent = "Running";
            }
            renderFrontendStatus();
            scheduleFrontendStatusReport();
        }
    });
    // ── Collapsible panels ──────────────────────────────────────────────────────
    function wireCollapsible() {
        document.querySelectorAll(".panel-header").forEach((header) => {
            header.addEventListener("click", (e) => {
                // Don't collapse when clicking log-action buttons inside the header
                if (e.target.closest(".log-actions"))
                    return;
                const targetId = header.dataset.target;
                if (!targetId)
                    return;
                const panel = document.getElementById(targetId);
                if (!panel)
                    return;
                const collapsed = panel.classList.toggle("collapsed");
                header.setAttribute("aria-expanded", String(!collapsed));
            });
        });
    }
    // ── Events ──────────────────────────────────────────────────────────────────
    function wireEvents() {
        byId("openOptions").addEventListener("click", () => chrome.runtime.openOptionsPage());
        btnGoogleLogin.addEventListener("click", handleGoogleLogin);
        btnStartAgent.addEventListener("click", handleStartAgent);
        btnStopAgent.addEventListener("click", handleStopAgent);
        btnConfigCheck.addEventListener("click", handleCheckAccess);
        btnConfigDefaults.addEventListener("click", handleResetConfig);
        btnConfigCopy.addEventListener("click", handleCopyConfig);
        btnAccessErrorRetry.addEventListener("click", () => {
            accessErrorDialog.close();
            handleCheckAccess();
        });
        btnAccessErrorCopy.addEventListener("click", () => {
            const email = accessErrorEmail.textContent?.trim();
            if (email) {
                navigator.clipboard.writeText(email).catch(() => undefined);
                btnAccessErrorCopy.textContent = "Copied";
                setTimeout(() => { btnAccessErrorCopy.textContent = "Copy"; }, 1200);
            }
        });
        btnSignOut.addEventListener("click", handleSignOut);
        btnClearSheet.addEventListener("click", handleClearSheet);
        spreadsheetUrl.addEventListener("input", () => {
            accessCheckMark.hidden = true;
            sourceStats.hidden = true;
            if (sheetVerified) {
                sheetVerified = false;
                applyWorkflowState();
            }
            handleSpreadsheetUrlInput();
        });
        grabberConfigEditor.addEventListener("input", () => {
            const result = parseGrabberConfig(grabberConfigEditor.value);
            renderConfigStatus(result);
            if (result.ok) {
                chrome.storage.local.set({ grabberConfigJson: grabberConfigEditor.value });
                if (spreadsheetUrl.value !== result.config.spreadsheetUrl) {
                    spreadsheetUrl.value = result.config.spreadsheetUrl;
                    accessCheckMark.hidden = true;
                    sourceStats.hidden = true;
                }
            }
        });
        byId("copyLogs").addEventListener("click", copyLogsPlain);
        byId("copyLogsJson").addEventListener("click", copyLogsJson);
        byId("refreshSystemStatus").addEventListener("click", () => refreshSystemStatus());
        byId("btnClearOutput")?.addEventListener("click", () => {
            outputBox.innerHTML = "";
        });
        byId("btnCopyOutput")?.addEventListener("click", () => {
            navigator.clipboard.writeText(outputBox.innerText);
        });
        document.querySelectorAll("[data-manual]").forEach((button) => {
            button.addEventListener("click", () => runManual(button.dataset.manual || ""));
        });
        byId("skillExecute").addEventListener("click", runSkillExecute);
        byId("skillHistory").addEventListener("click", runSkillHistory);
        skillSelect.addEventListener("change", onSkillSelectChange);
        btnSkillHostOpen.addEventListener("click", () => {
            const url = btnSkillHostOpen.dataset.openUrl;
            if (url)
                chrome.tabs.create({ url });
            skillHostDialog.close();
        });
    }
    async function initializeSystem() {
        await loadConfig();
        await loadAgentSettings();
        await loadServiceAccountEmail();
        renderFrontendStatus();
        connectSystemSocket();
        await refreshSystemStatus();
        await loadSkillsList();
    }
    // ── Config (invisible fields in DOM so shared.ts logic still works) ─────────
    async function loadConfig() {
        const response = await sendMessage({ type: "GET_CONFIG" });
        if (response.ok) {
            _liveConfig = response.config;
            appendLog({ level: "info", title: "Config loaded", message: "Settings are ready." });
        }
    }
    const DEFAULT_GRABBER_CONFIG = {
        spreadsheetUrl: "",
        sourceSheetName: "Test2SourceData",
        columns: {
            searchTerm: "Src Nom Prénom",
            location: "Location",
            extracted: "Extracted",
            lastName: "Nom",
            firstName: "Prénom"
        },
        batchSize: 10,
        scrapeTimeoutMs: 30000,
        cloudflareGraceMs: 300000,
        interRowDelayMs: 2000,
        extractSkillSlug: "pagesjaunes-extract-listings"
    };
    function defaultConfigJson() {
        return JSON.stringify(DEFAULT_GRABBER_CONFIG, null, 2);
    }
    function parseGrabberConfig(text) {
        let raw;
        try {
            raw = JSON.parse(text);
        }
        catch (error) {
            return { ok: false, error: error instanceof Error ? error.message : String(error) };
        }
        if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
            return { ok: false, error: "Configuration must be a JSON object." };
        }
        const merged = { ...DEFAULT_GRABBER_CONFIG, ...raw };
        merged.columns = { ...DEFAULT_GRABBER_CONFIG.columns, ...(raw.columns || {}) };
        if (typeof merged.spreadsheetUrl !== "string") {
            return { ok: false, error: "spreadsheetUrl must be a string." };
        }
        for (const key of ["batchSize", "scrapeTimeoutMs", "cloudflareGraceMs", "interRowDelayMs"]) {
            if (typeof merged[key] !== "number" || !Number.isFinite(merged[key]) || merged[key] < 0) {
                return { ok: false, error: `${key} must be a non-negative number.` };
            }
        }
        if (!merged.sourceSheetName) {
            return { ok: false, error: "sourceSheetName is required." };
        }
        if (!merged.extractSkillSlug) {
            return { ok: false, error: "extractSkillSlug is required." };
        }
        for (const [k, v] of Object.entries(merged.columns)) {
            if (typeof v !== "string" || !v) {
                return { ok: false, error: `columns.${k} must be a non-empty string.` };
            }
        }
        return { ok: true, config: merged };
    }
    function extractSpreadsheetId(urlOrId) {
        const trimmed = (urlOrId || "").trim();
        if (!trimmed)
            return "";
        const match = trimmed.match(/\/spreadsheets\/d\/([a-zA-Z0-9-_]+)/);
        return match ? match[1] : trimmed;
    }
    function renderConfigStatus(result) {
        if (result.ok) {
            configStatus.textContent = "Configuration is valid.";
            configStatus.className = "status mono ok";
            grabberConfigEditor.classList.remove("invalid");
        }
        else {
            configStatus.textContent = `Invalid JSON: ${result.error}`;
            configStatus.className = "status mono error";
            grabberConfigEditor.classList.add("invalid");
        }
    }
    async function loadAgentSettings() {
        const stored = await chrome.storage.local.get([
            "grabberConfigJson",
            "agentSpreadsheetUrl",
            "loginEmail",
            "sheetAccessVerified",
            "verifiedSpreadsheetId",
            "verifiedSpreadsheetTitle",
            "verifiedSpreadsheetUrl"
        ]);
        let json = stored.grabberConfigJson;
        if (!json) {
            // Migrate from the old single-URL setting.
            const url = stored.agentSpreadsheetUrl || "";
            json = JSON.stringify({ ...DEFAULT_GRABBER_CONFIG, spreadsheetUrl: url }, null, 2);
        }
        grabberConfigEditor.value = json;
        const result = parseGrabberConfig(json);
        renderConfigStatus(result);
        if (result.ok)
            spreadsheetUrl.value = result.config.spreadsheetUrl;
        const email = stored.loginEmail;
        if (email) {
            signedIn = true;
            userPillEmail.textContent = email;
            loginStatus.textContent = `Logged in as ${email}`;
        }
        if (signedIn && stored.sheetAccessVerified === true) {
            const title = stored.verifiedSpreadsheetTitle || "Spreadsheet";
            const sheetUrl = stored.verifiedSpreadsheetUrl || "";
            const verifiedId = stored.verifiedSpreadsheetId;
            sheetVerified = true;
            sheetPillLink.textContent = title;
            if (sheetUrl)
                sheetPillLink.href = sheetUrl;
            accessCheckMark.hidden = false;
            if (verifiedId)
                loadSourceStats(verifiedId);
        }
        applyWorkflowState();
    }
    async function saveAgentSettings(spreadsheetUrlValue) {
        spreadsheetUrl.value = spreadsheetUrlValue;
        const result = parseGrabberConfig(grabberConfigEditor.value);
        if (!result.ok) {
            // Editor is unparseable; rewrite from defaults + URL.
            grabberConfigEditor.value = JSON.stringify({ ...DEFAULT_GRABBER_CONFIG, spreadsheetUrl: spreadsheetUrlValue }, null, 2);
        }
        else if (result.config.spreadsheetUrl !== spreadsheetUrlValue) {
            const next = { ...result.config, spreadsheetUrl: spreadsheetUrlValue };
            grabberConfigEditor.value = JSON.stringify(next, null, 2);
        }
        renderConfigStatus(parseGrabberConfig(grabberConfigEditor.value));
        await chrome.storage.local.set({ grabberConfigJson: grabberConfigEditor.value });
    }
    function handleResetConfig() {
        grabberConfigEditor.value = defaultConfigJson();
        renderConfigStatus(parseGrabberConfig(grabberConfigEditor.value));
        chrome.storage.local.set({ grabberConfigJson: grabberConfigEditor.value });
        appendLog({ level: "info", title: "Config reset", message: "Restored default Grabber configuration." });
    }
    async function handleCopyConfig() {
        try {
            await navigator.clipboard.writeText(grabberConfigEditor.value);
            appendLog({ level: "success", title: "Config copied", message: "Configuration JSON copied to clipboard." });
        }
        catch (error) {
            appendLog({ level: "error", title: "Copy failed", message: error instanceof Error ? error.message : String(error) });
        }
    }
    async function handleGoogleLogin() {
        loginStatus.textContent = "Logging in...";
        chrome.identity.getAuthToken({ interactive: true }, async (token) => {
            if (chrome.runtime.lastError || !token) {
                loginStatus.textContent = "Login Failed";
                appendLog({ level: "error", title: "Login Error", message: chrome.runtime.lastError?.message || "No token" });
                return;
            }
            try {
                await ensureConfig();
                const response = await fetch(BrowserAgent.endpointUrl(_liveConfig, "/v1/auth/login"), {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({ token })
                });
                if (!response.ok) {
                    const err = await response.json().catch(() => ({}));
                    throw new Error(err.error?.message || `HTTP ${response.status}`);
                }
                const data = await response.json();
                loginStatus.textContent = `Logged in as ${data.email}`;
                userPillEmail.textContent = data.email;
                signedIn = true;
                await chrome.storage.local.set({ loginEmail: data.email });
                // Persist the OAuth access token as the backend bearer so subsequent
                // /v1/* calls clear the server-side OAuth + domain whitelist gate.
                if (_liveConfig) {
                    _liveConfig.authToken = token;
                    await chrome.storage.local.set({ [BrowserAgent.CONFIG_KEY]: _liveConfig });
                }
                if (data.spreadsheetId) {
                    await saveAgentSettings(data.spreadsheetId);
                }
                applyWorkflowState();
                appendLog({ level: "info", title: "Login Success", message: "Successfully authenticated with backend." });
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                loginStatus.textContent = "Login Failed (Backend)";
                appendLog({ level: "error", title: "Login Error", message });
            }
        });
    }
    async function handleSignOut() {
        chrome.identity.getAuthToken({ interactive: false }, (token) => {
            if (token) {
                chrome.identity.removeCachedAuthToken({ token: token }, () => undefined);
            }
        });
        signedIn = false;
        sheetVerified = false;
        hasOutput = false;
        userPillEmail.textContent = "";
        sheetPillLink.textContent = "";
        sheetPillLink.removeAttribute("href");
        spreadsheetUrl.value = "";
        accessCheckMark.hidden = true;
        sourceStats.hidden = true;
        loginStatus.textContent = "Not logged in";
        await chrome.storage.local.remove([
            "loginEmail",
            "sheetAccessVerified",
            "verifiedSpreadsheetId",
            "verifiedSpreadsheetTitle",
            "verifiedSpreadsheetUrl",
            "agentSpreadsheetUrl",
            "grabberConfigJson"
        ]);
        grabberConfigEditor.value = defaultConfigJson();
        renderConfigStatus(parseGrabberConfig(grabberConfigEditor.value));
        applyWorkflowState();
        appendLog({ level: "info", title: "Signed out", message: "Cleared local session." });
    }
    async function handleClearSheet() {
        sheetVerified = false;
        hasOutput = false;
        sheetPillLink.textContent = "";
        sheetPillLink.removeAttribute("href");
        spreadsheetUrl.value = "";
        accessCheckMark.hidden = true;
        sourceStats.hidden = true;
        await chrome.storage.local.remove([
            "sheetAccessVerified",
            "verifiedSpreadsheetId",
            "verifiedSpreadsheetTitle",
            "verifiedSpreadsheetUrl"
        ]);
        handleSpreadsheetUrlInput();
        applyWorkflowState();
    }
    function handleSpreadsheetUrlInput() {
        const value = spreadsheetUrl.value.trim();
        const parsed = parseGrabberConfig(grabberConfigEditor.value);
        const baseConfig = parsed.ok ? parsed.config : DEFAULT_GRABBER_CONFIG;
        const next = { ...baseConfig, spreadsheetUrl: value };
        grabberConfigEditor.value = JSON.stringify(next, null, 2);
        renderConfigStatus(parseGrabberConfig(grabberConfigEditor.value));
        chrome.storage.local.set({ grabberConfigJson: grabberConfigEditor.value });
    }
    async function handleCheckAccess() {
        const url = spreadsheetUrl.value.trim();
        if (!url) {
            showAccessError("Enter a Spreadsheet URL first.");
            return;
        }
        const spreadsheetId = extractSpreadsheetId(url);
        if (!spreadsheetId) {
            showAccessError("Could not parse a spreadsheet ID from the URL. Paste the full Google Sheets URL or just the sheet ID.");
            return;
        }
        accessCheckMark.hidden = true;
        sourceStats.hidden = true;
        btnConfigCheck.disabled = true;
        appendLog({ level: "info", title: "Checking sheet access", message: spreadsheetId });
        chrome.runtime.sendMessage({ type: "CHECK_ACCESS", payload: { spreadsheetId } }, (response) => {
            btnConfigCheck.disabled = false;
            const transportError = chrome.runtime.lastError?.message;
            if (transportError) {
                showAccessError(`Could not reach the backend: ${transportError}`);
                appendLog({ level: "error", title: "Access check failed", message: transportError });
                return;
            }
            if (response?.ok) {
                accessCheckMark.hidden = false;
                appendLog({ level: "success", title: "Access granted", message: "Service account can read/write the spreadsheet." });
                loadSourceStats(spreadsheetId);
                const title = (typeof response.title === "string" && response.title.trim()) || "Spreadsheet";
                const sheetUrl = `https://docs.google.com/spreadsheets/d/${spreadsheetId}/edit`;
                sheetPillLink.textContent = title;
                sheetPillLink.href = sheetUrl;
                sheetVerified = true;
                chrome.storage.local.set({
                    sheetAccessVerified: true,
                    verifiedSpreadsheetId: spreadsheetId,
                    verifiedSpreadsheetTitle: title,
                    verifiedSpreadsheetUrl: sheetUrl
                });
                applyWorkflowState();
                return;
            }
            const email = response?.email;
            const detail = response?.error;
            const message = email
                ? "The service account cannot reach this spreadsheet. Share it with the address below as Editor, then retry."
                : detail || "Could not verify access to the spreadsheet.";
            showAccessError(message, email);
            appendLog({
                level: "error",
                title: "Access denied",
                message: email ? `Share the sheet with the service account: ${email}` : (detail || "Could not reach the spreadsheet.")
            });
        });
    }
    function loadSourceStats(spreadsheetId) {
        const parsed = parseGrabberConfig(grabberConfigEditor.value);
        const cfg = parsed.ok ? parsed.config : DEFAULT_GRABBER_CONFIG;
        const payload = {
            spreadsheetId,
            sourceSheetName: cfg.sourceSheetName,
            columns: cfg.columns
        };
        chrome.runtime.sendMessage({ type: "GET_SOURCE_STATS", payload }, (response) => {
            if (chrome.runtime.lastError)
                return;
            if (!response?.ok)
                return;
            const total = Number(response.total || 0);
            const unparsed = Number(response.unparsed || 0);
            sourceStats.innerHTML = "";
            const t = document.createElement("strong");
            t.textContent = `${total}`;
            const u = document.createElement("strong");
            u.textContent = `${unparsed}`;
            sourceStats.append(t, ` rows · `, u, ` unparsed`);
            sourceStats.hidden = false;
        });
    }
    function showAccessError(message, email) {
        accessErrorMessage.textContent = message;
        if (email) {
            accessErrorEmail.textContent = email;
            accessErrorActions.hidden = false;
        }
        else {
            accessErrorEmail.textContent = "";
            accessErrorActions.hidden = true;
        }
        if (typeof accessErrorDialog.showModal === "function") {
            if (accessErrorDialog.open)
                accessErrorDialog.close();
            accessErrorDialog.showModal();
        }
        else {
            accessErrorDialog.setAttribute("open", "");
        }
    }
    async function handleStartAgent() {
        const url = spreadsheetUrl.value.trim();
        if (!url) {
            appendLog({ level: "warn", title: "Cannot start", message: "Enter a Spreadsheet URL first." });
            return;
        }
        const spreadsheetId = extractSpreadsheetId(url);
        if (!spreadsheetId) {
            appendLog({ level: "warn", title: "Cannot start", message: "Could not parse a spreadsheet ID from the URL." });
            return;
        }
        const parsed = parseGrabberConfig(grabberConfigEditor.value);
        if (!parsed.ok) {
            appendLog({ level: "error", title: "Cannot start", message: `Advanced configuration is invalid: ${parsed.error}` });
            return;
        }
        await saveAgentSettings(url);
        agentState.textContent = "Running";
        chrome.runtime.sendMessage({
            type: "START_AGENT",
            payload: {
                spreadsheetId,
                config: {
                    sourceSheetName: parsed.config.sourceSheetName,
                    columns: parsed.config.columns,
                    batchSize: parsed.config.batchSize,
                    scrapeTimeoutMs: parsed.config.scrapeTimeoutMs,
                    cloudflareGraceMs: parsed.config.cloudflareGraceMs,
                    interRowDelayMs: parsed.config.interRowDelayMs,
                    extractSkillSlug: parsed.config.extractSkillSlug
                }
            }
        });
    }
    async function handleStopAgent() {
        agentState.textContent = "Stopped";
        chrome.runtime.sendMessage({
            type: "STOP_AGENT"
        });
    }
    // ── Manual actions ───────────────────────────────────────────────────────────
    async function runManual(kind) {
        await ensureConfig();
        clearOutput(`Manual: ${kind}`);
        if (kind === "observe") {
            const response = await sendMessage({ type: "OBSERVE_TAB" });
            if (response.ok && response.observation) {
                renderObservation(response.observation);
                const summary = summarizeObservation(response.observation);
                appendLog({ level: "success", title: "Observed tab", message: "Captured DOM and screenshot state.", data: summary });
                showOutput(formatObservationText(response.observation));
            }
            else {
                appendLog({ level: "error", title: "Observe failed", message: response.error?.message || "Could not observe active tab.", data: response.error });
            }
            return;
        }
        if (kind === "screenshotDescribe") {
            await runScreenshotDescribe();
            return;
        }
        const action = manualAction(kind);
        if (!action) {
            appendLog({ level: "error", title: "Unknown manual action", message: kind });
            return;
        }
        const response = await sendMessage({
            type: "EXECUTE_MANUAL_ACTION",
            payload: { action }
        });
        const result = response.result;
        appendLog({
            level: response.ok && result?.ok ? "success" : "error",
            title: `${kind} test`,
            message: result?.message || response.error?.message || "Manual action failed.",
            data: result || response.error
        });
        if (kind === "screenshot" && result?.ok && typeof result.data?.screenshot === "string") {
            screenshotEl.src = result.data.screenshot;
            screenshotEl.classList.add("has-image");
            showOutput("Screenshot captured. Check the Current Tab panel for preview.");
        }
        else if (result?.ok) {
            showOutput(humanizeActionResult(kind, result));
        }
    }
    async function runScreenshotDescribe() {
        appendLog({ level: "info", title: "Screenshot & Describe", message: "Capturing screenshot…" });
        showOutput("Capturing screenshot…");
        // Capture the screenshot via content script
        const capRes = await sendMessage({
            type: "EXECUTE_MANUAL_ACTION",
            payload: { action: { type: "captureScreenshot" } }
        });
        if (!capRes.ok || !capRes.result?.ok) {
            const msg = capRes.error?.message || capRes.result?.message || "Screenshot capture failed.";
            appendLog({ level: "error", title: "Screenshot failed", message: msg });
            showOutput(`Error: ${msg}`);
            return;
        }
        const screenshot = capRes.result.data?.screenshot;
        if (!screenshot) {
            showOutput("Error: No screenshot data returned.");
            return;
        }
        // Show the image preview
        screenshotEl.src = screenshot;
        screenshotEl.classList.add("has-image");
        showOutput("Screenshot captured. Sending to MiniMax for analysis…");
        appendLog({ level: "info", title: "Screenshot captured", message: "Sending to MiniMax vision API…" });
        // Get config for server URL
        const config = _liveConfig;
        if (!config?.serverUrl) {
            showOutput("Error: No server URL configured. Open Options to configure.");
            return;
        }
        try {
            const res = await fetch(`${config.serverUrl.replace(/\/+$/, "")}/v1/browser-agent/screenshot-describe`, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    ...(config.authToken ? { "Authorization": `Bearer ${config.authToken}` } : {})
                },
                body: JSON.stringify({ screenshot, sessionId: BrowserAgent.createId("session") })
            });
            const data = await res.json();
            if (!res.ok || !data.ok) {
                const msg = data.error?.message || `Server error ${res.status}`;
                appendLog({ level: "error", title: "Describe failed", message: msg, data: data.error });
                showOutput(`Error: ${msg}`);
                return;
            }
            const description = data.description || "(No description returned)";
            appendLog({ level: "success", title: "Screenshot described", message: description.slice(0, 120) + (description.length > 120 ? "…" : "") });
            showOutput(description);
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            appendLog({ level: "error", title: "Describe request failed", message: msg });
            showOutput(`Error: ${msg}`);
        }
    }
    function manualAction(kind) {
        switch (kind) {
            case "screenshot": return { type: "captureScreenshot" };
            case "copy": return { type: "copyToClipboard", args: { source: "visibleText" } };
            case "click": return { type: "click", target: { text: "Record Click" } };
            case "type": return { type: "type", target: { selector: "#fake-doc" }, args: { text: "Browser Agent typed this line.", mode: "append" } };
            case "scroll": return { type: "scroll", args: { direction: "down", amount: 900 } };
            case "tables": return { type: "getTables" };
            case "links": return { type: "getLinks" };
            default: return null;
        }
    }
    // ── Skills ───────────────────────────────────────────────────────────────
    async function loadSkillsList() {
        if (!_liveConfig?.serverUrl)
            return;
        try {
            const response = await fetch(BrowserAgent.endpointUrl(_liveConfig, "/v1/skills"), {
                headers: _liveConfig.authToken ? { Authorization: `Bearer ${_liveConfig.authToken}` } : {}
            });
            const data = await response.json();
            if (data.ok && Array.isArray(data.skills)) {
                loadedSkills = data.skills;
                skillSelect.innerHTML = "";
                if (loadedSkills.length === 0) {
                    skillSelect.innerHTML = `<option value="">No skills available</option>`;
                    return;
                }
                for (const skill of loadedSkills) {
                    const option = document.createElement("option");
                    option.value = skill.slug;
                    option.textContent = skill.name;
                    skillSelect.appendChild(option);
                }
                onSkillSelectChange();
            }
        }
        catch {
            skillSelect.innerHTML = `<option value="">Failed to load skills</option>`;
        }
    }
    function onSkillSelectChange() {
        const slug = skillSelect.value;
        const skill = loadedSkills.find(s => s.slug === slug);
        if (skill) {
            const desc = skill.description || "No description.";
            skillDescription.textContent = skill.domainPattern
                ? `${desc} (Runs on: ${skill.domainPattern})`
                : desc;
            // Pre-populate parameters textarea with schema defaults
            if (skill.parameters?.properties) {
                const defaults = {};
                for (const [key, schema] of Object.entries(skill.parameters.properties)) {
                    defaults[key] = schema.type === "number" ? "0" : "";
                }
                skillParamsInput.value = JSON.stringify(defaults, null, 2);
            }
            else {
                skillParamsInput.value = "{}";
            }
        }
        else {
            skillDescription.textContent = "Select a skill to see its description.";
            skillParamsInput.value = "{}";
        }
    }
    // Translate a Chrome match-pattern (e.g. `https://docs.google.com/*`) or a
    // raw regex into a tester. Returns null if the pattern is unusable.
    function makeDomainTester(pattern) {
        if (!pattern)
            return null;
        try {
            const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
            const re = new RegExp(`^${escaped}$`);
            return (url) => re.test(url);
        }
        catch {
            try {
                const re = new RegExp(pattern);
                return (url) => re.test(url);
            }
            catch {
                return null;
            }
        }
    }
    function suggestedUrlFromPattern(pattern) {
        const m = pattern.match(/^(https?:\/\/[^/*\s]+)/);
        return m ? `${m[1]}/` : null;
    }
    async function getActiveTabUrl() {
        return new Promise((resolve) => {
            chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs) => {
                resolve(tabs?.[0]?.url || null);
            });
        });
    }
    function showSkillHostDialog(opts) {
        const { skill, currentUrl, reason, injectionMessage } = opts;
        const pattern = skill.domainPattern || "";
        const target = suggestedUrlFromPattern(pattern);
        skillHostTitle.textContent = reason === "preflight"
            ? `"${skill.name}" can't run on this tab`
            : `"${skill.name}" couldn't be injected`;
        let where = "(no active tab)";
        if (currentUrl) {
            try {
                where = new URL(currentUrl).host || currentUrl;
            }
            catch {
                where = currentUrl;
            }
        }
        skillHostMessage.textContent = reason === "preflight"
            ? `The active tab (${where}) doesn't match this skill's allowed host.`
            : (injectionMessage || `Chrome refused to inject the script into the active tab (${where}). The extension manifest doesn't have permission to access this host.`);
        skillHostPattern.textContent = pattern || "(skill has no domainPattern set)";
        if (target) {
            btnSkillHostOpen.dataset.openUrl = target;
            btnSkillHostOpen.hidden = false;
            btnSkillHostOpen.textContent = `Open ${target}`;
        }
        else {
            delete btnSkillHostOpen.dataset.openUrl;
            btnSkillHostOpen.hidden = true;
        }
        if (!skillHostDialog.open)
            skillHostDialog.showModal();
    }
    async function runSkillExecute() {
        await ensureConfig();
        const slug = skillSelect.value;
        if (!slug) {
            skillStatus.textContent = "Select a skill first.";
            return;
        }
        const skill = loadedSkills.find(s => s.slug === slug);
        let parameters = {};
        try {
            parameters = JSON.parse(skillParamsInput.value || "{}");
        }
        catch {
            skillStatus.textContent = "Invalid JSON in parameters.";
            return;
        }
        // Pre-flight: if the skill declares a domainPattern, make sure the active
        // tab matches before we round-trip to the backend.
        if (skill?.domainPattern) {
            const tester = makeDomainTester(skill.domainPattern);
            const currentUrl = await getActiveTabUrl();
            if (tester && (!currentUrl || !tester(currentUrl))) {
                skillStatus.textContent = `Active tab doesn't match ${skill.domainPattern}.`;
                showSkillHostDialog({ skill, currentUrl, reason: "preflight" });
                return;
            }
        }
        skillStatus.textContent = "Executing...";
        skillOutput.style.display = "block";
        skillOutput.textContent = "Running skill…";
        try {
            const response = await fetch(BrowserAgent.endpointUrl(_liveConfig, `/v1/skills/${encodeURIComponent(slug)}/execute`), {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    ...(_liveConfig?.authToken ? { Authorization: `Bearer ${_liveConfig.authToken}` } : {})
                },
                body: JSON.stringify({ parameters })
            });
            const data = await response.json();
            if (data.ok) {
                skillStatus.textContent = `Success (exec #${data.execution?.id || "?"}).`;
                skillOutput.textContent = JSON.stringify(data.result, null, 2);
            }
            else {
                const innerCode = data.result?.error?.code || data.error?.code;
                const innerMsg = data.result?.error?.message || data.error?.message;
                skillStatus.textContent = `Error: ${innerMsg || "Execution failed."}`;
                skillOutput.textContent = JSON.stringify(data, null, 2);
                // Surface a friendly panel when the script could not be injected
                // because of a missing host permission.
                if (skill && innerCode === "SKILL_INJECTION_FAILED") {
                    const currentUrl = await getActiveTabUrl();
                    showSkillHostDialog({ skill, currentUrl, reason: "injection-failed", injectionMessage: innerMsg });
                }
            }
        }
        catch (e) {
            skillStatus.textContent = "Request failed.";
            skillOutput.textContent = String(e);
        }
    }
    async function runSkillHistory() {
        await ensureConfig();
        const slug = skillSelect.value;
        if (!slug) {
            skillStatus.textContent = "Select a skill first.";
            return;
        }
        skillHistoryList.style.display = "grid";
        skillHistoryList.textContent = "Loading...";
        try {
            const response = await fetch(BrowserAgent.endpointUrl(_liveConfig, `/v1/skills/${encodeURIComponent(slug)}/executions?limit=15`), {
                headers: _liveConfig?.authToken ? { Authorization: `Bearer ${_liveConfig.authToken}` } : {}
            });
            const data = await response.json();
            if (data.ok && Array.isArray(data.executions)) {
                if (data.executions.length === 0) {
                    skillHistoryList.innerHTML = `<p style="color:var(--muted);font-size:12px;">No executions yet.</p>`;
                    return;
                }
                skillHistoryList.innerHTML = data.executions.map((exec) => `
          <div class="skill-exec-item" title="${exec.errorMessage || ''}">
            <span>#${exec.id} — ${exec.durationMs}ms — ${new Date(exec.createdAt).toLocaleTimeString()}</span>
            <span class="exec-status ${exec.status}">${exec.status}</span>
          </div>
        `).join("");
            }
            else {
                skillHistoryList.textContent = "Failed to load history.";
            }
        }
        catch (e) {
            skillHistoryList.textContent = String(e);
        }
    }
    // ── System status ───────────────────────────────────────────────────────────
    async function refreshSystemStatus() {
        if (!_liveConfig?.serverUrl) {
            renderBackendStatus("Missing URL", "Open Options to configure the proxy server.", null);
            renderSystemDetails(null);
            return;
        }
        renderBackendStatus("Checking", "Reading /health from the backend.", latestHealth);
        try {
            const res = await fetch(BrowserAgent.endpointUrl(_liveConfig, "/health"), {
                headers: _liveConfig.authToken ? { "Authorization": `Bearer ${_liveConfig.authToken}` } : undefined
            });
            const data = await res.json();
            latestHealth = data;
            renderBackendStatus(res.ok && data.ok ? "Healthy" : `HTTP ${res.status}`, describeBackend(data), data);
            renderSystemDetails(data);
            connectSystemSocket();
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            renderBackendStatus("Offline", message, latestHealth);
            renderSystemDetails(latestHealth);
        }
    }
    function connectSystemSocket() {
        if (!_liveConfig?.serverUrl) {
            renderSocketStatus("Disconnected", "No server URL configured.");
            return;
        }
        let nextUrl;
        try {
            nextUrl = buildWebSocketUrl(_liveConfig);
        }
        catch (error) {
            renderSocketStatus("Unavailable", error instanceof Error ? error.message : String(error));
            return;
        }
        if (systemSocket &&
            systemSocketUrl === nextUrl &&
            (systemSocket.readyState === WebSocket.OPEN || systemSocket.readyState === WebSocket.CONNECTING)) {
            return;
        }
        if (reconnectTimer !== undefined) {
            clearTimeout(reconnectTimer);
            reconnectTimer = undefined;
        }
        systemSocket?.close();
        systemSocketUrl = nextUrl;
        renderSocketStatus("Connecting", nextUrl.replace(/token=[^&]+/, "token=redacted"));
        const socket = new WebSocket(nextUrl);
        systemSocket = socket;
        socket.addEventListener("open", () => {
            renderSocketStatus("Connected", "Live server channel is active.");
            reportFrontendStatus();
        });
        socket.addEventListener("message", (event) => {
            lastSocketMessageAt = new Date().toISOString();
            handleSystemSocketMessage(String(event.data));
            renderSocketStatus("Connected", `Last message ${formatAbsoluteTime(lastSocketMessageAt)}.`);
        });
        socket.addEventListener("close", () => {
            if (systemSocket === socket) {
                renderSocketStatus("Disconnected", "Reconnecting in 3 seconds.");
                reconnectTimer = window.setTimeout(() => connectSystemSocket(), 3000);
            }
        });
        socket.addEventListener("error", () => {
            renderSocketStatus("Error", "WebSocket could not connect.");
        });
    }
    function handleSystemSocketMessage(raw) {
        let message;
        try {
            message = JSON.parse(raw);
        }
        catch {
            return;
        }
        if (message.type === "server.hello" && message.payload?.health) {
            systemClientId = message.payload.clientId;
            latestHealth = message.payload.health;
            renderBackendStatus("Healthy", describeBackend(latestHealth), latestHealth);
            renderSystemDetails(latestHealth);
            scheduleFrontendStatusReport();
            return;
        }
        if (message.type === "browser.observe") {
            handleBrowserObserveCommand(message);
            return;
        }
        if (message.type === "browser.act") {
            handleBrowserActCommand(message);
            return;
        }
        if (message.type === "browser.executeSkill") {
            handleBrowserExecuteSkillCommand(message);
            return;
        }
        if (isRunSocketMessage(message)) {
            handleRunSocketMessage(message);
            return;
        }
        if (message.type === "server.status" && message.payload?.database && latestHealth) {
            latestHealth = {
                ...latestHealth,
                database: message.payload.database,
                websocket: {
                    ...latestHealth.websocket,
                    clientCount: message.payload.websocketClients ?? latestHealth.websocket?.clientCount
                }
            };
            renderSystemDetails(latestHealth);
            return;
        }
        if (message.type === "server.clients" && latestHealth) {
            latestHealth = {
                ...latestHealth,
                websocket: {
                    ...latestHealth.websocket,
                    clientCount: message.payload?.clientCount ?? latestHealth.websocket?.clientCount
                }
            };
            renderSystemDetails(latestHealth);
        }
    }
    async function handleBrowserObserveCommand(message) {
        const payload = message.payload || {};
        appendLog({
            level: "info",
            title: "Backend requested observation",
            message: "Collecting active tab context for the autonomous run.",
            runId: asString(payload.runId),
            type: "browser.observe.request",
            data: { step: payload.step, task: payload.task }
        });
        try {
            const response = await sendMessage({
                type: "OBSERVE_TAB",
                payload
            });
            if (response.ok && response.observation) {
                renderObservation(response.observation);
            }
            sendSocketResponse(message.requestId, "browser.observation", response);
        }
        catch (error) {
            sendSocketResponse(message.requestId, "browser.observation", {
                ok: false,
                error: BrowserAgent.normalizeError(error, "BROWSER_OBSERVE_FAILED")
            });
        }
    }
    async function handleBrowserActCommand(message) {
        const payload = message.payload || {};
        const action = payload.action;
        if (!action) {
            sendSocketResponse(message.requestId, "browser.actionResult", {
                ok: false,
                error: { code: "ACTION_MISSING", message: "Backend command did not include an action." }
            });
            return;
        }
        appendLog({
            level: "info",
            title: "Backend action",
            message: humanizeBrowserAction(action),
            runId: asString(payload.runId),
            type: "browser.act.request",
            data: { action, step: payload.step, actionIndex: payload.actionIndex }
        });
        try {
            const response = await sendMessage({
                type: "EXECUTE_MANUAL_ACTION",
                payload: { action }
            });
            const result = response.result;
            appendLog({
                level: response.ok && result?.ok ? "success" : "error",
                title: `${action.type}: ${response.ok && result?.ok ? "ok" : "failed"}`,
                message: result?.message || response.error?.message || "Browser action failed.",
                runId: asString(payload.runId),
                actionId: result?.actionId || action.id,
                type: response.ok && result?.ok ? "action.ok" : "action.failed",
                data: { action, result: result || response.error },
                error: result?.error || response.error
            });
            sendSocketResponse(message.requestId, "browser.actionResult", response);
        }
        catch (error) {
            const normalized = BrowserAgent.normalizeError(error, "BROWSER_ACTION_FAILED");
            appendLog({ level: "error", title: "Backend action failed", message: normalized.message, runId: asString(payload.runId), error: normalized });
            sendSocketResponse(message.requestId, "browser.actionResult", {
                ok: false,
                error: normalized
            });
        }
    }
    async function handleBrowserExecuteSkillCommand(message) {
        const payload = message.payload || {};
        const skillSlug = payload.skillSlug || "unknown";
        const injectedJs = payload.injectedJs || "";
        const parameters = payload.parameters || {};
        appendLog({
            level: "info",
            title: `Skill: ${skillSlug}`,
            message: "Executing injected skill on the active tab.",
            type: "skill.execute.request",
            data: { skillSlug, parameters }
        });
        try {
            const response = await sendMessage({
                type: "EXECUTE_SKILL_IN_TAB",
                payload: { js: injectedJs, parameters }
            });
            const result = response.result;
            appendLog({
                level: result?.ok ? "success" : "error",
                title: `Skill ${skillSlug}: ${result?.ok ? "ok" : "failed"}`,
                message: result?.ok ? `Skill executed successfully (${result.durationMs || 0}ms).` : (result?.error?.message || "Skill execution failed."),
                type: result?.ok ? "skill.execute.ok" : "skill.execute.failed",
                data: { skillSlug, result },
                error: result?.error
            });
            sendSocketResponse(message.requestId, "browser.skillResult", {
                ok: response.ok,
                result,
                tabUrl: result?.tabUrl
            });
        }
        catch (error) {
            const normalized = BrowserAgent.normalizeError(error, "SKILL_EXECUTION_FAILED");
            appendLog({ level: "error", title: `Skill ${skillSlug} failed`, message: normalized.message, error: normalized, type: "skill.execute.failed" });
            sendSocketResponse(message.requestId, "browser.skillResult", {
                ok: false,
                error: normalized
            });
        }
    }
    function handleRunSocketMessage(message) {
        const payload = message.payload || {};
        const run = payload.run;
        if (run?.runId) {
            currentBackendRunId = ["completed", "failed", "stopped"].includes(run.status) ? undefined : run.runId;
            agentState.textContent = humanizeRunStatus(run.status, payload.message);
        }
        else if (typeof payload.status === "string") {
            agentState.textContent = humanizeRunStatus(payload.status);
        }
        const shouldAppend = message.type !== "run.status" || Boolean(payload.level || payload.type);
        if (shouldAppend) {
            const event = socketPayloadToRunEvent(message);
            appendLog(event);
            handleRunEventOutput(event);
        }
        renderFrontendStatus();
        scheduleFrontendStatusReport();
    }
    function sendSocketResponse(requestId, type, payload) {
        if (!requestId || !systemSocket || systemSocket.readyState !== WebSocket.OPEN) {
            return;
        }
        systemSocket.send(JSON.stringify({
            type,
            requestId,
            timestamp: new Date().toISOString(),
            payload
        }));
    }
    function isRunSocketMessage(message) {
        return [
            "agent.thought",
            "run.status",
            "run.complete",
            "run.failed",
            "run.stopped"
        ].includes(String(message.type || ""));
    }
    function socketPayloadToRunEvent(message) {
        const payload = message.payload || {};
        const run = payload.run;
        const nestedPayload = payload.payload;
        const type = asString(payload.type) || asString(message.type) || "event";
        return {
            level: payload.level || defaultLevelForSocketType(type),
            title: asString(payload.title) || titleForSocketType(type, asString(payload.status)),
            message: asString(payload.message) || run?.finalAnswer || asString(message.type) || "Browser Agent event",
            data: nestedPayload !== undefined ? nestedPayload : payload,
            sessionId: asString(payload.sessionId) || run?.sessionId,
            runId: asString(payload.runId) || run?.runId,
            requestId: asString(payload.requestId) || run?.requestId,
            actionId: asString(payload.actionId),
            source: payload.source || "server",
            type,
            tab: payload.tab,
            error: payload.error,
            timestamp: asString(payload.timestamp) || message.timestamp
        };
    }
    function defaultLevelForSocketType(type) {
        if (type === "run.complete" || type === "action.ok")
            return "success";
        if (type === "run.failed" || type === "action.failed")
            return "error";
        if (type === "run.stopped" || type === "run.stop_requested")
            return "warn";
        return "info";
    }
    function titleForSocketType(type, status) {
        if (type === "agent.thought")
            return "Thinking";
        if (type === "run.complete")
            return "Run complete";
        if (type === "run.failed")
            return "Run failed";
        if (type === "run.stopped")
            return "Run stopped";
        if (type === "run.started")
            return "Run started";
        if (type === "observation.captured")
            return "Observed tab";
        if (type === "action.ok")
            return "Action complete";
        if (type === "action.failed")
            return "Action failed";
        if (status)
            return `Run ${status}`;
        return type.replace(/\./g, " ");
    }
    function humanizeRunStatus(status, message) {
        const label = status.charAt(0).toUpperCase() + status.slice(1);
        return message && status === "failed" ? `${label}: ${message}` : label;
    }
    function asString(value) {
        return typeof value === "string" && value ? value : undefined;
    }
    function reportFrontendStatus() {
        if (!systemSocket || systemSocket.readyState !== WebSocket.OPEN) {
            return;
        }
        systemSocket.send(JSON.stringify({
            type: "client.status",
            payload: {
                client: "sidepanel",
                clientId: systemClientId,
                runState: agentState.textContent || "Idle",
                currentBackendRunId,
                logEvents: allLogEvents.length,
                outputChars: outputBox.textContent?.length || 0,
                manualTools: ["observe", "screenshot", "screenshotDescribe", "copy", "click", "type", "scroll", "tables", "links"],
                timestamp: new Date().toISOString()
            }
        }));
    }
    function scheduleFrontendStatusReport() {
        if (statusReportTimer !== undefined) {
            clearTimeout(statusReportTimer);
        }
        statusReportTimer = window.setTimeout(() => {
            statusReportTimer = undefined;
            reportFrontendStatus();
        }, 300);
    }
    function buildWebSocketUrl(config) {
        const url = new URL(BrowserAgent.endpointUrl(config, "/v1/browser-agent/events"));
        url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
        if (config.authToken) {
            url.searchParams.set("token", config.authToken);
        }
        return url.toString();
    }
    function renderBackendStatus(title, detail, health) {
        backendStatus.textContent = title;
        backendDetail.textContent = detail;
        if (health) {
            renderSystemDetails(health);
        }
    }
    function renderSocketStatus(title, detail) {
        websocketStatus.textContent = title;
        websocketDetail.textContent = detail;
    }
    function renderFrontendStatus() {
        frontendStatus.textContent = agentState.textContent || "Ready";
        frontendDetail.textContent = `${allLogEvents.length} log event(s), output ${outputBox.textContent?.length || 0} chars.`;
    }
    function renderSystemDetails(health) {
        if (!health) {
            systemDetails.textContent = "No backend health data yet.";
            return;
        }
        const tools = health.capabilities?.frontendTools?.length || 0;
        const backendTools = health.capabilities?.backendTools?.length || 0;
        const apis = health.capabilities?.backendApi?.length || 0;
        const dslActions = health.capabilities?.dsl?.stepActions?.length || 0;
        const database = health.database;
        const websocket = health.websocket;
        const runs = health.runs;
        systemDetails.textContent = [
            `Backend: ${health.provider || "unknown"} ${health.defaultModel || ""} | API key: ${health.hasApiKey ? "set" : "missing"}`,
            `Server: port ${health.server?.port || "unknown"}, uptime ${health.server?.uptimeSeconds ?? "?"}s, Node ${health.server?.nodeVersion || "unknown"}`,
            `Log DB: ${database?.ok ? "ok" : "problem"} (${database?.mode || "unknown"}), sessions ${database?.sessionCount ?? 0}, events ${database?.eventCount ?? 0}`,
            `Live channel: ${websocket?.enabled ? "on" : "off"}, connected clients ${websocket?.clientCount ?? 0}`,
            `Runs: ${runs?.activeCount ?? 0} active, ${runs?.count ?? 0} tracked`,
            `Capabilities: ${tools} frontend tools, ${backendTools} backend tools, ${apis} backend APIs, ${dslActions} DSL actions`
        ].join("\n");
    }
    function describeBackend(health) {
        const keyState = health.hasApiKey ? "API key set" : "API key missing";
        const dbState = health.database?.ok ? "DB ok" : "DB problem";
        return `${health.provider || "backend"} ${health.defaultModel || ""}; ${keyState}; ${dbState}.`;
    }
    // ── Output section ───────────────────────────────────────────────────────────
    function clearOutput(commandLabel) {
        outputBox.innerHTML = "";
        if (commandLabel) {
            const cmd = document.createElement("p");
            cmd.className = "output-command";
            cmd.textContent = commandLabel;
            outputBox.appendChild(cmd);
        }
    }
    function showRunResultLink(sheetUrl, sheetTitle) {
        revealOutputPanel();
        const placeholder = outputBox.querySelector(".output-placeholder");
        if (placeholder)
            placeholder.remove();
        let linkEl = outputBox.querySelector(".run-result-link");
        if (!linkEl) {
            linkEl = document.createElement("p");
            linkEl.className = "run-result-link";
            outputBox.prepend(linkEl);
        }
        linkEl.innerHTML = "";
        const label = document.createElement("span");
        label.textContent = "Result sheet:";
        const anchor = document.createElement("a");
        anchor.href = sheetUrl;
        anchor.target = "_blank";
        anchor.rel = "noopener noreferrer";
        anchor.textContent = `${sheetTitle} ↗`;
        linkEl.appendChild(label);
        linkEl.appendChild(anchor);
        const panel = document.getElementById("panel-output");
        if (panel?.classList.contains("collapsed")) {
            panel.classList.remove("collapsed");
            panel.querySelector(".panel-header")?.setAttribute("aria-expanded", "true");
        }
    }
    function revealOutputPanel() {
        if (!hasOutput) {
            hasOutput = true;
            applyWorkflowState();
        }
    }
    function showOutput(text) {
        revealOutputPanel();
        // Remove placeholder if present
        const placeholder = outputBox.querySelector(".output-placeholder");
        if (placeholder)
            placeholder.remove();
        // Replace existing output-text if present, otherwise append
        let textEl = outputBox.querySelector(".output-text");
        if (!textEl) {
            textEl = document.createElement("p");
            textEl.className = "output-text";
            outputBox.appendChild(textEl);
        }
        textEl.textContent = text;
        // Expand output panel if collapsed
        const panel = document.getElementById("panel-output");
        if (panel?.classList.contains("collapsed")) {
            panel.classList.remove("collapsed");
            panel.querySelector(".panel-header")?.setAttribute("aria-expanded", "true");
        }
    }
    function handleRunEventOutput(event) {
        // agent.run.completed carries the per-run result sheet URL
        if (event.type === "agent.run.completed") {
            const data = event.data;
            if (data?.sheetUrl && data.sheetTitle) {
                showRunResultLink(data.sheetUrl, data.sheetTitle);
            }
            return;
        }
        // run.complete carries the final answer from the LLM (e.g. extracted JSON)
        if (event.type === "run.complete" || event.title === "Run complete") {
            const data = event.data;
            const score = data?.success ? `\n\nSuccess: ${data.success.ok ? "yes" : "no"} (${Math.round(data.success.score * 100)}%). ${data.success.reason}` : "";
            showOutput(`${event.message || "(No output)"}${score}`);
            return;
        }
        // server.response may carry a final text when no further actions are needed
        if (event.type === "server.response") {
            const data = event.data;
            const finalText = data?.final || data?.message;
            if (finalText)
                showOutput(finalText);
            return;
        }
        // Warn conditions that affect output
        if (event.type === "run.max_steps") {
            showOutput(`Reached max steps limit. Last known action log is in the Logs panel.`);
            return;
        }
        if (event.type === "run.autopilot.disabled") {
            showOutput("Autopilot is disabled. Enable it in Options to run automated tasks.");
            return;
        }
    }
    // ── Log section ──────────────────────────────────────────────────────────────
    function appendLog(event) {
        allLogEvents.push(event);
        if (logCount)
            logCount.textContent = `(${allLogEvents.length})`;
        renderFrontendStatus();
        scheduleFrontendStatusReport();
        const entry = document.createElement("div");
        const isPlatformLimit = containsCode(event, "PAGE_RESTRICTED");
        entry.className = `log-entry ${event.level === "error" ? "error" : event.level === "warn" ? "warn" : event.level === "success" ? "success" : ""} ${isPlatformLimit ? "platform-limit" : ""}`;
        const meta = document.createElement("div");
        meta.className = "log-meta";
        const time = document.createElement("span");
        time.textContent = formatLogTime(event.timestamp);
        const badge = document.createElement("span");
        badge.className = "log-badge";
        badge.textContent = event.level;
        meta.append(time, badge);
        // Title
        const titleEl = document.createElement("div");
        titleEl.className = "log-title";
        titleEl.textContent = event.title;
        // Human-readable message
        const msgEl = document.createElement("div");
        msgEl.className = "log-message";
        msgEl.textContent = event.message;
        entry.append(meta, titleEl, msgEl);
        // DSL / actions list (human-readable)
        const actionList = extractActions(event);
        if (actionList.length > 0) {
            const ul = document.createElement("ul");
            ul.className = "log-actions-list";
            for (const item of actionList) {
                const li = document.createElement("li");
                li.textContent = item;
                ul.appendChild(li);
            }
            entry.appendChild(ul);
        }
        // Raw JSON toggle
        if (event.data !== undefined) {
            const toggleBtn = document.createElement("button");
            toggleBtn.className = "log-raw-toggle";
            toggleBtn.textContent = "Show raw JSON";
            const rawEl = document.createElement("pre");
            rawEl.className = "log-raw";
            rawEl.textContent = JSON.stringify(event.data, null, 2);
            toggleBtn.addEventListener("click", () => {
                const visible = rawEl.classList.toggle("visible");
                toggleBtn.textContent = visible ? "Hide raw JSON" : "Show raw JSON";
            });
            entry.append(toggleBtn, rawEl);
        }
        logContainer.prepend(entry);
    }
    function extractActions(event) {
        const data = event.data;
        if (!data)
            return [];
        // DSL program steps
        const program = data?.program;
        if (program?.steps?.length) {
            return program.steps.map((step) => humanizeStep(step));
        }
        // Direct actions array
        const actions = data?.actions;
        if (Array.isArray(actions) && actions.length) {
            return actions.map((a) => humanizeBrowserAction(a));
        }
        // Single action field
        const action = data?.action;
        if (action && action.type) {
            return [humanizeBrowserAction(action)];
        }
        return [];
    }
    function humanizeStep(step) {
        const target = step.target?.text || step.target?.selector || "";
        const text = step.args?.text || "";
        switch (step.action) {
            case "click": return `Click "${target}"`;
            case "type": return `Type "${text}" into "${target}"`;
            case "pressKey": return `Press key "${step.args?.key || ""}"`;
            case "scroll": return `Scroll ${step.args?.direction || ""}`;
            case "observe": return "Observe page";
            case "snapshot": return "Capture page snapshot";
            case "screenshot": return "Capture screenshot";
            case "selectText": return `Select text in "${target}"`;
            case "navigate": return `Navigate to ${step.args?.url || ""}`;
            case "waitFor": return `Wait for "${target}"`;
            case "extract": return `Extract data from "${target}"`;
            case "visibleText": return "Extract visible text";
            case "links": return "Extract links";
            case "tables": return "Extract tables";
            case "forms": return "Extract forms";
            case "assert": return `Assert "${target}"`;
            default: return step.action || "Unknown step";
        }
    }
    function humanizeBrowserAction(action) {
        const t = action.target;
        const target = t?.text || t?.selector || t?.role || (t?.coordinates ? `(${t.coordinates.x},${t.coordinates.y})` : "");
        const args = action.args || {};
        switch (action.type) {
            case "click": return `Click "${target}"`;
            case "type": return `Type "${args.text || ""}" into "${target}"`;
            case "scroll": return `Scroll ${args.direction || ""} ${args.amount ? args.amount + "px" : ""}`.trim();
            case "navigate": return `Navigate to ${args.url || ""}`;
            case "getPageSnapshot": return "Get page snapshot";
            case "captureScreenshot": return "Capture screenshot";
            case "getVisibleText": return "Get visible text";
            case "getLinks": return "Get page links";
            case "getTables": return "Get page tables";
            case "getForms": return "Get page forms";
            case "selectText": return `Select text in "${target}"`;
            case "pressKey": return `Press key "${args.key || ""}"`;
            case "waitForElement": return `Wait for "${target}"`;
            case "copyToClipboard": return `Copy ${args.source || "text"} to clipboard`;
            default: return action.type;
        }
    }
    function humanizeActionResult(kind, result) {
        const data = result.data;
        if (!data)
            return result.message;
        const links = data.links;
        if (links && links.length) {
            return `Found ${links.length} link(s):\n` + links.slice(0, 8).map((l) => `• ${l.text || l.href || "link"}`).join("\n");
        }
        const tables = data.tables;
        if (tables && tables.length) {
            return `Found ${tables.length} table(s). First table headers: ${tables[0]?.headers?.join(", ") || "(none)"}`;
        }
        const text = data.text;
        if (text)
            return text.slice(0, 400);
        return result.message;
    }
    function formatObservationText(obs) {
        const snap = obs.snapshot;
        const lines = [
            `URL: ${obs.tab.url || "unknown"}`,
            `Title: ${obs.tab.title || "untitled"}`,
        ];
        if (snap) {
            lines.push(`Text: ${snap.metadata.includedTextChars}/${snap.metadata.totalTextChars} chars`);
            lines.push(`Interactive elements: ${snap.metadata.interactiveElementCount}`);
            lines.push(`Links: ${snap.metadata.linkCount} | Tables: ${snap.metadata.tableCount}`);
            if (snap.visibleText)
                lines.push(`\nVisible text preview:\n${snap.visibleText.slice(0, 300)}`);
        }
        return lines.join("\n");
    }
    // ── Copy log buttons ─────────────────────────────────────────────────────────
    function copyLogsPlain() {
        const text = allLogEvents
            .map((e) => `[${formatLogTime(e.timestamp)}] [${e.level}] ${e.title}: ${e.message}`)
            .join("\n");
        navigator.clipboard.writeText(text).then(() => appendLog({ level: "success", title: "Copied", message: "Log copied as plain text." }), () => appendLog({ level: "error", title: "Copy failed", message: "Could not write to clipboard." }));
    }
    function copyLogsJson() {
        const text = JSON.stringify(allLogEvents, null, 2);
        navigator.clipboard.writeText(text).then(() => appendLog({ level: "success", title: "Copied", message: "Log copied as JSON." }), () => appendLog({ level: "error", title: "Copy failed", message: "Could not write to clipboard." }));
    }
    // ── Observation / tab panel ──────────────────────────────────────────────────
    function renderObservation(observation) {
        const snapshot = observation.snapshot;
        tabSummary.innerHTML = "";
        const items = [
            `URL: ${observation.tab.url || "unknown"}`,
            `Title: ${observation.tab.title || "untitled"}`,
            snapshot ? `Text: ${snapshot.metadata.includedTextChars}/${snapshot.metadata.totalTextChars} chars` : "DOM: not captured",
            snapshot ? `Controls: ${snapshot.metadata.interactiveElementCount}` : "",
            snapshot ? `Links: ${snapshot.metadata.linkCount}` : "",
            snapshot ? `Tables: ${snapshot.metadata.tableCount}` : "",
            observation.warnings.length ? `Warnings: ${observation.warnings.length}` : ""
        ].filter(Boolean);
        for (const item of items) {
            const div = document.createElement("div");
            div.textContent = item;
            tabSummary.appendChild(div);
        }
        if (observation.screenshot) {
            screenshotEl.src = observation.screenshot;
            screenshotEl.classList.add("has-image");
        }
    }
    function summarizeObservation(observation) {
        return {
            tab: observation.tab,
            snapshot: observation.snapshot ? observation.snapshot.metadata : undefined,
            screenshotChars: observation.screenshot?.length,
            warnings: observation.warnings
        };
    }
    // ── Helpers ──────────────────────────────────────────────────────────────────
    function containsCode(event, code) {
        if (event.error?.code === code)
            return true;
        const data = event.data;
        return Boolean(data?.warnings?.some((warning) => warning.code === code));
    }
    function formatLogTime(timestamp) {
        const date = timestamp ? new Date(timestamp) : new Date();
        const d = Number.isNaN(date.getTime()) ? new Date() : date;
        const pad = (n) => n.toString().padStart(2, "0");
        return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
    }
    function formatAbsoluteTime(timestamp) {
        return timestamp ? formatLogTime(timestamp) : "never";
    }
    async function ensureSystemSocketReady(timeoutMs = 3000) {
        connectSystemSocket();
        if (!systemSocket) {
            throw new Error("Live backend channel is not configured.");
        }
        if (systemSocket.readyState === WebSocket.OPEN) {
            reportFrontendStatus();
            return;
        }
        if (systemSocket.readyState !== WebSocket.CONNECTING) {
            throw new Error("Live backend channel is not connected.");
        }
        await new Promise((resolve, reject) => {
            const socket = systemSocket;
            if (!socket) {
                reject(new Error("Live backend channel is not available."));
                return;
            }
            const timer = window.setTimeout(() => {
                cleanup();
                reject(new Error("Timed out connecting to the live backend channel."));
            }, timeoutMs);
            const cleanup = () => {
                window.clearTimeout(timer);
                socket.removeEventListener("open", onOpen);
                socket.removeEventListener("close", onClose);
                socket.removeEventListener("error", onError);
            };
            const onOpen = () => {
                cleanup();
                reportFrontendStatus();
                resolve();
            };
            const onClose = () => {
                cleanup();
                reject(new Error("Live backend channel closed before it was ready."));
            };
            const onError = () => {
                cleanup();
                reject(new Error("Live backend channel failed to connect."));
            };
            socket.addEventListener("open", onOpen);
            socket.addEventListener("close", onClose);
            socket.addEventListener("error", onError);
        });
    }
    async function requestBackendJson(path, body) {
        if (!_liveConfig) {
            throw new Error("Configuration is not loaded.");
        }
        const controller = new AbortController();
        const timer = window.setTimeout(() => controller.abort(), _liveConfig.requestTimeoutMs);
        try {
            const response = await fetch(BrowserAgent.endpointUrl(_liveConfig, path), {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    ...(_liveConfig.authToken ? { Authorization: `Bearer ${_liveConfig.authToken}` } : {})
                },
                body: JSON.stringify(body),
                signal: controller.signal
            });
            const text = await response.text();
            let parsed;
            try {
                parsed = text ? JSON.parse(text) : {};
            }
            catch {
                throw new Error(`Backend returned non-JSON response (${response.status}): ${text.slice(0, 160)}`);
            }
            if (!response.ok) {
                const serverError = extractBackendError(parsed);
                const suffix = serverError.requestId ? ` Request ID: ${serverError.requestId}.` : "";
                throw new Error(`${serverError.message || `Backend returned HTTP ${response.status}.`}${suffix}`);
            }
            return parsed;
        }
        catch (error) {
            if (error.name === "AbortError") {
                throw new Error(`Backend request timed out after ${_liveConfig.requestTimeoutMs}ms.`);
            }
            throw error;
        }
        finally {
            window.clearTimeout(timer);
        }
    }
    function extractBackendError(parsed) {
        if (!parsed || typeof parsed !== "object") {
            return {};
        }
        const value = parsed;
        return {
            message: value.error?.message ? String(value.error.message) : value.message ? String(value.message) : undefined,
            requestId: value.error?.requestId ? String(value.error.requestId) : value.requestId ? String(value.requestId) : undefined
        };
    }
    async function ensureConfig() {
        // Always reload from storage so Options changes are picked up immediately.
        await loadConfig();
    }
    function sendMessage(message) {
        return chrome.runtime.sendMessage(message);
    }
    function byId(id) {
        const element = document.getElementById(id);
        if (!element)
            throw new Error(`Missing element #${id}`);
        return element;
    }
    // Hidden config inputs are not in the sidepanel DOM — use stubs that won't throw
    function hiddenInput(id) {
        return (document.getElementById(id) || document.createElement("input"));
    }
    function hiddenCheckbox(id) {
        return (document.getElementById(id) || document.createElement("input"));
    }
})(BrowserAgentSidePanel || (BrowserAgentSidePanel = {}));
//# sourceMappingURL=sidepanel.js.map