// import stringify = require('json-stringify-safe');
import { action, circular, excludedInJSON, immutable } from '../decorators';
import { config, history, utils, LEVEL_ENUM, Vertex, PackageJSON, App, Page, DataNode, View, Attr, Directive, Event, ExpressionNode, Logic, dataTypesMap, Entity, LogicItem, ActionOptions, ACTION_MODE, Nuims, Structure } from '..';
import * as compiler from 'vue-template-compiler';
import * as json5 from 'json5';
import { elementService, attributeService, eventService, directiveService } from '../../service/page';
import { vertexsMap } from '../cacheData';
import Variable from '../logic/Variable';
import * as babelParser from '@babel/parser';
import { traverse } from '../utils';
import { isPlainObject, cloneDeep } from 'lodash';

export interface ElementToVueOptions {
    indentStyle?: 'space' | 'tab';
    tabSize?: number;
    indentLevel?: number;
    aslIdAttr?: string | boolean;
    nodePathAttr?: string | boolean;
    getExtraParts?: (element?: Element) => Array<string>;
    attrFormat?: (attr: Attr | Event | Directive, element?: Element, defaultResult?: string) => string;
    finalCode?: boolean,
}

export interface ParseContext {
    variables?: Array<Variable>;
    structures?: Array<Structure>;
    dataSchema?: string;
    [key: string]: any;
}

/**
 * 前端页面元素
 * @example
 * <u-input size="small" v-model="value" @change="onChange"></u-input>
 */
