// /AIWK_COMMON/core/aiwk_bus_graph.js
// AIWK EXT4 v022
// 역할: 로컬 창간 BUS, GRAPH(zone/link), i18n 명령 해석, 누적 결과/대화 로그, 완성모듈 상태 관리.
(function () {
    "use strict";

    const VERSION = "v022";
    const ROOT = window;
    const AIWK = ROOT.AIWK || {};
    const LS_RESULT = "AIWK_EXT4_RESULT_LOG";
    const LS_DIALOG = "AIWK_EXT4_DIALOG_LOG";
    const LS_MODULE_STATUS = "AIWK_EXT4_MODULE_STATUS";

    function nowIso() {
        try { return new Date().toISOString(); } catch (e) { return ""; }
    }

    function safeText(v) {
        if (v === null || typeof v === "undefined") return "";
        if (typeof v === "string") return v;
        try { return JSON.stringify(v, null, 2); } catch (e) { return String(v); }
    }

    function readJson(key, fallback) {
        try {
            const raw = localStorage.getItem(key);
            if (!raw) return fallback;
            return JSON.parse(raw);
        } catch (e) {
            return fallback;
        }
    }

    function writeJson(key, value) {
        try { localStorage.setItem(key, JSON.stringify(value)); } catch (e) {}
    }

    function appendLocalText(key, text) {
        try {
            const old = localStorage.getItem(key) || "";
            const next = old ? old + "\n\n" + text : text;
            localStorage.setItem(key, next);
            return next;
        } catch (e) {
            return text;
        }
    }

    function moduleStatus() {
        return readJson(LS_MODULE_STATUS, {});
    }

    function setModuleStatus(winId, status, extra) {
        const all = moduleStatus();
        all[winId] = Object.assign({ status: status, at: nowIso() }, extra || {});
        writeJson(LS_MODULE_STATUS, all);
        return all[winId];
    }

    ROOT.AIWK_CMD = ROOT.AIWK_CMD || {};
    ROOT.AIWK_CMD.alias = ROOT.AIWK_CMD.alias || {
        whoami: ["너 누구냐?", "정체 밝혀라!", "whoami", "info"],
        whatcanido: ["너의 기능은?", "기능 목록 보내라!", "whatcanido", "list"],
        help: ["너의 사용법은?", "도움말 안내해!", "-h", "--help", "help", "?"],
        selftest: ["셀프테스트 해라!", "자기진단 해라!", "selftest"],
        run: ["실행해라!", "run"],
        result: ["결과 보내라!", "result"],
        result_brief: ["간결하게 보내라!", "brief"],
        result_detail: ["상세하게 보내라!", "detail"],
        stop: ["중단해라!", "stop"],
        next: ["다음 실행해라!", "next"]
    };

    ROOT.AIWK_CMD.resolveAction = ROOT.AIWK_CMD.resolveAction || function (input) {
        const s = String(input || "").trim();
        if (!s) return "help";
        const alias = ROOT.AIWK_CMD.alias || {};
        for (const action in alias) {
            const arr = alias[action] || [];
            for (let i = 0; i < arr.length; i += 1) {
                if (String(arr[i]).trim() === s) return action;
            }
        }
        return s;
    };

    ROOT.AIWK_LOG = ROOT.AIWK_LOG || {};
    ROOT.AIWK_LOG.resultKey = LS_RESULT;
    ROOT.AIWK_LOG.dialogKey = LS_DIALOG;
    ROOT.AIWK_LOG.appendResult = ROOT.AIWK_LOG.appendResult || function (title, data) {
        const block = "==============================\n" +
            "[" + nowIso() + "]\n" +
            String(title || "RESULT") + "\n" +
            safeText(data);
        const text = appendLocalText(LS_RESULT, block);
        const box = document.getElementById("aiwk-result-log");
        if (box) {
            box.textContent = text;
            box.scrollTop = box.scrollHeight;
        }
        return { ok: 1, key: LS_RESULT };
    };
    ROOT.AIWK_LOG.appendDialog = ROOT.AIWK_LOG.appendDialog || function (from, to, text, result) {
        const block = "[" + nowIso() + "] " + String(from || "") + " → " + String(to || "") + " : " + String(text || "") +
            (typeof result === "undefined" ? "" : "\n" + safeText(result));
        const saved = appendLocalText(LS_DIALOG, block);
        const box = document.getElementById("aiwk-dialog-log");
        if (box) {
            box.textContent = saved;
            box.scrollTop = box.scrollHeight;
        }
        return { ok: 1, key: LS_DIALOG };
    };
    ROOT.AIWK_LOG.clear = ROOT.AIWK_LOG.clear || function () {
        try { localStorage.removeItem(LS_RESULT); } catch (e) {}
        try { localStorage.removeItem(LS_DIALOG); } catch (e) {}
        const r = document.getElementById("aiwk-result-log");
        const d = document.getElementById("aiwk-dialog-log");
        if (r) r.textContent = "";
        if (d) d.textContent = "";
        return { ok: 1 };
    };
    ROOT.AIWK_LOG.getResult = ROOT.AIWK_LOG.getResult || function () {
        try { return localStorage.getItem(LS_RESULT) || ""; } catch (e) { return ""; }
    };
    ROOT.AIWK_LOG.getDialog = ROOT.AIWK_LOG.getDialog || function () {
        try { return localStorage.getItem(LS_DIALOG) || ""; } catch (e) { return ""; }
    };

    ROOT.AIWK_GRAPH = ROOT.AIWK_GRAPH || {
        version: VERSION,
        zones: {},
        nodes: {},
        links: []
    };

    function defaultActions(mod) {
        const winId = mod.win_id;
        return {
            whoami: function () {
                return {
                    ok: 1,
                    win_id: winId,
                    module: mod.module || "",
                    title: mod.title || winId,
                    version: mod.version || VERSION,
                    role: mod.role || "",
                    status: (moduleStatus()[winId] || {}).status || "testing"
                };
            },
            whatcanido: function () {
                const names = Object.keys((ROOT.AIWK_BUS.modules[winId] || {}).actions || {});
                return {
                    ok: 1,
                    win_id: winId,
                    module: mod.module || "",
                    actions: names.map(function (name) {
                        return { name: name, desc: (((mod.helpMap || {})[name]) || name) };
                    })
                };
            },
            help: function () {
                const names = Object.keys((ROOT.AIWK_BUS.modules[winId] || {}).actions || {});
                const lines = [];
                lines.push((mod.title || winId) + " 사용법");
                lines.push("");
                lines.push("너의 기능은?    가능한 기능 목록을 보낸다.");
                lines.push("너의 사용법은?  이 도움말을 보낸다.");
                lines.push("셀프테스트 해라! 자기진단을 실행한다.");
                lines.push("실행해라!       기본 run을 실행한다.");
                lines.push("");
                lines.push("지원 action: " + names.join(", "));
                return { ok: 1, win_id: winId, module: mod.module || "", text: lines.join("\n") };
            },
            selftest: async function (payload) {
                let result = null;
                if (typeof mod.selftest === "function") {
                    result = await mod.selftest(payload || {});
                } else if (typeof mod.actions.run === "function") {
                    result = await mod.actions.run(payload || {});
                }
                setModuleStatus(winId, "complete", { reason: "selftest-ok" });
                return { ok: 1, win_id: winId, module: mod.module || "", message: "selftest completed", result: result, received: payload || {} };
            },
            run: function (payload) {
                return { ok: 1, win_id: winId, module: mod.module || "", message: "run completed", received: payload || {} };
            }
        };
    }

    ROOT.AIWK_BUS = ROOT.AIWK_BUS || {
        version: VERSION,
        modules: {},
        registerModule: function (mod) {
            if (!mod || !mod.win_id) return { ok: 0, error: "INVALID_MODULE", message: "win_id missing" };
            const base = defaultActions(mod);
            const userActions = mod.actions || {};
            mod.actions = Object.assign({}, base, userActions);
            this.modules[mod.win_id] = mod;
            ROOT.AIWK_GRAPH.nodes[mod.win_id] = Object.assign(ROOT.AIWK_GRAPH.nodes[mod.win_id] || {}, {
                type: "win",
                title: mod.title || mod.win_id,
                module: mod.module || "",
                version: mod.version || VERSION,
                role: mod.role || ""
            });
            setModuleStatus(mod.win_id, "testing", { reason: "registered" });
            return { ok: 1, win_id: mod.win_id, module: mod.module || "", message: "module registered" };
        },
        call: async function (winId, action, payload) {
            const mod = this.modules[winId];
            const resolved = ROOT.AIWK_CMD.resolveAction(action || "help");
            if (!mod) {
                const fail = { ok: 0, win_id: winId, action: resolved, error: "WIN_NOT_FOUND", message: "대상 창을 찾을 수 없습니다." };
                ROOT.AIWK_LOG.appendResult("CALL FAIL", fail);
                return fail;
            }
            const fn = mod.actions && mod.actions[resolved];
            if (typeof fn !== "function") {
                const fail = { ok: 0, win_id: winId, module: mod.module || "", action: resolved, error: "ACTION_NOT_FOUND", message: "지원하지 않는 명령입니다.", available: Object.keys(mod.actions || {}) };
                ROOT.AIWK_LOG.appendResult("CALL FAIL", fail);
                return fail;
            }
            try {
                const ret = await fn.call(mod, payload || {});
                const out = Object.assign({ ok: 1, win_id: winId, module: mod.module || "", action: resolved }, ret || {});
                ROOT.AIWK_LOG.appendResult("CALL OK " + winId + "." + resolved, out);
                return out;
            } catch (e) {
                const fail = { ok: 0, win_id: winId, module: mod.module || "", action: resolved, error: "ACTION_ERROR", message: e && e.message ? e.message : String(e) };
                ROOT.AIWK_LOG.appendResult("CALL ERROR", fail);
                return fail;
            }
        },
        ask: async function (winId, phrase, payload) {
            ROOT.AIWK_LOG.appendDialog((payload && payload.source) || "launcher", winId, phrase);
            const result = await this.call(winId, phrase, payload || {});
            ROOT.AIWK_LOG.appendDialog(winId, (payload && payload.source) || "launcher", "응답", result);
            return result;
        },
        addZone: function (zoneId, zone) {
            ROOT.AIWK_GRAPH.zones[zoneId] = Object.assign({ id: zoneId, wins: [], links: [], order: 0 }, zone || {});
            return { ok: 1, zone_id: zoneId };
        },
        addLink: function (link) {
            if (!link || !link.id || !link.from || !link.to) return { ok: 0, error: "INVALID_LINK" };
            const links = ROOT.AIWK_GRAPH.links;
            const idx = links.findIndex(function (v) { return v.id === link.id; });
            if (idx >= 0) links[idx] = Object.assign({}, links[idx], link);
            else links.push(Object.assign({ order: 0, action: "run", label: "" }, link));
            return { ok: 1, link_id: link.id };
        },
        runLink: async function (linkId, payload) {
            const link = ROOT.AIWK_GRAPH.links.find(function (v) { return v.id === linkId; });
            if (!link) return { ok: 0, link_id: linkId, error: "LINK_NOT_FOUND" };
            const p = Object.assign({}, payload || {}, { source: link.from, link_id: link.id, zone_id: link.zone_id || "" });
            const cmd = link.label || link.action || "run";
            const result = await this.ask(link.to, cmd, p);
            return { ok: result.ok ? 1 : 0, type: "link.run", link_id: link.id, from: link.from, to: link.to, action: link.action || "", label: link.label || "", result: result };
        },
        runSteps: async function (steps, payload) {
            const results = [];
            const list = (steps || []).slice().sort(function (a, b) { return (a.order || 0) - (b.order || 0); });
            for (let i = 0; i < list.length; i += 1) {
                const step = list[i];
                const p = Object.assign({}, payload || {}, step.payload || {}, { index: i, source: step.source || "runSteps" });
                const result = await this.ask(step.win_id || step.to, step.label || step.action || "run", p);
                results.push({ index: i, step: step, result: result });
                if (!result.ok && step.stopOnFail !== false) break;
            }
            const out = { ok: results.every(function (v) { return v.result && v.result.ok; }) ? 1 : 0, type: "steps.run", count: results.length, results: results };
            ROOT.AIWK_LOG.appendResult("RUN STEPS", out);
            return out;
        },
        runZone: async function (zoneId, payload) {
            const zone = ROOT.AIWK_GRAPH.zones[zoneId];
            if (!zone) return { ok: 0, zone_id: zoneId, error: "ZONE_NOT_FOUND" };
            const linkIds = zone.links || [];
            const links = ROOT.AIWK_GRAPH.links.filter(function (v) { return linkIds.indexOf(v.id) >= 0 || v.zone_id === zoneId; }).sort(function (a, b) { return (a.order || 0) - (b.order || 0); });
            const steps = links.map(function (link) { return { win_id: link.to, action: link.action || "run", label: link.label || "", payload: { source: link.from, link_id: link.id, zone_id: zoneId }, order: link.order || 0 }; });
            const result = await this.runSteps(steps, payload || {});
            return Object.assign({ zone_id: zoneId, title: zone.title || zoneId }, result);
        },
        completedModules: function () {
            const all = moduleStatus();
            return Object.keys(all).filter(function (k) { return all[k] && all[k].status === "complete"; });
        }
    };

    ROOT.AIWK_BUS.registerModule({
        win_id: "aiwk-result-log",
        module: "core.result_log",
        title: "결과 로그",
        version: VERSION,
        role: "core",
        actions: {
            run: function (payload) { return ROOT.AIWK_LOG.appendResult("LOG RUN", payload || {}); },
            clear: function () { return ROOT.AIWK_LOG.clear(); },
            result: function () { return { ok: 1, text: ROOT.AIWK_LOG.getResult() }; }
        },
        helpMap: {
            clear: "누적 로그를 지운다.",
            result: "누적 결과 로그를 반환한다."
        }
    });

    AIWK.bus = ROOT.AIWK_BUS;
    AIWK.graph = ROOT.AIWK_GRAPH;
    AIWK.cmd = ROOT.AIWK_CMD;
    AIWK.log = ROOT.AIWK_LOG;
}());
