import { immutable, excludedInJSON, action } from '../decorators';
import { config, history, LEVEL_ENUM, Logic, Param, Process, ProcessInterface, Schema, ActionOptions } from '..';
import { convert2RefType } from '../data/dataTypeUtils';
import { getBasicTypeDefaultValue } from '../data/basicTypes';
import { schemaService } from '../../service/common';
import processService from '../../service/process';

/**
 * 流程输入参数
 */
export class ProcessParam extends Param {
    /**
     * 概念类型
     */
    @immutable()
    public readonly level: LEVEL_ENUM = LEVEL_ENUM.processParam;
    /**
     * 流程 Id
     */
    @immutable()
    public readonly parentId: string = undefined;
    /**
     * 视图
     */
    @excludedInJSON()
    @immutable()
    public readonly process: Process = undefined;
    /**
     * 默认值
     * 按 JSON string 处理
     * - string: 666 -> '666'
     * - string: true -> 'true'
     * - number: 666 -> 666
     * - boolean: true -> true
     */
    public defaultValue: string = undefined;
    /**
     * @param source 需要合并的部分参数
     */
    constructor(source?: Partial<ProcessParam>) {
        super();
        source && this.assign(source);
    }
    genCode() {
        return `ID_${this.process.id}.ID_${this.id}`;
    }
    /**
     * 添加流程输入参数
     */
    @action('添加流程输入参数')
    async create(none?: void, actionOptions?: ActionOptions) {
        config.defaultApp?.emit('saving');

        this.assign({ loading: true, editing: false });
        const body = this.toJSON();
        const result = await processService.addProcessVal({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'ProcessParam.create',
                operationDesc: actionOptions?.actionDesc || `添加流程"${this.process.name}"输入参数"${this.name}"`,
            },
            body,
        });
        this.assign(ProcessParam.from(result, this.process));
        this.updateInterface();

        await config.defaultApp?.history.load();
        config.defaultApp?.emit('saved');
        return this;
    }
    /**
     * 删除流程输入参数
     */
    @action('删除流程输入参数')
    async delete(none?: void, actionOptions?: ActionOptions) {
        if (this.id) {
            config.defaultApp?.emit('saving');

            const body = this.toJSON();
            await processService.deleteProcessVal({
                headers: {
                    appId: config.defaultApp?.id,
                    operationAction: actionOptions?.actionName || 'ProcessParam.delete',
                    operationDesc: actionOptions?.actionDesc || `删除流程"${this.process.name}"输入参数"${this.name}"`,
                },
                body,
            });
            const index = this.process.params.indexOf(this);
            ~index && this.process.params.splice(index, 1);
            this.destroy();
            this.updateInterface();

            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();
        const result = await processService.updateProcessVal({
            headers: {
                appId: config.defaultApp?.id,
                operationAction: actionOptions?.actionName || 'ProcessParam.update',
                operationDesc: actionOptions?.actionDesc || `修改流程"${this.process.name}"输入参数"${this.name}"`,
            },
            body,
        });
        this.assign(ProcessParam.from(result, this.process));
        this.updateInterface();

        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: `设置流程"${this.process.name}"的输入参数"${oldName}"的名称为"${name}"`,
        });
    }
    /**
     * 设置流程输入参数描述
     * @param description 描述
     */
    @action('设置流程输入参数描述')
    async setDescription(description: string) {
        this.assign({ description });
        await this.update(undefined, {
            actionDesc: `设置流程"${this.process.name}"的输入参数"${this.name}"的描述为"${description}"`,
        });
    }
    /**
     * 设置流程输入参数的数据类型
     * @param $ref 数据类型
     */
    @action('设置流程输入参数的数据类型')
    async setDataType(schema: Schema) {
        // 用于update失败后还原数据
        const originalData = {
            schema: Object.assign({}, this.schema),
            defaultValue: this.defaultValue,
        };
        Object.assign(this.schema, {
            $ref: undefined, //引用到Entity或者structure
            type: undefined,
            format: undefined,
            typeInstantiation: undefined,
        });
        Object.assign(this.schema, schema);
        this.assign({ defaultValue: getBasicTypeDefaultValue() });
        try {
            await this.update(undefined, {
                actionDesc: `设置流程"${this.process.name}"的输入参数"${this.name}"的数据类型为"${this.schema.typeKey}"`,
            });
            this.genSchemaChildren();
            this.process.checkType();
        } catch (err) {
            this.assign(originalData);
            config.defaultApp?.emit('saved');
            throw err;
        }
    }
    /**
     * 设置流程输入参数的默认值
     * @param defaultValue 默认值
     */
    @action('设置流程输入参数的默认值')
    async setDefaultValue(defaultValue: string) {
        this.assign({ defaultValue });
        await this.update(undefined, {
            actionDesc: `设置流程"${this.process.name}"的输入参数"${this.name}"的默认值为"${defaultValue}"`,
        });
    }
    /**
     * 查找schema 顶点被引用的流程输入参数顶点列表
     */
    async getSchemaUsage() {
        if (this.schema) {
            const schemaId = this.schema.type === 'genericType' ? this.id : this.schema.id;
            const result = await schemaService.getSchemaUsage({
                query: {
                    schemaId,
                    valSource: 'process_param',
                },
            });
            return result;
        }
    }
    /**
     * 流程接口会自动根据输入输出参数变更，更新流程接口的输入输出
     */
    async updateInterface() {
        const { launchProcessInterface } = this.process;
        if (launchProcessInterface) {
            const ifce = await processService.getInterface({ path: { id: launchProcessInterface.id } });
            const { logic } = launchProcessInterface;
            const oldParams = logic?.params || [];
            const newParams = (ifce?.logic?.params || []).map((param: Param) => {
                const existParam = oldParams.find((oldParam: Param) => oldParam.id === param.id);
                if (existParam) {
                    existParam.assign(Param.from(param, logic));
                    return existParam;
                }
                return Param.from(param, logic);
            });
            logic.assign({ params: newParams });
        }
    }
    /**
     * 从后端 JSON 生成规范的 ProcessParam 对象
     */
    public static from(source: any, process: Process | Logic) {
        convert2RefType(source.schema);
        source.process = process;
        source.code = `ID_${process.id}.ID_${source.id}`;
        source.parentId = process.id;
        const processParam = new ProcessParam(source);
        processParam.genSchemaChildren();
        return processParam;
    }
}

export default ProcessParam;
