/**
 * Copyright (c) 2020-present, Goldman Sachs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { computed, observable, action, makeObservable, flow, flowResult, override, } from 'mobx';
import { assertErrorThrown, LogEvent, guaranteeType, assertType, StopWatch, filterByType, assertTrue, ActionState, } from '@finos/legend-shared';
import { ElementEditorState } from './ElementEditorState.js';
import { GRAPH_MANAGER_EVENT, LAMBDA_PIPE, ParserError, ConcreteFunctionDefinition, RawLambda, buildSourceInformationSourceId, isStubbed_PackageableElement, reportGraphAnalytics, buildLambdaVariableExpressions, VariableExpression, observe_ValueSpecification, generateFunctionPrettyName, RawVariableExpression, CodeCompletionResult, } from '@finos/legend-graph';
import { ExecutionPlanState, LineageState, LambdaEditorState, LambdaParameterState, LambdaParametersState, PARAMETER_SUBMIT_ACTION, QUERY_BUILDER_EVENT, QueryBuilderTelemetryHelper, buildExecutionParameterValues, getExecutionQueryFromRawLambda, getRawLambdaForLetFuncs, } from '@finos/legend-query-builder';
import { FunctionActivatorState } from './FunctionActivatorState.js';
import { FunctionTestableState } from './function-activator/testable/FunctionTestableState.js';
import { openDataCube } from '../../data-cube/LegendStudioDataCubeHelper.js';
export var FUNCTION_EDITOR_TAB;
(function (FUNCTION_EDITOR_TAB) {
    FUNCTION_EDITOR_TAB["DEFINITION"] = "DEFINITION";
    FUNCTION_EDITOR_TAB["TAGGED_VALUES"] = "TAGGED_VALUES";
    FUNCTION_EDITOR_TAB["STEREOTYPES"] = "STEREOTYPES";
    FUNCTION_EDITOR_TAB["TEST_SUITES"] = "TEST_SUITES";
    FUNCTION_EDITOR_TAB["LAMBDAS"] = "LAMBDAS";
})(FUNCTION_EDITOR_TAB || (FUNCTION_EDITOR_TAB = {}));
export class FunctionDefinitionEditorState extends LambdaEditorState {
    editorStore;
    functionElement;
    convertingGrammarToProtocolAction = ActionState.create();
    isConvertingFunctionBodyToString = false;
    constructor(functionElement, editorStore) {
        super('', LAMBDA_PIPE, {
            typeAheadEnabled: editorStore.applicationStore.config.options.typeAheadEnabled,
        });
        makeObservable(this, {
            functionElement: observable,
            isConvertingFunctionBodyToString: observable,
            convertingGrammarToProtocolAction: observable,
        });
        this.functionElement = functionElement;
        this.editorStore = editorStore;
    }
    get lambdaId() {
        return buildSourceInformationSourceId([this.functionElement.path]);
    }
    *convertLambdaGrammarStringToObject() {
        if (this.lambdaString) {
            try {
                this.convertingGrammarToProtocolAction.inProgress();
                const lambda = (yield this.editorStore.graphManagerState.graphManager.pureCodeToLambda(this.fullLambdaString, this.lambdaId));
                this.setParserError(undefined);
                this.functionElement.expressionSequence = lambda.body;
            }
            catch (error) {
                assertErrorThrown(error);
                if (error instanceof ParserError) {
                    this.setParserError(error);
                }
                this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error);
            }
            finally {
                this.convertingGrammarToProtocolAction.complete();
            }
        }
        else {
            this.clearErrors();
            this.functionElement.expressionSequence = [];
        }
    }
    *convertLambdaObjectToGrammarString(options) {
        if (!isStubbed_PackageableElement(this.functionElement)) {
            this.isConvertingFunctionBodyToString = true;
            try {
                const lambdas = new Map();
                const functionLamba = new RawLambda([], this.functionElement.expressionSequence);
                lambdas.set(this.lambdaId, functionLamba);
                const isolatedLambdas = (yield this.editorStore.graphManagerState.graphManager.lambdasToPureCode(lambdas, options?.pretty));
                const grammarText = isolatedLambdas.get(this.lambdaId);
                if (grammarText) {
                    let grammarString = this.extractLambdaString(grammarText);
                    if (this.functionElement.expressionSequence.length > 1 &&
                        grammarString.endsWith('}')) {
                        // The lambda object to string converter wraps the lambda inside a '{}' in the case where there are more than one expressions inside the function
                        // causing a parsing error. To handle this we extract only whats inside the '{}' and add ';' to avoid error.
                        grammarString = grammarString.slice(0, -1);
                        grammarString = `${grammarString.endsWith('\n')
                            ? grammarString.slice(0, -1)
                            : grammarString};`;
                    }
                    this.setLambdaString(grammarString);
                }
                else {
                    this.setLambdaString('');
                }
                // `firstLoad` flag is used in the first rendering of the function editor (in a `useEffect`)
                // This flag helps block editing while the JSON is converting to text and to avoid reseting parser/compiler error in reveal error
                if (!options?.firstLoad) {
                    this.clearErrors({
                        preserveCompilationError: options?.preserveCompilationError,
                    });
                }
                this.isConvertingFunctionBodyToString = false;
            }
            catch (error) {
                assertErrorThrown(error);
                this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error);
                this.isConvertingFunctionBodyToString = false;
            }
        }
        else {
            this.clearErrors();
            this.setLambdaString('');
        }
    }
    async getCodeComplete(input) {
        try {
            return (await this.editorStore.graphManagerState.graphManager.getCodeComplete(input, this.editorStore.graphManagerState.graph, undefined, {
                ignoreElements: [this.functionElement.path],
            }));
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error);
            return new CodeCompletionResult();
        }
    }
}
export class FunctionParametersState extends LambdaParametersState {
    functionEditorState;
    constructor(functionEditorState) {
        super();
        makeObservable(this, {
            parameterValuesEditorState: observable,
            parameterStates: observable,
            addParameter: action,
            removeParameter: action,
            openModal: action,
            build: action,
            setParameters: action,
        });
        this.functionEditorState = functionEditorState;
    }
    openModal(lambda) {
        this.parameterStates = this.build(lambda);
        this.parameterValuesEditorState.open(() => flowResult(this.functionEditorState.runFunc()).catch(this.functionEditorState.editorStore.applicationStore
            .alertUnhandledError), PARAMETER_SUBMIT_ACTION.RUN);
    }
    openDataCubeModal(query, element, editorStore) {
        this.parameterStates = this.build(query);
        this.parameterValuesEditorState.open(() => this.functionEditorState
            .openingDataCube(element, editorStore)
            .catch(this.functionEditorState.editorStore.applicationStore
            .alertUnhandledError), PARAMETER_SUBMIT_ACTION.RUN);
    }
    build(lambda) {
        const parameters = buildLambdaVariableExpressions(lambda, this.functionEditorState.editorStore.graphManagerState)
            .map((parameter) => observe_ValueSpecification(parameter, this.functionEditorState.editorStore.changeDetectionState
            .observerContext))
            .filter(filterByType(VariableExpression));
        const states = parameters.map((variable) => {
            const parmeterState = new LambdaParameterState(variable, this.functionEditorState.editorStore.changeDetectionState.observerContext, this.functionEditorState.editorStore.graphManagerState.graph);
            parmeterState.mockParameterValue();
            return parmeterState;
        });
        return states;
    }
}
export class FunctionEditorState extends ElementEditorState {
    functionDefinitionEditorState;
    activatorPromoteState;
    functionTestableEditorState;
    selectedTab;
    isRunningFunc = false;
    isGeneratingPlan = false;
    isGeneratingLineage = false;
    executionResult; // NOTE: stored as lossless JSON string
    executionPlanState;
    parametersState;
    lineageState;
    funcRunPromise = undefined;
    constructor(editorStore, element) {
        super(editorStore, element);
        makeObservable(this, {
            selectedTab: observable,
            isRunningFunc: observable,
            isGeneratingPlan: observable,
            isGeneratingLineage: observable,
            executionResult: observable,
            executionPlanState: observable,
            label: override,
            functionElement: computed,
            setSelectedTab: action,
            reprocess: action,
            setExecutionResult: action,
            setIsRunningFunc: action,
            runFunc: flow,
            generatePlan: flow,
            handleRunFunc: flow,
            cancelFuncRun: flow,
            updateFunctionWithQuery: flow,
            generateLineage: flow,
        });
        assertType(element, ConcreteFunctionDefinition, 'Element inside function editor state must be a function');
        this.selectedTab = FUNCTION_EDITOR_TAB.DEFINITION;
        this.functionDefinitionEditorState = new FunctionDefinitionEditorState(element, this.editorStore);
        this.activatorPromoteState = new FunctionActivatorState(this);
        this.executionPlanState = new ExecutionPlanState(this.editorStore.applicationStore, this.editorStore.graphManagerState);
        this.parametersState = new FunctionParametersState(this);
        this.functionTestableEditorState = new FunctionTestableState(this);
        this.lineageState = new LineageState(this.editorStore.applicationStore);
    }
    get label() {
        return generateFunctionPrettyName(this.functionElement, {
            fullPath: true,
            spacing: false,
        });
    }
    get functionElement() {
        return guaranteeType(this.element, ConcreteFunctionDefinition, 'Element inside function editor state must be a function');
    }
    get activators() {
        const allActivators = this.editorStore.graphManagerState.graph.functionActivators;
        return allActivators.filter((activator) => activator.function.value === this.element);
    }
    setSelectedTab(tab) {
        this.selectedTab = tab;
    }
    revealCompilationError(compilationError) {
        let revealed = false;
        try {
            if (compilationError.sourceInformation) {
                this.setSelectedTab(FUNCTION_EDITOR_TAB.DEFINITION);
                this.functionDefinitionEditorState.setCompilationError(compilationError);
                revealed = true;
            }
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.logService.warn(LogEvent.create(GRAPH_MANAGER_EVENT.COMPILATION_FAILURE), `Can't locate error`, error);
        }
        return revealed;
    }
    clearCompilationError() {
        this.functionDefinitionEditorState.setCompilationError(undefined);
    }
    *updateFunctionWithQuery(val) {
        const lambdaParam = val.parameters ? val.parameters : [];
        const parameters = lambdaParam
            .map((param) => this.editorStore.graphManagerState.graphManager.buildRawValueSpecification(param, this.editorStore.graphManagerState.graph))
            .map((rawValueSpec) => guaranteeType(rawValueSpec, RawVariableExpression));
        assertTrue(Array.isArray(val.body), `Query body expected to be a list of expressions`);
        this.functionElement.expressionSequence = val.body;
        this.functionElement.parameters = parameters;
        yield flowResult(this.functionDefinitionEditorState.convertLambdaObjectToGrammarString({
            pretty: true,
            firstLoad: true,
        }));
    }
    reprocess(newElement, editorStore) {
        const functionEditorState = new FunctionEditorState(editorStore, newElement);
        functionEditorState.selectedTab = this.selectedTab;
        return functionEditorState;
    }
    setIsRunningFunc(val) {
        this.isRunningFunc = val;
    }
    setExecutionResult = (executionResult) => {
        this.executionResult = executionResult;
    };
    setFuncRunPromise = (promise) => {
        this.funcRunPromise = promise;
    };
    get bodyExpressionSequence() {
        return new RawLambda(this.functionElement.parameters.map((parameter) => this.editorStore.graphManagerState.graphManager.serializeRawValueSpecification(parameter)), this.functionElement.expressionSequence);
    }
    *generatePlan(debug) {
        if (this.isGeneratingPlan) {
            return;
        }
        try {
            const expressionSequence = this.bodyExpressionSequence;
            this.isGeneratingPlan = true;
            let rawPlan;
            const stopWatch = new StopWatch();
            const report = reportGraphAnalytics(this.editorStore.graphManagerState.graph);
            if (debug) {
                QueryBuilderTelemetryHelper.logEvent_ExecutionPlanDebugLaunched(this.editorStore.applicationStore.telemetryService);
                const debugResult = (yield this.editorStore.graphManagerState.graphManager.debugExecutionPlanGeneration(expressionSequence, undefined, undefined, this.editorStore.graphManagerState.graph, undefined, report));
                rawPlan = debugResult.plan;
                this.executionPlanState.setDebugText(debugResult.debug);
            }
            else {
                QueryBuilderTelemetryHelper.logEvent_ExecutionPlanGenerationLaunched(this.editorStore.applicationStore.telemetryService);
                rawPlan =
                    (yield this.editorStore.graphManagerState.graphManager.generateExecutionPlan(expressionSequence, undefined, undefined, this.editorStore.graphManagerState.graph, undefined, report));
            }
            stopWatch.record();
            try {
                this.executionPlanState.setRawPlan(rawPlan);
                const plan = this.editorStore.graphManagerState.graphManager.buildExecutionPlan(rawPlan, this.editorStore.graphManagerState.graph);
                this.executionPlanState.initialize(plan);
            }
            catch {
                // do nothing
            }
            stopWatch.record(QUERY_BUILDER_EVENT.BUILD_EXECUTION_PLAN__SUCCESS);
            // report
            report.timings =
                this.editorStore.applicationStore.timeService.finalizeTimingsRecord(stopWatch, report.timings);
            if (debug) {
                QueryBuilderTelemetryHelper.logEvent_ExecutionPlanDebugSucceeded(this.editorStore.applicationStore.telemetryService, report);
            }
            else {
                QueryBuilderTelemetryHelper.logEvent_ExecutionPlanGenerationSucceeded(this.editorStore.applicationStore.telemetryService, report);
            }
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error);
            this.editorStore.applicationStore.notificationService.notifyError(error);
        }
        finally {
            this.isGeneratingPlan = false;
        }
    }
    async handleOpeningDataCube(element, editorStore) {
        const query = this.bodyExpressionSequence;
        const parameters = (query.parameters ?? []);
        if (parameters.length) {
            this.parametersState.openDataCubeModal(query, element, editorStore);
        }
        else {
            await openDataCube(element, editorStore);
        }
    }
    async openingDataCube(element, editorStore) {
        const params = buildExecutionParameterValues(this.parametersState.parameterStates, this.editorStore.graphManagerState);
        const letFuncsRawLambda = getRawLambdaForLetFuncs(this.parametersState.parameterStates, this.editorStore.graphManagerState);
        await openDataCube(element, editorStore, params, letFuncsRawLambda);
    }
    *handleRunFunc() {
        if (this.isRunningFunc) {
            return;
        }
        const expressionSequence = this.bodyExpressionSequence;
        const parameters = (expressionSequence.parameters ?? []);
        if (parameters.length) {
            this.parametersState.openModal(expressionSequence);
        }
        else {
            this.runFunc();
        }
    }
    *runFunc() {
        if (this.isRunningFunc) {
            return;
        }
        QueryBuilderTelemetryHelper.logEvent_QueryRunLaunched(this.editorStore.applicationStore.telemetryService);
        // assure that you are using the latest protocol
        try {
            yield flowResult(this.functionDefinitionEditorState.convertLambdaGrammarStringToObject()).catch(this.editorStore.applicationStore.alertUnhandledError);
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.notificationService.notifyError(error);
            return;
        }
        let promise;
        try {
            this.isRunningFunc = true;
            const stopWatch = new StopWatch();
            const report = reportGraphAnalytics(this.editorStore.graphManagerState.graph);
            promise = this.editorStore.graphManagerState.graphManager.runQuery(getExecutionQueryFromRawLambda(this.bodyExpressionSequence, this.parametersState.parameterStates, this.editorStore.graphManagerState), undefined, undefined, this.editorStore.graphManagerState.graph, {
                useLosslessParse: false,
                parameterValues: buildExecutionParameterValues(this.parametersState.parameterStates, this.editorStore.graphManagerState),
            }, report);
            this.setFuncRunPromise(promise);
            const result = (yield promise);
            if (this.funcRunPromise === promise) {
                this.setExecutionResult(result.executionResult);
                this.parametersState.setParameters([]);
                // report
                report.timings =
                    this.editorStore.applicationStore.timeService.finalizeTimingsRecord(stopWatch, report.timings);
                QueryBuilderTelemetryHelper.logEvent_QueryRunSucceeded(this.editorStore.applicationStore.telemetryService, report);
            }
        }
        catch (error) {
            // When user cancels the query by calling the cancelQuery api, it will throw an execution failure error.
            // For now, we don't want to notify users about this failure. Therefore we check to ensure the promise is still the same one.
            // When cancelled the query, we set the queryRunPromise as undefined.
            if (this.funcRunPromise === promise) {
                assertErrorThrown(error);
                this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error);
                this.editorStore.applicationStore.notificationService.notifyError(error);
            }
        }
        finally {
            this.isRunningFunc = false;
        }
    }
    *cancelFuncRun() {
        this.setIsRunningFunc(false);
        this.setFuncRunPromise(undefined);
        try {
            yield this.editorStore.graphManagerState.graphManager.cancelUserExecutions(true);
        }
        catch (error) {
            // Don't notify users about success or failure
            this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error);
        }
    }
    *generateLineage() {
        if (this.isGeneratingLineage) {
            return;
        }
        try {
            this.isGeneratingLineage = true;
            const expressionSequence = this.bodyExpressionSequence;
            const lineageRawData = (yield this.editorStore.graphManagerState.graphManager.generateLineage(expressionSequence, undefined, undefined, this.editorStore.graphManagerState.graph, undefined));
            const lineageData = this.editorStore.graphManagerState.graphManager.buildLineage(lineageRawData);
            this.lineageState.setLineageData(lineageData);
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.LINEAGE_GENERATION_FAILURE), error);
            this.editorStore.applicationStore.notificationService.notifyError(error);
        }
        finally {
            this.isGeneratingLineage = false;
        }
    }
}
//# sourceMappingURL=FunctionEditorState.js.map