// /AIWK_COMMON/win/aiwk_win_menu.js
// AIWK v102-stage4.9-ext4-store-resize-noise-fix
(function () {
    "use strict";

    const AIWK = window.AIWK;

    AIWK.winMenu = {
        attach: function (win) {

            function editFunctionSetting() {
                const curModule = win.moduleName || "";
                const curRoute = win.route || "";
                const nextModule = window.prompt("이 창에 연결할 module 이름을 입력하십시오.\n예: comm.wss_test / dev.manual_test_manager / chart.apex_basic", curModule);
                if (nextModule === null) {
                    win.parts.status.textContent = "취소: 기능 설정 변경 없음";
                    return;
                }
                const nextSingleton = window.prompt("중복 가능 여부를 정하십시오.\n1 = 유일 창(singleton), 0 = 여러 개 가능", String(win.singleton === 0 ? 0 : 1));
                if (nextSingleton === null) {
                    win.parts.status.textContent = "취소: singleton 변경 없음";
                    return;
                }
                const nextRoute = window.prompt("폴더식 route를 입력하십시오.\n예: /default/st1_control/st2_comm/" + win.id + "/state", curRoute || ("/default/st1_control/st2_default/" + win.id + "/state"));
                if (nextRoute === null) {
                    win.parts.status.textContent = "취소: route 변경 없음";
                    return;
                }
                win.moduleName = String(nextModule || "").trim();
                win.singleton = Number(nextSingleton) ? 1 : 0;
                win.route = String(nextRoute || "").trim() || curRoute;
                win.folder_route = win.route;
                if (win.el) {
                    win.el.dataset.singleton = String(win.singleton);
                    win.el.dataset.folderRoute = win.folder_route || "";
                    win.el.dataset.aiwkModule = win.moduleName || "";
                }
                if (AIWK.store) {
                    AIWK.store.set("win_module_" + win.id, win.moduleName);
                    AIWK.store.set("win_singleton_" + win.id, win.singleton);
                    AIWK.store.set("win_folder_route_" + win.id, win.folder_route || "");
                }
                if (AIWK.win && typeof AIWK.win.persistOne === "function") {
                    AIWK.win.persistOne(win);
                }
                win.parts.status.textContent = "SAVED: 기능 설정 module=" + (win.moduleName || "-") + " / singleton=" + win.singleton + " / folder_route=" + (win.folder_route || "-");
            }

            function openAuthWin() {
                if (!AIWK.win || typeof AIWK.win.create !== "function") {
                    throw new Error("AUTH_PROMPT_STOPPED: AIWK.win.create is missing. 해결: win engine 로드 순서를 확인하십시오.");
                }
                let authWin = AIWK.win.get("auth-win");
                if (!authWin) {
                    authWin = AIWK.win.create({
                        id: "auth-win",
                        title: "Auth / Role",
                        module: "auth.panel",
                        rect: { x: 80, y: 420, w: 500, h: 430 }
                    });
                }
                authWin.bringFront();
                return authWin;
            }


            function openTestLauncher() {
                if (!AIWK.win || typeof AIWK.win.create !== "function") {
                    throw new Error("TEST_LAUNCHER_STOPPED: AIWK.win.create is missing.");
                }
                let testWin = AIWK.win.get("test-launcher-win");
                if (!testWin) {
                    testWin = AIWK.win.create({
                        id: "test-launcher-win",
                        title: "EXT4 v018 BUS/GRAPH TEST 런처",
                        module: "dev.test_launcher",
                        rect: { x: 40, y: 70, w: 760, h: 640 }
                    });
                }
                try { AIWK.group.addWin("zone-main", "test-launcher-win"); } catch (err) {}
                testWin.bringFront();
                return testWin;
            }

            function openJsRunner(targetWinId) {
                if (!AIWK.win || typeof AIWK.win.create !== "function") {
                    throw new Error("JS_RUNNER_STOPPED: AIWK.win.create is missing.");
                }
                let jsWin = AIWK.win.get("js-runner-win");
                if (!jsWin) {
                    jsWin = AIWK.win.create({
                        id: "js-runner-win",
                        title: "JS Runner v018",
                        module: "dev.js_runner",
                        rect: { x: 120, y: 120, w: 720, h: 620 }
                    });
                }
                try { AIWK.group.addWin("zone-main", "js-runner-win"); } catch (err) {}
                jsWin.bringFront();
                try { AIWK.events.emit("js-runner:target", { target_win_id: targetWinId || win.id }); } catch (err) {}
                return jsWin;
            }


            function closeFloatingMenus() {
                if (win.parts && win.parts.foldMenuWrap) {
                    win.parts.foldMenuWrap.classList.remove("open");
                }
                if (win.parts && win.parts.winMenuWrap) {
                    win.parts.winMenuWrap.classList.remove("open");
                }
                if (win.el) {
                    win.el.classList.remove("aiwk-menu-open");
                }
            }

            function nextValue(current, list) {
                const idx = list.indexOf(String(current || ""));
                return list[(idx + 1) % list.length];
            }


            function editWindowMenuMeta() {
                const curTitle = win.title || win.id;
                const curShort = win.shortTitle || (win.parts && win.parts.title ? win.parts.title.dataset.shortTitle || "" : "");
                const curIcon = win.icon || (win.parts && win.parts.iconBadge ? win.parts.iconBadge.textContent || "" : "");
                const curOrder = String(win.restoreOrder || win.createOrder || "");
                const nextTitle = window.prompt("창 제목을 입력하십시오.\n2단이 아닐 때 표시됩니다.", curTitle);
                if (nextTitle === null) { return; }
                const nextShort = window.prompt("2단 노드 상태에서 보일 짧은 제목을 입력하십시오.\n비우면 전체 제목 앞부분을 overflow로 잘라 표시합니다.", curShort);
                if (nextShort === null) { return; }
                const nextIcon = window.prompt("3단 아이콘 상태에서 보일 이모지를 입력하십시오.", curIcon || "🪟");
                if (nextIcon === null) { return; }
                const nextOrder = window.prompt("3단 아이콘/노드 실행 순서 order 번호를 입력하십시오.\n숫자만 권장합니다.", curOrder);
                if (nextOrder === null) { return; }
                if (typeof win.setDisplayMeta !== "function") {
                    throw new Error("MENU_EDIT_STOPPED: win.setDisplayMeta is missing. 해결: aiwk_win_class.js v037 로드 순서를 확인하십시오.");
                }
                win.setDisplayMeta({
                    title: String(nextTitle || "").trim() || curTitle,
                    shortTitle: String(nextShort || "").trim(),
                    icon: String(nextIcon || "").trim() || curIcon || "🪟",
                    order: String(nextOrder || "").trim() || curOrder
                });
                win.parts.status.textContent = "MENU_META_SAVED: title/2단제목/icon/order 저장 완료";
            }

            function applyWindowSkin(forced) {
                const skins = ["nssb-dark", "nssb-light", "cyan", "purple", "dark", "light", "clean"];
                const alias = { d: "nssb-dark", w: "nssb-light", c: "cyan", p: "purple" };
                const cur = win.el.dataset.aiwkSkin || (AIWK.store ? AIWK.store.get("win_skin_" + win.id, "nssb-dark") : "nssb-dark");
                const req = String(forced || "").toLowerCase();
                const next = alias[req] || (skins.indexOf(req) >= 0 ? req : nextValue(cur, skins));
                win.el.dataset.aiwkSkin = next;
                if (AIWK.store) AIWK.store.set("win_skin_" + win.id, next);
                try {
                    if (window.AIWKChartLab && typeof window.AIWKChartLab.applySkin === "function") {
                        window.AIWKChartLab.applySkin(next);
                    } else {
                        document.documentElement.dataset.aiwkChartSkin = next;
                        document.body.dataset.aiwkChartSkin = next;
                        document.documentElement.setAttribute("data-bs-theme", next.indexOf("light") >= 0 || next === "clean" ? "light" : "dark");
                    }
                } catch (err) {}
                win.parts.status.textContent = "SKIN_SELECTED: " + next + " / page·card·window·menu·input·chart 전체 스킨을 같이 전환합니다.";
            }

            function applyWindowTemplate() {
                const templates = ["win-dev", "win-clean", "win-dashboard", "win-hmi", "win-mini"];
                const cur = win.el.dataset.aiwkTemplate || (AIWK.store && AIWK.store.get ? AIWK.store.get("win_template_" + win.id, "win-dev") : "win-dev");
                const next = nextValue(cur, templates);
                win.el.dataset.aiwkTemplate = next;
                AIWK.store.set("win_template_" + win.id, next);
                win.parts.status.textContent = "TEMPLATE_SELECTED: " + next + " / 실제 재렌더링은 안전한 템플릿 엔진 단계에서 연결합니다.";
            }


            function alphaValue(level) {
                const map = { 1: 1, 2: 0.88, 3: 0.72, 4: 0.56, 5: 0.40 };
                return map[Number(level)] || 1;
            }

            function applyWindowAlpha(level) {
                const lv = Math.max(1, Math.min(5, Number(level) || 1));
                const alpha = alphaValue(lv);
                win.alphaLevel = lv;
                win.alpha = alpha;
                win.el.dataset.aiwkAlphaLevel = String(lv);
                win.el.dataset.aiwkAlpha = String(alpha);
                win.el.style.opacity = String(alpha);
                if (AIWK.store) {
                    AIWK.store.set("win_alpha_level_" + win.id, lv);
                    AIWK.store.set("win_alpha_" + win.id, alpha);
                }
                if (AIWK.win && typeof AIWK.win.persistOne === "function") {
                    AIWK.win.persistOne(win);
                }
                win.parts.status.textContent = "ALPHA_SELECTED: " + lv + "단계 / opacity=" + alpha + " / 겹쳐 비교할 때 사용합니다.";
            }

            function layoutEnvelope(kind, payload) {
                return {
                    aiwk_export_type: kind,
                    aiwk_layout_version: "1.0",
                    aiwk_version: AIWK.version || "v102-stage4.9-ext4-store-resize-noise-fix",
                    exported_at: new Date().toISOString(),
                    page_url: location.href,
                    payload: payload
                };
            }

            async function copyText(text) {
                try {
                    await navigator.clipboard.writeText(text);
                    return true;
                } catch (err) {
                    const ta = document.createElement("textarea");
                    ta.value = text;
                    ta.style.position = "fixed";
                    ta.style.left = "-9999px";
                    document.body.appendChild(ta);
                    ta.focus();
                    ta.select();
                    let ok = false;
                    try { ok = document.execCommand("copy"); } catch (e) { ok = false; }
                    ta.remove();
                    return ok;
                }
            }

            function exportWindowConfig() {
                const data = layoutEnvelope("window", win.toJSON ? win.toJSON() : { id: win.id });
                data.payload.alpha = win.alpha || Number(win.el.dataset.aiwkAlpha || 1) || 1;
                data.payload.alphaLevel = win.alphaLevel || Number(win.el.dataset.aiwkAlphaLevel || 1) || 1;
                const text = JSON.stringify(data, null, 2);
                copyText(text).then(function (ok) {
                    win.parts.status.textContent = (ok ? "EXPORT_WINDOW_COPIED" : "EXPORT_WINDOW_READY") + ": " + win.id + " / JSON을 클립보드에 복사했습니다.";
                });
                return data;
            }

            function normalizeImportedObject(raw) {
                const obj = JSON.parse(String(raw || "{}"));
                if (obj && obj.payload) return obj.payload;
                return obj;
            }

            function applyImportedWindow(row) {
                row = row || {};
                if (row.title || row.shortTitle || row.icon || row.order) {
                    win.setDisplayMeta({ title: row.title || win.title, shortTitle: row.shortTitle || win.shortTitle, icon: row.icon || win.icon, order: row.order || win.restoreOrder || win.createOrder });
                }
                if (row.module !== undefined) {
                    win.moduleName = String(row.module || "");
                    win.el.dataset.aiwkModule = win.moduleName;
                    if (AIWK.store) AIWK.store.set("win_module_" + win.id, win.moduleName);
                }
                if (row.route || row.folder_route) {
                    win.route = row.route || row.folder_route;
                    win.folder_route = win.route;
                    win.el.dataset.folderRoute = win.folder_route || "";
                    if (AIWK.store) AIWK.store.set("win_folder_route_" + win.id, win.folder_route || "");
                }
                if (row.rect) win.setRect(row.rect);
                if (row.collapseLevel !== undefined || row.fold_level !== undefined) win.setCollapseLevel(Number(row.collapseLevel !== undefined ? row.collapseLevel : row.fold_level) || 0);
                if (row.alphaLevel !== undefined || row.alpha !== undefined) {
                    if (row.alphaLevel !== undefined) applyWindowAlpha(Number(row.alphaLevel));
                    else {
                        const alpha = Math.max(0.2, Math.min(1, Number(row.alpha) || 1));
                        const level = alpha >= .94 ? 1 : (alpha >= .80 ? 2 : (alpha >= .64 ? 3 : (alpha >= .48 ? 4 : 5)));
                        applyWindowAlpha(level);
                    }
                }
                if (AIWK.win && typeof AIWK.win.persistOne === "function") AIWK.win.persistOne(win);
                win.parts.status.textContent = "IMPORT_WINDOW_OK: 현재 창에 JSON 설정을 적용했습니다.";
            }

            function importWindowConfig() {
                const raw = window.prompt("불러올 창 JSON을 붙여넣으십시오.\n내보내기한 window JSON 또는 payload 객체를 그대로 넣으면 됩니다.", "");
                if (raw === null) return;
                try { applyImportedWindow(normalizeImportedObject(raw)); }
                catch (err) { win.parts.status.textContent = "IMPORT_WINDOW_ERROR: " + (err && err.message ? err.message : String(err)); }
            }

            function exportLayoutConfig() {
                const state = AIWK.win && typeof AIWK.win.exportState === "function" ? AIWK.win.exportState() : { windows: [] };
                const data = layoutEnvelope("layout", {
                    page_id: document.body && document.body.dataset ? (document.body.dataset.pageId || location.hostname + location.pathname) : location.pathname,
                    template: document.body && document.body.dataset ? (document.body.dataset.aiwkTemplate || "developer.dashboard") : "developer.dashboard",
                    skin: document.body && document.body.dataset ? (document.body.dataset.aiwkChartSkin || document.documentElement.dataset.aiwkChartSkin || "nssb-dark") : "nssb-dark",
                    windows: state.windows || []
                });
                const text = JSON.stringify(data, null, 2);
                copyText(text).then(function (ok) {
                    win.parts.status.textContent = (ok ? "EXPORT_LAYOUT_COPIED" : "EXPORT_LAYOUT_READY") + ": windows=" + (data.payload.windows || []).length + " / JSON을 클립보드에 복사했습니다.";
                });
                return data;
            }

            function importLayoutConfig() {
                const raw = window.prompt("불러올 전체 layout JSON을 붙여넣으십시오.\nwindows 배열이 있으면 해당 창들을 생성/복원합니다.", "");
                if (raw === null) return;
                try {
                    const data = normalizeImportedObject(raw);
                    const windows = Array.isArray(data.windows) ? data.windows : [];
                    if (!windows.length) throw new Error("windows 배열이 없습니다.");
                    if (!AIWK.win || typeof AIWK.win.importLayout !== "function") throw new Error("AIWK.win.importLayout missing");
                    const res = AIWK.win.importLayout(data);
                    win.parts.status.textContent = "IMPORT_LAYOUT_OK: " + JSON.stringify(res);
                } catch (err) {
                    win.parts.status.textContent = "IMPORT_LAYOUT_ERROR: " + (err && err.message ? err.message : String(err));
                }
            }

            function setWindowMode(mode) {
                const modeMap = {
                    dev: { template: "win-chart-dev", label: "개발용", role: "dev" },
                    editor: { template: "win-chart-dev", label: "편집용", role: "editor" },
                    viewer: { template: "win-chart-viewer", label: "운영용", role: "guest" }
                };
                const next = modeMap[mode] || modeMap.viewer;
                win.el.dataset.aiwkMode = mode;
                win.el.dataset.aiwkTemplate = next.template;
                if (AIWK.store) {
                    AIWK.store.set("win_mode_" + win.id, mode);
                    AIWK.store.set("win_template_" + win.id, next.template);
                }
                if (win.parts && win.parts.status) {
                    win.parts.status.textContent = "MODE_SELECTED: " + next.label + " / template=" + next.template + " / token role=" + (AIWK.auth && AIWK.auth.role ? AIWK.auth.role : "guest");
                }
                try {
                    if (AIWK.events) AIWK.events.emit("win:mode", { win_id: win.id, mode: mode, template: next.template, label: next.label });
                } catch (err) {}
            }

            function runAction(action) {
                if (action === "run") {
                    try {
                        const res = win.run ? win.run({ source: "win-menu", action: "run", at: new Date().toISOString() }) : null;
                        if (res && typeof res.then === "function") {
                            res.then(function (out) { win.parts.status.textContent = "RUN_OK: " + JSON.stringify(out); }).catch(function (err) { win.parts.status.textContent = "RUN_ERROR: " + (err && err.message ? err.message : String(err)); });
                        } else {
                            win.parts.status.textContent = "RUN_OK: " + JSON.stringify(res || { ok: 1 });
                        }
                    } catch (err) {
                        win.parts.status.textContent = "RUN_ERROR: " + (err && err.message ? err.message : String(err));
                    }
                    return;
                }
                if (action === "log") {
                    win.parts.status.textContent = "LOG: 창별 로그 뷰어는 다음 단계에서 module별로 연결합니다.";
                    return;
                }
                if (action === "test") {
                    if (!(AIWK.modules && typeof AIWK.modules.get === "function" && AIWK.modules.get("dev.test_launcher"))) {
                        win.parts.status.textContent = "TEST_SKIPPED: dev.test_launcher 모듈이 현재 WEB chart 환경에 없습니다.";
                        return;
                    }
                    const testWin = openTestLauncher();
                    win.parts.status.textContent = "TEST 런처 표시: 전체 창 목록/전체 selftest/현재 창 테스트를 실행하십시오.";
                    try { AIWK.events.emit("test-launcher:focus", { source_win_id: win.id }); } catch (err) {}
                    if (testWin && testWin.parts && testWin.parts.status) {
                        testWin.parts.status.textContent = "target=" + win.id + " / 전체 창 테스트 메뉴 준비";
                    }
                    return;
                }
                if (action === "close") {
                    win.close();
                    return;
                }
                if (action === "collapse") {
                    win.toggleCollapse();
                    return;
                }
                if (action === "design") {
                    win.parts.status.textContent = "SUCCESS: design menu accepted. role=" + AIWK.auth.role + " / 스킨·템플릿 변경 메뉴를 사용하십시오.";
                    return;
                }
                if (action === "menu-edit") {
                    editWindowMenuMeta();
                    return;
                }
                if (/^skin-[dwcp]$/.test(action)) {
                    applyWindowSkin(action.split("-")[1]);
                    return;
                }
                if (action === "skin-next") {
                    applyWindowSkin();
                    return;
                }
                if (action === "template-next") {
                    applyWindowTemplate();
                    return;
                }
                if (/^alpha-[1-5]$/.test(action)) {
                    applyWindowAlpha(action.split("-")[1]);
                    return;
                }
                if (action === "export-window") {
                    exportWindowConfig();
                    return;
                }
                if (action === "import-window") {
                    importWindowConfig();
                    return;
                }
                if (action === "export-layout") {
                    exportLayoutConfig();
                    return;
                }
                if (action === "import-layout") {
                    importLayoutConfig();
                    return;
                }
                if (action === "mode-dev") {
                    setWindowMode("dev");
                    return;
                }
                if (action === "mode-editor") {
                    setWindowMode("editor");
                    return;
                }
                if (action === "mode-viewer") {
                    setWindowMode("viewer");
                    return;
                }
                if (action === "route-info") {
                    const routeText = "FOLDER_ROUTE: " + (win.folder_route || win.route || "") + "\nADDRESS: " + (win.address || "") + "\nGROUP: 기존 WSS group 유지" + "\nPROJECT/FOLDER1: " + (win.project_id || "") + "\nSTAGE1/FOLDER2: " + (win.stage1_id || "") + "\nSTAGE2/FOLDER3: " + (win.stage2_id || "") + "\nWIN: " + (win.id || "");
                    win.parts.status.textContent = routeText.replace(/\n/g, " / ");
                    try { navigator.clipboard.writeText(routeText); } catch (err) {}
                    return;
                }
                if (action === "function-setting") {
                    editFunctionSetting();
                    return;
                }
                if (action === "js-structure") {
                    win.parts.status.textContent = "PLAN: JS 구조 관리 / 창별 JS(on), 기능 파일, 패키지 JS 등록 구조를 패키지 팔레트 단계에서 연결합니다.";
                    return;
                }
                if (action === "css-structure") {
                    win.parts.status.textContent = "PLAN: CSS 구조 관리 / 전체>zone>window 스킨 상속, 템플릿별 CSS, 효과 CSS를 연결합니다.";
                    return;
                }
                if (action === "package-palette") {
                    win.parts.status.textContent = "PLAN: Node-RED식 팔레트 / 기본 패키지와 추가 패키지를 제어창에서 하나씩 추가하는 구조로 진행합니다.";
                    return;
                }
                if (action === "apex-chart-plan") {
                    win.parts.status.textContent = "PLAN: ApexCharts 패키지 / 차트 창, 대시보드 창, 데이터 소스 연결을 빠른 MVP로 분리합니다.";
                    return;
                }
                if (action === "hooking-plan") {
                    win.parts.status.textContent = "PLAN: 사이트 후킹 패키지 / 인스타·틱톡 추출, 좋아요/댓글 보조, DB 저장은 도메인 프로필 단계에서 분리합니다.";
                    return;
                }
                if (action === "js") {
                    openJsRunner(win.id);
                    win.parts.status.textContent = "JS Runner 표시: target=" + win.id;
                    return;
                }
                win.parts.status.textContent = AIWK.i18n.t("status.locked_or_stub", "준비 항목") + ": " + action;
            }

            win.parts.header.addEventListener("dblclick", function (ev) {
                const target = ev.target;
                if (target && target.closest && target.closest("button")) {
                    return;
                }
                if (win.collapseLevel > 0) {
                    win.setCollapseLevel(0);
                    if (win.parts && win.parts.foldMenuWrap) {
                        win.parts.foldMenuWrap.classList.remove("open");
                    }
                }
            });

            if (win.parts.version) {
                win.parts.version.addEventListener("click", function (ev) {
                    ev.stopPropagation();
                    if (win.parts && win.parts.winMenuWrap) {
                        win.parts.winMenuWrap.classList.remove("open");
                    }
                    if (win.parts && win.parts.foldMenuWrap) {
                        win.parts.foldMenuWrap.classList.toggle("open");
                    }
                });
            }
            if (win.parts.winMenuBtn) {
                win.parts.winMenuBtn.addEventListener("click", function (ev) {
                    ev.stopPropagation();
                    if (win.parts && win.parts.foldMenuWrap) {
                        win.parts.foldMenuWrap.classList.remove("open");
                    }
                    if (win.parts && win.parts.winMenuWrap) {
                        const isOpen = win.parts.winMenuWrap.classList.toggle("open");
                        if (win.el) {
                            win.el.classList.toggle("aiwk-menu-open", isOpen);
                        }
                    }
                });
            }
            if (win.parts.winMenuPanel) {
                win.parts.winMenuPanel.addEventListener("pointerdown", function (ev) {
                    ev.stopPropagation();
                });
                win.parts.winMenuPanel.addEventListener("click", function (ev) {
                    const btn = ev.target && ev.target.closest ? ev.target.closest("button[data-menu-kind]") : null;
                    if (!btn) {
                        return;
                    }
                    ev.stopPropagation();
                    const needRole = btn.dataset.needRole || "guest";
                    if (btn.dataset.menuKind === "submenu") {
                        const wrap = btn.closest ? btn.closest(".aiwk-win-submenu-wrap") : null;
                        if (wrap) {
                            wrap.classList.toggle("open");
                        }
                        return;
                    }
                    if (!AIWK.auth.can(needRole)) {
                        win.parts.status.textContent = "AUTH_REQUIRED: " + (btn.dataset.menuValue || "") + " requires " + needRole + ". 해결: Auth 창에 editor/dev 토큰을 입력하십시오.";
                        try { openAuthWin(); } catch (err) {}
                        return;
                    }
                    try {
                        if (btn.dataset.menuKind === "fold") {
                            win.setCollapseLevel(Number(btn.dataset.menuValue));
                        } else {
                            runAction(btn.dataset.menuValue || "");
                        }
                        closeFloatingMenus();
                    } catch (err) {
                        win.parts.status.textContent = "ERROR: " + (err && err.message ? err.message : String(err));
                    }
                });
            }
            if (win.parts.foldMenuBtn) {
                win.parts.foldMenuBtn.addEventListener("click", function (ev) {
                    ev.stopPropagation();
                    if (win.parts && win.parts.foldMenuWrap) {
                        win.parts.foldMenuWrap.classList.toggle("open");
                    }
                });
            }
            if (win.parts.foldMenu) {
                win.parts.foldMenu.addEventListener("pointerdown", function (ev) {
                    ev.stopPropagation();
                });
                win.parts.foldMenu.addEventListener("click", function (ev) {
                    const btn = ev.target && ev.target.closest ? ev.target.closest("button[data-fold-level]") : null;
                    if (!btn) {
                        return;
                    }
                    ev.stopPropagation();
                    win.setCollapseLevel(Number(btn.dataset.foldLevel));
                    if (win.parts && win.parts.foldMenuWrap) {
                        win.parts.foldMenuWrap.classList.remove("open");
                    }
                });
            }

            document.addEventListener("pointerdown", function (ev) {
                if (!win.el || !win.el.contains(ev.target)) {
                    closeFloatingMenus();
                }
            });

            win.el.addEventListener("click", function (ev) {
                const btn = ev.target.closest("button[data-action]");
                if (!btn) {
                    return;
                }
                const action = btn.dataset.action;
                const needRole = btn.dataset.needRole || "guest";
                if (!AIWK.auth.can(needRole)) {
                    const msg = "AUTH_REQUIRED: " + action + " requires " + needRole + ". 해결: Auth 창에 editor/dev 토큰을 입력하십시오.";
                    win.parts.status.textContent = msg;
                    try {
                        openAuthWin();
                    } catch (err) {
                        win.parts.status.textContent = "ERROR: " + err.message;
                    }
                    return;
                }
                if (action === "fold-menu" || action === "window-menu") {
                    return;
                }
                try {
                    runAction(action);
                } catch (err) {
                    win.parts.status.textContent = "ERROR: " + (err && err.message ? err.message : String(err));
                }
            });
        }
    };
}());
