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

    const AIWK = window.AIWK;

    class AIWKWin {
        constructor(cfg) {
            if (!cfg || !cfg.id) {
                throw new Error("AIWKWin constructor stopped: win id is required. 해결: cfg.id를 지정하십시오.");
            }
            if (!cfg.parts || !cfg.parts.el || !cfg.parts.body || !cfg.parts.status) {
                throw new Error("AIWKWin constructor stopped: win parts are required. 해결: AIWK.winRender.build() 결과를 전달하십시오.");
            }
            this.id = cfg.id;
            this.title = AIWK.store ? AIWK.store.get("win_title_" + cfg.id, cfg.title || cfg.id) : (cfg.title || cfg.id);
            this.shortTitle = AIWK.store ? AIWK.store.get("win_short_title_" + cfg.id, "") : "";
            this.moduleName = AIWK.store ? AIWK.store.get("win_module_" + cfg.id, cfg.module || "") : (cfg.module || "");
            const normalized = AIWK.windowState && typeof AIWK.windowState.normalize === "function" ? AIWK.windowState.normalize(Object.assign({}, cfg, { win_id: cfg.id, module: cfg.module || "" })) : {};
            this.group = normalized.group || normalized.net || cfg.group || cfg.net || "home";
            this.net = this.group;
            this.project_id = normalized.project_id || cfg.project_id || "default";
            this.stage1_id = normalized.stage1_id || cfg.stage1_id || "st1_control";
            this.stage2_id = normalized.stage2_id || cfg.stage2_id || "st2_default";
            this.singleton = normalized.singleton !== undefined ? normalized.singleton : (cfg.singleton !== undefined ? cfg.singleton : 1);
            this.route = AIWK.store ? AIWK.store.get("win_folder_route_" + cfg.id, normalized.folder_route || normalized.route || ("/" + [this.project_id, this.stage1_id, this.stage2_id, this.id, "state"].join("/"))) : (normalized.folder_route || normalized.route || ("/" + [this.project_id, this.stage1_id, this.stage2_id, this.id, "state"].join("/")));
            this.folder_route = this.route;
            this.address = normalized.address || "";
            const storedOrder = AIWK.store ? AIWK.store.get("win_order_" + cfg.id, cfg.order || 0) : (cfg.order || 0);
            this.createOrder = Number(storedOrder || cfg.order || 0) || 0;
            this.restoreOrder = this.createOrder;
            this.icon = AIWK.store ? AIWK.store.get("win_icon_" + cfg.id, cfg.icon || "") : (cfg.icon || "");
            this.el = cfg.parts.el;
            this.parts = cfg.parts;
            this.rect = Object.assign({ x: 80, y: 90, w: 420, h: 260, collapsed: false, collapseLevel: 0 }, cfg.rect || {});
            this.collapseLevel = Number(cfg.collapseLevel !== undefined ? cfg.collapseLevel : (this.rect.collapseLevel || (this.rect.collapsed ? 1 : 0))) || 0;
            this.rect.collapseLevel = this.collapseLevel;
            this.iconRect = AIWK.store ? AIWK.store.get("win_icon_rect_" + this.id, null) : null;
            this.alphaLevel = Number(AIWK.store ? AIWK.store.get("win_alpha_level_" + this.id, cfg.alphaLevel || cfg.alpha_level || 1) : (cfg.alphaLevel || cfg.alpha_level || 1)) || 1;
            this.alpha = Number(AIWK.store ? AIWK.store.get("win_alpha_" + this.id, cfg.alpha || 1) : (cfg.alpha || 1)) || 1;
            this.state = {};
            this.createdAt = Date.now();
            this.offAuthChange = null;
            this.lastGlobalCommandSeq = 0;
        }

        setRect(patch) {
            patch = patch || {};
            if (this.collapseLevel === 3 && (patch.x !== undefined || patch.y !== undefined) && patch.w === undefined && patch.h === undefined) {
                this.iconRect = Object.assign({}, this.iconRect || { x: this.rect.x, y: this.rect.y }, patch);
                this.el.style.left = this.iconRect.x + "px";
                this.el.style.top = this.iconRect.y + "px";
                this.el.dataset.iconRect = JSON.stringify(this.iconRect);
                return;
            }
            this.rect = Object.assign({}, this.rect, patch || {});
            this.rect.collapseLevel = this.collapseLevel || 0;
            this.rect.collapsed = this.collapseLevel > 0;
            this.el.style.left = this.rect.x + "px";
            this.el.style.top = this.rect.y + "px";
            this.el.style.width = this.rect.w + "px";
            this.el.style.height = this.rect.h + "px";
            this.el.dataset.rect = JSON.stringify(this.rect);
            if (this.alpha) {
                this.el.style.opacity = String(this.alpha);
                this.el.dataset.aiwkAlpha = String(this.alpha);
                this.el.dataset.aiwkAlphaLevel = String(this.alphaLevel || 1);
            }
        }

        setStatus(text) {
            this.parts.status.textContent = String(text || "");
        }

        rectText(label) {
            if (this.collapseLevel === 3) {
                const r = this.iconRect || { x: this.rect.x, y: this.rect.y };
                return label + " icon x:" + r.x + " y:" + r.y + " / w:" + this.rect.w + " h:" + this.rect.h;
            }
            return label + " x:" + this.rect.x + " y:" + this.rect.y + " w:" + this.rect.w + " h:" + this.rect.h;
        }

        showRectStatus(labelKey, fallback) {
            this.setStatus(this.rectText(AIWK.i18n.t(labelKey, fallback)));
        }

        saveRect() {
            if (this.collapseLevel === 3) {
                if (AIWK.store && AIWK.store.set) AIWK.store.set("win_icon_rect_" + this.id, this.iconRect || { x: this.rect.x, y: this.rect.y });
            } else {
                if (AIWK.store && AIWK.store.set) AIWK.store.set("win_rect_" + this.id, this.rect);
            }
            if (AIWK.win && typeof AIWK.win.persistOne === "function") {
                AIWK.win.persistOne(this);
            }
            this.showRectStatus("status.rect_saved", "saved");
        }

        bringFront() {
            if (!AIWK.win || typeof AIWK.win.nextZ !== "function") {
                throw new Error("AIWKWin bringFront stopped: AIWK.win.nextZ is missing. 해결: aiwk_win.js 로드 순서를 확인하십시오.");
            }
            this.el.style.zIndex = String(AIWK.win.nextZ());
        }

        applyTitleByCollapse() {
            if (!this.parts || !this.parts.title) {
                return;
            }
            const fullTitle = this.title || this.id;
            const shortTitle = this.shortTitle || "";
            this.parts.title.dataset.fullTitle = fullTitle;
            this.parts.title.dataset.shortTitle = shortTitle;
            this.parts.title.textContent = (this.collapseLevel === 2 && shortTitle) ? shortTitle : fullTitle;
            this.parts.title.title = shortTitle ? ("전체 제목: " + fullTitle + " / 2단 제목: " + shortTitle) : ("전체 제목: " + fullTitle);
        }

        setDisplayMeta(meta) {
            meta = meta || {};
            const nextTitle = String(meta.title || this.title || this.id).trim();
            const nextShortTitle = String(meta.shortTitle || "").trim();
            const nextIcon = String(meta.icon || this.icon || "🪟").trim();
            const rawOrder = String(meta.order || this.restoreOrder || this.createOrder || "").trim();
            const nextOrder = Number(rawOrder || 0) || 0;
            this.title = nextTitle || this.id;
            this.shortTitle = nextShortTitle;
            this.icon = nextIcon || this.icon || "🪟";
            if (nextOrder > 0) {
                this.createOrder = nextOrder;
                this.restoreOrder = nextOrder;
            }
            if (AIWK.store) {
                AIWK.store.set("win_title_" + this.id, this.title);
                AIWK.store.set("win_short_title_" + this.id, this.shortTitle);
                AIWK.store.set("win_icon_" + this.id, this.icon);
                if (nextOrder > 0) {
                    AIWK.store.set("win_order_" + this.id, nextOrder);
                }
            }
            this.applyTitleByCollapse();
            this.updateIconMeta();
            if (AIWK.win && typeof AIWK.win.persistOne === "function") {
                AIWK.win.persistOne(this);
            }
            return { ok: 1, id: this.id, title: this.title, shortTitle: this.shortTitle, icon: this.icon, order: this.restoreOrder || this.createOrder || 0, project_id: this.project_id, stage1_id: this.stage1_id, stage2_id: this.stage2_id, route: this.route };
        }

        updateIconMeta() {
            const order = this.restoreOrder || this.createOrder || 0;
            if (this.parts && this.parts.orderBadge) {
                this.parts.orderBadge.textContent = order ? String(order) : "";
                this.parts.orderBadge.title = "order: " + (order || "미지정");
            }
            if (this.parts && this.parts.iconBadge) {
                if (this.icon) {
                    this.parts.iconBadge.textContent = this.icon;
                }
                this.parts.iconBadge.title = "창 아이콘 / order=" + (order || "미지정") + " / " + (this.moduleName || this.id);
            }
            if (this.el) {
                this.el.dataset.aiwkOrder = order ? String(order) : "";
                this.el.dataset.aiwkModule = this.moduleName || "";
            }
        }

        applyCollapseClass() {
            this.applyTitleByCollapse();
            this.updateIconMeta();
            this.el.classList.toggle("aiwk-collapse-1", this.collapseLevel === 1);
            this.el.classList.toggle("aiwk-collapse-2", this.collapseLevel === 2);
            this.el.classList.toggle("aiwk-collapse-3", this.collapseLevel === 3);
            this.el.classList.toggle("aiwk-collapsed", this.collapseLevel > 0);
            this.rect.collapsed = this.collapseLevel > 0;
            this.rect.collapseLevel = this.collapseLevel;
            if (this.parts && this.parts.menu) {
                const btn = this.parts.menu.querySelector('button[data-action="collapse"]');
                if (btn) {
                    btn.textContent = this.collapseLevel === 0 ? "접기" : (this.collapseLevel === 1 ? "2단" : (this.collapseLevel === 2 ? "3단" : "펼침"));
                    btn.title = "3단 접기 순환: 정상 → 1단 → 2단 → 3단 아이콘 → 정상";
                }
            }
            if (this.parts && this.parts.header) {
                this.parts.header.title = this.collapseLevel === 3 ? "3단 아이콘 상태: 더블클릭하면 펼침, 드래그하면 이동" : "창 메뉴 ☰에서 접기/펼침을 선택하십시오.";
            }
            if (this.collapseLevel === 3) {
                const savedIcon = AIWK.store ? AIWK.store.get("win_icon_rect_" + this.id, null) : null;
                this.iconRect = savedIcon || this.iconRect || { x: this.rect.x, y: this.rect.y };
                this.el.style.left = this.iconRect.x + "px";
                this.el.style.top = this.iconRect.y + "px";
            } else {
                this.el.style.left = this.rect.x + "px";
                this.el.style.top = this.rect.y + "px";
                this.el.style.width = this.rect.w + "px";
                this.el.style.height = this.rect.h + "px";
            }
        }

        setCollapseLevel(level) {
            level = Number(level);
            if (![0, 1, 2, 3].includes(level)) {
                throw new Error("INVALID_COLLAPSE_LEVEL: " + level + ". 해결: 0,1,2,3 중 하나만 사용하십시오.");
            }
            if (this.collapseLevel === 3 && level !== 3 && AIWK.store) {
                if (AIWK.store && AIWK.store.set) AIWK.store.set("win_icon_rect_" + this.id, this.iconRect || { x: this.rect.x, y: this.rect.y });
            }
            this.collapseLevel = level;
            this.applyCollapseClass();
            this.saveRect();
        }

        toggleCollapse() {
            const next = this.collapseLevel >= 3 ? 0 : this.collapseLevel + 1;
            this.setCollapseLevel(next);
        }


        pulse(kind, detail) {
            if (!this.el) {
                return { ok: 0, win_id: this.id, error: "WIN_ELEMENT_MISSING" };
            }
            const cls = "aiwk-comm-pulse-" + String(kind || "data").replace(/[^a-z0-9_-]/gi, "").toLowerCase();
            this.el.classList.remove("aiwk-comm-pulse", "aiwk-comm-pulse-wss", "aiwk-comm-pulse-bus", "aiwk-comm-pulse-nr", "aiwk-comm-pulse-data");
            void this.el.offsetWidth;
            this.el.classList.add("aiwk-comm-pulse", cls);
            this.el.dataset.aiwkLastPulse = JSON.stringify({ kind: kind || "data", at: Date.now(), detail: detail || "" });
            if (this.parts && this.parts.status && detail) {
                this.setStatus("COMM " + String(kind || "data").toUpperCase() + ": " + String(detail).slice(0, 120));
            }
            setTimeout(() => {
                if (this.el) this.el.classList.remove("aiwk-comm-pulse", cls);
            }, 850);
            return { ok: 1, win_id: this.id, kind: kind || "data" };
        }

        applyGlobalCommand(cmd) {
            if (!cmd || !cmd.seq || this.lastGlobalCommandSeq === cmd.seq) {
                return { ok: 1, skipped: 1, win_id: this.id };
            }
            this.lastGlobalCommandSeq = cmd.seq;
            const action = String(cmd.action || "");
            if (action === "collapse_level") {
                this.setCollapseLevel(Number((cmd.payload && cmd.payload.level) || 0));
                return { ok: 1, action: action, win_id: this.id, level: this.collapseLevel };
            }
            if (action === "hide") {
                this.el.classList.add("aiwk-win-hidden");
                this.saveRect();
                return { ok: 1, action: action, win_id: this.id };
            }
            if (action === "show") {
                this.el.classList.remove("aiwk-win-hidden");
                this.saveRect();
                return { ok: 1, action: action, win_id: this.id };
            }
            if (action === "macro_break") {
                this.state.macroBreakRequested = true;
                this.setStatus("GLOBAL: macro break requested / " + this.id);
                return { ok: 1, action: action, win_id: this.id };
            }
            return { ok: 0, action: action, win_id: this.id, error: "UNKNOWN_GLOBAL_COMMAND" };
        }

        close() {
            this.el.classList.add("aiwk-win-closing");
            if (AIWK.win && typeof AIWK.win.removeFromSavedLayout === "function") {
                AIWK.win.removeFromSavedLayout(this.id);
            }
            this.el.remove();
            if (typeof this.offAuthChange === "function") {
                this.offAuthChange();
            }
            if (AIWK.win && AIWK.win.registry) {
                delete AIWK.win.registry[this.id];
            }
            AIWK.events.emit("win:close", { id: this.id });
        }

        safeJson(obj) {
            return JSON.stringify(obj, null, 2);
        }

        makePrint(status, testId, name, result, detail, extra) {
            const lines = [];
            lines.push("PRINT: " + status);
            lines.push("TEST: " + testId);
            lines.push("NAME: " + name);
            lines.push("RESULT: " + result);
            if (detail) {
                lines.push("DETAIL: " + detail);
            }
            if (extra) {
                Object.keys(extra).forEach((key) => {
                    const val = extra[key];
                    if (typeof val === "object") {
                        lines.push(key + ": " + this.safeJson(val));
                    } else {
                        lines.push(key + ": " + String(val));
                    }
                });
            }
            return lines.join("\n");
        }

        ensureTestBox() {
            let box = this.parts.body.querySelector('[data-aiwk-test-box="1"]');
            if (!box) {
                box = document.createElement("pre");
                box.className = "aiwk-module-test-result select-text";
                box.setAttribute("data-aiwk-test-box", "1");
                this.parts.body.insertBefore(box, this.parts.body.firstChild);
            }
            return box;
        }

        printTest(text) {
            const box = this.ensureTestBox();
            box.textContent = String(text || "");
            this.setStatus(String(text || "").split("\n")[0] || "TEST");
        }

        async test() {
            const testId = "module-test:" + this.id;
            const name = this.moduleName || this.id;
            try {
                const mod = AIWK.modules.get(this.moduleName);
                if (!this.moduleName) {
                    throw new Error("MODULE_TEST_STOPPED: module is empty for win " + this.id + ". 해결: win 생성 시 module을 지정하십시오.");
                }
                if (!mod) {
                    throw new Error("MODULE_TEST_STOPPED: module not found: " + this.moduleName + ". 해결: manifest 로드 순서와 AIWK.modules.register()를 확인하십시오.");
                }
                let result;
                if (typeof mod.test === "function") {
                    result = await mod.test({
                        win: this,
                        msg: { source: "win.test", check: "v011", at: new Date().toISOString() },
                        AIWK: AIWK
                    });
                } else {
                    result = await this.run({ source: "win.test", check: "v011", at: new Date().toISOString() });
                }
                const print = this.makePrint("정상", testId, name, "OK", "module test completed", {
                    WIN_ID: this.id,
                    MODULE: this.moduleName,
                    RESULT_JSON: result,
                    NEXT: "계속 진행 가능"
                });
                this.printTest(print);
                return { ok: 1, print: print, result: result };
            } catch (err) {
                const print = this.makePrint("오류", testId, name, "ERROR", err.message, {
                    WIN_ID: this.id,
                    MODULE: this.moduleName,
                    ERROR_CODE: "MODULE_TEST_FAIL",
                    FIX: "module 등록, run/test 함수, manifest 로드 순서를 확인하십시오.",
                    STOP: true
                });
                this.printTest(print);
                return { ok: 0, print: print, error: err.message };
            }
        }

        async run(msg) {
            const mod = AIWK.modules.get(this.moduleName);
            if (!this.moduleName) {
                throw new Error("WIN_RUN_STOPPED: module is empty for win " + this.id + ". 해결: win 생성 시 module을 지정하십시오.");
            }
            if (!mod) {
                throw new Error("WIN_RUN_STOPPED: module not found: " + this.moduleName + ". 해결: manifest 로드 순서와 AIWK.modules.register()를 확인하십시오.");
            }
            if (typeof mod.run === "function") {
                return await mod.run({
                    win: this,
                    msg: msg || {},
                    AIWK: AIWK
                });
            }
            if (typeof mod.mount === "function") {
                return {
                    ok: 1,
                    type: "mount-only",
                    win_id: this.id,
                    module: this.moduleName,
                    msg: msg || {}
                };
            }
            throw new Error("WIN_RUN_STOPPED: module has no run or mount: " + this.moduleName + ". 해결: module에 run(ctx) 또는 mount(ctx)를 추가하십시오.");
        }

        toJSON() {
            const hidden = !!(this.el && this.el.classList.contains("aiwk-win-hidden"));
            return {
                id: this.id,
                win_id: this.id,
                title: this.title,
                shortTitle: this.shortTitle || "",
                icon: this.icon || "",
                order: this.restoreOrder || this.createOrder || 0,
                module: this.moduleName,
                group: this.group || this.net || "home",
                net: this.group || this.net || "home",
                room: this.room || "prj_default",
                project_id: this.project_id || "default",
                stage1_id: this.stage1_id || "st1_control",
                stage2_id: this.stage2_id || "st2_default",
                folder_route: this.folder_route || this.route || "",
                route: this.route || this.folder_route || "",
                address: this.address || "",
                singleton: this.singleton === 0 ? 0 : 1,
                rect: this.rect,
                collapseLevel: this.collapseLevel || 0,
                fold_level: this.collapseLevel || 0,
                visible: hidden ? 0 : 1,
                hidden: hidden,
                iconRect: this.iconRect || null,
                alpha: this.alpha || 1,
                alphaLevel: this.alphaLevel || 1,
                state: this.state
            };
        }
    }



    AIWK.AIWKWin = AIWKWin;
}());
