/**
 * amis-ui v6.12.0-beta.0
 * Copyright 2018-2025 fex
 */

import { __assign, __read } from 'tslib';
import { findTree } from 'amis-core';
import { parse } from 'amis-formula';
import debounce from 'lodash/debounce';

/**
 * @file 扩展 codemirror
 */
function editorFactory(dom, cm, props, options) {
    registerLaunguageMode(cm);
    return cm(dom, __assign({ value: props.value || '', autofocus: false, mode: props.evalMode ? 'text/formula' : 'text/formula-template', readOnly: props.readOnly ? 'nocursor' : false }, options));
}
function traverseAst(ast, iterator) {
    if (!ast || !ast.type) {
        return;
    }
    var ret = iterator(ast);
    if (ret === false) {
        return;
    }
    Object.keys(ast).forEach(function (key) {
        var value = ast[key];
        if (Array.isArray(value)) {
            value.forEach(function (child) { return traverseAst(child, iterator); });
        }
        else {
            traverseAst(value, iterator);
        }
    });
}
var FormulaPlugin = /** @class */ (function () {
    function FormulaPlugin(editor, cm) {
        var _this = this;
        this.editor = editor;
        this.cm = cm;
        /**
         * 用于提示的变量集合，默认为空
         */
        this.variables = [];
        /**
         * 函数集合，默认不需要传，即  amis-formula 里面那个函数
         * 如果有扩充，则需要传。
         */
        this.functions = [];
        /**
         * evalMode 即直接就是表达式，否则就是混合模式
         */
        this.evalMode = true;
        this.highlightMode = 'formula';
        this.disableAutoMark = false;
        this.widgets = [];
        this.marks = [];
        // this.autoMarkText();
        this.autoMarkText = debounce(this.autoMarkText.bind(this), 250, {
            leading: false,
            trailing: true
        });
        editor.on('blur', function () { return _this.autoMarkText(); });
    }
    FormulaPlugin.prototype.setVariables = function (variables) {
        this.variables = Array.isArray(variables) ? variables : [];
    };
    FormulaPlugin.prototype.setFunctions = function (functions) {
        this.functions = Array.isArray(functions) ? functions : [];
    };
    FormulaPlugin.prototype.setEvalMode = function (evalMode) {
        this.evalMode = evalMode;
    };
    FormulaPlugin.prototype.setHighlightMode = function (highlightMode) {
        this.highlightMode = highlightMode;
    };
    FormulaPlugin.prototype.setDisableAutoMark = function (disableAutoMark) {
        this.disableAutoMark = disableAutoMark;
        this.autoMarkText(true);
    };
    FormulaPlugin.prototype.autoMarkText = function (forceClear) {
        var _a;
        if (forceClear === void 0) { forceClear = false; }
        if (forceClear || !this.editor.hasFocus()) {
            (_a = this.editor) === null || _a === void 0 ? void 0 : _a.getAllMarks().forEach(function (mark) { return mark.clear(); });
        }
        this.disableAutoMark || this.autoMark();
    };
    // 计算 `${`、`}` 括号的位置，如 ${a}+${b}, 结果是 [ { from: 0, to: 3 }, { from: 5, to: 8 } ]
    FormulaPlugin.prototype.computedBracesPosition = function (exp) {
        var braces = [];
        exp === null || exp === void 0 ? void 0 : exp.replace(/\$\{/g, function (val, offset) {
            if (val) {
                var charArr = exp.slice(offset + val.length).split('');
                var cache = ['${'];
                for (var index = 0; index < charArr.length; index++) {
                    var char = charArr[index];
                    if (char === '$' && charArr[index + 1] === '{') {
                        cache.push('${');
                    }
                    else if (char === '}') {
                        cache.pop();
                    }
                    if (cache.length === 0) {
                        braces.push({ begin: offset + 2, end: index + offset + 2 });
                        break;
                    }
                }
            }
            return '';
        });
        return braces;
    };
    // 判断字符串是否在 ${} 中
    FormulaPlugin.prototype.checkStrIsInBraces = function (_a, braces) {
        var _b = __read(_a, 2), from = _b[0], to = _b[1];
        var isIn = false;
        if (braces.length) {
            for (var index = 0; index < braces.length; index++) {
                var brace = braces[index];
                if (from >= brace.begin && to <= brace.end) {
                    isIn = true;
                    break;
                }
            }
        }
        return isIn;
    };
    FormulaPlugin.prototype.insertBraces = function (originFrom, originTo) {
        var str = this.editor.getValue();
        var braces = this.computedBracesPosition(str);
        if (!this.checkStrIsInBraces([originFrom.ch, originTo.ch], braces)) {
            this.editor.setCursor({
                line: originFrom.line,
                ch: originFrom.ch
            });
            this.editor.replaceSelection('${');
            this.editor.setCursor({
                line: originTo.line,
                ch: originTo.ch + 2
            });
            this.editor.replaceSelection('}');
        }
    };
    FormulaPlugin.prototype.insertContent = function (value, type) {
        var from = this.editor.getCursor();
        var evalMode = this.evalMode;
        if (type === 'variable') {
            this.editor.replaceSelection(value.key);
            var to = this.editor.getCursor();
            !evalMode && this.insertBraces(from, to);
        }
        else if (type === 'func') {
            this.editor.replaceSelection("".concat(value, "()"));
            var to = this.editor.getCursor();
            this.editor.setCursor({
                line: to.line,
                ch: to.ch - 1
            });
            if (!evalMode) {
                this.insertBraces(from, to);
                this.editor.setCursor({
                    line: to.line,
                    ch: to.ch + 1
                });
            }
        }
        else if (typeof value === 'string') {
            this.editor.replaceSelection(value);
            // 非变量、非函数，可能是组合模式，也需要标记
        }
        this.editor.focus();
    };
    FormulaPlugin.prototype.setValue = function (value) {
        this.editor.setValue(value);
    };
    FormulaPlugin.prototype.getValue = function () {
        return this.editor.getValue();
    };
    FormulaPlugin.prototype.markText = function (from, to, label, className, rawString) {
        if (className === void 0) { className = 'cm-func'; }
        var text = document.createElement('span');
        text.className = className;
        text.innerText = label;
        if (rawString) {
            text.setAttribute('data-tooltip', rawString);
            text.setAttribute('data-position', 'bottom');
        }
        return this.editor.markText(from, to, {
            atomic: true,
            replacedWith: text
        });
    };
    FormulaPlugin.prototype.autoMark = function () {
        var _this = this;
        var editor = this.editor;
        var value = editor.getValue();
        var functions = this.functions;
        var variables = this.variables;
        var highlightMode = this.highlightMode;
        // 把旧的清掉
        this.widgets.forEach(function (widget) { return editor.removeLineWidget(widget); });
        this.widgets = [];
        this.marks.forEach(function (mark) { return mark.clear(); });
        this.marks = [];
        try {
            var ast = parse(value, {
                evalMode: this.evalMode,
                variableMode: false
            });
            traverseAst(ast, function (ast) {
                var _a, _b;
                if (highlightMode === 'expression') {
                    if (ast.type === 'script') {
                        _this.markText({
                            line: ast.start.line - 1,
                            ch: ast.start.column - 1
                        }, {
                            line: ast.end.line - 1,
                            ch: ast.end.column - 1
                        }, value.substring(ast.start.index + 2, ast.end.index - 1), 'cm-expression', value);
                    }
                    return;
                }
                if (ast.type === 'func_call') {
                    var funName_1 = ast.identifier;
                    var exists = functions.some(function (item) {
                        return item.items.some(function (i) { return i.name === funName_1; });
                    });
                    if (exists) {
                        _this.markText({
                            line: ast.start.line - 1,
                            ch: ast.start.column - 1
                        }, {
                            line: ast.start.line - 1,
                            ch: ast.start.column + funName_1.length - 1
                        }, funName_1, 'cm-func');
                    }
                }
                else if (ast.type === 'getter') {
                    // 获取对象中的变量
                    var list = [ast];
                    var current = ast;
                    while ((current === null || current === void 0 ? void 0 : current.type) === 'getter') {
                        current = current.host;
                        list.unshift(current);
                    }
                    var host_1 = list.shift();
                    if ((host_1 === null || host_1 === void 0 ? void 0 : host_1.type) === 'variable') {
                        var variable = findTree(variables, function (item) { return item.value === host_1.name; });
                        if (variable) {
                            // 先标记顶层对象
                            _this.markText({
                                line: host_1.start.line - 1,
                                ch: host_1.start.column - 1
                            }, {
                                line: host_1.end.line - 1,
                                ch: host_1.end.column - 1
                            }, variable.label, 'cm-field', host_1.name);
                            // 再标记子对象
                            var path_1 = host_1.name + '.';
                            var vars = variable.children || [];
                            var _loop_1 = function (i, len) {
                                var item = (_a = list[i]) === null || _a === void 0 ? void 0 : _a.key;
                                // 只能识别这种固定下标的情况
                                if ((item === null || item === void 0 ? void 0 : item.type) === 'identifier') {
                                    var variable_1 = (_b = findTree(vars, function (v) { return v.value === path_1 + item.name; })) !== null && _b !== void 0 ? _b : findTree(vars, function (v) { return v.value === item.name; } // 兼容不带路径的情况
                                    );
                                    if (variable_1) {
                                        _this.markText({
                                            line: item.start.line - 1,
                                            ch: item.start.column - 1
                                        }, {
                                            line: item.end.line - 1,
                                            ch: item.end.column - 1
                                        }, variable_1.label, 'cm-field', item.name);
                                        path_1 += item.name + '.';
                                        vars = variable_1.children || [];
                                    }
                                    else {
                                        return "break";
                                    }
                                }
                            };
                            for (var i = 0, len = list.length; i < len; i++) {
                                var state_1 = _loop_1(i, len);
                                if (state_1 === "break")
                                    break;
                            }
                        }
                    }
                    return false;
                }
                else if (ast.type === 'variable') {
                    // 直接就是变量
                    var variable = findTree(variables, function (item) { return item.value === ast.name; });
                    if (variable) {
                        _this.markText({
                            line: ast.start.line - 1,
                            ch: ast.start.column - 1
                        }, {
                            line: ast.end.line - 1,
                            ch: ast.end.column - 1
                        }, variable.label, 'cm-field', ast.name);
                    }
                    return false;
                }
            });
        }
        catch (e) {
            var reg = /^Unexpected\stoken\s(.+)\sin\s(\d+):(\d+)$/.exec(e.message);
            if (reg) {
                var token = reg[1];
                var line = parseInt(reg[2], 10);
                var column = parseInt(reg[3], 10);
                var msg = document.createElement('div');
                var icon = msg.appendChild(document.createElement('span'));
                icon.innerText = '!!';
                icon.className = 'lint-error-icon';
                msg.appendChild(document.createTextNode("Unexpected token `".concat(token, "`")));
                msg.className = 'lint-error';
                this.widgets.push(editor.addLineWidget(line - 1, msg, {
                    coverGutter: false,
                    noHScroll: true
                }));
                this.marks.push(this.markText({
                    line: line - 1,
                    ch: column - 1
                }, {
                    line: line - 1,
                    ch: column + token.length - 1
                }, token, 'cm-error-token'));
            }
            console.warn('synax error, ignore it');
        }
    };
    // 焦点放在最后
    FormulaPlugin.prototype.focus = function (value) {
        this.editor.setCursor({
            line: 0,
            ch: (value === null || value === void 0 ? void 0 : value.length) || 0
        });
    };
    FormulaPlugin.prototype.dispose = function () {
        this.autoMarkText.cancel();
    };
    FormulaPlugin.prototype.validate = function () { };
    return FormulaPlugin;
}());
var modeRegisted = false;
function registerLaunguageMode(cm) {
    if (modeRegisted) {
        return;
    }
    modeRegisted = true;
    // TODO 自定义语言规则
    // 对应 evalMode
    cm.defineMode('formula', function (config, parserConfig) {
        var formula = cm.getMode(config, 'javascript');
        if (!parserConfig || !parserConfig.base)
            return formula;
        return cm.multiplexingMode(cm.getMode(config, parserConfig.base), {
            open: '${',
            close: '}',
            mode: formula
        });
    });
    cm.defineMIME('text/formula', { name: 'formula' });
    cm.defineMIME('text/formula-template', { name: 'formula', base: 'htmlmixed' });
}

export { FormulaPlugin, editorFactory };
