import { immutable, circular } from '../decorators';
import {
    LEVEL_ENUM, Vertex, Service, Entity,
    Interface, Process, Structure, dataTypesMap,
} from '..';
import { updateDataTypeList } from '../data/dataTypes';

export interface Category {
    level: LEVEL_ENUM.category;
    name: string;
    categoryInterfaces: Array<Interface>;
    categoryStructures: Array<Structure>;
    isLeaf: false;
    structureExpanded: false;
    interfaceExpanded: false;
}
/**
 * 数据分类
 */
export class GlobalLogicNode extends Vertex {
    /**
     * 概念类型
     */
    @immutable()
    public readonly level: LEVEL_ENUM = LEVEL_ENUM.gloablLogicNode;

    @immutable()
    public readonly type: 'logics' | 'interfaces' = undefined;
    @immutable()
    public globalLogics: Array<Interface> = [];
    public entityLogics: Array<Entity> = [];
    public processLogics: Array<Process> = [];
    public interfaces: Array<Interface> = [];
    public structures: Array<Structure> = [];
    public categories: Array<Category> = [];

    @circular()
    @immutable()
    public readonly service: Service = undefined;
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<GlobalLogicNode>) {
        super();
        source && this.assign(source);
    }

    createOrGetCatogory(category: string) {
        let ciInstance = this.categories.find((ci) => ci.name === category);

        if (!ciInstance) {
            ciInstance = {
                level: LEVEL_ENUM.category,
                name: category,
                categoryInterfaces: [],
                categoryStructures: [],
                isLeaf: false,
                interfaceExpanded: false,
                structureExpanded: false,
            };
            this.categories.unshift(ciInstance);
        }
        return ciInstance;
    }

    addCategoryInterface(iface: Interface) {
        const category = iface.category;
        const ciInstance = this.createOrGetCatogory(category);
        ciInstance.categoryInterfaces.unshift(iface);
    }

    addCategoryStructure(sturct: Structure) {
        const category = sturct.category;
        const ciInstance = this.createOrGetCatogory(category);
        const index = ciInstance.categoryStructures.findIndex((s) => s.id === sturct.id);
        if (index >= 0) {
            const [oldstruct] = ciInstance.categoryStructures.splice(index, 1, sturct);
            oldstruct.destroy();
        } else {
            ciInstance.categoryStructures.unshift(sturct);
        }
        updateDataTypeList();
    }

    checkExternalInterface(category: string, name: string) {
        let itface;
        const categoryInstance = this.categories.find((ci) => {
            itface = ci.categoryInterfaces.find((itface) => itface.name === name);
            return ci.name === category && !!itface;
        });
        return categoryInstance ? {
            category: categoryInstance,
            interface: itface,
        } : null;
    }

    removeCategory(categoryInstance: Category) {
        if (!categoryInstance.categoryInterfaces.length && !categoryInstance.categoryStructures.length) {
            const index = this.categories.indexOf(categoryInstance);
            ~index && this.categories.splice(index, 1);
        }
    }

    removeCategoryStructure(sturct: Structure) {
        const category = sturct.category;
        const ciInstance = this.createOrGetCatogory(category);
        const index = ciInstance.categoryStructures.findIndex((s) => s.id === sturct.id);
        if (index >= 0) {
            ciInstance.categoryStructures.splice(index, 1);
        }
        this.removeCategory(ciInstance);
    }

    removeInterface(itface: Interface) {
        if (itface.serviceType === 'external') {
            const category = itface.category;
            const categoryInstance = this.categories.find((ci) => ci.name === category);
            const index = categoryInstance.categoryInterfaces.indexOf(itface);
            ~index && categoryInstance.categoryInterfaces.splice(index, 1);
            this.removeCategory(categoryInstance);
        }
        if (itface.serviceType === 'micro') {
            if (itface.exportedInterface) {
                itface.exportedInterface.delete();
            }
            const index = this.globalLogics.indexOf(itface);
            ~index && this.globalLogics.splice(index, 1);
        }
    }

    syncEntityLogics() {
        const entityLogics: Array<Entity> = [];
        this.service.data.entities.forEach((entity) => {
            entityLogics.push(entity);
        });
        this.entityLogics = entityLogics;
    }

    syncStructures(structuresOrigin: Array<Structure>) {
        const structures = this.structures;
        const smap: { [id: string]: Structure } = {};
        // removeRef
        structures.forEach((s) => {
            delete dataTypesMap[s.schemaRef];
            smap[s.id] = s;
        });

        this.categories.forEach((category) => {
            category.categoryStructures.forEach((s) => {
                delete dataTypesMap[s.schemaRef];
                smap[s.id] = s;
            });
        });

        function replaceInStructureList(structures: Array<Structure>, structureNode: Structure) {
            const sid = structureNode.id;
            const index = structures.findIndex((s) => s.id === sid);
            structures.splice(index, 1, structureNode);
            delete smap[sid];
        }

        function removeInStructureList(structures: Array<Structure>, structureNode: Structure) {
            const sid = structureNode.id;
            const index = structures.findIndex((s) => s.id === sid);
            structures.splice(index, 1);
            structureNode.destroy();
        }

        structuresOrigin.forEach((item) => {
            const structureNode = Structure.from(item, this.service);
            const sid = structureNode.id;
            if (structureNode.serviceType === 'external' && structureNode.category) {
                const category = this.categories.find((c) => c.name === structureNode.category);
                if (category && smap[sid]) {
                    replaceInStructureList(category.categoryStructures, structureNode);
                } else {
                    this.addCategoryStructure(structureNode);
                }
            } else {
                if (smap[sid]) {
                    replaceInStructureList(structures, structureNode);
                } else {
                    structures.unshift(structureNode);
                }
            }
        });
        Object.keys(smap).forEach((key) => {
            const struct = smap[key];
            if (struct.category) {
                const category = this.categories.find((c) => c.name === struct.category);
                removeInStructureList(category.categoryStructures, struct);
            } else {
                removeInStructureList(structures, struct);
            }
        });

        updateDataTypeList();
    }
}

export default GlobalLogicNode;
