/**
 * @file modules/variables/var-commands.js
 * @description 变量斜杠命令与宏替换，常驻模块
 */

import { getContext } from "../../../../../extensions.js";
import { getLocalVariable, setLocalVariable } from "../../../../../variables.js";
import { createModuleEvents, event_types } from "../../core/event-manager.js";
import jsYaml from "../../libs/js-yaml.mjs";
import {
    lwbSplitPathWithBrackets,
    lwbSplitPathAndValue,
    normalizePath,
    ensureDeepContainer,
    safeJSONStringify,
    maybeParseObject,
    valueToString,
    deepClone,
} from "../../core/variable-path.js";

const MODULE_ID = 'varCommands';
const TAG_RE_XBGETVAR = /\{\{xbgetvar::([^}]+)\}\}/gi;
const TAG_RE_XBGETVAR_YAML = /\{\{xbgetvar_yaml::([^}]+)\}\}/gi;

let events = null;
let initialized = false;

function getMsgKey(msg) {
    return (typeof msg?.mes === 'string') ? 'mes'
         : (typeof msg?.content === 'string' ? 'content' : null);
}

export function parseValueForSet(value) {
    try {
        const t = String(value ?? '').trim();

        if (t.startsWith('{') || t.startsWith('[')) {
            try { return JSON.parse(t); } catch {}
        }

        const looksLikeJson = (t[0] === '{' || t[0] === '[') && /[:\],}]/.test(t);
        if (looksLikeJson && !t.includes('"') && t.includes("'")) {
            try { return JSON.parse(t.replace(/'/g, '"')); } catch {}
        }

        if (t === 'true' || t === 'false' || t === 'null') {
            return JSON.parse(t);
        }

        if (/^-?\d+(\.\d+)?$/.test(t)) {
            return JSON.parse(t);
        }

        return value;
    } catch {
        return value;
    }
}

function extractPathFromArgs(namedArgs, unnamedArgs) {
    try {
        if (namedArgs && typeof namedArgs.key === 'string' && namedArgs.key.trim()) {
            return String(namedArgs.key).trim();
        }
        const arr = Array.isArray(unnamedArgs) ? unnamedArgs : [unnamedArgs];
        const first = String(arr[0] ?? '').trim();
        const m = /^key\s*=\s*(.+)$/i.exec(first);
        return m ? m[1].trim() : first;
    } catch {
        return '';
    }
}

function ensureAbsTargetPath(basePath, token) {
    const t = String(token || '').trim();
    if (!t) return String(basePath || '');
    const base = String(basePath || '');
    if (t === base || t.startsWith(base + '.')) return t;
    return base ? (base + '.' + t) : t;
}

function segmentsRelativeToBase(absPath, basePath) {
    const segs = lwbSplitPathWithBrackets(absPath);
    const baseSegs = lwbSplitPathWithBrackets(basePath);
    if (!segs.length || !baseSegs.length) return segs || [];
    const matches = baseSegs.every((b, i) => String(segs[i]) === String(b));
    return matches ? segs.slice(baseSegs.length) : segs;
}

function setDeepBySegments(target, segs, value) {
    let cur = target;
    for (let i = 0; i < segs.length; i++) {
        const isLast = i === segs.length - 1;
        const key = segs[i];
        if (isLast) {
            cur[key] = value;
        } else {
            const nxt = cur[key];
            const nextSeg = segs[i + 1];
            const wantArray = (typeof nextSeg === 'number');

            // 已存在且类型正确：继续深入
            if (wantArray && Array.isArray(nxt)) {
                cur = nxt;
                continue;
            }
            if (!wantArray && (nxt && typeof nxt === 'object') && !Array.isArray(nxt)) {
                cur = nxt;
                continue;
            }

            // 不存在或类型不匹配：创建正确的容器
            cur[key] = wantArray ? [] : {};
            cur = cur[key];
        }
    }
}

export function lwbResolveVarPath(path) {
    try {
        const segs = lwbSplitPathWithBrackets(path);
        if (!segs.length) return '';

        const rootName = String(segs[0]);
        const rootRaw = getLocalVariable(rootName);

        if (segs.length === 1) {
            return valueToString(rootRaw);
        }

        const obj = maybeParseObject(rootRaw);
        if (!obj) return '';

        let cur = obj;
        for (let i = 1; i < segs.length; i++) {
            cur = cur?.[segs[i]];
            if (cur === undefined) return '';
        }

        return valueToString(cur);
    } catch {
        return '';
    }
}

export function replaceXbGetVarInString(s) {
    s = String(s ?? '');
    if (!s || s.indexOf('{{xbgetvar::') === -1) return s;

    TAG_RE_XBGETVAR.lastIndex = 0;
    return s.replace(TAG_RE_XBGETVAR, (_, p) => lwbResolveVarPath(p));
}

/**
 * 将 {{xbgetvar_yaml::路径}} 替换为 YAML 格式的值
 * @param {string} s
 * @returns {string}
 */
export function replaceXbGetVarYamlInString(s) {
    s = String(s ?? '');
    if (!s || s.indexOf('{{xbgetvar_yaml::') === -1) return s;

    TAG_RE_XBGETVAR_YAML.lastIndex = 0;
    return s.replace(TAG_RE_XBGETVAR_YAML, (_, p) => {
        const value = lwbResolveVarPath(p);
        if (!value) return '';

        // 尝试解析为对象/数组，然后转 YAML
        try {
            const parsed = JSON.parse(value);
            if (typeof parsed === 'object' && parsed !== null) {
                return jsYaml.dump(parsed, {
                    indent: 2,
                    lineWidth: -1,
                    noRefs: true,
                    quotingType: '"',
                }).trim();
            }
            return value;
        } catch {
            return value;
        }
    });
}

export function replaceXbGetVarInChat(chat) {
    if (!Array.isArray(chat)) return;

    for (const msg of chat) {
        try {
            const key = getMsgKey(msg);
            if (!key) continue;

            const old = String(msg[key] ?? '');
            const hasJson = old.indexOf('{{xbgetvar::') !== -1;
            const hasYaml = old.indexOf('{{xbgetvar_yaml::') !== -1;
            if (!hasJson && !hasYaml) continue;

            let result = hasJson ? replaceXbGetVarInString(old) : old;
            result = hasYaml ? replaceXbGetVarYamlInString(result) : result;
            msg[key] = result;
        } catch {}
    }
}

export function applyXbGetVarForMessage(messageId, writeback = true) {
    try {
        const ctx = getContext();
        const msg = ctx?.chat?.[messageId];
        if (!msg) return;

        const key = getMsgKey(msg);
        if (!key) return;

        const old = String(msg[key] ?? '');
        const hasJson = old.indexOf('{{xbgetvar::') !== -1;
        const hasYaml = old.indexOf('{{xbgetvar_yaml::') !== -1;
        if (!hasJson && !hasYaml) return;

        let out = hasJson ? replaceXbGetVarInString(old) : old;
        out = hasYaml ? replaceXbGetVarYamlInString(out) : out;
        if (writeback && out !== old) {
            msg[key] = out;
        }
    } catch {}
}

export function parseDirectivesTokenList(tokens) {
    const out = {
        ro: false,
        objectPolicy: null,
        arrayPolicy: null,
        constraints: {},
        clear: false
    };

    for (const tok of tokens) {
        const t = String(tok || '').trim();
        if (!t) continue;

        if (t === '$ro') { out.ro = true; continue; }
        if (t === '$ext') { out.objectPolicy = 'ext'; continue; }
        if (t === '$prune') { out.objectPolicy = 'prune'; continue; }
        if (t === '$free') { out.objectPolicy = 'free'; continue; }
        if (t === '$grow') { out.arrayPolicy = 'grow'; continue; }
        if (t === '$shrink') { out.arrayPolicy = 'shrink'; continue; }
        if (t === '$list') { out.arrayPolicy = 'list'; continue; }

        if (t.startsWith('$min=')) {
            const num = Number(t.slice(5));
            if (Number.isFinite(num)) out.constraints.min = num;
            continue;
        }
        if (t.startsWith('$max=')) {
            const num = Number(t.slice(5));
            if (Number.isFinite(num)) out.constraints.max = num;
            continue;
        }
        if (t.startsWith('$range=')) {
            const m = t.match(/^\$range=\[\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\]$/);
            if (m) {
                const a = Number(m[1]), b = Number(m[2]);
                if (Number.isFinite(a) && Number.isFinite(b)) {
                    out.constraints.min = Math.min(a, b);
                    out.constraints.max = Math.max(a, b);
                }
            }
            continue;
        }
        if (t.startsWith('$step=')) {
            const num = Number(t.slice(6));
            if (Number.isFinite(num)) {
                out.constraints.step = Math.max(0, Math.abs(num));
            }
            continue;
        }

        if (t.startsWith('$enum=')) {
            const m = t.match(/^\$enum=\{\s*([^}]+)\s*\}$/);
            if (m) {
                const vals = m[1].split(/[;；]/).map(s => s.trim()).filter(Boolean);
                if (vals.length) out.constraints.enum = vals;
            }
            continue;
        }

        if (t.startsWith('$match=')) {
            const raw = t.slice(7);
            if (raw.startsWith('/') && raw.lastIndexOf('/') > 0) {
                const last = raw.lastIndexOf('/');
                const pattern = raw.slice(1, last).replace(/\\\//g, '/');
                const flags = raw.slice(last + 1) || '';
                out.constraints.regex = { source: pattern, flags };
            }
            continue;
        }

        if (t === '$clear') { out.clear = true; continue; }
    }

    return out;
}

export function expandShorthandRuleObject(basePath, valueObj) {
    try {
        const base = String(basePath || '');
        const isObj = v => v && typeof v === 'object' && !Array.isArray(v);

        if (!isObj(valueObj)) return null;

        function stripDollarKeysDeep(val) {
            if (Array.isArray(val)) return val.map(stripDollarKeysDeep);
            if (isObj(val)) {
                const out = {};
                for (const k in val) {
                    if (!Object.prototype.hasOwnProperty.call(val, k)) continue;
                    if (String(k).trim().startsWith('$')) continue;
                    out[k] = stripDollarKeysDeep(val[k]);
                }
                return out;
            }
            return val;
        }

        function formatPathWithBrackets(pathStr) {
            const segs = lwbSplitPathWithBrackets(String(pathStr || ''));
            let out = '';
            for (const s of segs) {
                if (typeof s === 'number') out += `[${s}]`;
                else out += out ? `.${s}` : `${s}`;
            }
            return out;
        }

        function assignDeep(dst, src) {
            for (const k in src) {
                if (!Object.prototype.hasOwnProperty.call(src, k)) continue;
                const v = src[k];
                if (v && typeof v === 'object' && !Array.isArray(v)) {
                    if (!dst[k] || typeof dst[k] !== 'object' || Array.isArray(dst[k])) {
                        dst[k] = {};
                    }
                    assignDeep(dst[k], v);
                } else {
                    dst[k] = v;
                }
            }
        }

        const rulesTop = {};
        const dataTree = {};

        function writeDataAt(relPathStr, val) {
            const abs = ensureAbsTargetPath(base, relPathStr);
            const relSegs = segmentsRelativeToBase(abs, base);
            if (relSegs.length) {
                setDeepBySegments(dataTree, relSegs, val);
            } else {
                if (val && typeof val === 'object' && !Array.isArray(val)) {
                    assignDeep(dataTree, val);
                } else {
                    dataTree['$root'] = val;
                }
            }
        }

        function walk(node, currentRelPathStr) {
            if (Array.isArray(node)) {
                const cleanedArr = node.map(stripDollarKeysDeep);
                if (currentRelPathStr) writeDataAt(currentRelPathStr, cleanedArr);
                for (let i = 0; i < node.length; i++) {
                    const el = node[i];
                    if (el && typeof el === 'object') {
                        const childRel = currentRelPathStr ? `${currentRelPathStr}.${i}` : String(i);
                        walk(el, childRel);
                    }
                }
                return;
            }

            if (!isObj(node)) {
                if (currentRelPathStr) writeDataAt(currentRelPathStr, node);
                return;
            }

            const cleaned = stripDollarKeysDeep(node);
            if (currentRelPathStr) writeDataAt(currentRelPathStr, cleaned);
            else assignDeep(dataTree, cleaned);

            for (const key in node) {
                if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
                const v = node[key];
                const keyStr = String(key).trim();
                const isRule = keyStr.startsWith('$');

                if (!isRule) {
                    const childRel = currentRelPathStr ? `${currentRelPathStr}.${keyStr}` : keyStr;
                    if (v && typeof v === 'object') walk(v, childRel);
                    continue;
                }

                const rest = keyStr.slice(1).trim();
                if (!rest) continue;
                const parts = rest.split(/\s+/).filter(Boolean);
                if (!parts.length) continue;

                const targetToken = parts.pop();
                const dirs = parts.map(t =>
                    String(t).trim().startsWith('$') ? String(t).trim() : ('$' + String(t).trim())
                );
                const fullRelTarget = currentRelPathStr
                    ? `${currentRelPathStr}.${targetToken}`
                    : targetToken;

                const absTarget = ensureAbsTargetPath(base, fullRelTarget);
                const absDisplay = formatPathWithBrackets(absTarget);
                const ruleKey = `$ ${dirs.join(' ')} ${absDisplay}`.trim();
                rulesTop[ruleKey] = {};

                if (v !== undefined) {
                    const cleanedVal = stripDollarKeysDeep(v);
                    writeDataAt(fullRelTarget, cleanedVal);
                    if (v && typeof v === 'object') {
                        walk(v, fullRelTarget);
                    }
                }
            }
        }

        walk(valueObj, '');

        const out = {};
        assignDeep(out, rulesTop);
        assignDeep(out, dataTree);
        return out;
    } catch {
        return null;
    }
}

export function lwbAssignVarPath(path, value) {
    try {
        const segs = lwbSplitPathWithBrackets(path);
        if (!segs.length) return '';

        const rootName = String(segs[0]);
        let vParsed = parseValueForSet(value);

        if (vParsed && typeof vParsed === 'object') {
            try {
                if (globalThis.LWB_Guard?.loadRules) {
                    const res = globalThis.LWB_Guard.loadRules(vParsed, rootName);
                    if (res?.cleanValue !== undefined) vParsed = res.cleanValue;
                    if (res?.rulesDelta && typeof res.rulesDelta === 'object') {
                        if (globalThis.LWB_Guard?.applyDeltaTable) {
                            globalThis.LWB_Guard.applyDeltaTable(res.rulesDelta);
                        } else if (globalThis.LWB_Guard?.applyDelta) {
                            for (const [p, d] of Object.entries(res.rulesDelta)) {
                                globalThis.LWB_Guard.applyDelta(p, d);
                            }
                        }
                        globalThis.LWB_Guard.save?.();
                    }
                }
            } catch {}
        }

        const absPath = normalizePath(path);

        let guardOk = true;
        let guardVal = vParsed;
        try {
            if (globalThis.LWB_Guard?.validate) {
                const g = globalThis.LWB_Guard.validate('set', absPath, vParsed);
                guardOk = !!g?.allow;
                if ('value' in g) guardVal = g.value;
            }
        } catch {}

        if (!guardOk) return '';

        if (segs.length === 1) {
            if (guardVal && typeof guardVal === 'object') {
                setLocalVariable(rootName, safeJSONStringify(guardVal));
            } else {
                setLocalVariable(rootName, String(guardVal ?? ''));
            }
            return '';
        }

        const rootRaw = getLocalVariable(rootName);
        let obj;
        const parsed = maybeParseObject(rootRaw);
        if (parsed) {
            obj = deepClone(parsed);
        } else {
            // 若根变量不存在：A[0].x 这类路径期望根为数组
            obj = (typeof segs[1] === 'number') ? [] : {};
        }

        const { parent, lastKey } = ensureDeepContainer(obj, segs.slice(1));
        parent[lastKey] = guardVal;

        setLocalVariable(rootName, safeJSONStringify(obj));
        return '';
    } catch {
        return '';
    }
}

export function lwbAddVarPath(path, increment) {
    try {
        const segs = lwbSplitPathWithBrackets(path);
        if (!segs.length) return '';

        const currentStr = lwbResolveVarPath(path);
        const incStr = String(increment ?? '');

        const currentNum = Number(currentStr);
        const incNum = Number(incStr);
        const bothNumeric = currentStr !== '' && incStr !== '' 
            && Number.isFinite(currentNum) && Number.isFinite(incNum);

        const newValue = bothNumeric 
            ? (currentNum + incNum) 
            : (currentStr + incStr);

        lwbAssignVarPath(path, newValue);

        return valueToString(newValue);
    } catch {
        return '';
    }
}

/**
 * 删除变量或深层属性（支持点路径/中括号路径）
 * @param {string} path
 * @returns {string} 空字符串
 */
export function lwbDeleteVarPath(path) {
    try {
        const segs = lwbSplitPathWithBrackets(path);
        if (!segs.length) return '';

        const rootName = String(segs[0]);
        const absPath = normalizePath(path);

        // 只有根变量：对齐 /flushvar 的“清空”语义
        if (segs.length === 1) {
            try {
                if (globalThis.LWB_Guard?.validate) {
                    const g = globalThis.LWB_Guard.validate('delNode', absPath);
                    if (!g?.allow) return '';
                }
            } catch {}

            setLocalVariable(rootName, '');
            return '';
        }

        const rootRaw = getLocalVariable(rootName);
        const parsed = maybeParseObject(rootRaw);
        if (!parsed) return '';

        const obj = deepClone(parsed);
        const subSegs = segs.slice(1);

        let cur = obj;
        for (let i = 0; i < subSegs.length - 1; i++) {
            cur = cur?.[subSegs[i]];
            if (cur == null || typeof cur !== 'object') return '';
        }

        try {
            if (globalThis.LWB_Guard?.validate) {
                const g = globalThis.LWB_Guard.validate('delNode', absPath);
                if (!g?.allow) return '';
            }
        } catch {}

        const lastKey = subSegs[subSegs.length - 1];
        if (Array.isArray(cur)) {
            if (typeof lastKey === 'number' && lastKey >= 0 && lastKey < cur.length) {
                cur.splice(lastKey, 1);
            } else {
                const equal = (a, b) => a === b || a == b || String(a) === String(b);
                for (let i = cur.length - 1; i >= 0; i--) {
                    if (equal(cur[i], lastKey)) cur.splice(i, 1);
                }
            }
        } else {
            try { delete cur[lastKey]; } catch {}
        }

        setLocalVariable(rootName, safeJSONStringify(obj));
        return '';
    } catch {
        return '';
    }
}

/**
 * 向数组推入值（支持点路径/中括号路径）
 * @param {string} path
 * @param {*} value
 * @returns {string} 新数组长度（字符串）
 */
export function lwbPushVarPath(path, value) {
    try {
        const segs = lwbSplitPathWithBrackets(path);
        if (!segs.length) return '';

        const rootName = String(segs[0]);
        const absPath = normalizePath(path);
        const vParsed = parseValueForSet(value);

        // 仅根变量：将 root 视为数组
        if (segs.length === 1) {
            try {
                if (globalThis.LWB_Guard?.validate) {
                    const g = globalThis.LWB_Guard.validate('push', absPath, vParsed);
                    if (!g?.allow) return '';
                }
            } catch {}

            const rootRaw = getLocalVariable(rootName);
            let arr;
            try { arr = JSON.parse(rootRaw); } catch { arr = undefined; }
            if (!Array.isArray(arr)) arr = rootRaw != null && rootRaw !== '' ? [rootRaw] : [];
            arr.push(vParsed);
            setLocalVariable(rootName, safeJSONStringify(arr));
            return String(arr.length);
        }

        const rootRaw = getLocalVariable(rootName);
        let obj;
        const parsed = maybeParseObject(rootRaw);
        if (parsed) {
            obj = deepClone(parsed);
        } else {
            const firstSubSeg = segs[1];
            obj = typeof firstSubSeg === 'number' ? [] : {};
        }

        const { parent, lastKey } = ensureDeepContainer(obj, segs.slice(1));
        let arr = parent[lastKey];

        if (!Array.isArray(arr)) {
            arr = arr != null ? [arr] : [];
        }

        try {
            if (globalThis.LWB_Guard?.validate) {
                const g = globalThis.LWB_Guard.validate('push', absPath, vParsed);
                if (!g?.allow) return '';
            }
        } catch {}

        arr.push(vParsed);
        parent[lastKey] = arr;

        setLocalVariable(rootName, safeJSONStringify(obj));
        return String(arr.length);
    } catch {
        return '';
    }
}

function registerXbGetVarSlashCommand() {
    try {
        const ctx = getContext();
        const { SlashCommandParser, SlashCommand, SlashCommandArgument, ARGUMENT_TYPE } = ctx || {};

        if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) {
            return;
        }

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({
            name: 'xbgetvar',
            returns: 'string',
            helpString: `
                <div>通过点/中括号路径获取嵌套的本地变量值</div>
                <div>支持 ["0"] 强制字符串键、[0] 数组索引</div>
                <div><strong>示例：</strong></div>
                <pre><code>/xbgetvar 人物状态.姓名</code></pre>
                <pre><code>/xbgetvar A[0].name | /echo {{pipe}}</code></pre>
            `,
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: '变量路径',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    acceptsMultiple: false,
                }),
            ],
            callback: (namedArgs, unnamedArgs) => {
                try {
                    const path = extractPathFromArgs(namedArgs, unnamedArgs);
                    return lwbResolveVarPath(String(path || ''));
                } catch {
                    return '';
                }
            },
        }));
    } catch {}
}

