import { immutable, circular, excludedInJSON, action } from '../decorators';
import {
    config, utils, LEVEL_ENUM, Vertex, DataNode,
    ObjectSchema, Service, dataTypesMap, updateDataTypeList,
    StructureProperty, ActionOptions,
} from '..';
import { structureService } from '../../service/data';
import { convert2RefType } from './dataTypeUtils';
import { updateAllVariablesChildrenSchema } from '../index';

/**
 * 数据结构类
 */
export class Structure extends Vertex implements ObjectSchema {
    /**
     * 概念类型
     */
    @immutable()
    public readonly level: LEVEL_ENUM = LEVEL_ENUM.structure;
    /**
     * Id
     */
    @immutable()
    public readonly id: string = undefined;
    /**
     * dataTypes 中的唯一标识
     */
    @immutable()
    public readonly schemaRef: string = undefined;
    /**
     * 名称
     */
    @immutable()
    public readonly name: string = undefined;
    /**
     * 描述
     */
    @immutable()
    public readonly description: string = undefined;
    /**
     * 类型
     */
    @immutable()
    public readonly type: 'object' = 'object';
    /**
     * 属性列表
     */
    @immutable()
    readonly propertyList: Array<StructureProperty> = [];

    @immutable()
    public readonly resolvers: string = undefined;
    /**
     * 所属服务 Id
     */
    @immutable()
    public readonly serviceId: string = undefined;
    /**
     * catogory Name
     */
    @immutable()
    public readonly category: string = undefined;
    /**
     * 所属服务类型
     */
    @immutable()
    public readonly serviceType: string = undefined;
    /**
     * 所属实体 Id
     */
    @immutable()
    public readonly entityId: string = undefined;
    /**
     * 所属服务
     */
    @immutable()
    public readonly service: Service = undefined;
    /**
     * 父节点
     */
    @circular()
    @immutable()
    public readonly dataNode: DataNode = undefined;
    /**
     * 周边存在的名称
     */
    @excludedInJSON()
    public existingNames: Array<string> = [];
    /**
     * 周边存在的名称
     */
    constructor(source?: Partial<Structure>) {
        super();
        source && this.assign(source);
    }
    /**
     * 按当前 id 加载数据结构数据
     * @requires this.id
     */
    async load() {
        const result = await structureService.loadDetail({
            query: {
                id: this.id,
            },
            config: {
                mock: config.mock,
            },
        });

        this.assign(result);
        return this;
    }
    /**
     * 按当前 id 加载数据结构数据
     * @requires this.id
     */
    async loadPro() {
        const result = await structureService.loadDetail({
            query: {
                id: this.id,
            },
        });

        Object.assign(result, result.definition);
        delete result.definition;
        convert2RefType(result);
        const structure = new Structure(result);

        const propertyList = structure.propertyList as Array<StructureProperty>;
        propertyList.forEach((property, index) => {
            property = propertyList[index] = new StructureProperty(property);
            property.assign({ root: structure });
        });

        const service = this.dataNode.service;
        structure.assign({
            schemaRef: `#/${service.id}/${structure.id}`,
            dataNode: service.data,
            expanded: true,
        });
        dataTypesMap[structure.schemaRef] = structure;

        this.assign(structure);

        const pos = this.dataNode.structures.findIndex((item) => item.id === this.id);
        if (pos === -1)
            this.dataNode.structures.unshift(this);
        else {
            this.dataNode.structures.splice(pos, 1, structure);
        }

        updateDataTypeList();
        updateAllVariablesChildrenSchema();
        this.dataNode.service.emit('dataTypesChange');
        this.dataNode.service.emit('vertexIdToNameChange', this.id, this.name);

        return this;
    }
    /**
     * 添加数据结构
     */
    @action('添加数据结构')
    async create(none?: void, actionOptions?: ActionOptions, then?: () => Promise<any>) {
        config.defaultApp?.emit('saving');

        const body = this.toJSON();

        utils.logger.debug('添加数据结构', body);
        const result: Structure = await structureService.create({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'Structure.create',
                operationDesc: actionOptions?.actionDesc || `添加数据结构"${this.name}"`,
            },
            body,
        });

        this.deepPick(result, ['id']);

        this.assign({
            schemaRef: `#/${this.dataNode.service.id}/${result.id}`,
        });
        dataTypesMap[this.schemaRef] = this;

        await then?.();
        updateDataTypeList();
        this.dataNode.service.emit('dataTypesChange');
        this.dataNode.service.emit('vertexIdToNameChange', this.id, this.name);

        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 删除数据结构
     */
    @action('删除数据结构')
    async delete(none?: void, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving');

        if (this.id) {
            await structureService.delete({
                headers: {
                    appId: config.defaultApp?.id,
                    operationAction: actionOptions?.actionName || 'Structure.delete',
                    operationDesc: actionOptions?.actionDesc || `删除数据结构"${this.name}"`,
                },
                query: {
                    id: this.id,
                },
            });
        }
        const index = this.dataNode.structures.indexOf(this);
        ~index && this.dataNode.structures.splice(index, 1);
        this.dataNode.service.globalLogic.removeCategoryStructure(this);
        delete dataTypesMap[this.schemaRef];

        updateDataTypeList();
        this.destroy();
        this.dataNode.service.emit('dataTypesChange');

        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.toJSON('', ['propertyList']);

        utils.logger.debug('修改数据结构', body);
        const result: Structure = await structureService.update({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'Structure.update',
                operationDesc: actionOptions?.actionDesc || `修改数据结构"${this.name}"`,
            },
            body,
        });
        convert2RefType(result);
        this.plainAssign(result);

        await then?.();
        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 设置数据结构名称
     * @param name 名称
     */
    @action('设置数据结构名称')
    async setName(name: string) {
        const oldName = this.name;
        this.assign({ name });
        await this.update(undefined, {
            actionDesc: `设置数据结构"${oldName}"的名称为"${name}"`,
        });
        updateDataTypeList();

        this.dataNode.service.emit('dataTypesChange');
        this.dataNode.service.emit('vertexIdToNameChange', this.id, this.name);
    }
    /**
     * 设置数据结构描述
     * @param description 描述
     */
    @action('设置数据结构描述')
    async setDescription(description: string) {
        this.assign({ description });
        await this.update(undefined, {
            actionDesc: `设置数据结构"${this.name}"的描述为"${description}"`,
        });
    }
    /**
     * 从后端 JSON 生成规范的 Structure 对象
     */
    public static from(source: any, service: Service) {
        convert2RefType(source);
        const structure = new Structure(source);
        structure.assign({
            dataNode: service.data,
            schemaRef: `#/${service.id}/${structure.id}`,
        });

        const propertyList = structure.propertyList as Array<StructureProperty>;
        propertyList.forEach((property, index) => {
            property = propertyList[index] = new StructureProperty(property);
            property.assign({ root: structure });
        });
        structure.assign({ resolvers: undefined });

        dataTypesMap[structure.schemaRef] = structure;
        return structure;
    }
}

export default Structure;
