import { immutable, excludedInJSON, circular, action } from '../decorators';
import { config, history, typeCheck, utils, Vertex, Service, LEVEL_ENUM, Interface, Param, Return, Variable, Logic, View, Schema, ActionOptions, ACTION_MODE } from '..';
import { logicService } from '../../service/logic';
import { assert } from 'console';
import { traverse } from '../utils';
import { vertexsMap } from '../cacheData';
import { getSchemaOfExpressionNode } from './tools';
import Structure from '../data/Structure';

export enum LOGIC_TYPE {
    // LogicNode
    Start = 'Start',
    ProcessOutcome = 'ProcessOutcome',
    End = 'End',
    IfStatement = 'IfStatement',
    SwitchStatement = 'SwitchStatement',
    SwitchCase = 'SwitchCase',
    ForEachStatement = 'ForEachStatement',
    WhileStatement = 'WhileStatement',
    AssignmentExpression = 'AssignmentExpression',
    Comment = 'Comment',
    CallLogic = 'CallLogic',
    CallInterface = 'CallInterface',
    CallInterParam = 'CallInterParam',
    CallGraphQL = 'CallGraphQL',
    DBQuery = 'DBQuery',
    CronJob = 'CronJob',
    JSONSerialize = 'JSONSerialize',
    JSONDeserialize = 'JSONDeserialize',
    CallMessageShow = 'CallMessageShow',
    CallConsoleLog = 'CallConsoleLog',
    JSBlock = 'JSBlock',
    TypeNote = 'TypeNote',
    CallQueryComponent = 'CallQueryComponent',
    // Expression
    Identifier = 'Identifier',
    NumericLiteral = 'NumericLiteral',
    BooleanLiteral = 'BooleanLiteral',
    StringLiteral = 'StringLiteral',
    NullLiteral = 'NullLiteral',
    UnaryExpression = 'UnaryExpression',
    BinaryExpression = 'BinaryExpression',
    LogicalExpression = 'LogicalExpression',
    MemberExpression = 'MemberExpression',
    CallExpression = 'CallExpression',
    ObjectExpression = 'ObjectExpression',
    ArrayExpression = 'ArrayExpression',
    Destination = 'Destination',
    DestinationParam = 'DestinationParam',
    BuiltInFunction = 'BuiltInFunction',
    BuiltInFuncParam = 'BuiltInFuncParam',
    Unparsed = 'Unparsed',
    QuerySelectExpression = 'QuerySelectExpression',
    QueryFromExpression = 'QueryFromExpression',
    QueryJoinExpression = 'QueryJoinExpression',
    QueryAggregateExpression = 'QueryAggregateExpression',
}