function registerXbSetVarSlashCommand() {
    try {
        const ctx = getContext();
        const { SlashCommandParser, SlashCommand, SlashCommandArgument, ARGUMENT_TYPE } = ctx || {};

        if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) {
            return;
        }

        function joinUnnamed(args) {
            if (Array.isArray(args)) {
                return args.filter(v => v != null).map(v => String(v)).join(' ').trim();
            }
            return String(args ?? '').trim();
        }

        function splitTokensBySpace(s) {
            return String(s || '').split(/\s+/).filter(Boolean);
        }

        function isDirectiveToken(tok) {
            const t = String(tok || '').trim();
            if (!t) return false;
            if (['$ro', '$ext', '$prune', '$free', '$grow', '$shrink', '$list', '$clear'].includes(t)) {
                return true;
            }
            if (/^\$(min|max|range|enum|match|step)=/.test(t)) {
                return true;
            }
            return false;
        }

        function parseKeyAndValue(namedArgs, unnamedArgs) {
            const unnamedJoined = joinUnnamed(unnamedArgs);
            const hasNamedKey = typeof namedArgs?.key === 'string' && namedArgs.key.trim().length > 0;

            if (hasNamedKey) {
                const keyRaw = namedArgs.key.trim();
                const keyParts = splitTokensBySpace(keyRaw);

                if (keyParts.length > 1 && keyParts.every((p, i) =>
                    isDirectiveToken(p) || i === keyParts.length - 1
                )) {
                    const directives = keyParts.slice(0, -1);
                    const realPath = keyParts[keyParts.length - 1];
                    return { directives, realPath, valueText: unnamedJoined };
                }

                if (isDirectiveToken(keyRaw)) {
                    const m = unnamedJoined.match(/^\S+/);
                    const realPath = m ? m[0] : '';
                    const valueText = realPath ? unnamedJoined.slice(realPath.length).trim() : '';
                    return { directives: [keyRaw], realPath, valueText };
                }

                return { directives: [], realPath: keyRaw, valueText: unnamedJoined };
            }

            const firstRaw = joinUnnamed(unnamedArgs);
            if (!firstRaw) return { directives: [], realPath: '', valueText: '' };

            const sp = lwbSplitPathAndValue(firstRaw);
            let head = String(sp.path || '').trim();
            let rest = String(sp.value || '').trim();
            const parts = splitTokensBySpace(head);

            if (parts.length > 1 && parts.every((p, i) =>
                isDirectiveToken(p) || i === parts.length - 1
            )) {
                const directives = parts.slice(0, -1);
                const realPath = parts[parts.length - 1];
                return { directives, realPath, valueText: rest };
            }

            if (isDirectiveToken(head)) {
                const m = rest.match(/^\S+/);
                const realPath = m ? m[0] : '';
                const valueText = realPath ? rest.slice(realPath.length).trim() : '';
                return { directives: [head], realPath, valueText };
            }

            return { directives: [], realPath: head, valueText: rest };
        }

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({
            name: 'xbsetvar',
            returns: 'string',
            helpString: `
                <div>设置嵌套本地变量</div>
                <div>支持指令前缀：$ro, $min=, $max=, $list 等</div>
                <div><strong>示例：</strong></div>
                <pre><code>/xbsetvar A.B.C 123</code></pre>
                <pre><code>/xbsetvar key="$list 情节小结" ["item1"]</code></pre>
            `,
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: '变量路径或(指令 + 路径)',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    acceptsMultiple: false,
                }),
                SlashCommandArgument.fromProps({
                    description: '要设置的值',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: false,
                    acceptsMultiple: false,
                }),
            ],
            callback: (namedArgs, unnamedArgs) => {
                try {
                    const parsed = parseKeyAndValue(namedArgs, unnamedArgs);
                    const directives = parsed.directives || [];
                    const realPath = String(parsed.realPath || '').trim();
                    let rest = String(parsed.valueText || '').trim();

                    if (!realPath) return '';

                    if (directives.length > 0 && globalThis.LWB_Guard?.applyDelta) {
                        const delta = parseDirectivesTokenList(directives);
                        const absPath = normalizePath(realPath);
                        globalThis.LWB_Guard.applyDelta(absPath, delta);
                        globalThis.LWB_Guard.save?.();
                    }

                    let toSet = rest;
                    try {
                        const parsedVal = parseValueForSet(rest);
                        if (parsedVal && typeof parsedVal === 'object' && !Array.isArray(parsedVal)) {
                            const expanded = expandShorthandRuleObject(realPath, parsedVal);
                            if (expanded && typeof expanded === 'object') {
                                toSet = safeJSONStringify(expanded) || rest;
                            }
                        }
                    } catch {}

                    lwbAssignVarPath(realPath, toSet);
                    return '';
                } catch {
                    return '';
                }
            },
        }));
    } catch {}
}

