// /AIWK_COMMON/core/aiwk_protocol.js
// AIWK EXT4 v051 · aiwk://room[:token]@group.project.client#endpoint?action=... protocol parser/envelope/chat helper
(function () {
    "use strict";

    const AIWK = window.AIWK || (window.AIWK = {});
    const PROTOCOL_VERSION = "AIWK_MSG_1";
    const DEFAULT_CLIENT_ID = "C1";

    function now() { return Date.now(); }
    function uid(prefix) { return String(prefix || "m") + "_" + now() + "_" + Math.floor(Math.random() * 1000000); }
    function s(v) { return String(v === undefined || v === null ? "" : v).trim(); }
    function decode(v) {
        try { return decodeURIComponent(String(v || "").replace(/\+/g, "%20")); } catch (err) { return String(v || ""); }
    }
    function encode(v) { return encodeURIComponent(String(v === undefined || v === null ? "" : v)); }
    function maskToken(v) {
        v = s(v);
        if (!v) return "";
        if (v.length <= 12) return "***";
        return v.slice(0, 8) + "..." + v.slice(-4) + " (len=" + v.length + ")";
    }

    function parseQuery(qs) {
        const out = {};
        String(qs || "").replace(/^\?/, "").split("&").forEach(function (pair) {
            if (!pair) return;
            const pos = pair.indexOf("=");
            const k = decode(pos >= 0 ? pair.slice(0, pos) : pair);
            const v = decode(pos >= 0 ? pair.slice(pos + 1) : "");
            if (!k) return;
            if (Object.prototype.hasOwnProperty.call(out, k)) {
                if (!Array.isArray(out[k])) out[k] = [out[k]];
                out[k].push(v);
            } else {
                out[k] = v;
            }
        });
        return out;
    }

    function mergeQuery(a, b) {
        const out = Object.assign({}, a || {});
        Object.keys(b || {}).forEach(function (k) { out[k] = b[k]; });
        return out;
    }

    function parsePayload(query, dataPayload) {
        if (dataPayload) {
            try { return JSON.parse(dataPayload); } catch (err) { return { raw: String(dataPayload), parse_error: err.message || String(err) }; }
        }
        if (query.payload_json) {
            try { return JSON.parse(query.payload_json); } catch (err) { return { raw: String(query.payload_json), parse_error: err.message || String(err) }; }
        }
        if (query.payload) {
            try { return JSON.parse(query.payload); } catch (err) { return { text: String(query.payload) }; }
        }
        if (query.payload_text) return { text: String(query.payload_text) };
        if (query.text) return { text: String(query.text) };
        if (query.message) return { text: String(query.message) };
        if (query.chat) return { text: String(query.chat) };
        const payload = {};
        Object.keys(query || {}).forEach(function (k) {
            if (["action", "endpoint", "to_endpoint", "from_endpoint", "room", "room_id", "type", "timeout_ms", "timeout", "confirm", "text", "message", "chat", "token"].indexOf(k) >= 0) return;
            payload[k] = query[k];
        });
        return payload;
    }

    function splitFragment(fragment) {
        fragment = s(fragment).replace(/^#/, "");
        if (!fragment) return { endpoint: "", query: {} };
        const qPos = fragment.indexOf("?");
        if (qPos < 0) return { endpoint: decode(fragment), query: {} };
        return { endpoint: decode(fragment.slice(0, qPos)), query: parseQuery(fragment.slice(qPos + 1)) };
    }

    function parseAuthority(addr) {
        addr = s(addr);
        let userinfo = "";
        let host = addr;
        const atPos = addr.lastIndexOf("@");
        if (atPos >= 0) {
            userinfo = addr.slice(0, atPos);
            host = addr.slice(atPos + 1);
        }
        let roomId = "";
        let token = "";
        if (userinfo) {
            const colonPos = userinfo.indexOf(":");
            if (colonPos >= 0) {
                roomId = decode(userinfo.slice(0, colonPos));
                token = decode(userinfo.slice(colonPos + 1));
            } else {
                roomId = decode(userinfo);
            }
        }
        return { host: host, room_id: roomId, token: token };
    }

    function parseUri(uri, dataPayload) {
        const raw = s(uri);
        if (!/^aiwk:\/\//i.test(raw)) {
            return { ok: 0, code: "AIWK_URI_INVALID", uri: raw, error: "aiwk:// URI가 아닙니다." };
        }
        const body = raw.replace(/^aiwk:\/\//i, "");

        const hashPos = body.indexOf("#");
        const beforeHash = hashPos >= 0 ? body.slice(0, hashPos) : body;
        const fragment = hashPos >= 0 ? body.slice(hashPos + 1) : "";
        const frag = splitFragment(fragment);

        const qPos = beforeHash.indexOf("?");
        const addrRaw = qPos >= 0 ? beforeHash.slice(0, qPos) : beforeHash;
        const queryRaw = qPos >= 0 ? beforeHash.slice(qPos + 1) : "";
        const authority = parseAuthority(addrRaw);
        const query = mergeQuery(parseQuery(queryRaw), frag.query);

        const host = authority.host;
        const parts = host.split(".").map(s).filter(Boolean);
        const group = parts[0] || query.group || "home";
        const project = parts[1] || query.project || "IG_CRM";
        let clientId = parts.length >= 3 ? parts.slice(2).join(".") : (query.client_id || query.client || "");
        let routeKey = "";
        if (clientId.indexOf(":") >= 0) {
            const tmp = clientId.split(":");
            clientId = tmp.shift();
            routeKey = tmp.join(":");
        }
        routeKey = s(query.route_key || query.url_key || query.to_route_key || routeKey || "");
        const tabId = s(query.tab_id || query.to_tab_id || "");
        const endpoint = s(query.to_endpoint || query.endpoint || frag.endpoint || "ext4").replace(/^#/, "") || "ext4";
        const action = s(query.action || "aiwk_chat") || "aiwk_chat";
        const roomId = s(query.room_id || query.room || authority.room_id || "");
        const token = s(query.token || authority.token || "");
        return {
            ok: 1,
            v: PROTOCOL_VERSION,
            uri: raw,
            group: group,
            project: project,
            chat_id: group + "." + project,
            room_id: roomId,
            token: token,
            token_masked: maskToken(token),
            client_id: clientId,
            route_key: routeKey,
            url_key: s(query.url_key || routeKey || ""),
            to_route_key: routeKey,
            tab_id: tabId,
            to_tab_id: tabId,
            endpoint: endpoint,
            to: clientId,
            to_endpoint: endpoint,
            action: action,
            type: s(query.type || (action === "aiwk_chat" ? "chat" : "command")) || "command",
            timeout_ms: parseInt(query.timeout_ms || query.timeout || 10000, 10) || 10000,
            confirm: query.confirm === "0" || query.confirm === "false" ? 0 : 1,
            query: query,
            payload: parsePayload(query, dataPayload)
        };
    }

    function getProfile() {
        if (AIWK.comm && typeof AIWK.comm.getProfile === "function") return AIWK.comm.getProfile();
        try { return AIWK.store.get("profile", {}) || {}; } catch (err) { return {}; }
    }

    function endpointAddress(group, project, clientId, endpoint) {
        const routeKey = s(arguments.length > 4 ? arguments[4] : "");
        return [group || "home", project || "IG_CRM", clientId || DEFAULT_CLIENT_ID].join(".") + (routeKey ? ":" + routeKey : "") + "#" + (endpoint || "ext4");
    }

    function cleanAddress(group, project, clientId) {
        return [group || "home", project || "IG_CRM", clientId || ""].filter(Boolean).join(".");
    }

    function parseTargetAddress(v, defaults) {
        defaults = defaults || {};
        let raw = s(v);
        const out = {
            group: defaults.group || "home",
            project: defaults.project || "IG_CRM",
            client_id: defaults.client_id || "",
            endpoint: defaults.endpoint || defaults.to_endpoint || "ext4",
            room_id: defaults.room_id || "",
            token: defaults.token || "",
            tab_id: defaults.tab_id || defaults.to_tab_id || "",
            route_key: defaults.route_key || defaults.url_key || defaults.to_route_key || ""
        };
        if (!raw) return out;
        if (/^aiwk:\/\//i.test(raw)) {
            const u = parseUri(raw);
            if (u && u.ok) {
                out.group = u.group || out.group;
                out.project = u.project || out.project;
                out.client_id = u.client_id || out.client_id;
                out.route_key = u.route_key || u.url_key || out.route_key;
                out.tab_id = u.tab_id || u.to_tab_id || out.tab_id;
                out.endpoint = u.endpoint || out.endpoint;
                out.room_id = u.room_id || out.room_id;
                out.token = u.token || out.token;
            }
            return out;
        }
        if (raw.indexOf('@') >= 0) {
            const beforeAt = raw.slice(0, raw.lastIndexOf('@'));
            raw = raw.slice(raw.lastIndexOf('@') + 1);
            const colon = beforeAt.indexOf(':');
            out.room_id = decode(colon >= 0 ? beforeAt.slice(0, colon) : beforeAt) || out.room_id;
            if (colon >= 0) out.token = decode(beforeAt.slice(colon + 1)) || out.token;
        }
        const hash = raw.indexOf('#');
        if (hash >= 0) {
            const frag = splitFragment(raw.slice(hash + 1));
            if (frag.endpoint) out.endpoint = frag.endpoint;
            if (frag.query && frag.query.endpoint) out.endpoint = frag.query.endpoint;
            if (frag.query && frag.query.to_endpoint) out.endpoint = frag.query.to_endpoint;
            raw = raw.slice(0, hash);
        }
        const parts = raw.split('.').map(s).filter(Boolean);
        if (parts.length >= 3) {
            out.group = parts[0];
            out.project = parts[1];
            out.client_id = parts.slice(2).join('.');
        } else if (parts.length === 1) {
            out.client_id = parts[0];
        }
        if (out.client_id.indexOf(":") >= 0) {
            const tmp = out.client_id.split(":");
            out.client_id = tmp.shift();
            out.route_key = tmp.join(":") || out.route_key;
        }
        return out;
    }

    function buildUri(opt) {
        opt = opt || {};
        const group = s(opt.group || "home") || "home";
        const project = s(opt.project || "IG_CRM") || "IG_CRM";
        const clientId = s(opt.client_id || opt.client || opt.to || DEFAULT_CLIENT_ID) || DEFAULT_CLIENT_ID;
        const endpoint = s(opt.endpoint || opt.to_endpoint || "ext4") || "ext4";
        const routeKey = s(opt.route_key || opt.url_key || opt.to_route_key || opt.tab_id || opt.to_tab_id || "");
        const roomId = s(opt.room_id || opt.room || "");
        const token = s(opt.token || "");
        const action = s(opt.action || "aiwk_chat") || "aiwk_chat";
        const q = [];
        if (action) q.push("action=" + encode(action));
        if (opt.text) q.push("text=" + encode(opt.text));
        if (opt.timeout_ms) q.push("timeout_ms=" + encode(opt.timeout_ms));
        const auth = roomId ? encode(roomId) + (token ? ":" + encode(token) : "") + "@" : "";
        return "aiwk://" + auth + group + "." + project + "." + clientId + (routeKey ? ":" + routeKey : "") + "#" + endpoint + (q.length ? "?" + q.join("&") : "");
    }

    function buildEnvelope(parsed, opt) {
        parsed = parsed || {};
        opt = opt || {};
        const p = getProfile();
        const group = parsed.group || p.group || "home";
        const project = parsed.project || p.project || "IG_CRM";
        const roomId = parsed.room_id || p.roomId || "";
        const fromClient = opt.from || p.clientId || DEFAULT_CLIENT_ID;
        const fromEndpoint = opt.from_endpoint || "ext4";
        const msgId = opt.message_id || uid("m");
        const targetInfo = parseTargetAddress(parsed.to || parsed.client_id || "", { group: group, project: project, endpoint: parsed.to_endpoint || parsed.endpoint || "ext4", room_id: roomId, route_key: parsed.route_key || parsed.url_key || "" });
        const toClient = parsed.to_client || parsed.client_id || targetInfo.client_id || "";
        const toEndpoint = parsed.to_endpoint || parsed.endpoint || targetInfo.endpoint || "ext4";
        const toTabId = parsed.to_tab_id || parsed.tab_id || targetInfo.tab_id || "";
        const toRouteKey = parsed.to_route_key || parsed.route_key || parsed.url_key || targetInfo.route_key || "";
        const toGroup = targetInfo.group || group;
        const toProject = targetInfo.project || project;
        const toAddr = endpointAddress(toGroup, toProject, toClient, toEndpoint, toRouteKey);
        const fromRouteKey = opt.from_route_key || opt.route_key || "";
        const fromAddr = endpointAddress(group, project, fromClient, fromEndpoint, fromRouteKey);
        const cleanTo = cleanAddress(toGroup, toProject, toClient);
        const action = parsed.action || "aiwk_chat";
        const payload = parsed.payload || {};
        const typ = (parsed.type === "chat" || action === "aiwk_chat") ? "chat" : (parsed.type || "command");
        const packet = {
            v: PROTOCOL_VERSION,
            type: typ,
            action: action,
            uri: parsed.uri || buildUri({ group: group, project: project, client_id: toClient, endpoint: toEndpoint, room_id: roomId, action: action, route_key: toRouteKey }),
            chat_id: group + "." + project,
            group: group,
            project: project,
            room_id: roomId,
            message_id: msgId,
            msg_id: msgId,
            reply_to_message_id: opt.reply_to_message_id || parsed.reply_to_message_id || "",
            reply_to: opt.reply_to_message_id || parsed.reply_to_message_id || "",
            from: fromAddr,
            from_client: fromClient,
            from_endpoint: fromEndpoint,
            to: toAddr,
            target: toAddr,
            to_client: toClient,
            to_endpoint: toEndpoint,
            to_route_key: toRouteKey,
            to_tab_id: toTabId,
            clean_to: cleanTo,
            route: {
                scheme: "aiwk",
                from: cleanAddress(group, project, fromClient),
                to: cleanTo,
                to_endpoint: toEndpoint,
                to_route_key: toRouteKey,
                to_tab_id: toTabId,
                endpoint_addr: toAddr
            },
            client_ts: now(),
            sent_at_ms: now(),
            payload: payload,
            args: payload
        };
        if (typ === "chat") {
            packet.text = payload && payload.text ? String(payload.text) : "";
            packet.message = packet.text;
        }
        return packet;
    }

    function parseClientFromAddress(v) {
        let raw = s(v);
        if (/^aiwk:\/\//i.test(raw)) {
            const p = parseUri(raw);
            return { client: p.client_id || "", tab_id: p.tab_id || "", endpoint: p.endpoint || "" };
        }
        const hash = raw.indexOf("#");
        const endpoint = hash >= 0 ? raw.slice(hash + 1) : "";
        if (hash >= 0) raw = raw.slice(0, hash);
        if (raw.indexOf("@") >= 0) raw = raw.slice(raw.lastIndexOf("@") + 1);
        const parts = raw.split(".").filter(Boolean);
        let client = parts.length ? parts[parts.length - 1] : raw;
        let routeKey = "";
        if (client.indexOf(":") >= 0) {
            const tmp = client.split(":");
            client = tmp.shift();
            routeKey = tmp.join(":");
        }
        return { client: client, route_key: routeKey, tab_id: "", endpoint: endpoint };
    }

    function isForMe(packet, endpointName) {
        const p = getProfile();
        const me = s(p.clientId || DEFAULT_CLIENT_ID);
        const endpoint = s(endpointName || "ext4");
        const toClient = s(packet.to_client || packet.client_id || "");
        const toEndpoint = s(packet.to_endpoint || "").replace(/^#/, "");
        const parsedTo = parseClientFromAddress(packet.to || packet.target || "");
        const clientOk = !toClient || toClient === "*" || toClient === me || parsedTo.client === me || s(packet.to) === me;
        const endpointOk = !toEndpoint || toEndpoint === endpoint || !parsedTo.endpoint || parsedTo.endpoint === endpoint;
        return !!(clientOk && endpointOk);
    }

    function summary(parsed) {
        parsed = parsed || {};
        return endpointAddress(parsed.group || "home", parsed.project || "IG_CRM", parsed.client_id || parsed.to || "", parsed.endpoint || parsed.to_endpoint || "ext4") + " / " + (parsed.action || "");
    }

    AIWK.protocol = {
        version: PROTOCOL_VERSION,
        parseUri: parseUri,
        parseTargetAddress: parseTargetAddress,
        buildUri: buildUri,
        buildEnvelope: buildEnvelope,
        endpointAddress: endpointAddress,
        fullEndpoint: function (group, project, roomId, clientId, endpoint) { return endpointAddress(group, project, clientId, endpoint); },
        cleanAddress: cleanAddress,
        isForMe: isForMe,
        summary: summary,
        uid: uid,
        encode: encode,
        maskToken: maskToken
    };
}());