export class Element extends Vertex {
    /**
     * 概念类型
     */
    @immutable()
    public readonly level: LEVEL_ENUM = LEVEL_ENUM.element;
    /**
     * 元素类型
     * 去除了 expression 和 text，目前只有一种节点类型
     */
    @immutable()
    // public readonly type: 'element' = 'element';
    public readonly type: number = 0;
    /**
     * 元素 Id
     */
    @immutable()
    public readonly id: string = undefined;
    /**
     * 元素标签
     */
    @immutable()
    public readonly tag: string = undefined;
    /**
     * 元素名称
     * 用户可以自定义
     * Vue 中的 ref
     */
    @immutable()
    public readonly name: string = undefined;
    /**
     * 属性列表
     * 和原来的 attrsList 不同，注意区分
     */
    @immutable()
    public readonly attrList?: Array<Attr> = [];
    /**
     * 事件列表
     */
    @immutable()
    public readonly eventList?: Array<Event> = [];
    /**
     * 指令列表
     */
    @immutable()
    public readonly directiveList?: Array<Directive> = [];
    /**
     * 插槽目标
     * @example
     * 'cell'
     */
    @immutable()
    public readonly slotTarget?: string = undefined;
    /**
     * 插槽 scope 表达式
     * @example
     * 'scope'
     */
    @immutable()
    public readonly slotScope?: string = undefined;
    /**
     * 静态 class 名
     * 不计划支持动态 class
     */
    @immutable()
    public readonly staticClass?: string = undefined;
    /**
     * 静态 style
     * 不计划支持动态样式
     */
    @immutable()
    public readonly staticStyle?: string = undefined;
    /**
     * 当前节点路径，只在前端动态计算后使用
     */
    @excludedInJSON()
    public nodePath: string = undefined;
    /**
     * 所在父元素 Id
     */
    @immutable()
    public readonly parentId: string = undefined;
    /**
     * 所在位置
     */
    @immutable()
    public _posIndex: number = undefined;
    /**
     * 所在父元素
     */
    @circular()
    @immutable()
    public readonly parent?: Element = undefined;
    /**
     * 所在父元素 Id
     */
    @immutable()
    public readonly viewId: string = undefined;
    /**
     * 所在子页面
     */
    @circular()
    @immutable()
    public readonly view?: View = undefined;
    /**
     * 子元素
     */
    @immutable()
    public readonly children?: Array<Element> = [];
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<Element>) {
        super();
        source && this.assign(source);
        // 为了给生成的节点使用
        // !this.id && this.assign({ id: uuidv4() });
        // this.assign({ id: 'temp-' + uuidv4() });
    }
    assign(source?: any) {
        ['attrList', 'eventList', 'directiveList', 'children'].forEach((key) => {
            if (source[key] === null)
                delete source[key];
        });
        super.assign(source);
    }
    /**
     * 添加元素
     */
    @action('添加元素')
    async create(none?: void, actionOptions?: ActionOptions) {
        if (actionOptions?.actionMode === ACTION_MODE.local) {
            this.view && this.view.emit('local-change');
            return this;
        }

        config.defaultApp?.emit('saving');
        try {
            if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) {
                const body = this.toJSON();
                body.parentId = this.parent && this.parent.id;
                body._posIndex = this.parent && this.parent.children.indexOf(this);
                utils.logger.debug('添加元素', body);
                const result = await elementService.create({
                    headers: {
                        appId: config.defaultApp?.id,
                        operationAction: 'Element.create',
                        operationDesc: `添加组件"${this.getElementTitle()}"`,
                    },
                    body,
                });

                // Nuims createResource
                // Element 对应资源增删在 undo/redo 情况下可能存在权限丢失的情况
                // 暂时只增不减
                // new Nuims({
                //     domainName: this.view.page.service.app.name,
                //     element: this,
                // }).createResource();

                this.deepPick(result, ['id', 'parentId', 'elementId']);
                // attr,directive,event的id,exprssion需要合并
                this.attrList.forEach((attr, index) => {
                    attr.deepPick(result.attrList[index], ['id', 'expression']);
                });
                this.directiveList.forEach((directive, index) => {
                    directive.deepPick(result.directiveList[index], ['id', 'expression']);
                });
                this.eventList.forEach((event, index) => {
                    event.deepPick(result.eventList[index], ['id']);
                });
            }

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

            if (actionOptions?.loadHistory !== false) {
                await config.defaultApp?.history.load(actionOptions?.actionMode !== ACTION_MODE.undoRedo && {
                    operationAction: 'Element.create',
                    operationBeforeImage: null,
                    operationAfterImage: JSON.parse(JSON.stringify(this)),
                    operationDesc: `添加组件"${this.getElementTitle()}"`,
                });
            config.defaultApp?.emit('saved');
            }
            return this;
        } catch (err) {
            if (this.view) {
                await this.view.load();
                this.view.emit('change');
                config.defaultApp?.emit('saved');
            }
            throw err;
        }
    }
    /**
     * 删除元素
     */
    @action('删除元素')
    async delete(none?: void, actionOptions?: ActionOptions) {
        if (actionOptions?.actionMode === ACTION_MODE.local) {
            const index = this.parent.children.indexOf(this);
            ~index && this.parent.children.splice(index, 1);
            this.view && this.view.emit('local-change');
            return this;
        }

        config.defaultApp?.emit('saving');

        if (!this.parent)
            throw Error('该元素为根节点！');

        if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) {
            if (this.id) {
                try {
                    await elementService.delete({
                        headers: {
                            appId: config.defaultApp?.id,
                            operationAction: 'Element.delete',
                            operationDesc: `删除组件"${this.getElementTitle()}"`,
                        },
                        query: {
                            id: this.id,
                        },
                    });
                } catch (err) {
                    await config.defaultApp?.history.load();
                    throw err;
                }

                // Nuims deleteResource
                // Element 对应资源增删在 undo/redo 情况下可能存在权限丢失的情况
                // 暂时只增不减
                // new Nuims({
                //     domainName: this.view.page.service.app.name,
                //     element: this,
                // }).deleteResources();
            }
        }

        const index = this.parent.children.indexOf(this);
        ~index && this.parent.children.splice(index, 1);
        this.destroy();
        this.view && this.view.emit('change');
        if (actionOptions?.loadHistory !== false) {
            await config.defaultApp?.history.load(actionOptions?.actionMode !== ACTION_MODE.undoRedo && {
                operationAction: 'Element.delete',
                operationBeforeImage: JSON.parse(JSON.stringify(this)),
                operationAfterImage: null,
                operationDesc: `删除组件"${this.getElementTitle()}"`,
            });
            config.defaultApp?.emit('saved');
        }
    }
    /**
      * 修改元素
      */
    async update(source?: Element, actionOptions?: ActionOptions) {
            config.defaultApp?.emit('saving');

            source && this.assign(source);
            try {
                if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) {
                    const body = this.toPlainJSON();
                    utils.logger.debug('修改组件', body);
                    await elementService.update({
                        headers: {
                            appId: config.defaultApp?.id,
                            operationAction: actionOptions?.actionName || 'Element.update',
                            operationDesc: actionOptions?.actionDesc || `修改组件"${this.getElementTitle()}"`,
                        },
                        body,
                    });
                }

                this.view && this.view.emit('change');
                await config.defaultApp?.history.load(actionOptions?.actionMode !== ACTION_MODE.undoRedo && {
                    operationAction: 'Element.update',
                    operationBeforeImage: null, // JSON.parse(JSON.stringify(this)),
                    operationAfterImage: null,
                    operationDesc: `修改组件"${this.getElementTitle()}"`,
                });

                config.defaultApp?.emit('saved');
                return this;
            } catch (err) {
                if (this.view) {
                    await this.view.load();
                    this.view.emit('change');
                    config.defaultApp?.emit('saved');
                }
                throw err;
            }
    }
    async move(options: {
        parentId?: string,
        _posIndex?: number,
        nodePath?: string,
        position?: 'append' | 'insertBefore' | 'insertAfter',
    }, actionOptions?: ActionOptions) {
        try {
            if (!options.parentId)
                return;

            config.defaultApp?.emit('element.moving');

            const index = this.parent.children.indexOf(this);
            ~index && this.parent.children.splice(index, 1);

            const parent = vertexsMap.get(options.parentId) as Element;
            parent.children.splice(options._posIndex, 0, this);

            this.assign({
                parent,
                parentId: options.parentId,
                _posIndex: options._posIndex,
            });

            if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) {
                await this.update(undefined, {
                    actionName: 'Element.move',
                    actionDesc: `移动组件"${this.getElementTitle()}"`,
                });
                this.view.attachNodePath();
            } else {
                this.view.attachNodePath();
                this.view && this.view.emit('change');
                await config.defaultApp?.history.load();
            }

            config.defaultApp?.emit('element.moved');
        } catch (err) {
            if (this.view) {
                await this.view.load();
                this.view.emit('change');
            }
            throw err;
        }
    }
    /**
     * 添加元素副本
     * 在所在父节点的后面添加一个相同的元素
     */
    @action('添加元素副本')
    async duplicate() {
        const code = this.toVue();

        const index = this.parent.children.indexOf(this);
        const newNode = Element.fromHTML(code, this.parent, this.view);
        const mergeExpression = (originList: Array<Attr|Directive>, newList: Array<Attr|Directive>) => {
            newList.forEach((item) => {
                const originItem = originList.find((originItem) => originItem.name === item.name);
                if (originItem && originItem.expression) {
                    // item.expression可能为空
                    const expression = cloneDeep(originItem.expression);
                    Object.assign(item, { expression, value: '' });
                    Object.assign(item.expression, { id: '' });
                    utils.traverse((current) => {
                        delete current.node.editable;
                        delete current.node.index;
                        delete current.node.parentAttr;
                        delete current.node.parentId;
                        delete current.node.id;
                    }, { node: item.expression as any }, { mode: 'anyObject' });
                }
            });
        };

        // 遍历合并
        const traverseMergeNode = (origiNode:Element, newNode:Element) => {
            const originalStack: Array<Element> = [];
            const newStack: Array<Element> = [];
            originalStack.push(origiNode);
            newStack.push(newNode);
            let originalTempNode: Element;
            let newTempNode: Element;
            while (originalStack.length) {
                originalTempNode = originalStack.pop();
                newTempNode = newStack.pop();
                if (newTempNode) {
                    mergeExpression(originalTempNode.attrList, newTempNode.attrList);
                    mergeExpression(originalTempNode.directiveList, newTempNode.directiveList);
                    newTempNode.eventList.forEach((event) => {
                        const originEvent = originalTempNode.eventList.find((eventTemp:Event) => eventTemp.name === event.name);
                        if (originEvent) {
                            Object.assign(event, {
                                logicId: originEvent.logicId,
                            });
                        }
                    });
                    if (originalTempNode.children.length) {
                        for (let i = originalTempNode.children.length - 1; i >= 0; i--) {
                            originalStack.push(originalTempNode.children[i]);
                            newStack.push(newTempNode.children[i]);
                        }
                    }
                }
            }
        };

        traverseMergeNode(this, newNode);
        ~index && this.parent.children.splice(index + 1, 0, newNode);
        this.parent.children.forEach((item, index) => {
            item._posIndex = index;
        });

        try {
            await newNode.create();
        } catch {
            ~index && this.parent.children.splice(index + 1, 1);
            this.parent.children.forEach((item, index) => {
                item._posIndex = index;
            });
        }
    }
    /**
     * 添加子元素
     * @param child
     */
    addChild(child: string | Element, actionOptions?: ActionOptions) {
        if (typeof child === 'string') {
            child = Element.fromHTML(child, this, this.view);
        }
        if (isPlainObject(child)) {
            child = Element.from(child, this, this.view);
        }
        if (!this.children.includes(child)) {
            const index = child._posIndex === undefined ? this.children.length : child._posIndex;
            this.children.splice(index, 0, child);
        }
        return child.create(undefined, actionOptions);
    }
    /**
     * 删除子元素
     * @param child
     */
    removeChild(child: Element, actionOptions?: ActionOptions) {
        if (isPlainObject(child)) {
            child = this.children.find((item) => item.id === child.id);
        }
        return child.delete(undefined, actionOptions);
    }
    getElementTitle() {
        const currentNode = config.allNodesAPI?.[this.tag];
        return currentNode?.title || this.tag;
    }
    /**
     * 设置组件名称
     */
    @action('设置组件名称')
    async setName(name: string) {
        const oldName = this.name;
        this.assign({ name });
        await this.update(undefined, {
            actionDesc: `设置组件"${this.getElementTitle()}"的名称为"${name}"`,
        });
        // 同步权限
        new Nuims({
            domainName: this.view.page.service.app.name,
            element: this,
        }).editResourceFromResourceValue(oldName ? `${this.view.tempPath}/${oldName}` : null);

        this.view && this.view.emit('change');
        if (this.view && this.view.page && this.view.page.service) {
            this.view.page.service.emit('vertexIdToNameChange', this.id, this.name);
        }
    }
    /**
     * 添加组件属性
     */
    @action('添加组件属性')
    async addAttr(attr: Attr, actionOptions?: ActionOptions) {
        if (isPlainObject(attr)) {
            attr = Attr.from(attr, this);
        }
        if (!this.attrList.includes(attr))
            this.attrList.push(attr);

        await attr.create(undefined, Object.assign({
            actionDesc: `添加组件"${this.getElementTitle()}"属性"${attr.name}"`,
        }, actionOptions));
    }
    /**
     * 删除组件属性
     */
    @action('删除组件属性')
    async deleteAttr(attr: Attr, actionOptions?: ActionOptions) {
        if (!attr)
            throw Error(`该组件"${this.getElementTitle()}"没有属性"${attr.name}"`);
        if (isPlainObject(attr))
            attr = this.attrList.find((item) => item.id === attr.id);

        await attr.delete(undefined, Object.assign({
            actionDesc: `删除组件"${this.getElementTitle()}"属性"${attr.name}"`,
        }, actionOptions));
    }
    /**
     * 获取组件属性
     */
    getAttr(name: string) {
        return this.attrList.find((attr) => attr.name === name);
    }
    /**
     * 设置组件属性
     */
    @action('设置组件属性')
    async setAttr(name: string, type: 'string' | 'static' | 'dynamic', value?: any) {
        let attr = this.getAttr(name);
        //
        if (value === undefined) {

        } else {
            if (typeof value !== 'string')
                value = JSON.stringify(value);

            if (attr) {
                attr.assign({
                    type,
                    value,
                });
                await attr.update(undefined, {
                    actionDesc: `设置组件"${this.getElementTitle()}"属性"${attr.name}"的值为"${attr.value}"`,
                });
            } else {
                attr = Attr.from({
                    name,
                    type,
                    value,
                    elementId: this.id,
                }, this);
                await attr.create(undefined, {
                    actionDesc: `设置组件"${this.getElementTitle()}"属性"${attr.name}"的值为"${attr.value}"`,
                });
                this.attrList.push(attr);
            }
        }

        this.view && this.view.emit('change');
    }
    /**
     * 添加组件事件
     */
    @action('添加组件事件')
    async addEvent(data: Event, actionOptions?: ActionOptions) {
        // (data as any).element = this;
        let event = new Event(data);

        await event.create(undefined, Object.assign({
            actionDesc: `添加组件"${this.getElementTitle()}"事件"${event.name}"`,
        }, actionOptions));
        event = Event.from(event, this);
        this.eventList.push(event);

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

        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');

        return event;
    }
    /**
     * 删除组件属性
     */
    @action('删除组件事件')
    async deleteEvent(event: Event, actionOptions?: ActionOptions) {
        if (!event)
            throw Error(`该组件"${this.getElementTitle()}"没有事件"${event.name}"`);
        if (isPlainObject(event))
            event = this.eventList.find((item) => item.id === event.id);

        await event.delete(undefined, Object.assign({
            actionDesc: `删除组件"${this.getElementTitle()}"事件"${event.name}"`,
        }, actionOptions));
        const index = this.eventList.indexOf(event);
        ~index && this.eventList.splice(index, 1);

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

        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
    }
    /**
     * 添加组件指令
     */
    @action('添加组件指令')
    async addDirective(data: Directive, actionOptions?: ActionOptions) {
        // (data as any).element = this;
        const directive = new Directive(data);
        await directive.create(undefined, Object.assign({
            actionDesc: `添加组件"${this.getElementTitle()}"指令"${directive.name}"`,
        }, actionOptions));
        this.directiveList.push(Directive.from(directive, this));

        this.view && this.view.emit('change');
    }
    /**
     * 删除组件属性
     */
    @action('删除组件指令')
    async deleteDirective(directive: Directive, actionOptions?: ActionOptions) {
        if (!directive)
            throw Error(`该组件"${this.getElementTitle()}"没有指令"${directive.name}"`);
        if (isPlainObject(directive))
            directive = this.directiveList.find((item) => item.id === directive.id);

        await directive.delete(undefined, Object.assign({
            actionDesc: `删除组件"${this.getElementTitle()}"属性"${directive.name}"`,
        }, actionOptions));
        const index = this.directiveList.indexOf(directive);
        ~index && this.directiveList.splice(index, 1);

        this.view && this.view.emit('change');
    }
    /**
     * 转换成 Vue 的模板格式
     */
    toVue(options?: ElementToVueOptions) {
        options = Object.assign({
            indentStyle: 'space',
            tabSize: 4,
            indentLevel: 0,
            aslIdAttr: false,
            nodePathAttr: false,
            getExtraParts: () => [],
            attrFormat: (attr: Attr | Event | Directive, element?: Element, defaultResult?: string) => defaultResult,
        }, options);
        const tabString = ' '.repeat(options.tabSize * options.indentLevel);
        const insideTabString = ' '.repeat(options.tabSize * (options.indentLevel + 1));
        let shouldIndent = true;

        const content: string = !this.children ? '' : this.children.map((element) => {
            const childOptions = Object.assign({}, options);
            childOptions.indentLevel++;
            return (shouldIndent ? '\n' + insideTabString : '') + element.toVue(childOptions);
        }).join('');
        if (!content.length)
            shouldIndent = false;

        const parts = [];
        if (options.aslIdAttr) {
            if (options.aslIdAttr === true)
                options.aslIdAttr = 'asl-id';
            parts.push(`${options.aslIdAttr}="${this.id}"`);
        }
        if (options.nodePathAttr) {
            // 注入 asl 的 node-path
            parts.push(`vusion-node-path="${this.nodePath}"`);
        }

        this.slotTarget && parts.push(`#${this.slotTarget}` + (this.slotScope ? `="${this.slotScope}"` : ''));
        this.name && parts.push(`ref="${this.name}"`);
        this.staticClass && parts.push(`class="${this.staticClass}"`);
        this.staticStyle && parts.push(`style="${this.staticStyle}"`);

        const { finalCode } = options;
        [].concat(this.attrList, this.directiveList, this.eventList)
            .forEach((attr: Attr | Directive | Event) => {
                const result = options.attrFormat(attr, this, attr.toVue(this, finalCode));
                result && parts.push(result);
            });

        options.getExtraParts(this).forEach((part) => parts.push(part));

        let partsLength = 0;
        let partsString = '';
        parts.forEach((part) => {
            if (partsLength >= 120 || part.length >= 120) {
                partsString += '\n' + tabString + ' '.repeat(3); // ' '.repeat(el.tag.length + 1);
                partsLength = 0;
            }
            partsString += ' ' + part;
            partsLength += part.length;
        });

        return `<${this.tag}${partsString.length ? partsString : ''}>` + content + (shouldIndent ? '\n' + tabString : '') + `</${this.tag}>`;
    }
    /**
     * 转换成设计器中使用的 Vue 文件
     * @param options
     */
    toDesignerVue(options?: ElementToVueOptions) {
        options = Object.assign({
            nodePathAttr: true,
            attrFormat: (attr: Attr | Directive | Event, el: Element, defaultResult?: string) => {
                if (attr.level === LEVEL_ENUM.event || attr.level === LEVEL_ENUM.directive)
                    return false;
                if (attr.level === LEVEL_ENUM.attr) {
                    const api = config.allNodesAPI[el.tag];
                    const apiOfAttr = api && api.attrs && api.attrs.find((_attr) => _attr.name === attr.name);
                    if (apiOfAttr && apiOfAttr['designer-value']) {
                        const designerValue = apiOfAttr['designer-value'].replace(/"/g, '\'');
                        try {
                            json5.parse(designerValue);
                            return `:${attr.name}="${designerValue}"`;
                        } catch (e) {
                            return `${attr.name}="${designerValue}"`;
                        }
                    }
                    if ((attr as Attr).type === 'dynamic'
                    && !(el.tag === 'u-cascade-select' && attr.name === 'categories'))
                        return defaultResult.replace(/:?(\w+)(?:.*?)="(.*)"/, (m, name, value) => {
                            try {
                                const tempValue = json5.parse(value);
                                if (typeof tempValue === 'boolean' || typeof tempValue === 'number') { // 简单类型走属性
                                    return `:${name}="${value.replace(/"/g, '\'')}"`;
                                }
                            } catch (e) {
                                if (apiOfAttr && !apiOfAttr.type.includes('string'))
                                    return '';
                            }
                            return `${name}="{{ ${value.replace(/"/g, '\'')} }}"`;
                        });
                    else
                        return defaultResult;
                }
                return false;
            },
            getExtraParts: (el: Element) => {
                const parts = [];

                const api = config.allNodesAPI[el.tag];
                const emptySlot = api && api.slots && api.slots.find((slot) => slot.name === 'default' && slot['empty-background']);
                if (!el.children.length && emptySlot) {
                    let addEmpty = true;
                    if (el.tag === 'u-grid-view'
                        || el.tag === 'u-list-view'
                        || el.tag === 'van-list-view') {
                        const hasDataSource = el.attrList.find((attr) => attr.name === 'data-source');
                        addEmpty = !hasDataSource;
                    }
                    addEmpty && parts.push(`vusion-empty-background="${emptySlot['empty-background']}"`);
                }

                return parts;
            },
        }, options);
        return this.toVue(options);
    }
    /**
     * 同 toVue 方法
     * @param options 缩进等选项
     */
    toHTML(options?: ElementToVueOptions) {
        return this.toVue(options);
    }
    /**
     * 根据标签查找元素
     * @param tag
     */
    findElementByTag(tag: string): Element {
        if (this.tag === tag)
            return this;
        else {
            for (const child of this.children) {
                const result = child.findElementByTag(tag);
                if (result)
                    return result;
            }
        }
    }
    /**
     * 根据属性查找元素
     * @param name
     * @param value
     */
    findElementByAttr(name: string, value: string): Element {
        if (this.attrList.find((attr) => attr.name === name && attr.value === value))
            return this;
        else {
            for (const child of this.children) {
                const result = child.findElementByAttr(name, value);
                if (result)
                    return result;
            }
        }
    }
    /**
     * 从 Vue 的 ASTNode 转换成 ASL 元素
     * @param astNode Vue 的 ASTNode
     */
    private static _fromASTNode(astNode: compiler.ASTElement, context?: ParseContext): Element {
        // 临时处理组件的 text
        //h5-mock
        if (['u-text', 'van-text', 'u-link', 'u-button', 'u-label', 'u-radio', 'u-checkbox', 'u-navbar-item', 'u-sidebar-item', 'u-menu-item'].includes(astNode.tag)
        && astNode.children.length === 1 && astNode.children[0].type === 3) {
            astNode.attrs = astNode.attrs || [];
            astNode.attrs.push({ name: 'text', value: JSON.stringify(astNode.children[0].text) });
            astNode.children = [];
        }

        // 将 scopedSlots 合并到 children 中
        if (astNode.scopedSlots) {
            astNode.children = astNode.children || [];
            Object.keys(astNode.scopedSlots).forEach((key) => {
                if (!astNode.children.find((child) => key === (child as compiler.ASTElement).slotTarget))
                    astNode.children.unshift(astNode.scopedSlots[key]);
            });
            delete astNode.scopedSlots;
        }

        // 提示不支持的字段
        [
            'component', 'inlineTemplate', 'pre', 'ns',
            'transition', 'transitionOnAppear', 'transitionMode',
            'slotName', 'classBinding', 'styleBinding',
        ].forEach((key) => {
            if ((astNode as any)[key])
                console.warn(`[warn] Element NASL unsupports '${key}' field in node `, astNode);
        });

        let element: Element;
        if (astNode.type === 1) {
            if (astNode.tag === 'router-view') {
                if (config.scope === 'h5') {
                    astNode.tag = 'van-router-view';
                } else {
                    astNode.tag = 'u-router-view';
                }
            }

            // if (astNode.tag === 'template')
            //     astNode.tag = 'div';
            element = new Element({
                tag: astNode.tag,
                name: astNode.attrsMap.ref,
                staticClass: astNode.attrsMap.class,
                staticStyle: astNode.attrsMap.style,
                slotTarget: astNode.slotTarget && json5.parse(astNode.slotTarget),
                slotScope: astNode.slotScope === '_empty_' ? '' : astNode.slotScope,
                children: astNode.children as unknown as Array<Element>,
            });

            astNode.attrs && astNode.attrs.forEach((oldAttr) => {
                let attr: Attr;
                if (oldAttr.value === '""' && (oldAttr as any).end - (oldAttr as any).start === oldAttr.name.length) {
                    attr = Attr.from({
                        type: 'static',
                        name: oldAttr.name,
                        value: 'true',
                    }, element);
                } else {
                    try {
                        const tmp = json5.parse(oldAttr.value);
                        attr = Attr.from({
                            type: typeof tmp === 'string' ? 'string' : 'static',
                            name: oldAttr.name,
                            value: typeof tmp === 'string' ? tmp : oldAttr.value,
                        }, element);
                    } catch (e) {
                        const expression = <ExpressionNode> this._parseExpression(oldAttr.value, context);
                        attr = Attr.from({
                            type: 'dynamic',
                            name: oldAttr.name,
                            value: expression ? '' : oldAttr.value,
                            expression,
                        }, element);
                        if (astNode.attrsMap[`:${attr.name}.sync`]) {
                            attr.assign({ sync: true });
                        }
                    }
                }
                element.attrList.push(attr);
                // element.attrMap[attr.name] = attr;
            });

            // compiler 处理:value.sync 时会加上update:value事件，需要过滤
            astNode.events && Object.keys(astNode.events).filter((name) => !name.startsWith('update:')).forEach((name) => {
                const oldEvent = astNode.events[name] as compiler.ASTElementHandler;
                const value = oldEvent.value.split('(')[0];
                element.eventList.push(Event.from({
                    name,
                    value,
                    logicId: value[0] === '$' ? undefined : value,
                }, element));
                // element.eventMap[name] = new Event({
                //     name,
                //     value: oldEvent.value,
                // });
            });

            astNode.directives && astNode.directives.forEach((directive) => {
                if (directive.name === 'model') {
                    const valueAttr = astNode.attrs && astNode.attrs.find((attr) => attr.name === 'value');
                    if (!valueAttr) {
                        const expression = <ExpressionNode> this._parseExpression(directive.value, context);
                        element.attrList.push(Attr.from({
                            type: 'dynamic',
                            name: 'value',
                            value: expression ? '' : directive.value,
                            expression,
                            sync: true,
                        }, element));
                    }
                } else {
                    const expression = <ExpressionNode> this._parseExpression(directive.value, context);
                    element.directiveList.push(Directive.from({
                        type: expression ? 'string' : 'dynamic',
                        name: directive.name,
                        rawName: directive.rawName,
                        value: expression ? '' : directive.value,
                        expression,
                        arg: directive.arg,
                        modifiers: directive.modifiers,
                    }, element));
                    // element.directiveMap[directive.name] = );
                }
            });

            if (astNode.if) {
                element.directiveList.push(Directive.from({
                    type: 'dynamic',
                    name: 'if',
                    rawName: 'v-if',
                    value: '',
                    expression: <ExpressionNode> this._parseExpression(astNode.if, context),
                }, element));
            }

            // Removed
            // hasBindings:
            // static:
            // staticRoot:

            // refInFor?: boolean;

            // if?: string;
            // ifProcessed?: boolean;
            // elseif?: string;
            // else?: true;
            // ifConditions?: ASTIfCondition[];

            // for?: string;
            // forProcessed?: boolean;
            // key?: string;
            // alias?: string;
            // iterator1?: string;
            // iterator2?: string;

            // events?: ASTElementHandlers;
            // nativeEvents?: ASTElementHandlers;

            // model?: {
            //     value: string;
            //     callback: string;
            //     expression: string;
            // };

            // directives?: ASTDirective[];
        } else if (astNode.type === 2) {
            if (config.scope === 'h5') {
                element = new Element({
                    tag: 'van-text',
                });
            } else {
                element = new Element({
                    tag: 'u-text',
                });
            }
            const value = astNode.text.match(/{{(.*?)}}/)[1].trim();
            const expression = <ExpressionNode> this._parseExpression(value, context);
            element.attrList.push(new Attr({
                type: 'dynamic',
                name: 'text',
                value: expression ? '' : value,
                expression,
                element,
            }));
        } else if (astNode.type === 3) {
            if (config.scope === 'h5') {
                element = new Element({
                    tag: 'van-text',
                });
            } else {
                element = new Element({
                    tag: 'u-text',
                });
            }

            element.attrList.push(new Attr({
                name: 'text',
                value: astNode.text,
                element,
            }));
        }

        return element;
    }

    /**
     * 解析属性中的表达式
     * @param value
     * @param $def 目前 $def 只用来查找 $def.variables 和 $def.structures
     * @param dataSchema
     */
    private static _parseExpression(value: string, context: ParseContext = {}) {
        try {
            let ast = babelParser.parseExpression(value) as any;

            utils.traverse((current) => {
                if (!current.node)
                    return;
                current.node.level = 'expressionNode';
                delete current.node.loc;
                delete current.node.start;
                delete current.node.end;
                delete current.node.errors;
                delete current.node.comments;
                delete current.node.innerComments;
                delete current.node.leadingComments;
                delete current.node.trailingComments;
                if (current.node.type === 'CallExpression' && current.node.callee.object.name === '$utils') {
                    const calleeCode = current.node.callee.property.name || current.node.callee.property.value;
                    if (calleeCode === 'Enum') {
                        const enumName = current.node.arguments[0] && current.node.arguments[0].value;
                        if (enumName) {
                            let enumNode: any;
                            Object.keys(dataTypesMap).forEach((dataTypeKey) => {
                                if (dataTypesMap[dataTypeKey].type === 'enum') {
                                    if (dataTypesMap[dataTypeKey].name === enumName) {
                                        enumNode = dataTypesMap[dataTypeKey];
                                    }
                                }
                            });
                            if (enumNode) {
                                const code = 'ID_' + enumNode.id;
                                current.node.arguments[0].value = code;
                                current.node.arguments[0].code = code;
                            }
                        }
                    }
                    Object.assign(current.node, {
                        type: 'BuiltInFunction',
                        value: calleeCode,
                        calleeCode,
                        label: '内置函数',
                        level: 'expressionNode',
                        builtInFuncParams: current.node.arguments.map((argument: any) => ({
                            level: 'expressionNode',
                            type: 'BuiltInFuncParam',
                            name: '',
                            builtInFuncParamValue: Object.assign({
                                level: 'expressionNode',
                            }, argument),
                        })),
                    });
                    delete current.node.arguments;
                    delete current.node.callee;
                } else if (current.node.type === 'MemberExpression') {
                    let object = current.node.object;
                    const properties = [current.node.property];
                    while (object.type === 'MemberExpression') {
                        properties.push(object.property);
                        object = object.object;
                    }
                    if (object.name === 'scope') {
                        let schema: any = {
                            $ref: '',
                        };
                        if (context.dataSchema) { // 可以直接用 dataSchema，处理 mergeBlock 的情况
                            const typeKey = '#/genericTypes/ScopeOf';
                            schema = {
                                type: 'genericType',
                                typeKey,
                                typeInstantiation: {
                                    typeName: 'ScopeOf',
                                    typeParams: [
                                        {
                                            typeParamName: 'T',
                                            typeParamValue:
                                            {
                                                $ref: context.dataSchema,
                                                typeKey: context.dataSchema,
                                            },

                                        },
                                    ],
                                },
                            };
                        }
                        Object.assign(object, { schema, code: object.name });
                    }
                    if (context && object.name) {
                        let variable = (context.variables || []).find((variable:Variable) => variable.name === object.name);
                        variable = variable || object;
                        if (variable) {
                            let schemaData = dataTypesMap[variable.schema && variable.schema.$ref];
                            if (variable.schema && variable.schema.type === 'genericType') {
                                const genericClass = dataTypesMap[variable.schema.typeKey];
                                let propertyList: any = [];
                                if (genericClass) {
                                    propertyList = cloneDeep(genericClass.propertyList);
                                    propertyList = propertyList.map((property: any) => {
                                        if (property.type === 'genericParam' && variable.schema.typeInstantiation) {
                                            const param = variable.schema.typeInstantiation.typeParams.find((typeParam: any) => typeParam.typeParamName === property.typeParamName);
                                            if (param) {
                                                property = Object.assign({ name: property.name }, param.typeParamValue);
                                            }
                                        }
                                        return property;
                                    });
                                }
                                schemaData = { propertyList };
                            }

                            // 这部分功能还不稳定，先单独处理一下
                            if (object.name === 'scope' && properties.length >= 3) {
                                const tempStructure = context.structures.find((structure: any) => context.dataSchema === structure.schemaRef || context.dataSchema === structure.name);
                                if (tempStructure && !tempStructure.id) {
                                    properties.pop(); // scope.item 那一项
                                    let property = properties.pop();
                                    property.schemaRef = tempStructure.name + '.' + property.name;

                                    const originProp = tempStructure.propertyList.find((tempProp) => tempProp.name === property.name);
                                    schemaData = originProp && dataTypesMap[originProp.$ref];
                                    property = properties.pop();
                                    while (property && schemaData) {
                                        if (schemaData.propertyList) {
                                            const originProp = schemaData.propertyList.find((tempProp) => tempProp.name === property.name);
                                            if (originProp) {
                                                if (originProp.id) {
                                                    Object.assign(property, {
                                                        schemaRef: '#/' + originProp.id,
                                                        code: `ID_${originProp.id}`,
                                                    });
                                                }
                                                schemaData = dataTypesMap[originProp.$ref];
                                            }
                                        }
                                        property = properties.pop();
                                    }
                                } else if (tempStructure && tempStructure.id && context.recordProperty) {
                                    properties.pop(); // scope.item 那一项
                                    let property = properties.pop();
                                    property.schemaRef = context.recordProperty.$ref;
                                    property.code = `ID_${context.recordProperty.id}`;

                                    const originProp = context.recordProperty;
                                    schemaData = originProp && dataTypesMap[originProp.$ref];
                                    property = properties.pop();
                                    while (property && schemaData) {
                                        if (schemaData.propertyList) {
                                            const originProp = schemaData.propertyList.find((tempProp) => tempProp.name === property.name);
                                            if (originProp) {
                                                if (originProp.id) {
                                                    Object.assign(property, {
                                                        schemaRef: `#/${originProp.id}`,
                                                        code: `ID_${originProp.id}`,
                                                    });
                                                }
                                                schemaData = dataTypesMap[originProp.$ref];
                                            }
                                        }
                                        property = properties.pop();
                                    }
                                }
                            } else {
                                let property = properties.pop();
                                while (property && schemaData) {
                                    if (schemaData.propertyList) {
                                        const originProp = schemaData.propertyList.find((tempProp) => tempProp.name === property.name);
                                        if (originProp) {
                                            if (originProp.id) {
                                                Object.assign(property, {
                                                    schemaRef: '#/' + originProp.id,
                                                });
                                            }
                                            schemaData = dataTypesMap[originProp.$ref];
                                        }
                                    }
                                    property = properties.pop();
                                }
                            }
                        }
                    }

                    const property = current.node.property;
                    if (property.type === 'StringLiteral') {
                        // 区分单引号的变量和纯文字
                        // $utils['FormatDate']()
                        // 把 'FormatDate' 变成 Identifier, 因为内置函数名是通过选择出来的；如果不改，会展示成输入框
                        if (property.extra.raw.includes("'")) {
                            property.type = 'Identifier';
                            property.name = ast.value;
                            property.level = 'expressionNode';
                        }
                    }
                } else if (current.node.type === 'Identifier') {
                    if (current.node.name && current.node.name.startsWith('ID_ENUM_LIST_')) {
                        current.node.schemaRef = current.node.name.replace('ID_ENUM_LIST_', '');
                        current.node.code = current.node.name;
                    }
                }
                delete current.node.extra;
            }, { node: ast }, { mode: 'anyObject' });

            if (ast) {
                let hasSchema = !!context.dataSchema;
                let object = ast;
                while (!hasSchema && object) {
                    if (object.schema || object.schemaRef || (object.property && (object.property.schema || object.property.schemaRef)))
                        hasSchema = true;
                    object = object.object;
                }

                if (ast.type === 'ArrayExpression' || ast.type === 'ObjectExpression'
                    || ast.type === 'MemberExpression' && !hasSchema) {
                    ast = {
                        label: '原子项',
                        level: 'expressionNode',
                        type: 'Unparsed',
                        editable: true,
                        code: value,
                    };
                }
            }

            return LogicItem.from(ast, null, null);
        } catch (e) {
            return null;
        }
    }

    /**
     * 解析 Vue 模板
     * 该方法不会绑定 view 和 parent，如果是添加元素优先使用 fromHTML
     * @param html Vue 的模板
     * @TODO 处理多个元素的问题
     */
    public static parse(html: string, context?: ParseContext) {
        html = html || '<div></div>';

        const compilerOptions: compiler.CompilerOptions = {
            preserveWhitespace: false,
            outputSourceRange: true,
        };

        let ast = compiler.compile(html, compilerOptions).ast;
        if (ast.tag === 'template' && !ast.slotTarget)
            ast = ast.children[0] as compiler.ASTElement;

        if (context && !context.dataSchema) {
            const cap = html.match(/data-schema="(.*?)"/);
            context.dataSchema = cap ? cap[1] : '';
        }
        let root: Element;
        utils.traverse((current) => {
            // 处理 scopedSlots;
            if (!current.parent)
                root = this._fromASTNode(current.node, context);
            else
                (current.parent.children as any)[current.index] = this._fromASTNode(current.node, context);
        }, { node: ast });

        return root;
    }
    /**
     * 从模板生成规范的 Element 对象
     */
    public static fromHTML(html: string, parent: Element, view: View, context?: ParseContext) {
        const element = this.parse(html, context);
        utils.traverse((current) => {
            current.node.assign({ view, parent: current.parent });
        }, { node: element, parent });
        return element;
    }
    /**
     * 从后端 JSON 生成规范的 Element 对象
     */
    public static from(source: any, parent: Element, view: View, currentElement?: Element) {
        const element = new Element(source);
        currentElement = currentElement || element;

        element.assign({
            attrList: element.attrList.map((attr) => Attr.from(attr, currentElement)),
            eventList: element.eventList.map((ret) => Event.from(ret, currentElement)),
            directiveList: element.directiveList.map((directive) => Directive.from(directive, currentElement)),
            children: element.children.map((child) => Element.from(child, currentElement, view)),
            view,
            parent,
        });

        return element;
    }
}

export default Element;