function registerXbAddVarSlashCommand() {
    try {
        const ctx = getContext();
        const { SlashCommandParser, SlashCommand, SlashCommandArgument, SlashCommandNamedArgument, ARGUMENT_TYPE } = ctx || {};

        if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) {
            return;
        }

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({
            name: 'xbaddvar',
            returns: 'string',
            helpString: `
                <div>通过点路径增加变量值</div>
                <div>两者都为数字时执行加法，否则执行字符串拼接</div>
                <div><strong>示例：</strong></div>
                <pre><code>/xbaddvar key=人物状态.金币 100</code></pre>
                <pre><code>/xbaddvar A.B.count 1</code></pre>
                <pre><code>/xbaddvar 名字 _后缀</code></pre>
            `,
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({
                    name: 'key',
                    description: '变量路径',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: false,
                }),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: '路径+增量 或 仅增量',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                }),
            ],
            callback: (namedArgs, unnamedArgs) => {
                try {
                    let path, increment;

                    const unnamedJoined = Array.isArray(unnamedArgs)
                        ? unnamedArgs.filter(v => v != null).map(String).join(' ').trim()
                        : String(unnamedArgs ?? '').trim();

                    if (namedArgs?.key && String(namedArgs.key).trim()) {
                        path = String(namedArgs.key).trim();
                        increment = unnamedJoined;
                    } else {
                        const sp = lwbSplitPathAndValue(unnamedJoined);
                        path = sp.path;
                        increment = sp.value;
                    }

                    if (!path) return '';

                    return lwbAddVarPath(path, increment);
                } catch {
                    return '';
                }
            },
        }));
    } catch {}
}

