// /AIWK_EXT4/content.js
// 용도: 웹 페이지에 AIWK floating control과 module boot, 진단 report, EXT4 runtime bridge를 주입한다.
// AIWK EXT4 v152-storage-namespace-profile-lock - win/store ready guard hotfix
(function () {
    "use strict";

    const AIWK = window.AIWK || (window.AIWK = { rootId: "aiwk-root", version: "v152-storage-namespace-profile-lock" });
    let bootPromise = null;
    let bootOk = false;
    let aiwkFloatLabelTimer = null;
    let aiwkUserClosed = false;
    const ROUTE_USE_OFF_SESSION_KEY = "AIWK_ROUTE_USE_OFF_CURRENT_TAB";

    function isCurrentTabRouteUseOff() {
        try { return sessionStorage.getItem(ROUTE_USE_OFF_SESSION_KEY) === "1"; } catch (err) { return false; }
    }

    function setCurrentTabRouteUseOff(flag) {
        try {
            if (flag) sessionStorage.setItem(ROUTE_USE_OFF_SESSION_KEY, "1");
            else sessionStorage.removeItem(ROUTE_USE_OFF_SESSION_KEY);
        } catch (err) {}
        aiwkUserClosed = !!flag;
        try { window.__AIWK_USER_CLOSED__ = !!flag; } catch (err) {}
    }

    function ensureRoot() {
        let root = document.getElementById(AIWK.rootId);
        if (!root) {
            root = document.createElement("div");
            root.id = AIWK.rootId;
            document.documentElement.appendChild(root);
        }
        root.dataset.version = AIWK.version || "v152-storage-namespace-profile-lock";
        return root;
    }

    function assertRuntimeReady(stage) {
        const missing = [];
        if (!window.AIWK) missing.push("AIWK");
        if (!AIWK.store || typeof AIWK.store.get !== "function") missing.push("AIWK.store.get");
        if (!AIWK.modules || typeof AIWK.modules.get !== "function") missing.push("AIWK.modules.get");
        if (!AIWK.win || typeof AIWK.win.get !== "function" || typeof AIWK.win.create !== "function") missing.push("AIWK.win.get/create");
        if (!AIWK.group || typeof AIWK.group.create !== "function") missing.push("AIWK.group.create");
        if (missing.length) {
            const err = new Error("AIWK_RUNTIME_NOT_READY: " + missing.join(", ") + " 준비 전입니다. stage=" + (stage || ""));
            err.code = "AIWK_RUNTIME_NOT_READY";
            err.missing = missing;
            throw err;
        }
        return true;
    }

    function createWinOnce(cfg) {
        assertRuntimeReady("createWinOnce:" + (cfg && cfg.id || ""));
        const old = AIWK.win.get(cfg.id);
        if (old) {
            if (typeof old.bringFront === "function") old.bringFront();
            return old;
        }
        return AIWK.win.create(cfg);
    }

    function ensureMainZone() {
        try {
            AIWK.group.create("zone-main", { title: "Main Zone", order: 1, useOrder: 1 });
        } catch (err) {
            // 이미 생성된 경우를 포함해 zone 생성 오류가 첫 화면을 막지 않게 한다.
        }
    }

    function createDefaultAuthWindow() {
        const win = createWinOnce({
            id: "auth-win",
            title: "AIWK 메인 제어창 " + (AIWK.version || "v152-storage-namespace-profile-lock"),
            module: "auth.panel",
            rect: { x: 40, y: 70, w: 520, h: 560 }
        });
        try { AIWK.group.addWin("zone-main", "auth-win"); } catch (err) {}
        return win;
    }

    function createDefaultTestLauncher() {
        const win = createWinOnce({
            id: "test-launcher-win",
            title: "EXT4 " + (AIWK.version || "v152-storage-namespace-profile-lock") + " 통신 TEST 런처",
            module: "dev.test_launcher",
            rect: { x: 40, y: 70, w: 680, h: 580 }
        });
        try { AIWK.group.addWin("zone-main", "test-launcher-win"); } catch (err) {}
        return win;
    }


    function sameHost(urlA, urlB) {
        try {
            const a = new URL(String(urlA || ""), location.href);
            const b = new URL(String(urlB || ""), location.href);
            return !a.hostname || !b.hostname || a.hostname === b.hostname;
        } catch (err) { return true; }
    }

    async function applyTabScopedProfileFromStorage() {
        try {
            if (isCurrentTabRouteUseOff()) {
                aiwkUserClosed = true;
                try { window.__AIWK_USER_CLOSED__ = true; } catch (err) {}
                if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(false, "route_use_off_current_tab");
                disposeFloatingUi("route_use_off_current_tab");
                return { ok: 0, reason: "route_use_off_current_tab" };
            }
            if (!chrome || !chrome.runtime || !chrome.runtime.sendMessage) {
                if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(false, "chrome_runtime_missing");
                return { ok: 0, reason: "chrome_runtime_missing" };
            }
            const rid = AIWK.comm && AIWK.comm.getUrlRouteIdentity ? AIWK.comm.getUrlRouteIdentity() : {};
            const res = await chrome.runtime.sendMessage({ type: "AIWK_GET_CURRENT_TAB_CONFIG", url: location.href, route_key: rid.route_key || "", url_key: rid.url_key || "" });
            const cfg = res && res.config ? res.config : null;
            const tabState = cfg && cfg.tabs && cfg.tabs[String(res && (res.tab_id || res.tabId) || "")] ? cfg.tabs[String(res.tab_id || res.tabId)] : {};
            const status = String((cfg && (cfg.status || cfg.aiwk_status)) || "").toUpperCase();
            const clientId = cfg && (cfg.clientId || cfg.client_id) ? String(cfg.clientId || cfg.client_id).trim() : "";
            if (!res || !res.ok || !cfg) {
                if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(false, "tab_config_missing");
                return { ok: 0, reason: "tab_config_missing", res: res || null };
            }
            if (!clientId) {
                const reason = status === "NEW" ? "tab_config_new" : "tab_config_unassigned";
                if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(false, reason);
                return { ok: 0, reason: reason, tab_id: res.tab_id || "", status: status || "NEW", res: res || null };
            }
            const ended = tabState.enabled === false || !!tabState.ended || String(tabState.status || "").toUpperCase() === "END";
            if (ended) {
                aiwkUserClosed = true;
                if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(false, "route_config_disabled");
                return { ok: 0, reason: "route_config_disabled", clientId: cfg.clientId || cfg.client_id || "", tab_id: res.tab_id || "", end_label: cfg.end_label || ((res.tab_id || "") + "END") };
            }
            if (cfg.url && !sameHost(cfg.url, location.href)) {
                if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(false, "tab_config_host_mismatch");
                return { ok: 0, reason: "tab_config_host_mismatch", saved_url: cfg.url, current_url: location.href };
            }
            const profileForComm = {
                wsUrl: cfg.wsUrl || "",
                clientId: clientId,
                role: cfg.role || "",
                group: cfg.group || "home",
                project: cfg.project || "IG_CRM",
                roomId: cfg.roomId || cfg.room || "",
                authToken: cfg.authToken || cfg.token || "",
                token: cfg.authToken || cfg.token || "",
                authRole: cfg.authRole || "",
                authMode: cfg.authMode || "",
                authUserType: cfg.authUserType || "",
                authPayload: cfg.authPayload || {},
                profileInstanceId: cfg.profileInstanceId || cfg.profile_instance_id || "",
                profile_instance_id: cfg.profile_instance_id || cfg.profileInstanceId || ""
            };
            window.__AIWK_ACTIVE_ROUTE_PROFILE__ = Object.assign({}, profileForComm, {
                route_key: rid.route_key || "",
                url_key: rid.url_key || "",
                tab_id: res.tab_id || res.tabId || "",
                profile_source: "route_config"
            });
            if (AIWK.comm && typeof AIWK.comm.setProfile === "function") {
                AIWK.comm.setProfile(profileForComm, "tab_config_loaded");
            } else if (AIWK.store && typeof AIWK.store.set === "function") {
                AIWK.store.set("profile", profileForComm);
            }
            if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(true, "tab_config_loaded");
            return { ok: 1, clientId: clientId, tab_id: res.tab_id || "" };
        } catch (err) {
            if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(false, "tab_config_error");
            return { ok: 0, reason: "tab_config_error", error: err && err.message ? err.message : String(err) };
        }
    }

    async function boot() {
        if (bootPromise) {
            return bootPromise;
        }
        bootPromise = (async function () {
            try {
                if (!window.AIWK) { throw new Error("AIWK_CORE_NOT_READY: window.AIWK가 없습니다."); }
                if (!AIWK.auth || typeof AIWK.auth.init !== "function") { throw new Error("AIWK_AUTH_NOT_READY: AIWK.auth.init 준비 전입니다."); }
                if (!AIWK.loader || typeof AIWK.loader.init !== "function") { throw new Error("AIWK_LOADER_NOT_READY: AIWK.loader.init 준비 전입니다."); }
                AIWK.auth.init();
                ensureRoot();
                await AIWK.loader.init();
                assertRuntimeReady("boot.afterLoader");
                const tabProfileResult = await applyTabScopedProfileFromStorage();
                installRelayHooksOnce();
                try {
                    if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") {
                        AIWK.comm.setTabClientActive(!!(tabProfileResult && tabProfileResult.ok), (tabProfileResult && (tabProfileResult.reason || "tab_config_loaded")) || "tab_config_missing");
                    }
                } catch (err) {}
                try { if (AIWK.comm && typeof AIWK.comm.updateWindowTitle === "function") AIWK.comm.updateWindowTitle(); } catch (err) {}
                if (AIWK.domainLoader && typeof AIWK.domainLoader.scanCurrentAndStore === "function") {
                    AIWK.domainLoader.scanCurrentAndStore().catch(function(err){ console.warn("AIWK domain scan skipped", err); });
                }
                ensureMainZone();
                if (tabProfileResult && tabProfileResult.reason === "route_config_disabled") {
                    disposeFloatingUi("route_config_disabled");
                } else {
                    ensureFloatingIcon();
                }
                bootOk = true;
                // v074: 페이지 로딩 시 AIWK 고정 아이콘은 항상 표시한다. 큰 창은 사용자 메뉴로 연다.
                // v061: 페이지 로딩만으로 저장 창을 자동복원하지 않는다.
                // popup 접속창 또는 사용자의 명시적 창 제어 명령으로만 창을 연다.
                return { ok: 1, version: AIWK.version || "v152-storage-namespace-profile-lock", root_version: ensureRoot().dataset.version || "", message: "AIWK EXT4 v152-storage-namespace-profile-lock boot ready" };
            } catch (err) {
                bootOk = false;
                bootPromise = null;
                console.error("AIWK EXT4 boot stopped", err);
                throw err;
            }
        }());
        return bootPromise;
    }

    function applyPopupConfig(config) {
        config = config || {};
        try {
            if (AIWK.auth && typeof AIWK.auth.applyPopupLogin === "function") {
                AIWK.auth.applyPopupLogin(config);
            }
            if (AIWK.comm && typeof AIWK.comm.setProfile === "function") {
                AIWK.comm.setProfile(config, "popup_config");
            } else if (AIWK.store) {
                AIWK.store.set("profile", {
                    wsUrl: config.wsUrl || "",
                    clientId: config.clientId || "",
                    role: config.role || "",
                    group: config.group || "",
                    project: config.project || "",
                    roomId: config.roomId || "",
                    authToken: config.authToken || config.token || "",
                    token: config.authToken || config.token || "",
                    authRole: config.authRole || "",
                    authMode: config.authMode || "",
                    authUserType: config.authUserType || (config.authPayload && config.authPayload.user_type) || "",
                    authPayload: config.authPayload || {}
                });
            }
            try { if (AIWK.comm && typeof AIWK.comm.updateWindowTitle === "function") AIWK.comm.updateWindowTitle(); } catch (err) {}
        } catch (err) {
            console.error("AIWK EXT4 popup auth apply failed", err);
            throw err;
        }
    }

    async function openAuthFromAction(config) {
        await boot();
        ensureRoot();
        applyPopupConfig(config || {});
        createDefaultAuthWindow();
        return { ok: 1, action: "open_auth", boot_ok: bootOk, role: AIWK.auth && AIWK.auth.role, mode: AIWK.auth && AIWK.auth.mode };
    }




    async function controlWindows(action) {
        await boot();
        ensureRoot();
        if (!AIWK.win) {
            throw new Error("AIWK_WIN_MISSING: 창 관리자가 준비되지 않았습니다.");
        }
        if (action === "collapse_all") { return AIWK.win.collapseAll(); }
        if (action === "collapse2_all") { return AIWK.win.collapse2All(); }
        if (action === "expand_all") { return AIWK.win.expandAll(); }
        if (action === "hide_all") { return AIWK.win.hideAll(); }
        if (action === "show_all") { return AIWK.win.showAll(); }
        if (action === "icon_all") { return AIWK.win.iconAll(); }
        if (action === "close_all") {
            markClientUserClosed("close_all");
            try { if (AIWK.comm && typeof AIWK.comm.noderedDisconnect === "function") AIWK.comm.noderedDisconnect("close_all"); } catch (err) {}
            try { if (AIWK.comm && typeof AIWK.comm.tabUnregister === "function") await AIWK.comm.tabUnregister("tabbus"); } catch (err) {}
            try { if (AIWK.comm && typeof AIWK.comm.wssDisconnect === "function") AIWK.comm.wssDisconnect("close_all"); } catch (err) {}
            let winRes = null;
            try { if (window.AIWKWindowControls && typeof window.AIWKWindowControls.disposeAll === "function") window.AIWKWindowControls.disposeAll("user_closed"); } catch (err) {}
            try { winRes = AIWK.win.closeAll(); } catch (err) { winRes = { ok: 0, error: err && err.message ? err.message : String(err) }; }
            const floatRes = disposeFloatingUi("user_closed");
            return { ok: 1, action: "close_all", status: "USER_CLOSED", emoji: "⏹", win: winRes, floating: floatRes };
        }
        if (action === "restore_saved") { return await AIWK.win.restoreSavedSequential(); }
        throw new Error("UNKNOWN_WIN_CONTROL: " + action);
    }

    function createManualTestManager() {
        const win = createWinOnce({
            id: "manual-test-manager-win",
            title: "수동 테스트 관리 " + (AIWK.version || "v152-storage-namespace-profile-lock"),
            module: "dev.manual_test_manager",
            rect: { x: 70, y: 90, w: 760, h: 650 }
        });
        try { AIWK.group.addWin("zone-main", "manual-test-manager-win"); } catch (err) {}
        return win;
    }

    async function openManualTestFromAction() {
        await boot();
        ensureRoot();
        createManualTestManager();
        return { ok: 1, action: "open_manual_test", boot_ok: bootOk, version: AIWK.version || "v152-storage-namespace-profile-lock" };
    }

    function createCommWin(id, title, module, rect) {
        const win = createWinOnce({ id: id, title: title, module: module, rect: rect });
        try { AIWK.group.addWin("zone-main", id); } catch (err) {}
        return win;
    }

    function createChartDevWindow() {
        const win = createWinOnce({
            id: "chart-dev-win",
            title: "차트 개발용 template " + (AIWK.version || "v152-storage-namespace-profile-lock"),
            shortTitle: "차트DEV",
            icon: "📈",
            module: "chart.demo",
            rect: { x: 80, y: 100, w: 720, h: 650 }
        });
        try { AIWK.group.addWin("zone-main", "chart-dev-win"); } catch (err) {}
        return win;
    }

    function createChartViewerWindow() {
        const win = createWinOnce({
            id: "chart-viewer-win",
            title: "차트 운영용 viewer " + (AIWK.version || "v152-storage-namespace-profile-lock"),
            shortTitle: "차트VIEW",
            icon: "📊",
            module: "chart.demo",
            rect: { x: 830, y: 100, w: 620, h: 420 }
        });
        try { AIWK.group.addWin("zone-main", "chart-viewer-win"); } catch (err) {}
        return win;
    }

    async function openChartDevFromAction() {
        await boot();
        ensureRoot();
        createChartDevWindow();
        return { ok: 1, action: "open_chart_dev", boot_ok: bootOk, version: AIWK.version || "v152-storage-namespace-profile-lock" };
    }

    async function openChartViewerFromAction() {
        await boot();
        ensureRoot();
        createChartViewerWindow();
        return { ok: 1, action: "open_chart_viewer", boot_ok: bootOk, version: AIWK.version || "v152-storage-namespace-profile-lock" };
    }



    let relayHooksInstalled = false;
    function relayPost(event) {
        try {
            if (!chrome || !chrome.runtime || !chrome.runtime.sendMessage) return;
            const status = AIWK.comm && typeof AIWK.comm.status === "function" ? AIWK.comm.status() : null;
            const profile = status && status.profile ? status.profile : {};
            chrome.runtime.sendMessage({
                type: "AIWK_RELAY_EVENT",
                event: Object.assign({
                    source: "content",
                    version: AIWK.version || "v152-storage-namespace-profile-lock",
                    tab_state: {
                        client_id: profile.clientId || "",
                        group: profile.group || "",
                        project: profile.project || "",
                        room_id: profile.roomId || "",
                        url: location.href,
                        title: document.title,
                        counters: status && status.counters ? status.counters : {},
                        comm: status ? { wss: status.wss, tabbus: status.tabbus, nodered: status.nodered } : {}
                    }
                }, event || {})
            }).catch(function () {});
        } catch (err) {}
    }


    function wssSelectorActionToRuntimeAction(actionName) {
        const a = String(actionName || "").trim();
        if (a === "aiwk_selector_list_frames" || a === "selector_list_frames") return "list_frames";
        if (a === "aiwk_iframe_chat_send" || a === "iframe_chat_send" || a === "aiwk_agent_chat_send") return "iframe_chat_send";
        if (a === "aiwk_selector_click" || a === "selector_click") return "click";
        if (a === "aiwk_selector_input" || a === "selector_input") return "input";
        if (a === "aiwk_selector_text" || a === "selector_text") return "text";
        if (a === "aiwk_selector_html" || a === "selector_html") return "html";
        if (a === "aiwk_selector_find" || a === "selector_find" || a === "aiwk_selector") return "find";
        return "";
    }

    async function handleWssSelectorPacket(packet) {
        const actionName = String((packet && (packet.action || packet.aiwk_action)) || "").trim();
        const runAction = wssSelectorActionToRuntimeAction(actionName);
        if (!runAction) return null;
        const payload = packet && packet.payload ? packet.payload : {};
        const status = AIWK.comm && typeof AIWK.comm.status === "function" ? AIWK.comm.status() : {};
        const tabId = String((status && status.tab_id) || "").trim();
        const base = {
            target_tab_id: tabId,
            source: "wss",
            source_message_id: packet.message_id || packet.msg_id || packet.id || "",
            source_from: packet.from || ""
        };
        const req = Object.assign({}, payload || {}, base);
        let res;
        if (runAction === "list_frames") {
            res = await chrome.runtime.sendMessage(Object.assign({ type: "AIWK_SELECTOR_LIST_FRAMES" }, req));
        } else if (runAction === "iframe_chat_send") {
            res = await chrome.runtime.sendMessage(Object.assign({ type: "AIWK_IFRAME_AGENT_CHAT_SEND" }, req));
        } else {
            res = await chrome.runtime.sendMessage(Object.assign({ type: "AIWK_SELECTOR_RUN", action: runAction }, req));
        }
        try {
            relayPost({ dir: "selector", channel: "wss", action: actionName, ok: res && res.ok ? 1 : 0, result: res || {} });
        } catch (err) {}
        try {
            const to = String(packet.from || "").trim();
            if (to && AIWK.comm && typeof AIWK.comm.wssSend === "function") {
                AIWK.comm.wssSend(to, "aiwk_selector_result", Object.assign({ request_action: actionName }, res || {}), "ext4");
            }
        } catch (err) {}
        return res;
    }

    function installRelayHooksOnce() {
        if (relayHooksInstalled) return;
        relayHooksInstalled = true;
        try {
            if (AIWK.events && typeof AIWK.events.on === "function") {
                AIWK.events.on("comm:wss:send", function (packet) {
                    relayPost({ dir: "tx", channel: "wss", action: packet && (packet.action || packet.aiwk_action) || "", packet: packet || {} });
                });
                AIWK.events.on("comm:wss:message", function (packet) {
                    relayPost({ dir: "rx", channel: "wss", action: packet && (packet.action || packet.aiwk_action || packet.type) || "", packet: packet || {} });
                    handleWssSelectorPacket(packet).catch(function (err) {
                        relayPost({ dir: "selector", channel: "wss", action: packet && (packet.action || packet.aiwk_action || packet.type) || "", ok: 0, error: err && err.message ? err.message : String(err) });
                    });
                });
                AIWK.events.on("comm:tab:message", function (packet) {
                    relayPost({ dir: "rx", channel: "bus", action: packet && (packet.action || packet.type) || "", packet: packet || {} });
                });
                AIWK.events.on("comm:status", function (status) {
                    relayPost({ dir: "status", channel: "comm", action: "status", status: status || {} });
                });
            }
            relayPost({ dir: "boot", channel: "content", action: "relay_hooks_installed" });
        } catch (err) {
            console.warn("AIWK relay hook install failed", err);
        }
    }

    function buildRelaySnapshot() {
        const status = AIWK.comm && typeof AIWK.comm.status === "function" ? AIWK.comm.status() : null;
        return {
            ok: 1,
            version: AIWK.version || "v152-storage-namespace-profile-lock",
            url: location.href,
            title: document.title,
            comm: status,
            counters: status && status.counters ? status.counters : {},
            at: new Date().toISOString()
        };
    }

    function stopAllLocalLoops(reason) {
        try { if (AIWK.comm && typeof AIWK.comm.stopAllRuntime === "function") AIWK.comm.stopAllRuntime(reason || "global_use_off"); } catch (err) {}
        try { if (AIWK.comm && typeof AIWK.comm.setNrAutoKeep === "function") AIWK.comm.setNrAutoKeep(false); } catch (err) {}
        try { if (AIWK.comm && typeof AIWK.comm.setBusAutoKeep === "function") AIWK.comm.setBusAutoKeep(false); } catch (err) {}
        try { if (AIWK.comm && typeof AIWK.comm.setAutoReconnect === "function") AIWK.comm.setAutoReconnect(false); } catch (err) {}
        try { if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(false, reason || "global_use_off"); } catch (err) {}
        try { relayPost({ dir: "control", channel: "content", action: "stop_all_loops", reason: reason || "relay" }); } catch (err) {}
        return { ok: 1, action: "stop_all_loops", reason: reason || "relay" };
    }


    function createCommTestWindows() {
        createCommWin("bus-test-win", "BUS 통신 테스트창 " + (AIWK.version || "v152-storage-namespace-profile-lock"), "comm.bus_test", { x: 40, y: 90, w: 560, h: 520 });
        createCommWin("wss-test-win", "WSS 통신 테스트창 " + (AIWK.version || "v152-storage-namespace-profile-lock"), "comm.wss_test", { x: 620, y: 90, w: 580, h: 560 });
        createCommWin("nr-test-win", "Node-RED 통신 테스트창 " + (AIWK.version || "v152-storage-namespace-profile-lock"), "comm.nr_test", { x: 1220, y: 90, w: 580, h: 520 });
        return { ok: 1, action: "open_comm_test_windows", wins: ["bus-test-win", "wss-test-win", "nr-test-win"] };
    }

    async function openTestFromAction() {
        await boot();
        ensureRoot();
        createDefaultTestLauncher();
        return { ok: 1, action: "open_test", boot_ok: bootOk };
    }

    async function requireActiveTabConfigForControl(expectedConfig) {
        expectedConfig = expectedConfig || {};
        const rid = AIWK.comm && AIWK.comm.getUrlRouteIdentity ? AIWK.comm.getUrlRouteIdentity() : {};
        const res = await chrome.runtime.sendMessage({ type: "AIWK_GET_CURRENT_TAB_CONFIG", url: location.href, route_key: rid.route_key || "", url_key: rid.url_key || "" });
        const cfg = res && res.config ? res.config : null;
        const key = "AIWK_ROUTE_CONFIG:" + String((res && (res.route_key || res.url_key)) || rid.route_key || "");
        if (!res || !res.ok || !cfg) {
            throw new Error("AIWK_ROUTE_CONFIG_MISSING: 필요 키 없음: " + key + " / WSS 인증등록 중단. 먼저 현재 url5에 client_id/token을 저장하십시오.");
        }
        const status = String(cfg.status || cfg.aiwk_status || "").toUpperCase();
        if (status === "NEW" || !String(cfg.clientId || cfg.client_id || "").trim()) {
            throw new Error("AIWK_ROUTE_CONFIG_NOT_ASSIGNED: " + key + " status=" + (status || "NEW") + " client_id 없음 / 해결: [프로필 저장]으로 현재 url5에 client_id/token을 저장하십시오.");
        }
        const tabState = cfg.tabs && cfg.tabs[String(res.tab_id || res.tabId || "")] ? cfg.tabs[String(res.tab_id || res.tabId || "")] : null;
        if (!tabState) {
            throw new Error("AIWK_ROUTE_TAB_DISABLED: 현재 tab_id=" + String(res.tab_id || res.tabId || "") + " 는 사용 OFF 상태입니다. 접속창 맨 위 [사용]을 체크한 뒤 다시 실행하십시오.");
        }
        if (status === "END" || cfg.ended || tabState.enabled === false || !!tabState.ended || String(tabState.status || "").toUpperCase() === "END") {
            throw new Error("AIWK_ROUTE_CONFIG_ENDED: " + key + " 는 END/OFF 상태입니다.");
        }
        if (!String(cfg.profile_instance_id || cfg.profileInstanceId || "").trim()) {
            throw new Error("AIWK_PROFILE_INSTANCE_ID_MISSING: " + key + " 안에 profile_instance_id가 없습니다. 해결: popup에서 [프로필 저장]을 다시 누르십시오.");
        }
        const want = String(expectedConfig.clientId || expectedConfig.client_id || "").trim();
        const got = String(cfg.clientId || cfg.client_id || "").trim();
        if (want && got && want !== got) {
            throw new Error("AIWK_ROUTE_CONFIG_CLIENT_MISMATCH: " + key + " 저장 client=" + got + " / 요청 client=" + want + " / 폼만 바뀌고 route config가 갱신되지 않았습니다.");
        }
        return { ok:1, key:key, tab_id:String(res.tab_id || res.tabId || ""), config:cfg };
    }


    async function commControlFromAction(msg) {
        await boot();
        ensureRoot();
        const checkedCfg = await requireActiveTabConfigForControl(msg.config || {});
        const cfgForApply = Object.assign({}, msg.config || {}, checkedCfg.config || {});
        if (!msg.skip_config) applyPopupConfig(cfgForApply);
        if (!AIWK.comm) {
            throw new Error("AIWK_COMM_MISSING: 통신 공용 객체가 준비되지 않았습니다.");
        }
        const action = String((msg && msg.action) || "").trim();
        const extra = (msg && msg.extra) || {};
        if (action === "wss_connect") {
            return AIWK.comm.wssConnect({ force: !!extra.force, auth_register: !!extra.auth_register });
        }
        if (action === "wss_disconnect") {
            return AIWK.comm.wssDisconnect("popup-manual");
        }
        if (action === "wss_keep") {
            return AIWK.comm.setAutoReconnect(!!extra.checked, extra.interval_ms || extra.reconnect_ms || 0);
        }
        if (action === "retry_interval") {
            if (typeof AIWK.comm.setRetryInterval === "function") return AIWK.comm.setRetryInterval(extra.seconds || extra.sec || 0);
            return { ok: 0, code: "RETRY_INTERVAL_MISSING" };
        }
        if (action === "bus_connect") {
            return await AIWK.comm.tabRegister("tabbus");
        }
        if (action === "bus_disconnect") {
            if (typeof AIWK.comm.tabUnregister === "function") return await AIWK.comm.tabUnregister("tabbus");
            return { ok: 0, code: "TAB_UNREGISTER_MISSING" };
        }
        if (action === "bus_keep") {
            if (typeof AIWK.comm.setBusAutoKeep === "function") return AIWK.comm.setBusAutoKeep(!!extra.checked, extra.interval_ms || extra.keep_ms || 0);
            return { ok: 0, code: "BUS_AUTO_KEEP_MISSING" };
        }
        if (action === "bus_interval") {
            if (typeof AIWK.comm.setBusKeepInterval === "function") return AIWK.comm.setBusKeepInterval(extra.seconds || extra.sec || 0);
            return { ok: 0, code: "BUS_INTERVAL_MISSING" };
        }
        if (action === "chat_send") {
            const to = String(extra.to || "").trim();
            const text = String(extra.text || "");
            if (!to) throw new Error("CHAT_TO_EMPTY");
            return AIWK.comm.wssSend(to, "aiwk_chat", { text: text, at: new Date().toISOString() }, "ext4");
        }
        if (action === "ping_send") {
            const to = String(extra.to || "").trim();
            if (!to) throw new Error("PING_TO_EMPTY");
            return AIWK.comm.wssSend(to, "aiwk_ping", { text: String(extra.text || ""), at: new Date().toISOString() }, "ext4");
        }
        if (action === "list_clients_send") {
            if (typeof AIWK.comm.wssSendPacket !== "function") {
                throw new Error("WSS_SEND_PACKET_MISSING");
            }
            const p = AIWK.comm.getProfile ? AIWK.comm.getProfile() : {};
            return AIWK.comm.wssSendPacket({
                type: "command",
                from: "ext4",
                group: p.group || "home",
                project: p.project || "IG_CRM",
                room_id: p.roomId || "",
                client_id: p.clientId || "C1",
                action: "aiwk_list_clients",
                payload: { scope: extra.scope || "auto", at: new Date().toISOString() },
                need_ack: true
            });
        }
        if (action === "nr_connect") {
            const p = AIWK.comm.getProfile ? AIWK.comm.getProfile() : {};
            const room = p.roomId ? String(p.roomId) + "@" : "";
            const target = "aiwk://" + room + [p.group || "home", p.project || "IG_CRM", "N1"].join(".") + ":nodrd#node-red";
            return AIWK.comm.noderedSend(target, "aiwk_ping", { text: "AIWK EXT4 popup NR connect test", at: new Date().toISOString() });
        }
        if (action === "nr_disconnect") {
            if (typeof AIWK.comm.noderedDisconnect === "function") return AIWK.comm.noderedDisconnect("popup-manual");
            return { ok: 0, code: "NR_DISCONNECT_MISSING" };
        }
        if (action === "nr_keep") {
            if (typeof AIWK.comm.setRetryInterval === "function" && (extra.seconds || extra.interval_ms)) AIWK.comm.setRetryInterval(extra.seconds || Math.round((parseInt(extra.interval_ms, 10) || 0) / 1000));
            if (typeof AIWK.comm.setNrAutoKeep === "function") return AIWK.comm.setNrAutoKeep(!!extra.checked);
            return { ok: 0, code: "NR_AUTO_KEEP_MISSING" };
        }
        if (action === "count_reset") {
            if (typeof AIWK.comm.resetCounters === "function") return AIWK.comm.resetCounters();
            return { ok: 0, code: "COUNT_RESET_MISSING" };
        }
        throw new Error("UNKNOWN_COMM_CONTROL: " + action);
    }


    function insertTextIntoChatGptPrompt(text) {
        const value = String(text || "");
        const selectors = ["#prompt-textarea", "textarea[data-id=\"root\"]", "textarea", "[contenteditable=\"true\"]"];
        let target = null;
        for (let i = 0; i < selectors.length; i += 1) {
            target = document.querySelector(selectors[i]);
            if (target) { break; }
        }
        if (!target) {
            return { ok: 0, code: "CHATGPT_PROMPT_NOT_FOUND", selector: "#prompt-textarea" };
        }
        target.focus();
        if (target.tagName === "TEXTAREA" || target.tagName === "INPUT") {
            const old = String(target.value || "");
            target.value = old ? (old + "\n\n" + value) : value;
            target.selectionStart = target.selectionEnd = target.value.length;
            target.dispatchEvent(new Event("input", { bubbles: true }));
            target.dispatchEvent(new Event("change", { bubbles: true }));
        } else {
            const old = String(target.textContent || "");
            target.textContent = old ? (old + "\n\n" + value) : value;
            target.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText", data: value }));
            try {
                const range = document.createRange();
                range.selectNodeContents(target);
                range.collapse(false);
                const sel = window.getSelection();
                sel.removeAllRanges();
                sel.addRange(range);
            } catch(e) {}
        }
        return { ok: 1, code: "CHATGPT_PROMPT_APPENDED", selector: target.id ? "#" + target.id : target.tagName, len: value.length };
    }




    const AIWK_FLOAT_ICON_ID = "aiwk-floating-control-icon";
    const AIWK_FLOAT_MENU_ID = "aiwk-floating-control-menu";
    const AIWK_FLOAT_POS_KEY = "AIWK_FLOATING_ICON_POS_V074";

    function safeText(v) {
        return String(v == null ? "" : v);
    }

    function loadFloatPos() {
        try {
            const raw = localStorage.getItem(AIWK_FLOAT_POS_KEY);
            if (!raw) return null;
            const pos = JSON.parse(raw);
            if (!pos || typeof pos !== "object") return null;
            const x = Number(pos.x);
            const y = Number(pos.y);
            if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
            return { x: Math.max(0, x), y: Math.max(0, y) };
        } catch (err) { return null; }
    }

    function saveFloatPos(x, y) {
        try { localStorage.setItem(AIWK_FLOAT_POS_KEY, JSON.stringify({ x: Math.round(x), y: Math.round(y), at: Date.now() })); } catch (err) {}
    }

    function getCurrentClientIdText() {
        try {
            if (AIWK.comm && typeof AIWK.comm.isTabClientActive === "function" && !AIWK.comm.isTabClientActive()) return "";
            const routeProfile = window.__AIWK_ACTIVE_ROUTE_PROFILE__ || null;
            if (routeProfile && (routeProfile.clientId || routeProfile.client_id)) return safeText(routeProfile.clientId || routeProfile.client_id || "").trim();
            const p = AIWK.comm && typeof AIWK.comm.getProfile === "function" ? AIWK.comm.getProfile() : (AIWK.store && AIWK.store.get ? AIWK.store.get("profile", {}) : {});
            return safeText(p.client_id || p.clientId || p.client || "").trim();
        } catch (err) { return ""; }
    }

    function resyncCommProfileFromRouteCache() {
        try {
            const routeProfile = window.__AIWK_ACTIVE_ROUTE_PROFILE__ || null;
            if (!routeProfile || !(routeProfile.clientId || routeProfile.client_id)) return;
            if (!AIWK.comm || typeof AIWK.comm.getProfile !== "function" || typeof AIWK.comm.setProfile !== "function") return;
            const current = AIWK.comm.getProfile() || {};
            const want = safeText(routeProfile.clientId || routeProfile.client_id || "").trim();
            const got = safeText(current.clientId || current.client_id || "").trim();
            if (want && want !== got) {
                AIWK.comm.setProfile(routeProfile, "floating_route_profile_resync");
                if (typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(true, "floating_route_profile_resync");
            }
        } catch (err) {}
    }

    function getCurrentStatusEmojiText() {
        try {
            if (AIWK.comm && typeof AIWK.comm.statusEmojiTriple === "function") return AIWK.comm.statusEmojiTriple();
            const st = AIWK.comm && typeof AIWK.comm.status === "function" ? AIWK.comm.status() : null;
            const wss = st && st.wss ? st.wss : {};
            const bus = st && st.tabbus ? st.tabbus : {};
            const nr = st && st.nodered ? st.nodered : {};
            const reason = String(wss.last_error || wss.last_close_reason || "");
            const e1 = (wss.duplicate_route || /same_route_client_reconnected|replaced/i.test(reason)) ? "♻️" : (wss.open ? "🟢" : (wss.connecting || wss.auto_reconnect ? "🟡" : "🔴"));
            const e2 = bus.registered ? "🟢" : (bus.ok ? "🟡" : "🔴");
            const e3 = nr.ok ? "🟢" : (nr.auto_keep ? "🟡" : "🔴");
            return e1 + e2 + e3;
        } catch (err) {}
        return "🔴🔴🔴";
    }

    function updateFloatingIconLabel() {
        if (aiwkUserClosed || window.__AIWK_USER_CLOSED__) { disposeFloatingUi("user_closed"); return; }
        const btn = document.getElementById(AIWK_FLOAT_ICON_ID);
        if (!btn) return;
        resyncCommProfileFromRouteCache();
        const cid = getCurrentClientIdText();
        const emoji = cid ? getCurrentStatusEmojiText() : "";
        const label = btn.querySelector("[data-aiwk-float-label]");
        if (label) label.textContent = cid ? ("AIWK " + emoji + " " + cid) : "AIWK";
        btn.title = cid ? ("AIWK 자동화 메뉴 / " + emoji + cid + " / 드래그 이동 가능") : "AIWK 자동화 메뉴 / 이 탭에는 client_id가 아직 배정되지 않았습니다.";
        const mt = document.querySelector("[data-aiwk-float-menu-title]");
        if (mt) mt.textContent = cid ? ("AIWK " + emoji + cid + " 자동화 메뉴") : "AIWK 자동화 메뉴";
    }

    function floatButtonStyle(el, bg) {
        el.style.cssText = [
            "display:block",
            "width:100%",
            "box-sizing:border-box",
            "margin:3px 0",
            "padding:6px 8px",
            "border:1px solid rgba(148,163,184,.55)",
            "border-radius:8px",
            "background:" + (bg || "rgba(15,23,42,.92)"),
            "color:#fff",
            "font:12px/1.25 Arial, sans-serif",
            "text-align:left",
            "cursor:pointer",
            "user-select:text",
            "-webkit-user-select:text"
        ].join(";");
    }

    function ensureFloatingMenu(icon) {
        let menu = document.getElementById(AIWK_FLOAT_MENU_ID);
        if (menu) return menu;
        menu = document.createElement("div");
        menu.id = AIWK_FLOAT_MENU_ID;
        menu.setAttribute("data-aiwk-ui", "floating-menu");
        menu.style.cssText = [
            "position:fixed",
            "z-index:2147483646",
            "min-width:180px",
            "max-width:260px",
            "padding:8px",
            "border:1px solid rgba(148,163,184,.65)",
            "border-radius:12px",
            "background:rgba(2,6,23,.94)",
            "box-shadow:0 8px 24px rgba(0,0,0,.28)",
            "color:#fff",
            "font:12px/1.35 Arial, sans-serif",
            "display:none",
            "user-select:text",
            "-webkit-user-select:text"
        ].join(";");
        const title = document.createElement("div");
        title.style.cssText = "font-weight:700;margin:0 0 6px 0;user-select:text;-webkit-user-select:text;";
        title.textContent = "AIWK 자동화 메뉴";
        title.setAttribute("data-aiwk-float-menu-title", "1");
        menu.appendChild(title);
        const rows = [
            ["Relay 창", "relay", "rgba(37,99,235,.92)"],
            ["설정창", "auth", "rgba(22,101,52,.92)"],
            ["수동테스트", "manual", "rgba(88,28,135,.92)"],
            ["차트 개발용", "chart_dev", "rgba(124,58,237,.92)"],
            ["차트 운영용", "chart_viewer", "rgba(109,40,217,.92)"],
            ["전체1단", "collapse", "rgba(51,65,85,.92)"],
            ["전체2단", "collapse2", "rgba(51,65,85,.92)"],
            ["아이콘화", "icon", "rgba(51,65,85,.92)"],
            ["펼침", "expand", "rgba(51,65,85,.92)"]
        ];
        rows.forEach(function (row) {
            const b = document.createElement("button");
            b.type = "button";
            b.textContent = row[0];
            b.setAttribute("data-aiwk-float-action", row[1]);
            floatButtonStyle(b, row[2]);
            menu.appendChild(b);
        });
        document.documentElement.appendChild(menu);
        menu.addEventListener("click", function (ev) {
            const b = ev.target && ev.target.closest ? ev.target.closest("[data-aiwk-float-action]") : null;
            if (!b) return;
            runFloatingAction(b.getAttribute("data-aiwk-float-action") || "");
        });
        document.addEventListener("click", function (ev) {
            if (!menu || menu.style.display === "none") return;
            if (icon && icon.contains(ev.target)) return;
            if (menu.contains(ev.target)) return;
            menu.style.display = "none";
        }, true);
        return menu;
    }

    function placeFloatingMenu(icon, menu) {
        const r = icon.getBoundingClientRect();
        const w = 210;
        const left = Math.min(Math.max(8, r.left), Math.max(8, window.innerWidth - w - 8));
        const top = Math.min(Math.max(8, r.bottom + 6), Math.max(8, window.innerHeight - 280));
        menu.style.left = left + "px";
        menu.style.top = top + "px";
    }

    function sendRuntimeMessageSafe(msg) {
        return new Promise(function (resolve) {
            try {
                chrome.runtime.sendMessage(msg || {}, function (res) {
                    const le = chrome.runtime.lastError;
                    if (le) { resolve({ ok:0, error:le.message }); return; }
                    resolve(res || { ok:1 });
                });
            } catch (err) { resolve({ ok:0, error:err && err.message ? err.message : String(err) }); }
        });
    }

    async function runFloatingAction(action) {
        if (action === "relay") {
            const title = document.querySelector("[data-aiwk-float-menu-title]");
            const first = await sendRuntimeMessageSafe({ type:"AIWK_OPEN_RELAY_PANEL", source:"floating_icon" });
            if (first && first.ok) {
                if (title) title.textContent = "AIWK 자동화 메뉴";
                return;
            }
            const second = await sendRuntimeMessageSafe({ type:"AIWK_OPEN_RELAY_WINDOW", source:"floating_icon_fallback" });
            if (!second || !second.ok) {
                const err = (second && (second.error || second.code)) || (first && (first.error || first.code)) || "Relay 창 열기 실패";
                if (title) title.textContent = "Relay 창 실패: " + String(err).slice(0, 60);
                try { console.warn("AIWK Relay open failed", { first:first, second:second }); } catch (e) {}
            }
            return;
        }
        if (action === "auth") { await boot().catch(function () {}); createDefaultAuthWindow(); return; }
        if (action === "manual") { await openManualTestFromAction().catch(function () {}); return; }
        if (action === "chart_dev") { await openChartDevFromAction().catch(function () {}); return; }
        if (action === "chart_viewer") { await openChartViewerFromAction().catch(function () {}); return; }
        if (action === "collapse") { await controlWindows("collapse_all").catch(function () {}); return; }
        if (action === "collapse2") { await controlWindows("collapse2_all").catch(function () {}); return; }
        if (action === "icon") { await controlWindows("icon_all").catch(function () {}); return; }
        if (action === "expand") { await controlWindows("expand_all").catch(function () {}); return; }
    }


    function disposeFloatingUi(reason) {
        try { if (aiwkFloatLabelTimer) clearInterval(aiwkFloatLabelTimer); } catch (err) {}
        aiwkFloatLabelTimer = null;
        const ids = [AIWK_FLOAT_MENU_ID, AIWK_FLOAT_ICON_ID, AIWK.rootId];
        ids.forEach(function (id) {
            try { const el = document.getElementById(id); if (el) el.remove(); } catch (err) {}
        });
        try {
            Array.from(document.querySelectorAll("[data-aiwk-ui='floating-icon'],[data-aiwk-ui='floating-menu']")).reverse().forEach(function (el) { try { el.remove(); } catch (err) {} });
        } catch (err) {}
        try { window.__AIWK_USER_CLOSED__ = true; } catch (err) {}
        return { ok: 1, code: "AIWK_FLOATING_UI_DISPOSED", reason: reason || "user_closed" };
    }

    function markClientUserClosed(reason) {
        aiwkUserClosed = true;
        try { window.__AIWK_USER_CLOSED__ = true; } catch (err) {}
        try { if (AIWK.comm && typeof AIWK.comm.setTabClientActive === "function") AIWK.comm.setTabClientActive(false, reason || "user_closed"); } catch (err) {}
        try { if (AIWK.comm && typeof AIWK.comm.setNrAutoKeep === "function") AIWK.comm.setNrAutoKeep(false); } catch (err) {}
        try { if (AIWK.comm && typeof AIWK.comm.setAutoReconnect === "function") AIWK.comm.setAutoReconnect(false); } catch (err) {}
    }

    function ensureFloatingIcon() {
        if (aiwkUserClosed || window.__AIWK_USER_CLOSED__) return;
        if (document.getElementById(AIWK_FLOAT_ICON_ID)) {
            updateFloatingIconLabel();
            return;
        }
        const btn = document.createElement("button");
        btn.id = AIWK_FLOAT_ICON_ID;
        btn.type = "button";
        btn.setAttribute("data-aiwk-ui", "floating-icon");
        btn.innerHTML = '<span data-aiwk-float-label style="font-weight:800;user-select:text;-webkit-user-select:text;cursor:text;">AIWK</span>';
        const pos = loadFloatPos();
        const x = pos ? pos.x : Math.max(12, window.innerWidth - 108);
        const y = pos ? pos.y : 96;
        btn.style.cssText = [
            "position:fixed",
            "z-index:2147483647",
            "left:" + x + "px",
            "top:" + y + "px",
            "min-width:76px",
            "height:34px",
            "padding:0 9px",
            "border:1px solid rgba(255,255,255,.6)",
            "border-radius:999px",
            "background:rgba(15,23,42,.62)",
            "color:#fff",
            "box-shadow:0 4px 14px rgba(0,0,0,.24)",
            "backdrop-filter:blur(4px)",
            "font:12px/32px Arial, sans-serif",
            "cursor:move",
            "opacity:.78",
            "user-select:text",
            "-webkit-user-select:text",
            "white-space:nowrap"
        ].join(";");
        document.documentElement.appendChild(btn);
        const menu = ensureFloatingMenu(btn);
        let dragging = false;
        let moved = false;
        let sx = 0, sy = 0, ox = 0, oy = 0;
        btn.addEventListener("dblclick", function (ev) {
            try {
                const sel = window.getSelection && window.getSelection();
                const range = document.createRange();
                range.selectNodeContents(btn);
                if (sel) {
                    sel.removeAllRanges();
                    sel.addRange(range);
                }
                ev.preventDefault();
                ev.stopPropagation();
            } catch (err) {}
        });
        btn.addEventListener("pointerdown", function (ev) {
            if (ev.button !== 0) return;
            dragging = true;
            moved = false;
            sx = ev.clientX;
            sy = ev.clientY;
            ox = btn.offsetLeft;
            oy = btn.offsetTop;
            try { btn.setPointerCapture(ev.pointerId); } catch (e) {}
        });
        btn.addEventListener("pointermove", function (ev) {
            if (!dragging) return;
            const dx = ev.clientX - sx;
            const dy = ev.clientY - sy;
            if (Math.abs(dx) + Math.abs(dy) > 4) moved = true;
            const nx = Math.min(Math.max(0, ox + dx), Math.max(0, window.innerWidth - btn.offsetWidth));
            const ny = Math.min(Math.max(0, oy + dy), Math.max(0, window.innerHeight - btn.offsetHeight));
            btn.style.left = nx + "px";
            btn.style.top = ny + "px";
            if (menu.style.display !== "none") placeFloatingMenu(btn, menu);
        });
        btn.addEventListener("pointerup", function (ev) {
            if (!dragging) return;
            dragging = false;
            try { btn.releasePointerCapture(ev.pointerId); } catch (e) {}
            saveFloatPos(btn.offsetLeft, btn.offsetTop);
            if (!moved) {
                updateFloatingIconLabel();
                placeFloatingMenu(btn, menu);
                menu.style.display = menu.style.display === "none" ? "block" : "none";
            }
        });
        window.addEventListener("resize", function () {
            const nx = Math.min(Math.max(0, btn.offsetLeft), Math.max(0, window.innerWidth - btn.offsetWidth));
            const ny = Math.min(Math.max(0, btn.offsetTop), Math.max(0, window.innerHeight - btn.offsetHeight));
            btn.style.left = nx + "px";
            btn.style.top = ny + "px";
            saveFloatPos(nx, ny);
        });
        updateFloatingIconLabel();
        if (!aiwkFloatLabelTimer) aiwkFloatLabelTimer = setInterval(updateFloatingIconLabel, 2500);
    }

    function buildDiagnosticReportFromAction(msg) {
        const extra = msg && msg.extra ? msg.extra : {};
        const notes = Array.isArray(msg && msg.notes) ? msg.notes : [];
        const reportExtra = Object.assign({}, extra, { source: "AIWK_EXT4", notes: notes.length ? notes : ["popup diagnostic", "v152-storage-namespace-profile-lock"] });
        if (AIWK.diagnostic && typeof AIWK.diagnostic.buildReport === "function") {
            return { ok: 1, report: AIWK.diagnostic.buildReport(reportExtra) };
        }
        return {
            ok: 1,
            report: {
                kind: "AIWK_DIAG_FALLBACK",
                aiwk_version: AIWK.version || "v152-storage-namespace-profile-lock",
                build_version: AIWK.buildVersion || "0.0.113-stage5.1",
                source: "AIWK_EXT4",
                created_at: new Date().toISOString(),
                url: location.href,
                title: document.title,
                comm: AIWK.comm && typeof AIWK.comm.status === "function" ? AIWK.comm.status() : { ok: 0, reason: "AIWK.comm.status 없음" },
                windows: AIWK.win && typeof AIWK.win.exportState === "function" ? AIWK.win.exportState() : { ok: 0, reason: "AIWK.win.exportState 없음" },
                notes: notes
            }
        };
    }

    function safeSendResponse(sendResponse, payload) {
        try { sendResponse(payload || { ok: 1 }); } catch (err) {}
    }

    if (chrome && chrome.runtime && chrome.runtime.onMessage) {
        chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
            if (!msg || !msg.type) return false;
            (async function () {
                try {
                    if (msg.type === "AIWK_EXT4_OPEN_AUTH") {
                        safeSendResponse(sendResponse, await openAuthFromAction(msg.config || {}));
                        return;
                    }
                    if (msg.type === "AIWK_EXT4_WIN_CONTROL") {
                        safeSendResponse(sendResponse, await controlWindows(msg.action || ""));
                        return;
                    }
                    if (msg.type === "AIWK_FRAME_DISPOSE_ALL") {
                        setCurrentTabRouteUseOff(true);
                        markClientUserClosed(msg.reason || "user_closed");
                        try { if (window.AIWKWindowControls && typeof window.AIWKWindowControls.disposeAll === "function") window.AIWKWindowControls.disposeAll(msg.reason || "user_closed"); } catch (err) {}
                        safeSendResponse(sendResponse, Object.assign({ ok: 1, code: "AIWK_TOP_FRAME_DISPOSED", status: "USER_CLOSED", emoji: "⏹" }, disposeFloatingUi(msg.reason || "user_closed")));
                        return;
                    }
                    if (msg.type === "AIWK_ROUTE_USE_STATUS") {
                        safeSendResponse(sendResponse, { ok: 1, enabled: isCurrentTabRouteUseOff() ? 0 : 1, reason: isCurrentTabRouteUseOff() ? "route_use_off_current_tab" : "route_use_on_current_tab" });
                        return;
                    }
                    if (msg.type === "AIWK_ROUTE_USE_SET") {
                        const enabled = msg.enabled !== false;
                        setCurrentTabRouteUseOff(!enabled);
                        if (enabled) {
                            bootOk = false;
                            bootPromise = null;
                            try {
                                await boot();
                                ensureFloatingIcon();
                            } catch (err) {}
                            safeSendResponse(sendResponse, { ok: 1, code: "AIWK_ROUTE_USE_ON", enabled: 1 });
                        } else {
                            markClientUserClosed(msg.reason || "route_use_off");
                            try { stopAllLocalLoops(msg.reason || "route_use_off"); } catch (err) {}
                            try { if (window.AIWKWindowControls && typeof window.AIWKWindowControls.disposeAll === "function") window.AIWKWindowControls.disposeAll(msg.reason || "route_use_off"); } catch (err) {}
                            safeSendResponse(sendResponse, Object.assign({ ok: 1, code: "AIWK_ROUTE_USE_OFF", enabled: 0 }, disposeFloatingUi(msg.reason || "route_use_off")));
                        }
                        return;
                    }
                    if (msg.type === "AIWK_EXT4_OPEN_MANUAL_TEST") {
                        safeSendResponse(sendResponse, await openManualTestFromAction());
                        return;
                    }
                    if (msg.type === "AIWK_EXT4_OPEN_CHART_DEV") {
                        safeSendResponse(sendResponse, await openChartDevFromAction());
                        return;
                    }
                    if (msg.type === "AIWK_EXT4_OPEN_CHART_VIEWER") {
                        safeSendResponse(sendResponse, await openChartViewerFromAction());
                        return;
                    }
                    if (msg.type === "AIWK_EXT4_COMM_CONTROL") {
                        safeSendResponse(sendResponse, await commControlFromAction(msg));
                        return;
                    }
                    if (msg.type === "AIWK_RELAY_SNAPSHOT") {
                        await boot().catch(function () {});
                        safeSendResponse(sendResponse, buildRelaySnapshot());
                        return;
                    }
                    if (msg.type === "AIWK_STOP_ALL_LOOPS") {
                        await boot().catch(function () {});
                        safeSendResponse(sendResponse, stopAllLocalLoops(msg.reason || "relay"));
                        return;
                    }
                    if (msg.type === "AIWK_RESET_COUNTERS_FROM_RELAY") {
                        await boot().catch(function () {});
                        if (AIWK.comm && typeof AIWK.comm.resetCounters === "function") safeSendResponse(sendResponse, AIWK.comm.resetCounters());
                        else safeSendResponse(sendResponse, { ok: 0, code: "COUNT_RESET_MISSING" });
                        return;
                    }
                    if (msg.type === "AIWK_BUILD_DIAGNOSTIC_REPORT") {
                        await boot().catch(function () {});
                        safeSendResponse(sendResponse, buildDiagnosticReportFromAction(msg));
                        return;
                    }
                    if (msg.type === "AIWK_CHATGPT_INSERT_IN_TAB") {
                        safeSendResponse(sendResponse, insertTextIntoChatGptPrompt(msg.text || ""));
                        return;
                    }
                    if (msg.type === "AIWK_IFRAME_STREAM_TO_WSS") {
                        await boot().catch(function () {});
                        const target = String(msg.target || msg.to || "").trim();
                        const payload = msg.payload || {};
                        const text = String(msg.text || payload.text || "");
                        if (!target) {
                            safeSendResponse(sendResponse, { ok: 0, code: "STREAM_TARGET_EMPTY", solution: "iframe module target aiwk:// 값을 지정하십시오." });
                            return;
                        }
                        if (!text) {
                            safeSendResponse(sendResponse, { ok: 0, code: "STREAM_TEXT_EMPTY", solution: "수집된 텍스트가 비어 있습니다." });
                            return;
                        }
                        if (!AIWK.comm || typeof AIWK.comm.wssSend !== "function") {
                            safeSendResponse(sendResponse, { ok: 0, code: "WSS_SEND_MISSING", solution: "AIWK WSS 연결/인증 후 다시 실행하십시오." });
                            return;
                        }
                        const res = AIWK.comm.wssSend(target, "aiwk_iframe_stream_msg", Object.assign({ text: text, from_url: location.href, from_title: document.title }, payload || {}), "iframe_agent");
                        try { relayPost({ dir: "stream", channel: "wss", action: "aiwk_iframe_stream_msg", to: target, text: text.slice(0, 200), result: res || {} }); } catch (err) {}
                        safeSendResponse(sendResponse, Object.assign({ ok: 1, code: "STREAM_WSS_SEND_CALLED", target: target, text_len: text.length }, res || {}));
                        return;
                    }
                    if (msg.type === "AIWK_WEB1_RECEIVE_DIAG") {
                        window.dispatchEvent(new CustomEvent("AIWK_WEB1_RECEIVE_DIAG", { detail: msg }));
                        safeSendResponse(sendResponse, { ok: 1, code: "WEB1_DIAG_RECEIVED", version: AIWK.version || "v152-storage-namespace-profile-lock" });
                        return;
                    }
                    safeSendResponse(sendResponse, { ok: 0, code: "UNKNOWN_CONTENT_MESSAGE", type: msg.type });
                } catch (err) {
                    safeSendResponse(sendResponse, { ok: 0, code: "CONTENT_MESSAGE_ERROR", type: msg.type, error: err && err.message ? err.message : String(err) });
                }
            }());
            return true;
        });
    }

    // 중요: v017부터 페이지 로딩 시 창을 자동으로 띄우지 않는다.
    // Chrome EXT 아이콘 클릭 또는 내부 명령으로만 접속창/테스트창을 연다.
    AIWK.ready(function () {
        boot().catch(function (err) {
            console.error("AIWK EXT4 boot failed", err);
        });
    });
}());