export function evaluate(node: LogicNode | ExpressionNode, finalCode = true): string {
    if (!node)
        return '';
    if (node.type === 'Identifier')
        return node.code || node.name;

    if (node.type === 'NumericLiteral')
        return <string>node.value;

    if (node.type === 'BooleanLiteral')
        return `${node.value}`;

    if (node.type === 'StringLiteral')
        return `'${node.value}'`;

    if (node.type === 'NullLiteral')
        return 'null';

    if (node.type === 'BinaryExpression') {
        let left: string = evaluate(<LogicNode>node.left, finalCode);
        if (!finalCode && node.left?.type === LOGIC_TYPE.MemberExpression)
            left = `(${left})`;

        let right: string = evaluate(<LogicNode>node.right, finalCode);
        if (!finalCode && node.right?.type === LOGIC_TYPE.MemberExpression)
            right = `(${right})`;

        return `${left} ${node.operator} ${right}`;
    }

    if (node.type === 'LogicalExpression') {
        let left: string = evaluate(<LogicNode>node.left, finalCode);
        if (!finalCode && node.left?.type === LOGIC_TYPE.MemberExpression)
            left = `(${left})`;

        let right: string = evaluate(<LogicNode>node.right, finalCode);
        if (!finalCode && node.right?.type === LOGIC_TYPE.MemberExpression)
            right = `(${right})`;

        return `${left} ${node.operator} ${right}`;
    }

    if (node.type === LOGIC_TYPE.UnaryExpression && node.operator === '!') {
        const argument = (<ExpressionNode>node).argument;
        const code = evaluate(argument, finalCode);
        const arr = [LOGIC_TYPE.BinaryExpression, LOGIC_TYPE.LogicalExpression];
        if (!finalCode)
            arr.push(LOGIC_TYPE.MemberExpression);
        return arr.includes(argument?.type) ? `!(${code})` : `!${code}`;
    }

    if (node.type === 'Unparsed') {
        return node.code;
    }

    if (node.type === 'MemberExpression') {
        const object: string = evaluate((<ExpressionNode>node).object, finalCode);
        const property = (<ExpressionNode>node).property;

        if (finalCode || property.code?.includes('ID_ENUMVALUE_')) {
            if (property.type === 'Identifier')
                return `${object}.${property.code || property.name}`;
            else if (property.type === 'StringLiteral')
                return `${object}['${property.value}']`;
            else if (property.type === 'NumericLiteral')
                return `${object}[${property.value}]`;
        } else {
            const lastPart = object.split(' && ').pop();
            if (property.type === 'Identifier')
                return `${object} && ${lastPart}.${property.code || property.name}`;
            else if (property.type === 'StringLiteral')
                return `${object} && ${lastPart}['${property.value}']`;
            else if (property.type === 'NumericLiteral')
                return `${object} && ${lastPart}[${property.value}]`;
        }
    }

    if (node.type === 'CallExpression') {
        const args = [];
        for (const argumentNode of (<LogicNode>node).arguments) {
            args.push(evaluate(argumentNode, finalCode));
        }
        const name = (<ExpressionNode>node).callee.name;
        return `${name}(${args.join(', ')})`;
    }

    if (node.type === 'ObjectExpression') {
        const obj: {
            [prop: string]: any
        } = {};
        for (let { key, value } of (<ExpressionNode>node).properties) {
            let keyValue = evaluate(key, finalCode);
            if (/^"(.*)"$/.test(keyValue))
                keyValue = RegExp.$1;

            const valueTemp = evaluate(<ExpressionNode>value, finalCode);
            if (/^"(.*)"$/.test(valueTemp))
                value = RegExp.$1;
            obj[keyValue] = value;
        }
        return JSON.stringify(obj);
    }

    if (node.type === 'ArrayExpression') {
        const arr = [];
        for (const elementNode of (<ExpressionNode>node).elements) {
            arr.push(evaluate(elementNode, finalCode));
        }
        return '[' + arr.join(', ') + ']';
    }

    if (node.type === 'Destination') {
        const params = ((node as any).params || [])
            .filter((param: LogicNode) => param.pageParamKey && param.pageParamKeyValue)
            .map((param: LogicNode) => evaluate(param.pageParamKey, finalCode) + '=${' + evaluate(param.pageParamKeyValue, finalCode) + '}');
        let result = node.code;
        if (params.length) {
            result = '`' + `${node.code}?${params.join('&')}` + '`';
        } else {
            result = '`' + `${result}` + '`';
        }
        return result.replace(/"/g, "'");
    }

    if (node.type === 'BuiltInFunction') {
        const calleeCode = `$utils['${(node as LogicNode).calleeCode}']`;
        if (!calleeCode)
            return;
        // 需要保持params顺序
        const params = ((node as any).builtInFuncParams || [])
            .map((param: any) => param.builtInFuncParamValue ? evaluate(param.builtInFuncParamValue, finalCode) : null);
        let paramResult = params.join(',');
        if (paramResult === ',') {
            paramResult = '';
        } else {
            paramResult = params.join(',');
        }
        return `${calleeCode}(${paramResult})`.replace(/"/g, "'");
    }

    if (node.type === 'TypeNote') {
        const { type, format } = node.schema;
        return `{ type: '${type}', format: '${format}' }`;
    }
}

export const logicItemKeyOfLogicItem: string[] = ['test', 'left', 'right', 'each', 'item', 'index', 'start', 'end', 'variables', 'taskId', 'processInstanceId', 'startedBy', 'errorMessage', 'callInterParamValue', 'builtInFuncParamValue', 'pageParamKey', 'pageParamKeyValue', 'argument',
    'select', 'from', 'limit', 'orderElement', 'aggregateParam', 'order', 'pageElement', 'pageSizeElement', 'groupElement',
];
export const logicItemArrayKeyOfLogicItem: string[] = ['body', 'consequent', 'alternate', 'cases', 'params', 'arguments', 'builtInFuncParams',
    'groupBy', 'orderBy', 'selectElementList', 'joinPartList', 'onExpressionList', 'where', 'having',
];

/**
 * 逻辑项类
 */
export class LogicItem extends Vertex {
    /**
     * 概念类型
     */
    @immutable()
    public readonly level: LEVEL_ENUM = LEVEL_ENUM.logicItem;
    /**
     * Id
     */
    @immutable()
    public readonly id: string = undefined;
    /**
     * 父级 Id
     */
    @immutable()
    public readonly parentId: string = undefined;
    /**
     * 所属父级属性
     */
    @immutable()
    public readonly parentAttr: string = undefined;
    @excludedInJSON()
    @immutable()
    public readonly parent: LogicItem = undefined;
    @immutable()
    public readonly logicId: string = undefined;
    @immutable()
    public readonly logic: Logic = undefined;
    /**
     * 标题
     */
    @immutable()
    public readonly label: string = undefined;
    @immutable()
    public folded: boolean = false;
    @immutable()
    public placeholderName: string = undefined;
    /**
     * OffsetX
     */
    @immutable()
    public readonly offsetX: number = undefined;
    /**
     * OffsetY
     */
    @immutable()
    public readonly offsetY: number = undefined;
    /**
     * 所在位置
     */
    @immutable()
    public readonly _posIndex: number = undefined;
    /**
    * 标题
    */
    @immutable()
    public readonly type: string = undefined;
    /**
     * Ref
     */
    @excludedInJSON()
    @immutable()
    public readonly refTarget: any = undefined;
    @immutable()
    public readonly callInterParam: any = undefined;
    @immutable()
    public readonly callInterParamValue: any = undefined;

    @immutable()
    public readonly builtInFuncParamValue: any = undefined;

    @immutable()
    public readonly pageParamKey: any = undefined;
    @immutable()
    public readonly pageParamKeyValue: any = undefined;
    /**
     * CallQueryComponent 对应的 structure 的 id
     */
    @immutable()
    public readonly structureRef: string = undefined;
    @immutable()
    public readonly schema?: Schema = undefined;
    /**
     * 类型校验结果
     */
    @excludedInJSON()
    public typeCheckNote: any = undefined;
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<LogicItem>) {
        super();
        source && this.assign(source);
    }
    /**
     * 纯前端添加
     */
    addIn(parent: LogicItem, parentId: string, parentAttr: string, _posIndex: number) {
        if (!parent) {
            if (!this.logic)
                return;
            if (this.logic.playgroundId === parentId) { // In playgroundId
                const index = _posIndex || this.logic.playground.length;
                this.logic.playground.splice(index, 0, this);
            } else { // In main body 这个后面让忠杰改成数组吧
                const index = this.logic.body.findIndex((item) => item.id === parentId);
                ~index && this.logic.body.splice(index + 1, 0, this);
                this.logic.body.forEach((item, index) => {
                    if (index) {
                        item.assign({ parentId: this.logic.body[index - 1].id });
                    }
                });
            }
        } else {
            this.assign({ parent, parentAttr });
            const attr = parent[parentAttr as keyof LogicItem];
            if (Array.isArray(attr)) {
                const index = _posIndex;
                ~index && attr.splice(index, 0, this);
            } else {
                if (!parentAttr)
                    return;
                parent.assign({ [parentAttr]: this });
            }
        }
    }
    /**
     * @temp 临时的
     * @param body
     */
    static preprocess(body: any) {
        traverse((current) => {
            delete current.node.editable;
            if (current.node.type !== 'ForEachStatement')
                delete current.node.index;

            // 处理 CallGraphQL
            if (current.node.type === 'CallGraphQL') {
                const body = current.node;
                delete body.paramsType;
                delete body.optionalParams;
                body.params && body.params.forEach((param: any, index: number) => {
                    param._posIndex = index;
                });
                delete body.querySchemaMap;
                delete body.singularResolverName;
                body.querySchemaList = JSON.stringify(body.querySchemaList || []);
            }
        }, { node: body }, { mode: 'anyObject' });

        // 处理 CallInterface
        if (body.type === 'CallInterface') {
            delete body.paramsType;
            delete body.optionalParams;

            body.params.forEach((param: any, index: number) => {
                param._posIndex = index;
            });
        }
    }
    /**
     * 创建
     * @requires this.logic
     * @param parent
     * @param parentId
     * @param parentAttr
     * @param _posIndex
     * @param cache 在前端不处理节点
     */
    async create({
        parent,
        parentId,
        parentAttr,
        _posIndex,
        cache,
        offsetX,
        offsetY,
    }: {
        parent: LogicItem,
        parentId: string,
        parentAttr: string,
        _posIndex?: number,
        cache?: boolean,
        offsetX?: number,
        offsetY?: number,
    }, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving');

        const prevParent = this.parent;
        if (parent) {
            const attr = parent[parentAttr as keyof LogicItem];
            if (_posIndex === undefined && Array.isArray(attr))
                _posIndex = attr.length;
        }

        offsetX = offsetX ?? this.offsetX;
        offsetY = offsetY ?? this.offsetY;
        const body = this.toJSON();
        body.parentId = parentId;
        body.parentAttr = parentAttr;
        body._posIndex = _posIndex;
        body.offsetX = offsetX;
        body.offsetY = offsetY;

        LogicItem.preprocess(body);

        let result: LogicItem;
        if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) {
            utils.logger.debug(`在该逻辑节点的属性"${parentAttr}"上插入一个节点"`, body);

            body.logicId = this.logic && this.logic.id;
            result = await logicService.addItem({
                headers: {
                    appId: config.defaultApp?.id,
                    serviceId: this.logic?.interface?.serviceId,
                    operationAction: 'LogicItem.create',
                    operationDesc: actionOptions?.actionDesc || `添加逻辑项"${this.label || this.type}"`,
                    operationIgnore: actionOptions?.actionIgnore,
                },
                body,
            });
            this.deepPick(result, ['id', 'parentId', 'parentAttr', 'joinPartRef', 'structureRef']);
        }

        this.assign({
            parent,
            parentId,
            parentAttr,
            offsetX,
            offsetY,
            logicId: this.logic && this.logic.id,
        });
        LogicItem.from(this, this.logic, this.parent, false);

        !cache && this.addIn(parent, parentId, parentAttr, _posIndex);

        utils.traverse(({ node }) => {
            if (node.type === LOGIC_TYPE.CallQueryComponent) {
                // 兼容 流程-流程节点 内部逻辑
                const service = this.logic.processComponent ? this.logic.processComponent.process?.service : this.logic.interface?.service;
                const structure = Structure.from({
                    id: node.structureRef,
                }, service);
                structure.loadPro();
            }
        }, { node: this }, {
            mode: 'anyObject',
            excludedKeySet: this.JSON_EXCLUDED_KEYS,
        });

        this.logic && this.logic.emit('change');

        if (actionOptions?.checkTypeIgnore !== true) {
            this.checkType();
            prevParent && prevParent.checkType();
        }

        await config.defaultApp?.history.load(actionOptions?.actionMode !== ACTION_MODE.undoRedo && {
            operationAction: 'LogicItem.create',
            operationBeforeImage: null,
            operationAfterImage: JSON.parse(JSON.stringify(this)),
            operationDesc: `添加逻辑项"${this.label || this.type}"`,
        });
        config.defaultApp?.emit('saved');
    }
    /**
     * 创建或移动
     * @requires this.logic
     * @param parent
     * @param parentId
     * @param parentAttr
     * @param _posIndex
     * @param cache 在前端不处理节点
     */
    async move({
        parent,
        parentId,
        parentAttr,
        _posIndex,
        cache,
        offsetX,
        offsetY,
    }: {
        parent: LogicItem,
        parentId: string,
        parentAttr: string,
        _posIndex?: number,
        cache?: boolean,
        offsetX?: number,
        offsetY?: number,
    }, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving');

        const prevParent = this.parent;
        if (parentAttr && !parent) {
            parent = vertexsMap.get(parentId) as LogicItem;
        }
        if (parent) {
            const attr = parent[parentAttr as keyof LogicItem];
            if (_posIndex === undefined && Array.isArray(attr))
                _posIndex = attr.length;
        }

        const body = this.toJSON();
        body.parentId = parentId;
        body.parentAttr = parentAttr;
        body._posIndex = _posIndex;

        LogicItem.preprocess(body);

        if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) {
            utils.logger.debug(`在该逻辑节点的属性"${parentAttr}"上插入一个节点"`, body);

            const result: LogicItem = await logicService.moveItem({
                headers: {
                    appId: config.defaultApp?.id,
                    operationAction: 'LogicItem.move',
                    operationDesc: actionOptions?.actionDesc || `移动逻辑项"${this.label || this.type}"`,
                },
                body: {
                    id: body.id,
                    moveFromLogicItem: {
                        parentId: this.parentId,
                        parentAttr: this.parentAttr,
                        _posIndex: this._posIndex,
                        offsetX: this.offsetX,
                        offsetY: this.offsetY,
                    },
                    moveToLogicItem: {
                        parentId,
                        parentAttr,
                        _posIndex,
                        offsetX,
                        offsetY,
                    },
                },
            });
        }

        if (!(this.parentId === parentId && this.parentId === this.logic.playgroundId && this.parentAttr === parentAttr)) {
            // 以下几句顺序不能变，注意！
            !cache && this.remove();
            this.assign({
                parent,
                parentId,
                parentAttr,
                offsetX,
                offsetY,
            });
            !cache && this.addIn(parent, parentId, parentAttr, _posIndex);
        } else {
            this.assign({
                offsetX,
                offsetY,
            });
        }

        this.logic && this.logic.emit('change');

        this.checkType();
        prevParent && prevParent.checkType();

        await config.defaultApp?.history.load(actionOptions?.actionMode !== ACTION_MODE.undoRedo && {
            operationAction: 'LogicItem.move',
            operationBeforeImage: null,
            operationAfterImage: null, // JSON.parse(JSON.stringify(this)),
            operationDesc: `移动逻辑项"${this.label || this.type}"`,
        });
        config.defaultApp?.emit('saved');
    }
    /**
     * 创建或移动
     * @requires this.logic
     * @param parent
     * @param parentId
     * @param parentAttr
     * @param _posIndex
     * @param cache 在前端不处理节点
     */
    createOrMove(options: {
        parent: LogicItem,
        parentId: string,
        parentAttr: string,
        _posIndex?: number,
        cache?: boolean,
        offsetX?: number,
        offsetY?: number,
    }, actionOptions?: ActionOptions) {
        if (this.id)
            return this.move(options, actionOptions);
        else
            return this.create(options, actionOptions);
    }
    /**
     * 粘贴
     * @param logicItems
     * @param targetType
     * @param targetId
     */
    static async paste(logicItems: LogicItem[] | LogicItem, targetType: 'logic' | 'html' | 'processComponent', targetId: string) {
        if (!Array.isArray(logicItems))
            logicItems = [logicItems];

        logicItems.forEach((logicItem) => {
            LogicItem.preprocess(logicItem);
        });
        const res = await logicService.paste({
            body: {
                type: 'logicItem',
                logicItems,
                targetId,
                targetType,
            },
            headers: {
                appId: config.defaultApp?.id,
                operationAction: 'LogicItem.paste',
                operationDesc: `粘贴逻辑项"${logicItems[0].label || logicItems[0].type}"`,
            },
        });

        LogicItem.redoPaste(res);
    }
    static async redoPaste(res: any) {
        const logic = vertexsMap.get(res.targetId) as Logic;
        res.logicItems.forEach((logicItem: LogicItem) => {
            logicItem = LogicItem.from(logicItem, logic, null);
            logic.playground.push(logicItem);
            logicItem.checkType();

            utils.traverse(({ node }) => {
                if (node.type === LOGIC_TYPE.CallQueryComponent) {
                    // 兼容 流程-流程节点 内部逻辑
                    const service = node.logic.processComponent ? node.logic.processComponent.process?.service : node.logic.interface?.service;
                    const structure = Structure.from({
                        id: node.structureRef,
                    }, service);
                    structure.loadPro();
                }
            }, { node: logicItem }, {
                mode: 'anyObject',
                excludedKeySet: logicItem.JSON_EXCLUDED_KEYS,
            });
        });

        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
    }
    static async undoPaste(res: any) {
        const logic = vertexsMap.get(res.targetId) as Logic;
        const { playground } = logic;
        for (let i = playground.length - 1; i >= 0; i--) {
            if (res.logicItems.find((item: any) => item.id === playground[i].id)) {
                typeCheck.delete(playground[i].id);
                playground.splice(i, 1);
            }
        }

        logic.interface?.service?.syncStructures(); // 处理 CallQueryComponent 嵌套的情形

        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
    }
    /**
     * 粘贴预处理，用于粘贴到表达式输入弹窗。此处，粘贴没有立即提交到后端，点击确定按钮后才提交。
     * @param logicItems
     * @param targetType
     * @param targetId
     */
    static async pretreatmentBeforePaste(logicItems: LogicItem[] | LogicItem, targetType: 'logic' | 'html' | 'processComponent', targetId: string) {
        if (!Array.isArray(logicItems))
            logicItems = [logicItems];

        logicItems.forEach((logicItem) => {
            LogicItem.preprocess(logicItem);
        });

        function removeId(logicItems: LogicItem[]) {
            return JSON.parse(JSON.stringify(logicItems, (key, value) => key === 'id' ? undefined : value));
        }

        return logicService.pretreatmentBeforePaste({
            body: {
                type: 'logicItem',
                logicItems,
                targetId,
                targetType,
            },
        }).then((res: any) => {
            res.logicItems = removeId(res.logicItems);
            return res;
        });
    }
    /**
     * 纯前端创建
     */
    virtualCreate(parent: LogicItem, parentId: string, parentAttr: string, _posIndex?: number, cache?: boolean) {
        if (parent) {
            const attr = parent[parentAttr as keyof LogicItem];
            if (_posIndex === undefined && Array.isArray(attr))
                _posIndex = attr.length;
        }
        this.addIn(parent, parentId, parentAttr, _posIndex);
    }

    /**
     * 纯前端 replaceWith
     */
    async virtualReplaceWith(logicItem: LogicItem) {
        this.remove();

        let _posIndex: number;
        if (this.parent) {
            const attr = this.parent[this.parentAttr as keyof LogicItem];
            if (Array.isArray(attr))
                _posIndex = attr.indexOf(this);
        }
        logicItem.virtualCreate(this.parent, this.parentId, this.parentAttr, _posIndex, false);
        this.assign(logicItem);
        return this;
    }

    /**
     * 纯前端删除
     */
    remove() {
        // 处理 old
        if (!this.parent) {
            if (!this.logic)
                return;
            if (this.logic.playgroundId === this.parentId) { // In playgroundId
                const index = this.logic.playground.indexOf(this);
                ~index && this.logic.playground.splice(index, 1);
            } else { // In main body 这个后面让忠杰改成数组吧
                const index = this.logic.body.indexOf(this);
                ~index && this.logic.body.splice(index, 1);
                this.logic.body.forEach((item, index) => {
                    if (index) {
                        item.assign({ parentId: this.logic.body[index - 1].id });
                    }
                });
            }
        } else {
            const attr = this.parent[this.parentAttr as keyof LogicItem];
            if (Array.isArray(attr)) {
                const index = attr.indexOf(this);
                ~index && attr.splice(index, 1);
            } else {
                this.parent.assign({ [this.parentAttr]: null });
            }
        }
    }
    /**
     * 删除逻辑节点
     * @param cache 在前端不处理节点
     */
    @action('删除逻辑节点')
    async delete(cache?: boolean, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving');

        if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) {
            if (this.id) {
                const body = this.toPlainJSON();
                /// @TODO: 不知道哪里来的 index
                delete body.index;
                ///

                try {
                    await logicService.removeItem({
                        headers: {
                            appId: config.defaultApp?.id,
                            serviceId: this.logic?.interface?.serviceId,
                            operationAction: 'LogicItem.delete',
                            operationDesc: `删除逻辑项"${this.label || this.type}"`,

                        },
                        body,
                    });
                } catch (err) {
                    await config.defaultApp?.history.load();
                    throw err;
                }
            }
        }

        if (this.id) {
            typeCheck.delete(this.id);
            this.logic.interface?.service?.syncStructures(); // 处理 CallQueryComponent 嵌套的情形

            const parent: LogicItem = this.parent;
            if (parent && parent.checkType) {
                parent.checkType();
            }
        }

        !cache && this.remove();
        this.destroy();
        this.logic && this.logic.emit('change');
        await config.defaultApp?.history.load(actionOptions?.actionMode !== ACTION_MODE.undoRedo && {
            operationAction: 'LogicItem.delete',
            operationBeforeImage: JSON.parse(JSON.stringify(this)),
            operationAfterImage: null,
            operationDesc: `删除逻辑项"${this.label || this.type}"`,
        });
        config.defaultApp?.emit('saved');
    }
    /**
     * LogicItem 有选择框的情况，为了减少变量变更，使用 replaceWith 的方式
     */
    async replaceWith(logicItem: LogicItem) {
        await this.delete(true);

        let _posIndex: number;
        if (this.parent) {
            const attr = this.parent[this.parentAttr as keyof LogicItem];
            if (Array.isArray(attr))
                _posIndex = attr.indexOf(this);
        }
        await logicItem.createOrMove({
            parent: this.parent,
            parentId: this.parentId,
            parentAttr: this.parentAttr,
            _posIndex,
            cache: true,
        });
        logicItem.assign({
            offsetX: this.offsetX,
            offsetY: this.offsetY,
        });
        this.assign(logicItem);
        this.logic && this.logic.emit('change');
        return this;
    }
    /**
     * 修改逻辑项
     */
    async update(source?: LogicItem, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving');

        if (source) {
            // 处理子节点
            if (Object.values(source).some((value) => typeof value === 'object')) {
                source = LogicItem.from(source, this.logic, this.parent);
                utils.clearObject(source);
            }
            this.assign(source);
        }
        // const body = this.toPlainJSON();
        const body = this.toJSON();

        body.params = (this as any).params && JSON.parse(JSON.stringify((this as any).params));
        body.querySchemaList = (this as any).querySchemaList && Array.from((this as any).querySchemaList);
        body.returnSchema = (this as any).returnSchema;

        if (this.parentAttr === 'item')
            body.schema = (this as any).schema;

        if (this.type === 'TypeNote')
            body.schema = (this as any).schema;

        LogicItem.preprocess(body);

        utils.logger.debug('修改逻辑项', body);
        if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) {
            const result = await logicService.updateItem({
                headers: {
                    appId: config.defaultApp?.id,
                    operationAction: 'LogicItem.update',
                    operationDesc: `修改逻辑项"${this.label || this.type}"`,
                    operationIgnore: actionOptions?.actionIgnore,
                },
                body,
            });
            // this.deepPick(result, ['id', 'parentId', 'parentAttr']);

            // 合并params里的id
            if (body.params && body.params.length) {
                const logicItem = LogicItem.from(result, this.logic, this.parent);
                this.assign({ params: (logicItem as any).params });
            }
            if (body.builtInFuncParams && body.builtInFuncParams.length) {
                const logicItem = LogicItem.from(result, this.logic, this.parent);
                this.assign({ builtInFuncParams: (logicItem as any).builtInFuncParams });
            }
        }

        // 处理 each 的问题
        if (this.parentAttr === 'each') {
            const each = this;
            const item: any = (this.parent as LogicNode).item;
            let eachSchema = getSchemaOfExpressionNode(each);
            if (eachSchema) {
                eachSchema = eachSchema.schema || eachSchema;
                // eachSchema = convert2SchemaType(eachSchema);
                (item as any).schema = eachSchema.items; // 兼容老的items形式
                if (eachSchema.type === 'genericType') {
                    (item as any).schema = eachSchema.typeInstantiation.typeParams[0].typeParamValue;
                }
                if (!(item as any).schema) {
                    (item as any).schema = eachSchema;
                }
                return item.update(undefined, {
                    actionIgnore: true,
                });
            }
        }

        this.emitVertexIdToNameChange();

        this.logic && this.logic.emit('change');
        this.checkType();

        await config.defaultApp?.history.load(actionOptions?.actionMode !== ACTION_MODE.undoRedo && {
            operationAction: 'LogicItem.update',
            operationBeforeImage: null, // JSON.parse(JSON.stringify(this)),
            operationAfterImage: null,
            operationDesc: `修改逻辑项"${this.label || this.type}"`,
        });
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 设置逻辑项标题
     * @param label 标题
     */
    @action('设置逻辑项标题')
    async setLabel(label: string) {
        this.assign({ label });
        await this.update(undefined, {
            actionDesc: '设置逻辑项标题',
        });
    }
    /**
     * 从后端 JSON 生成规范的 ExpressionNode 对象
     * @param source JSON
     * @param logic 父级 logic
     */
    public static from(source: any, logic: Logic, parent: LogicItem, shouldCreateNewItem = true) {
        if (source.level === LEVEL_ENUM.variable && source.type === LOGIC_TYPE.Identifier)
            source.code = `ID_${source.id}`;

        let logicItem: LogicItem;
        if (!shouldCreateNewItem && (source instanceof LogicItem))
            logicItem = source;
        else
            logicItem = source.level === 'logicNode' ? new LogicNode(source) : new ExpressionNode(source);

        logicItem.assign({
            logic,
            parent,
        });

        if (logicItem.type === LOGIC_TYPE.CallGraphQL) {
            const querySchemaList = (logicItem as any).querySchemaList;
            logicItem.assign({ querySchemaList: querySchemaList && typeof querySchemaList === 'string' ? JSON.parse(querySchemaList || '[]') : querySchemaList });
        }

        if (logicItem.type === LOGIC_TYPE.CallQueryComponent) {
            logicItem.assign({
                logicName: logic.name,
                serviceId: logic.interface?.serviceId,
            });
        }
        if (['QueryFromExpression', 'QueryJoinExpression', 'QueryAggregateExpression'].includes(logicItem.type)) {
            logicItem.assign({
                serviceId: logic?.interface?.serviceId,
            });
        }

        logicItemKeyOfLogicItem.forEach((key) => {
            if (source[key]) {
                if (source[key].level === LEVEL_ENUM.logicNode)
                    logicItem.assign({ [key]: LogicNode.from(source[key], logic, logicItem) });
                else if (source[key].level === LEVEL_ENUM.expressionNode || source[key].level === LEVEL_ENUM.variable)
                    logicItem.assign({ [key]: ExpressionNode.from(source[key], logic, logicItem) });
                else if (!Array.isArray(source[key]) && !Object.keys(source[key]).length)
                    logicItem.assign({ [key]: null });
            }
        });

        logicItemArrayKeyOfLogicItem.forEach((key) => {
            if (Array.isArray(source[key]))
                logicItem.assign({ [key]: source[key].map((item: any) => LogicItem.from(item, logic, logicItem)) });
        });

        if ((logicItem as any).callInterParam) {
            logicItem.assign({ refTarget: LogicItem.getVertexByRef((logicItem as any).callInterParam) });
        } else if ((logicItem as any).interfaceKey) {
            logicItem.assign({ refTarget: LogicItem.getVertexByRef((logicItem as any).interfaceKey) });
        }

        return logicItem;
    }
    /**
     * 校验类型
     */
    async checkType() {
        let crt: LogicItem = this;
        const nodes = [];
        do {
            if (
                !['builtInFuncParams'].includes(crt.parentAttr)
                && !(crt instanceof ExpressionNode && crt.parent instanceof LogicNode)
                && (crt instanceof ExpressionNode || !crt.parent)
            )
                nodes.push(crt);

            // CallQueryComponent 内部的节点不用checkType
            if (crt.parent?.type === LOGIC_TYPE.CallQueryComponent)
                nodes.splice(0, nodes.length);

            crt = crt.parent;
        } while (crt instanceof LogicItem);

        for (const node of nodes) {
            await this._checkType(node);
        }
    }

    private async _checkType(node: LogicItem) {
        const res = await logicService.checkType({
            query: {
                logicId: this.logic && this.logic.id,
                loItemId: node.id,
            },
        });
        if (res) {
            res.logicId = this.logic && this.logic.id;
            typeCheck.pushAll([res]);

            LogicItem.assignTypeCheckResult(node, res);
        }

        return res;
    }

    static assignTypeCheckResult(logicItem: LogicItem | LogicItem[], typeCheckResult: any) {
        if (Array.isArray(logicItem)) {
            const map = new Map();
            for (const item of typeCheckResult) {
                map.set(item.id, item);
            }
            for (let i = 0; i < logicItem.length; i++) {
                LogicItem.assignTypeCheckResult(logicItem[i], map.get(logicItem[i].id));
            }
            return;
        }

        if (!typeCheckResult || !logicItem || typeof typeCheckResult !== 'object')
            return;

        // if(logicItem.id === typeCheckResult.id)
        logicItem.typeCheckNote = typeCheckResult.typeCheckNote;

        for (const [key, object] of Object.entries(typeCheckResult)) {
            if (['id', 'typeCheckNote', 'processComponentId', 'attributes'].includes(key))
                continue;

            LogicItem.assignTypeCheckResult((logicItem as any)[key], object);
        }
    }

    /**
     * 生成 JS 脚本
     */
    toScript() { return ''; }

    getLogicItem(type: LOGIC_TYPE) {
        let node: LogicItem = this;
        while (node) {
            if (node.type === LOGIC_TYPE[type])
                return node;

            node = node.parent;
        }
        return undefined;
    }

    getSelectedTabOfCallQueryComponent(activeImage: any) {
        const map: { [key: string]: string } = {
            where: 'where',
            from: 'from',
            groupBy: 'groupBy',
            select: 'groupBy',
            having: 'groupBy',
            orderBy: 'orderBy',
            limit: 'orderBy',
        };

        let node: LogicItem = this;
        while (node) {
            if (node.parent?.type === LOGIC_TYPE.CallQueryComponent) {
                const parentAttr = node.parentAttr;
                return {
                    tab: map[parentAttr],
                    parentAttr,
                };
            }

            node = node.parent;
        }

        if (activeImage?.parentAttr && map[activeImage.parentAttr])
            return {
                tab: map[activeImage.parentAttr],
                parentAttr: activeImage.parentAttr,
            };
    }
}