function registerXbDelVarSlashCommand() {
    try {
        const ctx = getContext();
        const { SlashCommandParser, SlashCommand, SlashCommandArgument, ARGUMENT_TYPE } = ctx || {};

        if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) {
            return;
        }

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({
            name: 'xbdelvar',
            returns: 'string',
            helpString: `
                <div>删除变量或深层属性</div>
                <div><strong>示例：</strong></div>
                <pre><code>/xbdelvar 临时变量</code></pre>
                <pre><code>/xbdelvar 角色状态.临时buff</code></pre>
                <pre><code>/xbdelvar 背包[0]</code></pre>
            `,
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: '变量路径',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                }),
            ],
            callback: (namedArgs, unnamedArgs) => {
                try {
                    const path = extractPathFromArgs(namedArgs, unnamedArgs);
                    if (!path) return '';
                    return lwbDeleteVarPath(path);
                } catch {
                    return '';
                }
            },
        }));
    } catch {}
}

function registerXbPushVarSlashCommand() {
    try {
        const ctx = getContext();
        const { SlashCommandParser, SlashCommand, SlashCommandArgument, SlashCommandNamedArgument, ARGUMENT_TYPE } = ctx || {};

        if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) {
            return;
        }

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({
            name: 'xbpushvar',
            returns: 'string',
            helpString: `
                <div>向数组推入值</div>
                <div>返回新数组长度</div>
                <div><strong>示例：</strong></div>
                <pre><code>/xbpushvar key=背包 苹果</code></pre>
                <pre><code>/xbpushvar 角色.技能列表 火球术</code></pre>
            `,
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({
                    name: 'key',
                    description: '数组路径',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: false,
                }),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: '路径+值 或 仅值',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                }),
            ],
            callback: (namedArgs, unnamedArgs) => {
                try {
                    let path, value;

                    const unnamedJoined = Array.isArray(unnamedArgs)
                        ? unnamedArgs.filter(v => v != null).map(String).join(' ').trim()
                        : String(unnamedArgs ?? '').trim();

                    if (namedArgs?.key && String(namedArgs.key).trim()) {
                        path = String(namedArgs.key).trim();
                        value = unnamedJoined;
                    } else {
                        const sp = lwbSplitPathAndValue(unnamedJoined);
                        path = sp.path;
                        value = sp.value;
                    }

                    if (!path) return '';
                    return lwbPushVarPath(path, value);
                } catch {
                    return '';
                }
            },
        }));
    } catch {}
}

function onMessageRendered(data) {
    try {
        if (globalThis.LWB_Guard?.validate) return;

        const id = typeof data === 'object' && data !== null
            ? (data.messageId ?? data.id ?? data)
            : data;

        if (typeof id === 'number') {
            applyXbGetVarForMessage(id, true);
        }
    } catch {}
}

export function initVarCommands() {
    if (initialized) return;
    initialized = true;

    events = createModuleEvents(MODULE_ID);

    registerXbGetVarSlashCommand();
    registerXbSetVarSlashCommand();
    registerXbAddVarSlashCommand();
    registerXbDelVarSlashCommand();
    registerXbPushVarSlashCommand();

    events.on(event_types.USER_MESSAGE_RENDERED, onMessageRendered);
    events.on(event_types.CHARACTER_MESSAGE_RENDERED, onMessageRendered);
    events.on(event_types.MESSAGE_UPDATED, onMessageRendered);
    events.on(event_types.MESSAGE_EDITED, onMessageRendered);
    events.on(event_types.MESSAGE_SWIPED, onMessageRendered);
}

export function cleanupVarCommands() {
    if (!initialized) return;

    events?.cleanup();
    events = null;

    initialized = false;
}

export {
    MODULE_ID,
};
