// /AIWK_EXT4/frame_bridge.js
// 용도: page/frame 간 AIWK 메시지 전달을 보조하는 bridge runtime이다.
// AIWK EXT4 v152-storage-namespace-profile-lock
(function () {
    "use strict";

    const VERSION = "v152-storage-namespace-profile-lock";
    if (window.__AIWK_FRAME_BRIDGE_V119__) return;
    window.__AIWK_FRAME_BRIDGE_V119__ = true;

    function safeString(v) { return v === undefined || v === null ? "" : String(v); }

    function frameInfo() {
        return {
            ok: 1,
            version: VERSION,
            is_top: window.top === window,
            href: location.href,
            origin: location.origin,
            title: document.title || "",
            referrer: document.referrer || "",
            ready_state: document.readyState || "",
            frame_bridge: 1
        };
    }

    function cssPath(el) {
        if (!el || !el.nodeType || el.nodeType !== 1) return "";
        if (el.id) return "#" + String(el.id).replace(/([ #;?%&,.+*~':"!^$[\]()=>|/@])/g, "\\$1");
        const parts = [];
        let cur = el;
        for (let depth = 0; cur && cur.nodeType === 1 && depth < 8; depth += 1) {
            let name = cur.nodeName.toLowerCase();
            if (cur.classList && cur.classList.length) {
                name += "." + Array.from(cur.classList).slice(0, 2).map(function (c) {
                    return String(c).replace(/([ #;?%&,.+*~':"!^$[\]()=>|/@])/g, "\\$1");
                }).join(".");
            }
            const parent = cur.parentElement;
            if (parent) {
                const same = Array.from(parent.children).filter(function (x) { return x.nodeName === cur.nodeName; });
                if (same.length > 1) name += ":nth-of-type(" + (same.indexOf(cur) + 1) + ")";
            }
            parts.unshift(name);
            cur = parent;
        }
        return parts.join(" > ");
    }

    function describeElement(el) {
        if (!el) return null;
        const rect = el.getBoundingClientRect ? el.getBoundingClientRect() : null;
        return {
            tag: el.tagName || "",
            id: el.id || "",
            name: el.getAttribute ? (el.getAttribute("name") || "") : "",
            type: el.getAttribute ? (el.getAttribute("type") || "") : "",
            className: safeString(el.className || ""),
            text: safeString(el.innerText || el.textContent || "").slice(0, 300),
            value: "value" in el ? safeString(el.value).slice(0, 300) : "",
            href: el.getAttribute ? (el.getAttribute("href") || "") : "",
            src: el.getAttribute ? (el.getAttribute("src") || "") : "",
            contenteditable: el.getAttribute ? (el.getAttribute("contenteditable") || "") : "",
            selector_path: cssPath(el),
            rect: rect ? { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) } : null
        };
    }

    function queryDeep(root, selector, opt) {
        opt = opt || {};
        if (!root || !selector) return null;
        let direct = null;
        try { if (root.querySelector) direct = root.querySelector(selector); } catch (err) { err.code = "SELECTOR_INVALID"; throw err; }
        if (direct) return direct;
        if (opt.deep_shadow === false) return null;
        const list = root.querySelectorAll ? root.querySelectorAll("*") : [];
        for (const el of list) {
            if (el && el.shadowRoot) {
                const found = queryDeep(el.shadowRoot, selector, opt);
                if (found) return found;
            }
        }
        return null;
    }

    function setElementValue(el, value) {
        const v = safeString(value);
        if (!el) throw new Error("ELEMENT_MISSING");
        const tag = safeString(el.tagName).toLowerCase();
        const editable = el.isContentEditable || safeString(el.getAttribute && el.getAttribute("contenteditable")).toLowerCase() === "true";
        try { el.focus && el.focus(); } catch (err) {}
        if (editable) {
            el.innerText = v;
            el.textContent = v;
        } else if (tag === "input" || tag === "textarea" || tag === "select" || "value" in el) {
            el.value = v;
        } else {
            el.textContent = v;
        }
        try { el.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText", data: v })); } catch (err) { try { el.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) {} }
        try { el.dispatchEvent(new Event("change", { bubbles: true })); } catch (err) {}
        return describeElement(el);
    }

    function runSelectorCommand(req) {
        req = req || {};
        const selector = safeString(req.selector || (req.command && req.command.selector) || "").trim();
        const action = safeString(req.action || (req.command && req.command.action) || "find").trim() || "find";
        if (!selector) return Object.assign(frameInfo(), { ok: 0, code: "SELECTOR_EMPTY" });
        const el = queryDeep(document, selector, { deep_shadow: req.deep_shadow !== false });
        if (!el) return Object.assign(frameInfo(), { ok: 0, code: "SELECTOR_NOT_FOUND", selector: selector, action: action });
        let value = req.value;
        if (value === undefined && req.command) value = req.command.value;
        if (action === "input" || action === "set" || action === "value") {
            return Object.assign(frameInfo(), { ok: 1, code: "SELECTOR_INPUT_OK", selector: selector, action: action, element: setElementValue(el, value) });
        }
        if (action === "click") {
            try { el.scrollIntoView && el.scrollIntoView({ block: "center", inline: "center" }); } catch (err) {}
            try { el.focus && el.focus(); } catch (err) {}
            el.click();
            return Object.assign(frameInfo(), { ok: 1, code: "SELECTOR_CLICK_OK", selector: selector, action: action, element: describeElement(el) });
        }
        if (action === "text") {
            return Object.assign(frameInfo(), { ok: 1, code: "SELECTOR_TEXT_OK", selector: selector, action: action, text: safeString(el.innerText || el.textContent || ""), element: describeElement(el) });
        }
        if (action === "html") {
            return Object.assign(frameInfo(), { ok: 1, code: "SELECTOR_HTML_OK", selector: selector, action: action, html: safeString(el.innerHTML || ""), element: describeElement(el) });
        }
        return Object.assign(frameInfo(), { ok: 1, code: "SELECTOR_FOUND", selector: selector, action: action, element: describeElement(el) });
    }


    function sendAgentEvent(action, event) {
        try {
            chrome.runtime.sendMessage({
                type: "AIWK_IFRAME_AGENT_EVENT",
                action: action || "event",
                frame: frameInfo(),
                event: event || {}
            }).catch(function () {});
        } catch (err) {}
    }

    function runtimeErrorCode(err) {
        const msg = err && err.message ? String(err.message) : String(err || "");
        if (msg.indexOf("message channel closed") >= 0 || msg.indexOf("asynchronous response") >= 0) return "RUNTIME_MESSAGE_CHANNEL_CLOSED";
        if (msg.indexOf("Receiving end does not exist") >= 0) return "RUNTIME_RECEIVER_MISSING";
        if (msg.indexOf("Extension context invalidated") >= 0) return "EXTENSION_CONTEXT_INVALIDATED";
        return "RUNTIME_MESSAGE_FAIL";
    }

    async function safeRuntimeMessage(payload, timeoutMs) {
        timeoutMs = timeoutMs || 2500;
        return new Promise(function (resolve) {
            let done = false;
            function finish(res) {
                if (done) return;
                done = true;
                resolve(res || { ok: 0, code: "RUNTIME_EMPTY_RESPONSE" });
            }
            const timer = setTimeout(function () {
                finish({ ok: 0, code: "RUNTIME_MESSAGE_TIMEOUT", solution: "service_worker 응답 지연/중단. 확장 새로고침 또는 활성 탭 새로고침 확인" });
            }, timeoutMs);
            function finishClear(res) { clearTimeout(timer); finish(res); }
            try {
                const ret = chrome.runtime.sendMessage(payload);
                if (ret && typeof ret.then === "function") {
                    ret.then(function (res) { finishClear(res); }).catch(function (err) {
                        finishClear({ ok: 0, code: runtimeErrorCode(err), error: err && err.message ? err.message : String(err), solution: "service_worker/listener가 응답하기 전에 닫혔습니다. 해당 메시지 handler return/sendResponse 확인" });
                    });
                } else {
                    finishClear({ ok: 0, code: "RUNTIME_PROMISE_UNAVAILABLE" });
                }
            } catch (err) {
                finishClear({ ok: 0, code: runtimeErrorCode(err), error: err && err.message ? err.message : String(err) });
            }
        });
    }


    function cleanUriPart(v) {
        v = safeString(v).trim();
        try { v = decodeURIComponent(v); } catch (err) {}
        v = v.replace(/[\\]/g, "/").replace(/^\/+|\/+$/g, "");
        return v.replace(/[^A-Za-z0-9._\-가-힣]/g, "_");
    }
    function isDefaultPort(protocol, port) {
        protocol = safeString(protocol).toLowerCase();
        port = safeString(port);
        return !port || (protocol === "http:" && port === "80") || (protocol === "https:" && port === "443");
    }
    function currentDomainIndexCandidates() {
        let u;
        try { u = new URL(location.href); } catch (err) { return []; }
        const host = cleanUriPart(u.hostname.toLowerCase() + (isDefaultPort(u.protocol, u.port) ? "" : "_" + u.port));
        if (!host) return [];
        const parts = safeString(u.pathname || "").split("/").filter(Boolean).map(cleanUriPart).filter(Boolean);
        const folders = [];
        let cur = "AIWK_COMMON/domains/" + host;
        folders.push(cur);
        parts.forEach(function (p) { cur += "/" + p; folders.push(cur); });
        return folders.reverse().map(function (base) { return base + "/index.js"; });
    }
    function autoInjectCurrentDomainIndex() {
        const key = "__AIWK_DOMAIN_INDEX_AUTO_INJECT_V125__";
        if (window[key]) return;
        window[key] = true;
        const entries = currentDomainIndexCandidates();
        if (!entries.length) return;
        safeRuntimeMessage({ type: "AIWK_DOMAIN_INDEX_AUTO_INJECT", entries: entries, href: location.href, source: "frame_bridge" }, 5000).then(function (res) {
            if (res && res.ok) sendAgentEvent("domain_index_auto_inject", res);
        });
    }

    function enableFloatingPanel(panel, key, label) {
        if (!panel) return;
        panel.setAttribute("data-aiwk-module-window", "1");
        panel.setAttribute("data-aiwk-window-label", label || panel.id || "AIWK");
        if (window.AIWKWindowControls && typeof window.AIWKWindowControls.enhance === "function") {
            window.AIWKWindowControls.enhance(panel, { key: key, label: label || "AIWK" });
            return;
        }
        // fallback only: 제목 텍스트 드래그 금지. 전용 ↔ 버튼으로만 이동한다.
        const title = panel.querySelector("[data-aiwk-win-title]") || panel.firstElementChild;
        panel.style.resize = "both";
        panel.style.overflow = "auto";
        const saved = (function () { try { return JSON.parse(localStorage.getItem(key) || "null"); } catch (err) { return null; } }());
        if (saved) {
            if (saved.left !== undefined) { panel.style.left = saved.left + "px"; panel.style.right = "auto"; }
            if (saved.top !== undefined) { panel.style.top = saved.top + "px"; panel.style.bottom = "auto"; }
            if (saved.width) panel.style.width = saved.width + "px";
            if (saved.height) panel.style.height = saved.height + "px";
        }
        function persist() {
            try {
                const r = panel.getBoundingClientRect();
                localStorage.setItem(key, JSON.stringify({ left: Math.round(r.left), top: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) }));
            } catch (err) {}
        }
        let drag = title && title.querySelector("[data-aiwk-wc-drag='1']");
        if (title && !drag) {
            drag = document.createElement("button");
            drag.type = "button";
            drag.textContent = "↔";
            drag.title = "드래그 이동";
            drag.setAttribute("data-aiwk-wc-drag", "1");
            drag.style.cssText = "background:#374151;color:#fff;border:0;border-radius:4px;padding:1px 6px;cursor:move;font:12px Arial;line-height:1.4;min-width:23px;";
            title.insertBefore(drag, title.firstChild);
            title.style.userSelect = "text";
        }
        if (drag) {
            drag.addEventListener("mousedown", function (ev) {
                if (ev.button !== 0) return;
                ev.preventDefault();
                const r = panel.getBoundingClientRect();
                const sx = ev.clientX, sy = ev.clientY, sl = r.left, st = r.top;
                panel.style.left = sl + "px"; panel.style.top = st + "px"; panel.style.right = "auto"; panel.style.bottom = "auto";
                function move(e) { panel.style.left = Math.max(0, sl + e.clientX - sx) + "px"; panel.style.top = Math.max(0, st + e.clientY - sy) + "px"; }
                function up() { document.removeEventListener("mousemove", move, true); document.removeEventListener("mouseup", up, true); persist(); }
                document.addEventListener("mousemove", move, true);
                document.addEventListener("mouseup", up, true);
            }, true);
        }
        const restoreId = key + "_restore";
        let restore = document.getElementById(restoreId);
        if (!restore) {
            restore = document.createElement("button");
            restore.id = restoreId;
            restore.type = "button";
            restore.textContent = label || "AIWK";
            restore.style.cssText = "position:fixed;right:8px;top:8px;z-index:2147483647;background:#111827;color:#fff;border:1px solid #f59e0b;border-radius:999px;padding:4px 8px;font:12px Arial;display:none;cursor:pointer";
            restore.addEventListener("click", function () { panel.style.display = "block"; restore.style.display = "none"; });
            (document.body || document.documentElement).appendChild(restore);
        }
        panel.__aiwkHideToButton = function () { panel.style.display = "none"; restore.style.display = "block"; persist(); };
        panel.__aiwkDestroy = function () { try { panel.remove(); } catch (err) {} try { restore.remove(); } catch (err) {} };
    }

    function unloadDomainModule(moduleId, panel) {
        const id = safeString(moduleId || "*");
        try { window.dispatchEvent(new CustomEvent("AIWK_MODULE_UNLOAD", { detail: { module_id: id, source: "domain_module_manager" } })); } catch (err) {}
        const ids = id === "*" ? ["core.iframe.chat_agent", "core.selector.tool", "core.pc.local_bridge"] : [id];
        ids.forEach(function (mid) {
            const nodes = Array.from(document.querySelectorAll('[data-aiwk-module-id="' + mid.replace(/"/g, '\\"') + '"]'));
            nodes.forEach(function (node) { try { if (node.__aiwkDestroy) node.__aiwkDestroy(); else node.remove(); } catch (err) {} });
        });
        if (panel) appendModuleManagerLog(panel, "unload " + id);
    }

    function unloadRemovedModules(oldIds, newIds, panel) {
        const next = new Set((newIds || []).map(String));
        (oldIds || []).map(String).forEach(function (id) {
            if (!next.has(id)) unloadDomainModule(id, panel);
        });
    }

    function addProbeDiv(req) {
        req = req || {};
        let probe = document.getElementById("aiwk_iframe_probe");
        if (!probe) {
            probe = document.createElement("div");
            probe.id = "aiwk_iframe_probe";
            probe.setAttribute("data-aiwk", "iframe-agent-probe");
            probe.style.cssText = [
                "position:fixed",
                "left:8px",
                "bottom:8px",
                "z-index:2147483646",
                "background:#111827",
                "color:#fff",
                "font:12px/1.4 monospace",
                "padding:6px 8px",
                "border:1px solid #22c55e",
                "border-radius:6px",
                "box-shadow:0 2px 12px rgba(0,0,0,.35)",
                "user-select:text"
            ].join(";");
            (document.body || document.documentElement).appendChild(probe);
        }
        probe.textContent = safeString(req.probe_text || req.value || "AIWK iframe agent probe") + " / " + new Date().toLocaleTimeString();
        sendAgentEvent("probe_added", { probe_id: probe.id, text: probe.textContent, source: req.source || "frame_agent" });
        return Object.assign(frameInfo(), { ok: 1, code: "IFRAME_AGENT_PROBE_OK", probe_id: probe.id, probe_text: probe.textContent });
    }

    function pickSendButton(req) {
        req = req || {};
        const sendSelector = safeString(req.send_selector || req.button_selector || "").trim();
        if (sendSelector) return queryDeep(document, sendSelector, { deep_shadow: req.deep_shadow !== false });
        const candidates = [
            "#send-button",
            "button#send-button",
            "button[aria-label='Send']",
            "button[aria-label*='Send']",
            "button[aria-label*='전송']",
            "button[type='submit']"
        ];
        for (const sel of candidates) {
            try {
                const el = queryDeep(document, sel, { deep_shadow: req.deep_shadow !== false });
                if (el) return el;
            } catch (err) {}
        }
        return null;
    }

    function runIframeChatSend(req) {
        req = req || {};
        const selector = safeString(req.selector || req.input_selector || "#input").trim();
        const value = safeString(req.value || req.message || req.text || "");
        const clickSend = req.click_send === false || req.no_click === true ? false : true;
        if (!selector) {
            return Object.assign(frameInfo(), {
                ok: 0,
                code: "CHAT_INPUT_SELECTOR_EMPTY",
                solution: "selector 또는 input_selector를 지정하십시오. 기본값은 #input 입니다."
            });
        }
        if (!value) {
            return Object.assign(frameInfo(), {
                ok: 0,
                code: "CHAT_MESSAGE_EMPTY",
                selector: selector,
                solution: "value/message/text 중 하나에 전달할 내용을 넣으십시오."
            });
        }
        const inputResult = runSelectorCommand({
            selector: selector,
            action: "input",
            value: value,
            deep_shadow: req.deep_shadow !== false
        });
        if (!inputResult || !inputResult.ok) {
            return Object.assign(frameInfo(), {
                ok: 0,
                code: "CHAT_INPUT_FAIL",
                selector: selector,
                input_result: inputResult || null,
                solution: "iframe 내부 agent 창에서 input selector를 먼저 확인하십시오. 사이트별 selector는 domains/{HTTP_HOST}/{path}/index.js에서 preset으로 전달합니다."
            });
        }
        let clickResult = null;
        if (clickSend) {
            const btn = pickSendButton(req);
            if (!btn) {
                clickResult = {
                    ok: 0,
                    code: "CHAT_SEND_BUTTON_NOT_FOUND",
                    send_selector: safeString(req.send_selector || req.button_selector || ""),
                    solution: "send_selector를 직접 지정하거나 click_send:false로 입력까지만 테스트하십시오."
                };
            } else {
                try { btn.scrollIntoView && btn.scrollIntoView({ block: "center", inline: "center" }); } catch (err) {}
                try { btn.focus && btn.focus(); } catch (err) {}
                btn.click();
                clickResult = { ok: 1, code: "CHAT_SEND_BUTTON_CLICK_OK", element: describeElement(btn) };
            }
        }
        const ok = clickSend ? !!(clickResult && clickResult.ok) : true;
        const res = Object.assign(frameInfo(), {
            ok: ok ? 1 : 0,
            code: ok ? "IFRAME_CHAT_SEND_OK" : "IFRAME_CHAT_INPUT_OK_SEND_FAIL",
            selector: selector,
            value_len: value.length,
            input_result: inputResult,
            click_send: clickSend ? 1 : 0,
            click_result: clickResult
        });
        sendAgentEvent("chat_send", res);
        return res;
    }

    function appendAgentLog(panel, text) {
        try {
            const log = panel && panel.querySelector('[data-aiwk-agent-log="1"]');
            if (!log) return;
            const line = document.createElement("div");
            line.textContent = new Date().toLocaleTimeString() + " " + safeString(text).slice(0, 500);
            log.appendChild(line);
            while (log.children.length > 6) log.removeChild(log.firstChild);
            log.scrollTop = log.scrollHeight;
        } catch (err) {}
    }


    function ensureIframeAgentPanel() {
        if (window.top === window) return null;
        if (!document.documentElement) return null;
        let panel = document.getElementById("aiwk_iframe_agent_panel");
        if (panel) return panel;
        panel = document.createElement("div");
        panel.id = "aiwk_iframe_agent_panel";
        panel.setAttribute("data-aiwk-module-window", "1");
        panel.setAttribute("data-aiwk-window-label", "AIWK iframe");
        panel.setAttribute("data-aiwk", "iframe-agent-panel");
        panel.style.cssText = [
            "position:fixed",
            "right:8px",
            "bottom:8px",
            "z-index:2147483647",
            "min-width:150px",
            "max-width:260px",
            "background:rgba(17,24,39,.94)",
            "color:#fff",
            "font:12px/1.35 Arial,sans-serif",
            "border:1px solid #60a5fa",
            "border-radius:8px",
            "box-shadow:0 2px 14px rgba(0,0,0,.4)",
            "padding:7px",
            "user-select:text"
        ].join(";");
        panel.innerHTML = [
            '<div data-aiwk-win-title="1" style="display:flex;gap:6px;align-items:center;margin-bottom:5px;">',
            '<b style="font-size:12px;">AIWK iframe</b>',
            '<span style="margin-left:auto;color:#86efac;">agent/chat</span>',
            '<button type="button" data-aiwk-agent-hide="1" style="background:#374151;color:#fff;border:0;border-radius:4px;padding:1px 5px;cursor:pointer;">×</button>',
            '</div>',
            '<div data-aiwk-agent-info="1" style="word-break:break-all;color:#d1d5db;margin-bottom:5px;max-height:38px;overflow:hidden;"></div>',
            '<input data-aiwk-chat-selector="1" value="#input" placeholder="input selector" style="box-sizing:border-box;width:100%;margin:0 0 4px 0;background:#030712;color:#fff;border:1px solid #4b5563;border-radius:4px;padding:4px;font:12px monospace;">',
            '<input data-aiwk-send-selector="1" value="#send-button" placeholder="send button selector, optional" style="box-sizing:border-box;width:100%;margin:0 0 4px 0;background:#030712;color:#fff;border:1px solid #4b5563;border-radius:4px;padding:4px;font:12px monospace;">',
            '<textarea data-aiwk-chat-message="1" placeholder="iframe 내부 #input으로 전달할 내용" style="box-sizing:border-box;width:100%;height:46px;resize:vertical;margin:0 0 4px 0;background:#030712;color:#fff;border:1px solid #4b5563;border-radius:4px;padding:4px;font:12px/1.3 Arial,sans-serif;"></textarea>',
            '<div style="display:flex;gap:5px;flex-wrap:wrap;margin-bottom:4px;">',
            '<button type="button" data-aiwk-agent-chat-input="1" style="background:#7c3aed;color:#fff;border:0;border-radius:4px;padding:3px 6px;cursor:pointer;">input</button>',
            '<button type="button" data-aiwk-agent-chat-send="1" style="background:#dc2626;color:#fff;border:0;border-radius:4px;padding:3px 6px;cursor:pointer;">send</button>',
            '<button type="button" data-aiwk-agent-probe="1" style="background:#2563eb;color:#fff;border:0;border-radius:4px;padding:3px 6px;cursor:pointer;">probe</button>',
            '<button type="button" data-aiwk-agent-ping="1" style="background:#059669;color:#fff;border:0;border-radius:4px;padding:3px 6px;cursor:pointer;">ping</button>',
            '</div>',
            '<div data-aiwk-agent-log="1" style="max-height:74px;overflow:auto;background:rgba(0,0,0,.28);border:1px solid #374151;border-radius:4px;padding:3px;color:#d1d5db;font:11px/1.25 monospace;"></div>'
        ].join("");
        const mount = document.body || document.documentElement;
        mount.appendChild(panel);
        const info = panel.querySelector('[data-aiwk-agent-info="1"]');
        if (info) info.textContent = location.href;
        const hideBtn = panel.querySelector('[data-aiwk-agent-hide="1"]');
        if (hideBtn) hideBtn.addEventListener("click", function () { panel.style.display = "none"; sendAgentEvent("panel_hide", {}); });
        const probeBtn = panel.querySelector('[data-aiwk-agent-probe="1"]');
        if (probeBtn) probeBtn.addEventListener("click", function () { addProbeDiv({ probe_text: "AIWK iframe probe button", source: "iframe_panel_button" }); });
        const pingBtn = panel.querySelector('[data-aiwk-agent-ping="1"]');
        if (pingBtn) pingBtn.addEventListener("click", function () { sendAgentEvent("panel_ping", { href: location.href, title: document.title || "" }); appendAgentLog(panel, "ping sent"); });
        function readChatReq(clickSend) {
            const sel = panel.querySelector('[data-aiwk-chat-selector="1"]');
            const sendSel = panel.querySelector('[data-aiwk-send-selector="1"]');
            const msg = panel.querySelector('[data-aiwk-chat-message="1"]');
            return {
                selector: sel ? sel.value : "#input",
                send_selector: sendSel ? sendSel.value : "#send-button",
                value: msg ? msg.value : "",
                click_send: !!clickSend,
                source: "iframe_agent_panel"
            };
        }
        const chatInputBtn = panel.querySelector('[data-aiwk-agent-chat-input="1"]');
        if (chatInputBtn) chatInputBtn.addEventListener("click", function () {
            const res = runIframeChatSend(readChatReq(false));
            appendAgentLog(panel, (res.ok ? "OK " : "ERR ") + res.code);
        });
        const chatSendBtn = panel.querySelector('[data-aiwk-agent-chat-send="1"]');
        if (chatSendBtn) chatSendBtn.addEventListener("click", function () {
            const res = runIframeChatSend(readChatReq(true));
            appendAgentLog(panel, (res.ok ? "OK " : "ERR ") + res.code);
        });
        sendAgentEvent("panel_created", { panel_id: panel.id, href: location.href, chat_ui: 1 });
        if (window.AIWKWindowControls) window.AIWKWindowControls.enhance(panel, { key: "AIWK_IFRAME_AGENT_PANEL_WIN", label: "AIWK iframe" });
        return panel;
    }

    function scheduleIframeAgentPanel() {
        if (window.top === window) return;
        const run = function () { try { ensureIframeAgentPanel(); } catch (err) { sendAgentEvent("panel_error", { error: err && err.message ? err.message : String(err) }); } };
        if (document.readyState === "loading") {
            document.addEventListener("DOMContentLoaded", run, { once: true });
        } else {
            run();
        }
    }



    function getDomainModuleKey() {
        return "AIWK_DOMAIN_MODULES::" + safeString(location.hostname || location.origin || "unknown");
    }

    function parseEnabledDoc(raw) {
        if (!raw) return [];
        const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
        if (Array.isArray(parsed)) return parsed.map(String);
        if (parsed && Array.isArray(parsed.enabled_modules)) return parsed.enabled_modules.map(String);
        return [];
    }

    function readEnabledModuleIds() {
        const key = getDomainModuleKey();
        try {
            const raw = localStorage.getItem(key);
            const ids = parseEnabledDoc(raw);
            if (ids.length) { window.__AIWK_DOMAIN_ENABLED_CACHE__ = ids; return ids; }
        } catch (err) {
            window.__AIWK_DOMAIN_LAST_STORAGE_ERROR__ = err && err.message ? err.message : String(err);
        }
        if (Array.isArray(window.__AIWK_DOMAIN_ENABLED_CACHE__)) return window.__AIWK_DOMAIN_ENABLED_CACHE__.map(String);
        return [];
    }

    function loadEnabledModuleIdsAsync(done) {
        const localIds = readEnabledModuleIds();
        if (localIds.length) { if (done) done(localIds, "localStorage_or_cache"); return; }
        try {
            if (!chrome || !chrome.storage || !chrome.storage.local) { if (done) done([], "none"); return; }
            const key = getDomainModuleKey();
            chrome.storage.local.get([key], function (res) {
                let ids = [];
                try { ids = parseEnabledDoc(res && res[key]); } catch (err) { window.__AIWK_DOMAIN_LAST_STORAGE_ERROR__ = err && err.message ? err.message : String(err); }
                window.__AIWK_DOMAIN_ENABLED_CACHE__ = ids;
                if (done) done(ids, "chrome.storage.local");
            });
        } catch (err) {
            window.__AIWK_DOMAIN_LAST_STORAGE_ERROR__ = err && err.message ? err.message : String(err);
            if (done) done([], "error");
        }
    }

    function saveEnabledModuleIds(ids) {
        const unique = Array.from(new Set((ids || []).map(String).filter(Boolean)));
        const doc = {
            version: VERSION,
            domain: location.hostname || location.origin || "",
            href: location.href,
            enabled_modules: unique,
            saved_at: new Date().toISOString()
        };
        const key = getDomainModuleKey();
        const json = JSON.stringify(doc);
        window.__AIWK_DOMAIN_ENABLED_CACHE__ = unique;
        try {
            localStorage.setItem(key, json);
            doc.saved_via = "localStorage";
            return doc;
        } catch (err1) {
            doc.localStorage_error = err1 && err1.message ? err1.message : String(err1);
            try {
                localStorage.removeItem(key);
                localStorage.setItem(key, json);
                doc.saved_via = "localStorage_retry";
                return doc;
            } catch (err2) {
                doc.localStorage_retry_error = err2 && err2.message ? err2.message : String(err2);
            }
        }
        try {
            if (chrome && chrome.storage && chrome.storage.local) {
                const put = {};
                put[key] = json;
                chrome.storage.local.set(put, function () {
                    if (chrome.runtime && chrome.runtime.lastError) {
                        console.warn("[AIWK] chrome.storage.local save failed", chrome.runtime.lastError.message);
                    }
                });
                doc.saved_via = "chrome.storage.local";
                return doc;
            }
        } catch (err3) {
            doc.chromeStorage_error = err3 && err3.message ? err3.message : String(err3);
        }
        doc.saved_via = "memory_only";
        doc.storage_warning = "LOCAL_STORAGE_QUOTA_AND_CHROME_STORAGE_UNAVAILABLE";
        return doc;
    }

    function groupRegistryModules(registry) {
        const mods = registry && Array.isArray(registry.modules) ? registry.modules : [];
        const groups = {};
        mods.forEach(function (m) {
            const c1 = safeString(m.category1 || (m.type === "paid" ? "구매/추가 모듈" : "공통 기본 모듈"));
            const c2 = safeString(m.category2 || "기타");
            if (!groups[c1]) groups[c1] = {};
            if (!groups[c1][c2]) groups[c1][c2] = [];
            groups[c1][c2].push(m);
        });
        return groups;
    }

    function appendModuleManagerLog(panel, text) {
        try {
            const log = panel && panel.querySelector('[data-aiwk-module-manager-log="1"]');
            if (!log) return;
            const line = document.createElement("div");
            line.textContent = new Date().toLocaleTimeString() + " " + safeString(text).slice(0, 700);
            log.appendChild(line);
            while (log.children.length > 18) log.removeChild(log.firstChild);
            log.scrollTop = log.scrollHeight;
        } catch (err) {}
    }

    async function loadDomainModuleRegistry(panel) {
        const res = await safeRuntimeMessage({ type: "AIWK_MODULE_REGISTRY_GET", source: "frame_bridge", href: location.href }, 3000);
        if (!res || !res.ok) {
            const msg = res && (res.error || res.code) ? ((res.code || "") + " " + (res.error || "")) : "MODULE_REGISTRY_GET_FAIL";
            appendModuleManagerLog(panel, "ERR " + msg + " / 해결: AIWK_COMMON/modules/module_registry.json 또는 service_worker 응답 확인");
            return null;
        }
        appendModuleManagerLog(panel, "registry ok modules=" + ((res.registry.modules || []).length));
        return res.registry;
    }

    async function injectDomainModule(panel, moduleDef) {
        if (!moduleDef || !moduleDef.id || !moduleDef.entry) {
            appendModuleManagerLog(panel, "ERR MODULE_DEF_INVALID / 해결: registry id/entry 확인");
            return;
        }
        const res = await safeRuntimeMessage({ type: "AIWK_MODULE_INJECT", module_id: moduleDef.id, entry: moduleDef.entry, source: "module_manager", href: location.href }, 5000);
        if (!res || !res.ok) {
            appendModuleManagerLog(panel, "ERR inject " + moduleDef.id + " " + (res && (res.code || res.error) || "unknown") + " / 해결: entry 경로와 manifest 권한/service_worker 응답 확인");
            return;
        }
        appendModuleManagerLog(panel, "OK inject " + moduleDef.id + " frame=" + res.frame_id);
    }

    function renderDomainModuleRegistry(panel, registry) {
        const list = panel.querySelector('[data-aiwk-module-manager-list="1"]');
        if (!list) return;
        const enabled = new Set(readEnabledModuleIds());
        const groups = groupRegistryModules(registry);
        list.innerHTML = "";
        Object.keys(groups).forEach(function (c1) {
            const c1Node = document.createElement("div");
            c1Node.style.cssText = "margin:5px 0 3px 0;font-weight:bold;color:#fde68a;";
            c1Node.textContent = c1;
            list.appendChild(c1Node);
            Object.keys(groups[c1]).forEach(function (c2) {
                const c2Node = document.createElement("div");
                c2Node.style.cssText = "margin:3px 0 2px 8px;color:#bfdbfe;";
                c2Node.textContent = c2;
                list.appendChild(c2Node);
                groups[c1][c2].forEach(function (m) {
                    const row = document.createElement("label");
                    row.style.cssText = "display:flex;gap:5px;align-items:flex-start;margin:2px 0 2px 16px;cursor:pointer;";
                    const cb = document.createElement("input");
                    cb.type = "checkbox";
                    cb.setAttribute("data-aiwk-module-id", safeString(m.id));
                    cb.checked = enabled.has(safeString(m.id));
                    const span = document.createElement("span");
                    span.innerHTML = '<b>' + safeString(m.name || m.id) + '</b><br><small style="color:#d1d5db;">' + safeString(m.id) + '</small>';
                    row.title = safeString(m.description || "");
                    row.appendChild(cb);
                    row.appendChild(span);
                    list.appendChild(row);
                });
            });
        });
        panel.__aiwkModuleRegistry = registry;
        appendModuleManagerLog(panel, "render ok domain=" + (location.hostname || location.origin));
    }

    function ensureDomainModuleManagerPanel() {
        if (window.top === window) return null;
        if (!document.documentElement) return null;
        let panel = document.getElementById("aiwk_domain_module_manager_panel");
        if (panel) return panel;
        panel = document.createElement("div");
        panel.id = "aiwk_domain_module_manager_panel";
        panel.setAttribute("data-aiwk-module-window", "1");
        panel.setAttribute("data-aiwk-window-label", "AIWK modules");
        panel.setAttribute("data-aiwk", "domain-module-manager");
        panel.style.cssText = [
            "position:fixed",
            "right:8px",
            "top:8px",
            "z-index:2147483647",
            "width:320px",
            "min-width:220px",
            "max-width:92vw",
            "max-height:88vh",
            "background:rgba(3,7,18,.96)",
            "color:#fff",
            "font:12px/1.35 Arial,sans-serif",
            "border:1px solid #f59e0b",
            "border-radius:8px",
            "box-shadow:0 2px 14px rgba(0,0,0,.45)",
            "padding:7px",
            "user-select:text"
        ].join(";");
        panel.innerHTML = [
            '<div data-aiwk-win-title="1" style="display:flex;gap:6px;align-items:center;margin-bottom:5px;">',
            '<b>AIWK 도메인 모듈</b>',
            '<span style="margin-left:auto;color:#facc15;">v126</span>',
            '<button type="button" data-aiwk-module-manager-hide="1" style="background:#374151;color:#fff;border:0;border-radius:4px;padding:1px 5px;cursor:pointer;">×</button>',
            '</div>',
            '<div style="word-break:break-all;color:#d1d5db;margin-bottom:5px;">현재 도메인: <b style="color:#93c5fd;">' + safeString(location.hostname || location.origin) + '</b></div>',
            '<div style="font-size:11px;color:#fca5a5;margin-bottom:5px;">체크하지 않은 모듈은 로드/표시되지 않습니다.</div>',
            '<div data-aiwk-module-manager-list="1" style="max-height:190px;overflow:auto;background:rgba(255,255,255,.04);border:1px solid #374151;border-radius:4px;padding:4px;margin-bottom:5px;"></div>',
            '<div style="display:flex;gap:5px;flex-wrap:wrap;margin-bottom:4px;">',
            '<button type="button" data-aiwk-module-manager-refresh="1" style="background:#2563eb;color:#fff;border:0;border-radius:4px;padding:3px 6px;cursor:pointer;">목록</button>',
            '<button type="button" data-aiwk-module-manager-save="1" style="background:#7c3aed;color:#fff;border:0;border-radius:4px;padding:3px 6px;cursor:pointer;">저장</button>',
            '<button type="button" data-aiwk-module-manager-apply="1" style="background:#059669;color:#fff;border:0;border-radius:4px;padding:3px 6px;cursor:pointer;">선택모듈 실행</button>',
            '<button type="button" data-aiwk-module-manager-clear="1" style="background:#dc2626;color:#fff;border:0;border-radius:4px;padding:3px 6px;cursor:pointer;">해제저장</button>',
            '</div>',
            '<div data-aiwk-module-manager-log="1" style="max-height:92px;overflow:auto;background:rgba(0,0,0,.32);border:1px solid #374151;border-radius:4px;padding:3px;color:#d1d5db;font:11px/1.25 monospace;"></div>'
        ].join("");
        (document.body || document.documentElement).appendChild(panel);
        enableFloatingPanel(panel, "AIWK_WIN::domain_module_manager::" + (location.hostname || "unknown"), "AIWK modules");
        panel.querySelector('[data-aiwk-module-manager-hide="1"]').addEventListener("click", function () { if (panel.__aiwkHideToButton) panel.__aiwkHideToButton(); else panel.style.display = "none"; sendAgentEvent("module_manager_hide", {}); });
        async function refresh() {
            try {
                const reg = await loadDomainModuleRegistry(panel);
                if (reg) renderDomainModuleRegistry(panel, reg);
            } catch (err) {
                appendModuleManagerLog(panel, "ERR registry " + (err && err.message ? err.message : String(err)));
            }
        }
        function selectedIds() {
            return Array.from(panel.querySelectorAll('input[data-aiwk-module-id]:checked')).map(function (x) { return x.getAttribute("data-aiwk-module-id"); }).filter(Boolean);
        }
        panel.querySelector('[data-aiwk-module-manager-refresh="1"]').addEventListener("click", refresh);
        panel.querySelector('[data-aiwk-module-manager-save="1"]').addEventListener("click", function () {
            const saved = saveEnabledModuleIds(selectedIds());
            appendModuleManagerLog(panel, "saved " + saved.enabled_modules.join(",") + " via=" + (saved.saved_via || "unknown") + (saved.localStorage_error ? " / localStorage ERR" : ""));
            if (saved.storage_warning) appendModuleManagerLog(panel, "WARN " + saved.storage_warning + " / 설정은 현재 메모리에만 유지됩니다");
            sendAgentEvent("module_domain_saved", saved);
        });
        panel.querySelector('[data-aiwk-module-manager-clear="1"]').addEventListener("click", function () {
            const oldIds = readEnabledModuleIds().concat(selectedIds());
            panel.querySelectorAll('input[data-aiwk-module-id]').forEach(function (x) { x.checked = false; });
            unloadRemovedModules(oldIds, [], panel);
            const saved = saveEnabledModuleIds([]);
            appendModuleManagerLog(panel, "saved empty via=" + (saved.saved_via || "unknown") + " / 현재 로드된 모듈도 제거됨");
            if (saved.storage_warning) appendModuleManagerLog(panel, "WARN " + saved.storage_warning + " / 설정은 현재 메모리에만 유지됩니다");
            sendAgentEvent("module_domain_cleared", saved);
        });
        panel.querySelector('[data-aiwk-module-manager-apply="1"]').addEventListener("click", async function () {
            const registry = panel.__aiwkModuleRegistry;
            if (!registry || !Array.isArray(registry.modules)) {
                appendModuleManagerLog(panel, "ERR REGISTRY_EMPTY / 해결: 목록 버튼 먼저 실행");
                return;
            }
            const oldIds = readEnabledModuleIds();
            const ids = selectedIds();
            unloadRemovedModules(oldIds, ids, panel);
            const saved = saveEnabledModuleIds(ids);
            appendModuleManagerLog(panel, "apply " + (ids.length ? ids.join(",") : "none"));
            sendAgentEvent("module_domain_apply", saved);
            for (const id of ids) {
                const m = registry.modules.find(function (x) { return String(x.id) === String(id); });
                if (!m) {
                    appendModuleManagerLog(panel, "ERR MODULE_NOT_FOUND " + id);
                    return;
                }
                if (m.target && Array.isArray(m.target) && m.target.indexOf("iframe") >= 0 && window.top === window) {
                    appendModuleManagerLog(panel, "ERR TARGET_IFRAME_ONLY " + id);
                    return;
                }
                await injectDomainModule(panel, m);
            }
        });
        appendModuleManagerLog(panel, "manager ready / 외부 JSON registry 로드 대기");
        loadEnabledModuleIdsAsync(function (loadedIds, via) {
            if (via && via !== "none") appendModuleManagerLog(panel, "enabled load via=" + via + " count=" + (loadedIds || []).length);
            refresh().then(async function () {
                const ids = readEnabledModuleIds();
                if (!ids.length) {
                    appendModuleManagerLog(panel, "enabled empty / 기능 모듈 미표시 정상");
                    return;
                }
                const registry = panel.__aiwkModuleRegistry;
                if (!registry || !Array.isArray(registry.modules)) return;
                appendModuleManagerLog(panel, "auto apply saved modules=" + ids.join(","));
                for (const id of ids) {
                    const m = registry.modules.find(function (x) { return String(x.id) === String(id); });
                    if (m) await injectDomainModule(panel, m);
                    else appendModuleManagerLog(panel, "ERR SAVED_MODULE_NOT_FOUND " + id);
                }
            }).catch(function (err) {
                appendModuleManagerLog(panel, "ERR AUTO_REFRESH " + (err && err.message ? err.message : String(err)));
            });
        });
        sendAgentEvent("module_manager_created", { panel_id: panel.id, domain: location.hostname || location.origin });
        return panel;
    }

    function scheduleDomainModuleManagerPanel() {
        if (window.top === window) return;
        const run = function () { try { ensureDomainModuleManagerPanel(); } catch (err) { sendAgentEvent("module_manager_error", { error: err && err.message ? err.message : String(err) }); } };
        if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", run, { once: true });
        else run();
    }

    try {
        chrome.runtime.sendMessage({ type: "AIWK_SELECTOR_FRAME_REGISTER", frame: frameInfo() }).catch(function () {});
    } catch (err) {}
    try {
        if (window.top !== window) {
            chrome.runtime.sendMessage({ type: "AIWK_IFRAME_AGENT_REGISTER", frame: frameInfo() }).catch(function () {});
        }
    } catch (err) {}
    autoInjectCurrentDomainIndex();
    scheduleDomainModuleManagerPanel();

    if (chrome && chrome.runtime && chrome.runtime.onMessage) {
        chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
            if (!msg || msg.type !== "AIWK_SELECTOR_FRAME_INFO") return false;
            sendResponse(frameInfo());
            return true;
        });
        chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
            if (!msg || msg.type !== "AIWK_IFRAME_AGENT_ADD_PROBE") return false;
            try {
                sendResponse(addProbeDiv(msg.request || msg));
            } catch (err) {
                sendResponse(Object.assign(frameInfo(), { ok: 0, code: "IFRAME_AGENT_PROBE_ERROR", error: err && err.message ? err.message : String(err) }));
            }
            return true;
        });
        chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
            if (!msg || msg.type !== "AIWK_IFRAME_AGENT_SHOW_PANEL") return false;
            try {
                const panel = ensureIframeAgentPanel();
                if (panel) panel.style.display = "block";
                sendResponse(Object.assign(frameInfo(), { ok: panel ? 1 : 0, code: panel ? "IFRAME_AGENT_PANEL_OK" : "IFRAME_AGENT_PANEL_NOT_IFRAME" }));
            } catch (err) {
                sendResponse(Object.assign(frameInfo(), { ok: 0, code: "IFRAME_AGENT_PANEL_ERROR", error: err && err.message ? err.message : String(err) }));
            }
            return true;
        });
        chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
            if (!msg || msg.type !== "AIWK_IFRAME_AGENT_CHAT_SEND") return false;
            try {
                sendResponse(runIframeChatSend(msg.request || msg));
            } catch (err) {
                sendResponse(Object.assign(frameInfo(), { ok: 0, code: "IFRAME_CHAT_SEND_ERROR", error: err && err.message ? err.message : String(err), selector: msg.selector || (msg.request && msg.request.selector) || "" }));
            }
            return true;
        });

        chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
            if (!msg || msg.type !== "AIWK_FRAME_DISPOSE_ALL") return false;
            try {
                const res = window.AIWKWindowControls && typeof window.AIWKWindowControls.disposeAll === "function"
                    ? window.AIWKWindowControls.disposeAll(msg.reason || "user_closed")
                    : { ok: 1, disposed: 0, reason: msg.reason || "user_closed" };
                sendResponse(Object.assign(frameInfo(), res, { code: "AIWK_FRAME_DISPOSED" }));
            } catch (err) {
                sendResponse(Object.assign(frameInfo(), { ok: 0, code: "AIWK_FRAME_DISPOSE_ERROR", error: err && err.message ? err.message : String(err) }));
            }
            return true;
        });
        chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
            if (!msg || msg.type !== "AIWK_SELECTOR_RUN_IN_FRAME") return false;
            try {
                sendResponse(runSelectorCommand(msg.request || msg));
            } catch (err) {
                sendResponse(Object.assign(frameInfo(), { ok: 0, code: err && err.code ? err.code : "SELECTOR_RUN_ERROR", error: err && err.message ? err.message : String(err), selector: msg.selector || (msg.request && msg.request.selector) || "" }));
            }
            return true;
        });
    }
}());