export class LogicNode extends LogicItem {
    /**
     * 逻辑节点类型
     */
    @immutable()
    public readonly type: LOGIC_TYPE = undefined;
    @immutable()
    public readonly test: ExpressionNode = undefined;
    @immutable()
    public readonly consequent: Array<LogicNode> = undefined;
    public readonly alternate: Array<LogicNode> = undefined;
    public readonly cases: Array<LogicNode> = undefined;
    @immutable()
    public readonly each: ExpressionNode = undefined;
    @immutable()
    public readonly item: ExpressionNode = undefined;
    @immutable()
    public readonly index: ExpressionNode = undefined;
    @immutable()
    public readonly start: ExpressionNode = undefined;
    @immutable()
    public readonly end: ExpressionNode = undefined;
    @immutable()
    public readonly body: Array<LogicNode> = undefined;
    @immutable()
    public readonly operator: string = undefined;
    @immutable()
    public readonly left: LogicItem = undefined;
    @immutable()
    public readonly right: LogicItem = undefined;
    @immutable()
    public readonly arguments: Array<ExpressionNode> = undefined;
    @immutable()
    public readonly params: Array<ExpressionNode> = undefined;
    @immutable()
    public readonly value: string = undefined;
    @immutable()
    public readonly method: string = undefined;
    @immutable()
    public readonly entity: string = undefined;
    @immutable()
    public readonly action: string = undefined;
    @immutable()
    public readonly processDefinitionKey: string = undefined;
    @immutable()
    public readonly taskId: string = undefined;
    @immutable()
    public readonly finished: string = undefined;
    @immutable()
    public readonly processInstanceId: string = undefined;
    @immutable()
    public readonly schemaRef: string = undefined;
    @immutable()
    public readonly returnSchema: Schema = undefined;
    @immutable()
    public readonly cron: string = undefined;
    @immutable()
    public readonly page: string = undefined;
    @immutable()
    public readonly url: string = undefined;
    @immutable()
    public readonly code: string = undefined;
    @immutable()
    public readonly calleeCode: string = undefined;
    @immutable()
    public readonly interfaceKey: string = undefined;
    @immutable()
    public readonly name: string = undefined;
    @immutable()
    public readonly from: ExpressionNode = undefined;
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<LogicNode>) {
        super();

        const hasInstantiated = source && source.id && vertexsMap.get(source.id) instanceof LogicNode;
        if (hasInstantiated) {
            console.warn(`LogicNode (${source.id}) 多次实例化`);
            // Object.assign(vertexsMap.get(source.id), source);
            // return vertexsMap.get(source.id) as LogicNode;
        }

        source && this.assign(source);
    }
}

