import { immutable, circular, action, excludedInJSON } from '../decorators';
import { config, history, LEVEL_ENUM, Vertex, PackageJSON, App, Enum, Service, WebService, Structure, Page, Element, utils, ViewParam, ViewVariable, Lifecycle, genFinalCode, generator, Nuims, ActionOptions, ACTION_MODE, vertexsMap, updateDataTypeList, Interface } from '..';
import Block from './Block';
import { viewService } from '../../service/page';
import { Param } from '../logic/Param';
import Logic from '../logic/Logic';
import { logicService } from '../../service/logic';
import { blockService } from '../../service/assets';
import { TEMPLATE_MAP } from '../../service/page/templates';
import { ElementToVueOptions } from './Element';
import translator from '../logic/translator';
import { isPlainObject, throttle } from 'lodash';
import { traverse } from '../utils';
import lifecycle from '@/service/page/lifecycle';
import * as babel from '@babel/core';
import view from '@/service/page/view';
import Entity from '../data/Entity';
import { postServiceType } from '../../service/common/preprocess';

/**
 * 子页面类，前端路由控制
 */
export class View extends Block {
    /**
     * 概念类型
     */
    @immutable()
    public readonly level: LEVEL_ENUM = LEVEL_ENUM.view;
    /**
     * 子页面 Id
     */
    @immutable()
    public readonly id: string = undefined;
    /**
     * 子页面名称
     */
    @immutable()
    public readonly name: string = undefined;
    /**
     * 前端路径，不带 page 名
     */
    @immutable()
    public readonly path: string = undefined;
    /**
     * 前后端路径
     * 由于 name 会改的关系，尽量不要用
     * @deprecated
     */
    @immutable()
    public readonly tempPath: string = undefined;
    /**
     * 唯一标识码
     */
    @immutable()
    public readonly code: string = undefined;
    /**
     * 标题
     */
    @immutable()
    public readonly title: string = undefined;
    /**
     * 面包屑
     */
    @immutable()
    public readonly crumb: string = undefined;
    /**
     * 是否为默认跳转页
     */
    @immutable()
    public readonly first: string = undefined;
    /**
     * 子页面 Id
     */
    @immutable()
    public readonly parentId: string = undefined;
    /**
     * 附加的 script
     */
    @immutable()
    public readonly script: string = undefined;
    /**
     * 页面 Id
     */
    @immutable()
    public readonly pageId: string = undefined;
    /**
     * 页面模板 Id
     */
    @immutable()
    public readonly pageTemplateId: number = undefined;
    /**
     * 所属页面
     */
    @circular()
    @immutable()
    public readonly page: Page = undefined;
    /**
     * 父级子页面
     */
    @circular()
    @immutable()
    public readonly parent: View = undefined;
    /**
     * 子页面
     */
    @immutable()
    public readonly children: Array<View> = [];
    /**
     * 正在请求的 Promise
     * 前端 UI 状态
     */
    @excludedInJSON()
    public contentPromise: Promise<any> = undefined;
    /**
     * 输入参数
     */
    // @immutable()
    // public readonly params: Array<Param> = [];
    /**
     * 逻辑列表
     */
    // @immutable()
    // public readonly logics: Array<Logic> = [];
    // vueFile: VueFile;
    /**
     * 树组件的子节点字段
     */
    @immutable()
    public readonly moreChildrenFields: Array<string> = ['$def.params', '$def.variables', '$def.logics'];
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<View>) {
        super();
        source && this.assign(source);

        this.on('change', throttle(
            this._onChange.bind(this),
            config.throttleWait,
            { leading: true, trailing: true },
        ));
    }

    private async _onChange() {
        // 在子页面修改时生成缓存，注意不需要 await
        if (config.webFileCache) {
            const tasks = [];
            // 保存 cache 之前需要保证 view 和下面的 logic 全部 load 完成
            if (!this.isContentLoaded())
                tasks.push(this.load());
            this.$def.logics.forEach((logic) => {
                if (!logic.isContentLoaded())
                    tasks.push(logic.load());
            });
            await Promise.all(tasks);

            try {
                generator.saveViewCache(this);
            } catch (e) {
                this.emit('translate-error');
            }
        }
        generator.saveViewDetailCache(this);
    }
    /**
     * 在 mergeBlock 的情况下使用
     * 在 loadDetail 的情况下使用
     * @returns
     */
    load() {
        // 如果有正在进行的 Promise，则直接返回它
        if (this.contentPromise)
            return this.contentPromise;

        return this.contentPromise = (async () => {
            try {
                const result: View = await viewService.load({
                    path: {
                        id: this.id,
                    },
                });

                const $def = result.$def;
                delete (result as any).$def;
                delete (result as any).children;

                const temp = View.from(result, this.parent, this.page, this);
                this.assign({
                    $html: temp.$html,
                    script: temp.script,
                    // style: temp.style,
                    definition: temp.definition,
                });

                // def 情况比较特殊，需要单独处理，防止逻辑重新加载
                this._syncDef($def);
                this.attachNodePath(true);
                return this;
            } catch (err) { }
        })().finally(() => this.contentPromise = undefined);
    }
    loadAll() {
        // 如果有正在进行的 Promise，则直接返回它
        if (this.contentPromise)
            return this.contentPromise;

        return this.contentPromise = (async () => {
            try {
                const result: View = await viewService.load({
                    path: {
                        id: this.id,
                    },
                });

                const temp = View.from(result, this.parent, this.page, this);
                this.assign(temp);
                utils.traverse((current) => {
                    current.node.attachNodePath(true);
                }, { node: this });
                return this;
            } catch (err) { }
        })().finally(() => this.contentPromise = undefined);
    }
    isContentLoaded() {
        return !!this.$html;
    }
    /**
     * 后续改为根据 id 查找 Element
     */
    attachNodePath(clear?: boolean) {
        if (!this.$html)
            return;

        utils.traverse((current) => {
            current.node.nodePath = current.nodePath;
            if (clear && current.node.tag === 'd-progress') {
                current.parent.children.splice(current.index, 1);
            }
        }, { node: this.$html });
    }
    /**
     * 根据路径查找子节点
     * @param nodePath 节点路径，/1/2 表示根节点的第1个子节点的第2个子节点
     * @param node 查找的起始节点
     * @examples
     * - findElementByNodePath('', root) 指根节点本身
     * - findElementByNodePath('/', root) 指根节点本身
     * - findElementByNodePath('/0', root) 指第0个子节点
     * - findElementByNodePath('/2/1', root) 指第2个子节点的第1个子节点
     */
    findElementByNodePath(nodePath: string, node: Element = this.$html): Element {
        if (nodePath[0] === '/') // 这个里边相对和绝对是一样的
            nodePath = nodePath.slice(1);
        const arr = nodePath.split('/');
        if (!nodePath || !arr.length)
            return node;
        else {
            const parent = node;
            return this.findElementByNodePath(arr.slice(1).join('/'), parent.children[+arr[0]]);
        }
    }
    /**
     * 根据标签查找元素
     * @param tag
     */
    findElementByTag(tag: string) {
        return this.$html.findElementByTag(tag);
    }
    /**
     * 根据属性查找元素
     * @param name
     * @param value
     */
    findElementByAttr(name: string, value: string) {
        return this.$html.findElementByAttr(name, value);
    }
    /* 绑定数据结构和接口 */
    addData(res: any) {
        const microService = this.parent.page.service.app.firstMicroService;
        const interfaces = res.interfaces as Array<Interface>;
        if (interfaces && interfaces.length) {
            interfaces.forEach((item, index) => {
                const itface = interfaces[index] = Interface.from(item, microService);
                postServiceType(itface);
            });
            microService.interfaces.unshift(...interfaces);
            microService.emit('interfacesChange');
        }

        const enums = res.enums as Array<Enum>;
        if (enums && enums.length) {
            enums.forEach((item, index) => enums[index] = Enum.from(item, microService));
            microService.data.enums.unshift(...enums);
            updateDataTypeList();
        }

        const entities = res.entities as Array<Entity>;
        if (entities && entities.length) {
            entities.forEach((item, index) => entities[index] = Entity.from(item, microService));
            microService.data.entities.unshift(...entities);
            updateDataTypeList();
        }

        const structures = res.structures as Array<Structure>;
        if (structures && structures.length) {
            structures.forEach((item, index) => structures[index] = Structure.from(item, microService));
            microService.data.structures.unshift(...structures);
            updateDataTypeList();
        }
    }

    @action('添加子页面')
    async importViewCreate(none?: void, actionOptions?: ActionOptions, then?: () => Promise<any>) {
        config.defaultApp?.emit('saving');

        const body = this.toJSON();
        utils.logger.debug('添加子页面', body);
        const result = await viewService.importView({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'View.create',
                operationDesc: actionOptions?.actionDesc || `添加子页面"${this.name}${this.title ? `(${this.title})` : ''}"`,
            },
            body,
        });
        this.deepPick(result.view, ['id']);
        // 添加数据
        this.addData(result);
        if (result.view) {
            const temp = View.from(result.view, this.parent, this.parent.page);
            this.assign({
                $html: temp.$html,
                script: temp.script,
                $def: temp.$def,
                tempPath: temp.tempPath,
                code: temp.code,
                parent: temp.parent,
                page: temp.page,
                children: temp.children,
                definition: temp.definition,
            });
            utils.traverse((current) => {
                current.node.attachNodePath(true);
            }, { node: this });
        }
        this.attachNodePath();

        await then?.();
        // 在子页面修改时生成缓存，注意不需要 await
        this.page.service.emit('pageTreeChange');

        const microService = this.page.service.app.firstMicroService;
        microService.emit('dataTypesChange');
        microService.emit('enumsChange');
        microService.emit('vertexIdToNameChange');

        this.emit('change');
        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 添加子页面
     */
    @action('添加子页面')
    async create(none?: void, actionOptions?: ActionOptions, then?: () => Promise<any>) {
        config.defaultApp?.emit('saving');

        const body = this.toJSON();
        body.serviceId = this.page.service.id;
        utils.logger.debug('添加子页面', body);
        const result = await viewService.create({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'View.create',
                operationDesc: actionOptions?.actionDesc || `添加子页面"${this.name}${this.title ? `(${this.title})` : ''}"`,
            },
            body,
        });
        this.deepPick(result, ['id']);

        this.assign({
            tempPath: this.parent ? `${this.parent.tempPath}/${this.name}` : `/${this.page.rootView.name}`,
            code: this.parent ? `${this.parent.code}/ID_${this.id}` : `/ID_${this.page.rootView.id}`,
        });
        this.attachNodePath();

        await then?.();
        // 在子页面修改时生成缓存，注意不需要 await
        this.page.service.emit('pageTreeChange');
        this.emit('change');
        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 删除子页面
     */
    @action('删除子页面')
    async delete(none?: void, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving');

        if (!this.parent)
            throw Error('该子页面为根节点！');

        try {
            await viewService.delete({
                headers: {
                    appId: config.defaultApp?.id,
                    operationAction: actionOptions?.actionName || 'View.delete',
                    operationDesc: actionOptions?.actionDesc || `删除子页面"${this.name}${this.title ? `(${this.title})` : ''}"`,
                },
                path: {
                    id: this.id,
                },
            });
        } catch (err) {
            await config.defaultApp?.history.load();
            throw err;
        }

        const index = this.parent.children.indexOf(this);
        ~index && this.parent.children.splice(index, 1);
        // 删除的子页面为默认跳转页时，取消默认跳转页面
        if (this.parent.first && this.parent.first === this.name) {
            this.parent.assign({ first: null });
            await this.parent.update(undefined, {
                actionDesc: `取消子页面"${this.name}"为默认跳转页`,
            });
        }
        this.destroy();
        this.page.service.emit('pageTreeChange');

        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
    }
    /**
     * 修改子页面
     */
    async update(none?: void, actionOptions?: ActionOptions, then?: () => Promise<any>) {
        config.defaultApp?.emit('saving');

        const body = this.toPlainJSON();
        utils.logger.debug('修改页面', body);
        const result = await viewService.update({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'View.update',
                operationDesc: actionOptions?.actionDesc || `修改子页面"${this.name}${this.title ? `(${this.title})` : ''}"`,
            },
            body,
        });

        await then?.();
        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 设置子页面名称
     * 如果为顶级子页面，会同时设置 Page 的属性
     * @param name 名称
     */
    @action('设置子页面名称')
    async setName(name: string) {
        const oldName = this.name;
        const oldTempPath = this.tempPath;
        this.assign({ name });
        this.assign({
            tempPath: this.parent ? `${this.parent.tempPath}/${name}` : `/${name}`,
        });
        await this.update(undefined, {
            actionDesc: `设置页面"${oldName}"的名称为"${name}"`,
        });
        if (this.page.rootView === this) {
            this.page.assign({ name });
            await this.page.update();
        }
        // 同步权限
        new Nuims({
            domainName: this.page.service.app.name,
            view: this,
        }).editResourceFromResourceValue(oldTempPath);
        this.page.service.emit('pageTreeChange');
        this.emit('change');
    }
    /**
     * 设置子页面标题
     * 如果为顶级子页面，会同时设置 Page 的属性
     * @param title 标题
     */
    @action('设置子页面标题')
    async setTitle(title: string) {
        const oldTitle = this.title;
        this.assign({ title });
        await this.update(undefined, {
            actionDesc: `设置页面"${oldTitle}"的标题为"${title}"`,
        });
        if (this.page.rootView === this) {
            this.page.assign({ title });
            await this.page.update();
        }

        this.page.service.emit('pageTreeChange');
        this.emit('change');
    }
    /**
     * 设置子页面面包屑
     * @param crumb 面包屑
     */
    @action('设置子页面面包屑')
    async setCrumb(crumb: string) {
        const oldCrumb = this.crumb;
        this.assign({ crumb });
        await this.update(undefined, {
            actionDesc: `设置页面"${oldCrumb}"的面包屑为"${crumb}"`,
        });

        this.page.service.emit('pageTreeChange');
        this.emit('change');
    }
    /**
     * 设置该子页面为默认跳转页
     */
    @action('设置该子页面为默认跳转页')
    async setAsFirst() {
        if (!this.parent)
            return;

        this.parent.assign({ first: this.name });
        await this.parent.update(undefined, {
            actionDesc: `设置子页面"${this.name}"为默认跳转页`,
        });

        this.page.service.emit('pageTreeChange');
        this.parent.emit('change');
    }
    @action('是否是默认跳转页')
    canCancelFirst() {
        return this.parent.first === this.name;
    }
    @action('设置该子页面为取消默认跳转页')
    async setAsFirstFalse() {
        if (!this.parent)
            return;
        if (this.parent.first && this.parent.first === this.name) {
            this.parent.assign({ first: null });
            await this.parent.update(undefined, {
                actionDesc: `设置子页面"${this.name}"为取消默认跳转页`,
            });
            this.page.service.emit('pageTreeChange');
            this.parent.emit('change');
        }
    }
    /**
     * 添加子页面
     * @param view
     */
    async importView(view: View) {
        view.assign({
            pageId: this.page && this.page.id,
            parentId: this.id,
            parent: this,
        });
        await view.importViewCreate(undefined, undefined, async () => {
            this.children.unshift(view);
        });

        return view;
    }
    /**
     * 添加子页面
     * @param view
     */
    async addView(view: View) {
        view.assign({
            pageId: this.page && this.page.id,
            parentId: this.id,
            parent: this,
        });
        await view.create(undefined, undefined, async () => {
            this.children.unshift(view);
        });

        return view;
    }
    /**
     * 从模板添加子页面
     * @param view 页面名称或页面 Options
     * @param templateId 页面模板 Id
     */
    async addViewFromTemplate(viewName: string | Partial<View>, templateId: number) {
        let view: View;
        if (typeof viewName === 'string') {
            view = new View({
                name: viewName,
                title: viewName,
                pageId: this.page.id,
                page: this.page,
                parentId: this.id,
                parent: this,
                pageTemplateId: templateId,
            });
        } else {
            view = new View(Object.assign({
                pageId: this.page.id,
                page: this.page,
                parentId: this.id,
                parent: this,
                pageTemplateId: templateId,
            }, viewName));
        }

        await view.importViewCreate(undefined, undefined, async () => {
            this.children.unshift(view);
        });

        return view;
    }
    /**
     * 添加页面事件
     */
    @action('添加页面事件')
    async addLifecycle(data: Lifecycle, actionOptions?: ActionOptions) {
        // (data as any).element = this;
        let lifecycle = new Lifecycle(data);
        await lifecycle.create(undefined, Object.assign({
            actionDesc: `添加页面"${this.name}"事件"${lifecycle.name}"`,
        }, actionOptions));
        lifecycle = Lifecycle.from(lifecycle, this);
        this.$def.lifecycles.push(lifecycle);

        this.emit('change');
        return lifecycle;
    }
    /**
     * 删除页面属性
     */
    @action('删除页面事件')
    async removeLifecycle(lifecycle: Lifecycle, actionOptions?: ActionOptions) {
        if (!lifecycle)
            throw Error(`该页面"${this.name}"没有事件"${lifecycle.name}"`);
        if (isPlainObject(lifecycle))
            lifecycle = this.$def.lifecycles.find((item) => item.id === lifecycle.id);

        await lifecycle.delete(undefined, Object.assign({
            actionDesc: `删除页面"${this.name}"事件"${lifecycle.name}"`,
        }, actionOptions));
        const index = this.$def.lifecycles.indexOf(lifecycle);
        ~index && this.$def.lifecycles.splice(index, 1);

        this.emit('change');
    }
    /**
     * 转换成 Vue 中需要的 JS 代码
     * @TODO 这一版先做成 componentOptions 式的，后面再美化
     */
    toScript(options?: ElementToVueOptions) {
        let componentOptions = 'const componentOptions = {};';
        if (this.script)
            componentOptions = this.script.trim().replace(/export default |module\.exports +=/, 'const componentOptions = ');

        const $def = JSON.parse(JSON.stringify(this.$def));
        $def.title = this.title;
        $def.crumb = this.crumb;
        $def.first = this.first;

        // utils.traverse((current) => {
        //     const node = current.node;
        //     if (node.type === 'Unparsed') {
        //         // 借 Identifier 的鸡下个蛋
        //         node.type = 'Identifier';
        //         node.name = node.code;
        //     }else if (node.type === 'BuiltInFunction') {
        //         Object.assign(
        //             node,
        //             ((babel.parse(`this.$utils['${node.calleeCode}']()`, {
        //                 filename: 'file.js',
        //             })as babel.types.File).program.body[0] as babel.types.ExpressionStatement).expression,
        //             {
        //                 arguments: (node.builtInFuncParams || []).map((param: any) => {
        //                     return param.builtInFuncParamValue;
        //                 }),
        //             },
        //         );
        //     }
        // }, { node: $def }, { mode: 'anyObject' });
        return `${componentOptions}
        ${genFinalCode(translator($def), options && options.finalCode)}
        export default componentOptions;
`;
    }
    /**
     * 转换成 Vue 文件
     */
    toVue(options?: ElementToVueOptions) {
        let result = '';
        if (this.$html)
            result += `<template>\n${this.$html.toVue(options)}\n</template>\n`;
        if (this.script || this.$def)
            result += `<script>\n${this.toScript(options)}\n</script>\n`;
        // if (this.$def)
        //     result += ''; // `<script>\n${this.$html.toVue()}\n</script>\n`;
        return result;
    }
    /**
     * 转换成设计器中使用的 Vue 文件
     * @param options
     */
    toDesignerVue(options?: ElementToVueOptions) {
        let result = '';
        if (this.$html)
            result += `<template>\n${this.$html.toDesignerVue(options)}\n</template>\n`;
        // if (this.script)
        //     result += `<script>\n${this.script}\n</script>\n`;
        if (this.$def)
            result += ''; // `<script>\n${this.$html.toVue()}\n</script>\n`;
        return result;
    }
    /**
     * 转换成 VueOptions
     * @TODO 后面 template 可以优化成 render 函数
     */
    toVueOptions(options?: ElementToVueOptions) {
        return {
            template: this.$html.toVue(options),
            script: this.toScript(options),
            // definition: JSON.stringify(this.$def),
        };
    }
    /**
     * 合并代码
     */
    @action('根据实体自动生成组件和逻辑')
    async mergeBlock({ code, nodePath, position, parentNodePath }: {
        code: string, nodePath: string, position: string, parentNodePath: string
    }, actionOptions?: ActionOptions, then?: () => Promise<any>) {
        config.defaultApp?.emit('saving', true);

        type Replacement = { [name: string]: string };
        /**
         * 本来的 Name 相关的集合
         */
        const thisNameSets: {
            viewVariables: Set<string>,
            logics: Set<string>,
            elements: Set<string>,
        } = {
            viewVariables: new Set(),
            logics: new Set(),
            elements: new Set(),
        };
        /**
         * 需要替换的 Map
         */
        const replacements: {
            viewVariables: Replacement,
            logics: Replacement,
            elements: Replacement,
        } = {
            viewVariables: {},
            logics: {},
            elements: {},
        };

        this.$def.params.forEach((param) => thisNameSets.viewVariables.add(param.name));
        this.$def.variables.forEach((param) => thisNameSets.viewVariables.add(param.name));
        this.$def.logics.forEach((logic) => thisNameSets.logics.add(logic.name));
        utils.traverse((current) => {
            if (current.node.level === 'element' && current.node.name)
                thisNameSets.elements.add(current.node.name);
        }, { node: this.$html });

        const setNewNameIfNotUnique = (obj: any, key: string, type: 'viewVariables' | 'logics' | 'elements') => {
            // name 去重
            const newName = utils.unique(obj[key], thisNameSets[type]);
            if (newName !== obj[key])
                obj[key] = replacements[type][obj[key]] = newName;
        };

        const body: any = {};
        const template = utils.sliceTagContent(code, 'template');
        const parentNode = this.findElementByNodePath(parentNodePath);

        const viewId = parentNode.view.id;
        const parentId = parentNode.id;
        body.id = viewId;

        const definition = utils.sliceTagContent(code, 'definition');
        if (definition) {
            const $def: any = JSON.parse(definition);
            body.$def = $def;
        }

        if (template) {
            const newNode = Element.parse(template, body.$def);
            const paramObj = {
                viewId,
                parentId,
            };

            newNode.assign(paramObj);
            body.$html = newNode.toJSON();

            if (position === 'append') {
                // 父节点最后追加
                body.$html._posIndex = parentNode.children && parentNode.children.length || 0;
            } else {
                // 添加目标标节点的位置
                const posIndex = nodePath.split('/').pop();
                body.$html._posIndex = +posIndex;
            }

            traverse((current) => {
                if (current.node.level === 'element' && current.node.name)
                    setNewNameIfNotUnique(current.node, 'name', 'elements');
            }, { node: body.$html });
        }

        if (definition) { // Element 需要逻辑变量没改名字时处理
            const $def = body.$def;
            delete $def.dataSchema;
            if ($def.interfaces) {
                body.interfaces = $def.interfaces;
                delete $def.interfaces;
            }
            if ($def.structures) {
                body.structures = $def.structures;
                delete $def.structures;
            }

            [].concat($def.params || [], $def.variables || []).forEach((param: any) => {
                param.viewId = viewId;
                setNewNameIfNotUnique(param, 'name', 'viewVariables');
            });

            $def.lifecycles = $def.lifecycles || [];
            $def.lifecycles.filter((lifecycle: any) => !this.$def.lifecycles.find((thisLifecycle) => thisLifecycle.name === lifecycle.name));
            $def.lifecycles.forEach((lifecycle: any) => {
                lifecycle.viewId = viewId;
            });

            ($def.logics || []).forEach((logic: any) => {
                logic.moduleType = 'view';
                logic.moduleId = viewId;
                setNewNameIfNotUnique(logic, 'name', 'logics');
            });
        }

        const hasKeyThenReplace = (obj: any, key: string, test: RegExp, replacement: Replacement) => {
            if (obj[key] === undefined || !Object.keys(replacement).length)
                return;

            if (!test)
                test = /^(\w+)$/;
            const cap = obj[key].match(test);
            if (!cap)
                return;

            const oldName = cap[1];
            const newName = replacement[oldName];
            if (newName)
                obj[key] = obj[key].replace(test, () => test.toString().replace(/[\^\\/]/g, '').replace(/\$$/, '').replace(/\(w\+\)/, newName));
        };

        const { interfaces } = body;
        delete body.interfaces;
        traverse((current) => {
            if (current.node.level === 'lifecycle' || current.node.level === 'event') {
                hasKeyThenReplace(current.node, 'logicId', null, replacements.logics);
                hasKeyThenReplace(current.node, 'value', null, replacements.logics);
                hasKeyThenReplace(current.node, 'value', /^\$refs\.(\w+)\./, replacements.elements);
            } else if (current.node.level === 'logicNode' && current.node.type === 'CallLogic') {
                hasKeyThenReplace(current.node, 'callee', null, replacements.logics);
                hasKeyThenReplace(current.node, 'calleeCode', null, replacements.logics);
                hasKeyThenReplace(current.node, 'calleeCode', /^\$refs\.(\w+)\./, replacements.elements);
            } else if (current.node.level === 'expressionNode' && current.node.type === 'Identifier') {
                if (current.parent && (current.parent.type !== 'MemberExpression' || current.node === current.parent.object)) {
                    hasKeyThenReplace(current.node, 'code', null, replacements.viewVariables);
                    hasKeyThenReplace(current.node, 'name', null, replacements.viewVariables);
                    hasKeyThenReplace(current.node, 'name', null, replacements.logics); // 处理表格 data-source 的问题
                }
            } // else if (current.node.level === 'expressionNode')
        }, { node: body }, { mode: 'anyObject' });
        body.interfaces = interfaces;
        const res = await viewService.mergeBlock({
            body,
            headers: {
                appId: config.defaultApp?.id,
                operationAction: 'View.mergeBlock',
                operationDesc: '根据实体自动生成组件和逻辑',
            },
        });

        await then?.();
        this.redoMergeBlock(res);
        // await config.defaultApp?.history.load();
        // config.defaultApp?.emit('saved');
    }

    _removeDef($def: View['$def']) {
        const removedParams = this.$def.params.filter((param) => $def.params?.find((item) => param.id === item.id));
        this.$def.params = this.$def.params.filter((param) => !$def.params?.find((item) => param.id === item.id));
        removedParams.forEach((param) => {
            param.destroy();
        });

        const removedVariables = this.$def.variables.filter((param) => $def.variables?.find((item) => param.id === item.id));
        this.$def.variables = this.$def.variables.filter((variable) => !$def.variables?.find((item) => variable.id === item.id));
        removedVariables.forEach((variable) => {
            variable.destroy();
        });

        const removedLifecycles = this.$def.lifecycles.filter((param) => $def.lifecycles?.find((item) => param.id === item.id));
        this.$def.lifecycles = this.$def.lifecycles.filter((lifecycle) => !$def.lifecycles?.find((item) => lifecycle.id === item.id));
        removedLifecycles.forEach((lifecycle) => {
            lifecycle.destroy();
        });

        const removedLogics = this.$def.logics.filter((logic) => $def.logics?.find((item) => logic.id === item.id));
        this.$def.logics = this.$def.logics.filter((logic) => !$def.logics?.find((item) => logic.id === item.id));
        removedLogics.forEach((logic) => {
            // @TODO: 目前只做 logic 的 remove，后面页面中的可以切过来
            config.defaultApp?.emit('some-remove', logic);
            logic.destroy();
        });
    }

    async undoMergeBlock(res: any) {
        const microService = this.page.service.app.firstMicroService;
        const structures = res.structures as Array<Structure>;
        if (structures && structures.length) {
            const allStructures = microService.data.structures;
            for (let i = allStructures.length - 1; i >= 0; i--) {
                if (structures.find((item) => item.id === allStructures[i].id))
                    allStructures.splice(i, 1);
            }

            updateDataTypeList();
        }
        const interfaces = res.interfaces as Array<Interface>;
        if (interfaces && interfaces.length) {
            const allInterfaces = microService.interfaces;
            for (let i = interfaces.length - 1; i >= 0; i--) {
                if (allInterfaces.find((item) => item.id === interfaces[i].id))
                    allInterfaces.splice(i, 1);
            }
            const allglobalLogics = microService.globalLogic.globalLogics;
            for (let i = interfaces.length - 1; i >= 0; i--) {
                if (allglobalLogics.find((item) => item.id === interfaces[i].id))
                    allglobalLogics.splice(i, 1);
            }
            microService.emit('interfacesChange');
        }

        const parent = vertexsMap.get(res.$html.parentId) as Element;
        parent.removeChild(res.$html, { actionMode: ACTION_MODE.undoRedo, loadHistory: false });
        this._removeDef(res.$def);

        this.page.service.emit('pageTreeChange');
        this.emit('change');
        this.page.service.emit('vertexIdToNameChange');

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

    _addDef($def: View['$def']) {
        $def.params?.forEach((param) => {
            this.$def.params.push(ViewParam.from(param, this));
        });
        $def.variables?.forEach((variable) => {
            this.$def.variables.push(ViewVariable.from(variable, this));
        });
        $def.lifecycles?.forEach((lifecycle) => {
            this.$def.lifecycles.push(Lifecycle.from(lifecycle, this));
        });
        $def.logics?.forEach((logic) => {
            this.$def.logics.push(Logic.from(logic, this));
        });
    }

    async redoMergeBlock(res: any) {
        const microService = this.page.service.app.firstMicroService;
        const structures = res.structures as Array<Structure>;
        if (structures && structures.length) {
            structures.forEach((item, index) => structures[index] = Structure.from(item, microService));
            microService.data.structures.unshift(...structures);
            updateDataTypeList();
        }
        const interfaces = res.interfaces as Array<Interface>;
        if (interfaces && interfaces.length) {
            interfaces.forEach((item, index) => {
                const itface = interfaces[index] = Interface.from(item, microService);
                itface.assign({ serviceType: 'micro' });
            });
            microService.interfaces.unshift(...interfaces);
            microService.globalLogic.globalLogics.unshift(...interfaces);
            microService.emit('interfacesChange');
        }

        const parent = vertexsMap.get(res.$html.parentId) as Element;
        parent.addChild(res.$html, { actionMode: ACTION_MODE.undoRedo, loadHistory: false });
        this._addDef(res.$def);

        this.page.service.emit('pageTreeChange');
        this.emit('change');
        this.page.service.emit('vertexIdToNameChange');

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

    private _syncDef($def: View['$def']) {
        $def.params?.forEach((param) => {
            if (!this.$def.params.find((thisParam) => thisParam.id === param.id))
                this.$def.params.push(ViewParam.from(param, this));
        });
        $def.variables?.forEach((variable) => {
            if (!this.$def.variables.find((thisVariable) => thisVariable.id === variable.id))
                this.$def.variables.push(ViewVariable.from(variable, this));
        });
        $def.lifecycles?.forEach((lifecycle) => {
            if (!this.$def.lifecycles.find((thisLifecycle) => thisLifecycle.id === lifecycle.id))
                this.$def.lifecycles.push(Lifecycle.from(lifecycle, this));
        });
        $def.logics?.forEach((logic) => {
            if (!this.$def.logics.find((thisLogic) => thisLogic.id === logic.id))
                this.$def.logics.push(Logic.from(logic, this));
        });
    }
    /**
     * 从后端 JSON 生成规范的 View 对象
     * @param currentView 方便构建子对象时关联，主要在 load 中使用
     */
    public static from(source: any, parent: View, page: Page, currentView?: View) {
        const view = new View(source);
        currentView = currentView || view;

        if (page && page.rootView) {
            view.assign({
                tempPath: parent ? `${parent.tempPath}/${view.name}` : `/${page.rootView.name}`,
                code: parent ? `${parent.code}/ID_${view.id}` : `/ID_${page.rootView.id}`,
            });
        }

        view.assign({
            parent,
            page,
            children: view.children?.map((child) => View.from(child, currentView, page)),
        });

        view.$html && view.assign({ $html: Element.from(view.$html, null, currentView) });
        view.$def.params = view.$def.params.map((param) => ViewParam.from(param, currentView));
        view.$def.variables = view.$def.variables.map((param) => ViewVariable.from(param, currentView));
        view.$def.lifecycles = view.$def.lifecycles.map((lifecycle) => Lifecycle.from(lifecycle, currentView));
        view.$def.logics = view.$def.logics.map((param) => Logic.from(param, currentView));

        return view;
    }
    /**
     * 按照模板创建
     */
    public static async fromTemplate(templateName: string, source: any, parent: View, page: Page, scope?: 'pc' | 'h5') {
        // const template = TEMPLATE_MAP[templateName];

        // @TODO: 后面改到资产中心
        const { rows } = await blockService.loadList({
            query: {
                keyword: (scope || config.scope) === 'h5' ? `@lcap/mobile-ui/${templateName}.vue` : `@cloud-ui/s-${templateName}.vue`,
            },
        });
        if (!rows[0])
            throw new Error('Cannot find block ' + templateName);

        const content = rows[0].content.replace(/https:\/\/static-vusion\.163yun\.com/g, config.staticURL);
        const html = utils.sliceTagContent(content, 'template');
        const script = utils.sliceTagContent(content, 'script');

        const view = this.from(source, parent, page);
        view.assign({
            $html: Element.fromHTML(html, null, view),
            script,
        });
        return view;
    }
}

export default View;
