import { action, excludedInJSON, immutable } from '../decorators';
import { LEVEL_ENUM, vertexsMap, utils, EventEmitter } from '..';
import { at } from 'lodash';
import { findUsageService } from '../../service/common';
import { v4 as uuidv4 } from 'uuid';

/**
 * 顶点类
 * 属性均为只读，仅在初始化和 load 的情况下可以修改
 */
export class Vertex extends EventEmitter {
    /**
     * 可修改标志，内部属性
     * 标记在构造函数结束后是否可修改
     */
    @excludedInJSON()
    protected _mutable = true;
    /**
     * 概念类型
     */
    @immutable()
    public readonly level: LEVEL_ENUM = LEVEL_ENUM.vertex;
    /**
     * toJSON 时需要去除的键，为了避免出现 CircularJSON
     */
    @immutable()
    protected readonly JSON_EXCLUDED_KEYS: Set<string>;
    /**
     * 顶点 id
     */
    @immutable()
    public readonly id: string = undefined;
    /**
     * 唯一 uuid
     * 目前主要用于前端 Debug 区分两个实例
     */
    @excludedInJSON()
    public readonly _uuid: string = uuidv4();
    /**
     * 前端记录缓存的时间戳
     */
    @excludedInJSON()
    public readonly _cacheTimestamp: string = undefined;
    /**
     * 名称
     */
    @immutable()
    public readonly name: string;
    /**
     * 创建人
     */
    @immutable()
    public readonly createdBy: string = undefined;
    /**
     * 创建时间
     */
    @immutable()
    public readonly createdTime: string = undefined;
    /**
     * 修改人
     */
    @immutable()
    public readonly updatedBy: string = undefined;
    /**
     * 修改时间
     */
    @immutable()
    public readonly updatedTime: string = undefined;
    /**
     * IDE 版本
     */
    @immutable()
    public readonly ideVersion: string = undefined;
    /**
     * 是否可编辑
     */
    public editable: boolean = true;
    /**
     * 是否正在编辑
     * 前端 UI 状态
     */
    @excludedInJSON()
    public editing: boolean = false;
    /**
     * 是否正在请求
     * 前端 UI 状态
     */
    @excludedInJSON()
    public loading: boolean = false;
    /**
     * 节点是否为展开状态
     * 前端 UI 状态
     */
    @excludedInJSON()
    public expanded: boolean = false;
    /**
     * 是否为叶子节点
     * 前端 UI 状态
     */
    @excludedInJSON()
    public isLeaf: boolean = false;
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<Vertex>) {
        super();
        if (typeof window !== 'undefined') {
            const globalData = (window as any).globalData;
            this.ideVersion = globalData && globalData.ideVersion;
        } else {
            this.ideVersion = '1.2';
        }
        source && this.assign(source);
        if (this.hasOwnProperty('JSON_EXCLUDED_KEYS') && this.JSON_EXCLUDED_KEYS === undefined)
            delete (this as any).JSON_EXCLUDED_KEYS;
    }

    assign(source: any) {
        this._mutable = true;

        const oldId = this.id;

        Object.assign(this, source);

        oldId && vertexsMap.delete(oldId);
        this.id && vertexsMap.set(this.id, this);

        this.emitVertexIdToNameChange();

        this._mutable = false;
    }

    emitVertexIdToNameChange() {
        if (typeof window === 'undefined')
            return;

        const { id, name } = this;
        const service = window
            && (window as any).$data
            && (window as any).$data.app
            && (window as any).$data.app.firstMicroService;
        if (service)
            service.emit('vertexIdToNameChange', id, name);
    }

    plainAssign(source: any) {
        this._mutable = true;

        const oldId = this.id;

        Object.keys(source).forEach((key) => {
            const value = source[key];
            if (typeof value !== 'object' && typeof value !== 'function')
                (this as any)[key] = value;
        });

        oldId && vertexsMap.delete(oldId);
        this.id && vertexsMap.set(this.id, this);

        this.emitVertexIdToNameChange();

        this._mutable = false;
    }

    pick(source: any, keys: Array<string> = []) {
        const obj: { [name: string]: any } = {};
        keys.forEach((key) => obj[key] = source[key]);
        // 这里统一用 assign，集中处理 id 的问题
        this.assign(obj);
    }
    /**
     * 从对象中深度获取
     * @param source
     * @param keys
     * @example 比如一般后端返回只是添加了个 id
     * ```
     * this.deepPick(result, ['id'])
     */
    deepPick(source: any, keys: Array<string> = []) {
        utils.traverse((current) => {
            const subSource = current.jsonPath ? at(source, [current.jsonPath])[0] : source;
            const target = current.jsonPath ? at(this as any, [current.jsonPath])[0] : this;
            if (!target)
                return;

            const obj: { [name: string]: any } = {};
            keys.forEach((key) => {
                if (subSource.hasOwnProperty(key)) {
                    obj[key] = subSource[key];
                }
            });
            // 这里统一用 assign，集中处理 id 的问题
            target.assign ? target.assign(obj) : Object.assign(target, obj);
        }, { node: source }, { mode: 'anyObject' });
    }
    /**
     * 去除循环依赖，转为纯 JSON
     * @param parentKey 外面的 key，提供给 JSON.stringify 使用
     * @param excludedKeys 需要额外排除的 keys
     */
    toJSON(parentKey?: string, excludedKeys: Array<string> = []): any {
        const JSON_EXCLUDED_KEYS = new Set(this.JSON_EXCLUDED_KEYS || []); // Object.getPrototypeOf(this).constructor.JSON_EXCLUDED_KEYS || [];
        excludedKeys.forEach((key) => JSON_EXCLUDED_KEYS.add(key));

        const temp: { [prop: string]: any } = {};
        Object.keys(this).forEach((key) => {
            if (!JSON_EXCLUDED_KEYS.has(key))
                temp[key] = (this as any)[key];
        });
        return JSON.parse(JSON.stringify(temp));
    }
    /**
     * 转为单层的 JSON
     * @param parentKey 外面的 key，提供给 JSON.stringify 使用
     * @param excludedKeys 需要额外排除的 keys
     */
    toPlainJSON(parentKey?: string, excludedKeys: Array<string> = []): any {
        const JSON_EXCLUDED_KEYS = new Set(this.JSON_EXCLUDED_KEYS || []); // Object.getPrototypeOf(this).constructor.JSON_EXCLUDED_KEYS || [];
        excludedKeys.forEach((key) => JSON_EXCLUDED_KEYS.add(key));

        const temp: { [prop: string]: any } = {};
        Object.keys(this).forEach((key) => {
            const value = (this as any)[key];
            if (!JSON_EXCLUDED_KEYS.has(key)
                && typeof value !== 'object' && typeof value !== 'function')
                temp[key] = value;
        });
        return JSON.parse(JSON.stringify(temp));
    }
    /**
     * 查找引用
     */
    @action('查找引用')
    async findUsage() {
        const result = await findUsageService.findUsage({
            query: {
                id: this.id,
            },
        });
        return result;
    }
    /**
     * 销毁
     * 从 Map 中删除点和子节点
     */
    destroy() {
        utils.traverse((current) => {
            vertexsMap.delete(current.node.id);
        }, { node: this }, {
            mode: 'anyObject',
            excludedKeySet: this.JSON_EXCLUDED_KEYS,
        });
    }
    /**
     * 通过 Ref 去查找点
     * @param ref
     */
    public static getVertexByRef(ref: string) {
        const arr = ref.split('/');
        const id = arr[arr.length - 1];
        return vertexsMap.get(id);
    }
}

export default Vertex;