export class ExpressionNode extends LogicItem {
    /**
     * 逻辑节点类型
     */
    @immutable()
    public readonly type: LOGIC_TYPE = undefined;
    @immutable()
    public readonly left: LogicItem = undefined;
    @immutable()
    public readonly right: LogicItem = undefined;
    @immutable()
    public readonly code: string = undefined;
    @immutable()
    public readonly name: string = undefined;
    @immutable()
    public readonly key: ExpressionNode = undefined;
    @immutable()
    public readonly value: string | ExpressionNode = undefined;
    @immutable()
    public readonly object: ExpressionNode = undefined;
    @immutable()
    public readonly property: ExpressionNode = undefined;
    @immutable()
    public readonly operator: string = undefined;
    @immutable()
    public readonly properties: Array<ExpressionNode> = undefined;
    @immutable()
    public readonly elements: Array<ExpressionNode> = undefined;
    @immutable()
    public readonly callee: ExpressionNode = undefined;
    @immutable()
    public readonly argument: ExpressionNode = undefined;
    /**
     * SchemaRef
     */
    @immutable()
    public readonly schemaRef: string = undefined;
    @immutable()
    public readonly joinType: 'INNER' | 'LEFT' | 'RIGHT' | 'FULL' = undefined;
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<ExpressionNode>) {
        super();
        source && this.assign(source);
    }
}
