/**
 * 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 { ActionAlertActionType, ActionAlertType, } from '@finos/legend-application';
import { GRAPH_MANAGER_EVENT, EngineError, GraphBuilderError, reportGraphAnalytics, INTERNAL__UnknownElement, } from '@finos/legend-graph';
import { assertErrorThrown, LogEvent, ActionState, StopWatch, assertNonNullable, filterByType, assertTrue, } from '@finos/legend-shared';
import { makeObservable, flow, flowResult, observable, action } from 'mobx';
import { GrammarTextEditorState } from './editor-state/GrammarTextEditorState.js';
import { ExplorerTreeState } from './ExplorerTreeState.js';
import { TextLocalChangesState } from './sidebar-state/LocalChangesState.js';
import { GraphCompilationOutcome } from './EditorGraphState.js';
import { GRAPH_EDITOR_MODE, PANEL_MODE } from './EditorConfig.js';
import { graph_dispose } from '../graph-modifier/GraphModifierHelper.js';
import { LegendStudioTelemetryHelper } from '../../__lib__/LegendStudioTelemetryHelper.js';
import { GraphEditorMode } from './GraphEditorMode.js';
import { ElementEditorState } from './editor-state/element-editor-state/ElementEditorState.js';
import { LEGEND_STUDIO_APP_EVENT } from '../../__lib__/LegendStudioEvent.js';
export var GRAMMAR_MODE_EDITOR_ACTION;
(function (GRAMMAR_MODE_EDITOR_ACTION) {
    GRAMMAR_MODE_EDITOR_ACTION["GO_TO_ELEMENT_DEFINITION"] = "go-to-element-definition";
})(GRAMMAR_MODE_EDITOR_ACTION || (GRAMMAR_MODE_EDITOR_ACTION = {}));
export class GraphEditGrammarModeState extends GraphEditorMode {
    grammarTextEditorState;
    generatedFile;
    constructor(editorStore) {
        super(editorStore);
        makeObservable(this, {
            grammarTextEditorState: observable,
            generatedFile: observable,
            setGeneratedFile: action,
            compileText: flow,
            goToElement: flow,
        });
        this.grammarTextEditorState = new GrammarTextEditorState(this.editorStore);
    }
    get headerLabel() {
        return 'Text Mode';
    }
    setGeneratedFile(val) {
        this.generatedFile = val;
    }
    *initialize(isFallback) {
        this.editorStore.localChangesState = new TextLocalChangesState(this.editorStore, this.editorStore.sdlcState);
        this.editorStore.graphState.clearProblems();
        this.editorStore.changeDetectionState.stop();
        try {
            const sourceInformationIndex = new Map();
            const entities = (yield this.editorStore.graphManagerState.graphManager.pureCodeToEntities(this.grammarTextEditorState.graphGrammarText, {
                sourceInformationIndex,
            }));
            this.grammarTextEditorState.setSourceInformationIndex(sourceInformationIndex);
            //Include the UnknownPackageableElements when sending to compute local changes
            //Otherwise, they get deleted because they dont exist in the graphGrammarText
            const unknownEntities = this.getUnknownPackageableElementsAsEntities();
            entities.push(...unknownEntities);
            yield flowResult(this.editorStore.changeDetectionState.computeLocalChangesInTextMode(entities));
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.logService.warn(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error);
        }
        if (isFallback?.isCompilationFailure ||
            isFallback?.isGraphBuildFailure ||
            isFallback?.useStoredEntities) {
            yield flowResult(this.globalCompile({
                ignoreBlocking: true,
                suppressCompilationFailureMessage: true,
            }));
        }
        // navigate to the currently opened element immediately after entering text mode editor
        if (this.editorStore.tabManagerState.currentTab instanceof ElementEditorState) {
            const sourceInformation = this.grammarTextEditorState.sourceInformationIndex.get(this.editorStore.tabManagerState.currentTab.element.path);
            if (sourceInformation) {
                this.grammarTextEditorState.setForcedCursorPosition({
                    lineNumber: sourceInformation.startLine,
                    column: 0,
                });
            }
        }
    }
    *goToElement(elementPath) {
        try {
            const sourceInformationIndex = new Map();
            (yield this.editorStore.graphManagerState.graphManager.pureCodeToEntities(this.grammarTextEditorState.graphGrammarText, {
                sourceInformationIndex,
            }));
            this.grammarTextEditorState.setSourceInformationIndex(sourceInformationIndex);
            const sourceInformation = this.grammarTextEditorState.sourceInformationIndex.get(elementPath);
            assertNonNullable(sourceInformation, `No definition found for current element in current grammar. Element may not exist of be defined in dependencies`);
            this.grammarTextEditorState.setForcedCursorPosition({
                lineNumber: sourceInformation.startLine,
                column: 0,
            });
            this.editorStore.applicationStore.logService.info(LogEvent.create(LEGEND_STUDIO_APP_EVENT.TEXT_MODE_ACTION_KEYBOARD_SHORTCUT_GO_TO_DEFINITION__SUCCESS));
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.notificationService.notifyError(`Unable to go to element ${elementPath}: ${error.message}`);
            this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.TEXT_MODE_ACTION_KEYBOARD_SHORTCUT_GO_TO_DEFINITION__ERROR), error);
        }
    }
    *compileText(options, report) {
        return (yield this.editorStore.graphManagerState.graphManager.compileText(this.grammarTextEditorState.graphGrammarText, this.editorStore.graphManagerState.graph, options));
    }
    getCurrentGraphHash() {
        return this.grammarTextEditorState.currentTextGraphHash;
    }
    getUnknownPackageableElementsAsEntities() {
        return this.editorStore.graphManagerState.graph.allOwnElements.filter(filterByType(INTERNAL__UnknownElement));
    }
    *addElement(element, packagePath, openAfterCreate) {
        return;
    }
    *deleteElement(element) {
        return;
    }
    *renameElement(element, newPath) {
        return;
    }
    getGraphTextInputOption() {
        assertTrue(this.editorStore.graphState.mostRecentCompilationOutcome ===
            GraphCompilationOutcome.SUCCEEDED, 'Please ensure compilation has succeeded before proceeding');
        return {
            graphGrammar: this.grammarTextEditorState.graphGrammarText,
        };
    }
    get mode() {
        return GRAPH_EDITOR_MODE.GRAMMAR_TEXT;
    }
    /**
     * Creates a new explorer tree state when compiling in text mode. It resets the explorer state properly
     * after the new graph is built. It tries to maintain the explorer state similar to what it was before compilation.
     * To achieve that we store node ids of the opened nodes before creating a new explorer state. After creating a
     * new state we open the nodes which were opened before so that user see the same explorer state as before.
     */
    reprocessExplorerTreeInTextMode() {
        const mainTreeOpenedNodeIds = this.editorStore.explorerTreeState.treeData
            ? Array.from(this.editorStore.explorerTreeState.treeData.nodes.values())
                .filter((node) => node.isOpen)
                .map((node) => node.id)
            : [];
        const generationTreeOpenedNodeIds = this.editorStore.explorerTreeState
            .generationTreeData
            ? Array.from(this.editorStore.explorerTreeState.generationTreeData.nodes.values())
                .filter((node) => node.isOpen)
                .map((node) => node.id)
            : [];
        // Storing dependencyTree, filegenerationTree, systemTree as is as they don't
        // hold any reference to actual graph
        const systemTreeData = this.editorStore.explorerTreeState.systemTreeData;
        const dependencyTreeData = this.editorStore.explorerTreeState.dependencyTreeData;
        const selectedNodeId = this.editorStore.explorerTreeState.selectedNode?.id;
        this.editorStore.explorerTreeState = new ExplorerTreeState(this.editorStore);
        this.editorStore.explorerTreeState.systemTreeData = systemTreeData;
        this.editorStore.explorerTreeState.dependencyTreeData = dependencyTreeData;
        this.editorStore.explorerTreeState.buildTreeInTextMode();
        this.editorStore.explorerTreeState.openExplorerTreeNodes(mainTreeOpenedNodeIds, generationTreeOpenedNodeIds, selectedNodeId);
    }
    *updateGraphAndApplication(entities) {
        const startTime = Date.now();
        this.editorStore.graphState.isUpdatingApplication = true;
        this.editorStore.graphState.isUpdatingGraph = true;
        try {
            const newGraph = this.editorStore.graphManagerState.createNewGraph();
            yield flowResult(this.editorStore.graphState.rebuildDependencies(newGraph));
            yield flowResult(graph_dispose(this.editorStore.graphManagerState.graph));
            const graphBuildState = ActionState.create();
            yield this.editorStore.graphManagerState.graphManager.buildLightGraph(newGraph, entities, graphBuildState, {
                TEMPORARY__preserveSectionIndex: this.editorStore.applicationStore.config.options
                    .TEMPORARY__preserveSectionIndex,
                strict: this.editorStore.graphState.enableStrictMode,
            });
            this.editorStore.graphManagerState.graph = newGraph;
            // NOTE: here we don't want to modify the current graph build state directly
            // instead, we quietly run this in the background and then sync it with the current build state
            this.editorStore.graphManagerState.graphBuildState.sync(graphBuildState);
            this.reprocessExplorerTreeInTextMode();
            this.editorStore.applicationStore.logService.info(LogEvent.create(GRAPH_MANAGER_EVENT.UPDATE_AND_REBUILD_GRAPH__SUCCESS), '[TOTAL]', Date.now() - startTime, 'ms');
            this.editorStore.graphState.isUpdatingGraph = false;
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.GRAPH_BUILDER_FAILURE), error);
            this.editorStore.graphState.isUpdatingGraph = false;
            if (error instanceof GraphBuilderError) {
                this.editorStore.applicationStore.notificationService.notifyError(`Can't build graph: ${error.message}`);
            }
        }
        finally {
            this.editorStore.graphState.isUpdatingApplication = false;
            this.editorStore.applicationStore.alertService.setBlockingAlert(undefined);
        }
    }
    // TODO: when we support showing multiple notifications, we can take this `suppressCompilationFailureMessage` out as
    // we can show the transition between form mode and text mode warning and the compilation failure warning at the same time
    *globalCompile(options) {
        if (!options?.ignoreBlocking &&
            this.editorStore.graphState.checkIfApplicationUpdateOperationIsRunning()) {
            this.editorStore.graphState.setMostRecentCompilationOutcome(GraphCompilationOutcome.SKIPPED);
            return;
        }
        const stopWatch = new StopWatch();
        const report = reportGraphAnalytics(this.editorStore.graphManagerState.graph);
        LegendStudioTelemetryHelper.logEvent_TextCompilationLaunched(this.editorStore.applicationStore.telemetryService);
        const currentGraphHash = this.getCurrentGraphHash();
        try {
            this.editorStore.graphState.isRunningGlobalCompile = true;
            this.editorStore.graphState.clearProblems();
            if (options?.openConsole) {
                this.editorStore.setActivePanelMode(PANEL_MODE.CONSOLE);
            }
            const compilationResult = (yield flowResult(this.compileText({}, report)));
            const entities = compilationResult.entities;
            //Include the UnknownPackageableElements when updating graph and sending to compute local changes
            //Otherwise, they get deleted because they dont exist in the CompilationResult
            const unknownEntities = this.getUnknownPackageableElementsAsEntities();
            entities.push(...unknownEntities);
            this.editorStore.graphState.setMostRecentCompilationGraphHash(currentGraphHash);
            this.editorStore.graphState.warnings = compilationResult.warnings
                ? this.editorStore.graphState.TEMPORARY__removeDependencyProblems(compilationResult.warnings)
                : [];
            if (!options?.disableNotificationOnSuccess) {
                if (this.editorStore.graphState.warnings.length) {
                    this.editorStore.applicationStore.notificationService.notifyWarning(`Compilation succeeded with warnings`);
                }
                else {
                    if (!options?.disableNotificationOnSuccess) {
                        this.editorStore.applicationStore.notificationService.notifySuccess('Compiled successfully');
                    }
                }
            }
            this.grammarTextEditorState.setSourceInformationIndex(compilationResult.sourceInformationIndex);
            yield flowResult(this.updateGraphAndApplication(entities));
            // Remove `SectionIndex when computing changes in text mode as engine after
            // transforming grammarToJson would return `SectionIndex` which is not
            // required to do change detection.
            yield flowResult(this.editorStore.changeDetectionState.computeLocalChangesInTextMode(this.editorStore.graphManagerState.graphManager.getElementEntities(entities)));
            this.editorStore.graphState.setMostRecentCompilationOutcome(GraphCompilationOutcome.SUCCEEDED);
            report.timings =
                this.editorStore.applicationStore.timeService.finalizeTimingsRecord(stopWatch, report.timings);
            LegendStudioTelemetryHelper.logEvent_TextCompilationSucceeded(this.editorStore.applicationStore.telemetryService, report);
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.graphState.setMostRecentCompilationGraphHash(currentGraphHash);
            let detail = undefined;
            if (error instanceof EngineError) {
                this.editorStore.graphState.error = error;
                if (error.sourceInformation) {
                    this.grammarTextEditorState.setForcedCursorPosition({
                        lineNumber: error.sourceInformation.startLine,
                        column: error.sourceInformation.startColumn,
                    });
                }
                detail = error.trace;
            }
            if (!this.editorStore.applicationStore.notificationService.notification ||
                !options?.suppressCompilationFailureMessage) {
                this.editorStore.applicationStore.notificationService.notifyWarning(`Compilation failed: ${error.message}`, detail);
            }
            this.editorStore.graphState.setMostRecentCompilationOutcome(GraphCompilationOutcome.FAILED);
        }
        finally {
            this.editorStore.graphState.isRunningGlobalCompile = false;
        }
    }
    goToProblem(problem) {
        // NOTE: in text mode, we allow click to go to position even when the problems might already be stale
        if (problem.sourceInformation) {
            this.grammarTextEditorState.setForcedCursorPosition({
                lineNumber: problem.sourceInformation.startLine,
                column: problem.sourceInformation.startColumn,
            });
        }
    }
    *onLeave(fallbackOptions) {
        if (!fallbackOptions) {
            this.editorStore.graphState.isApplicationLeavingGraphEditMode = true;
            this.editorStore.graphState.clearProblems();
            this.editorStore.applicationStore.alertService.setBlockingAlert({
                message: 'Compiling graph before leaving text mode...',
                showLoading: true,
            });
            const compilationResult = (yield flowResult(this.compileText(
            // surpress the modal to reveal error properly in the text editor
            // if the blocking modal is not dismissed, the edior will not be able to gain focus as modal has a focus trap
            // therefore, the editor will not be able to get the focus
            {
                onError: () => this.editorStore.applicationStore.alertService.setBlockingAlert(undefined),
            })));
            this.editorStore.graphState.setMostRecentCompilationOutcome(GraphCompilationOutcome.SUCCEEDED);
            this.editorStore.graphState.warnings = compilationResult.warnings
                ? this.editorStore.graphState.TEMPORARY__removeDependencyProblems(compilationResult.warnings)
                : [];
            const entities = compilationResult.entities;
            //Include the UnknownPackageableElements when updating graph
            //Otherwise, they get deleted because they dont exist in the CompilationResult
            const unknownEntities = this.getUnknownPackageableElementsAsEntities();
            entities.push(...unknownEntities);
            this.editorStore.graphState.compilationResultEntities = entities;
            this.editorStore.applicationStore.alertService.setBlockingAlert({
                message: 'Leaving text mode and rebuilding graph...',
                showLoading: true,
            });
        }
    }
    *handleCleanupFailure(error) {
        assertErrorThrown(error);
        this.editorStore.graphState.setMostRecentCompilationOutcome(GraphCompilationOutcome.FAILED);
        this.editorStore.graphState.setMostRecentCompilationGraphHash(this.getCurrentGraphHash());
        if (error instanceof EngineError && error.sourceInformation) {
            this.grammarTextEditorState.setForcedCursorPosition({
                lineNumber: error.sourceInformation.startLine,
                column: error.sourceInformation.startColumn,
            });
        }
        this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.COMPILATION_FAILURE), 'Compilation failed:', error);
        if (this.editorStore.graphManagerState.graphBuildState.hasFailed) {
            // TODO: when we support showing multiple notification, we can split this into 2 messages
            this.editorStore.applicationStore.notificationService.notifyWarning(`Can't build graph, please resolve compilation error before leaving text mode. Compilation failed with error: ${error.message}`);
        }
        else {
            this.editorStore.applicationStore.notificationService.notifyWarning(`Compilation failed: ${error.message}`);
            this.editorStore.applicationStore.alertService.setActionAlertInfo({
                message: 'Project is not in a compiled state',
                prompt: 'All changes made since the last time the graph was built successfully will be lost',
                type: ActionAlertType.CAUTION,
                actions: [
                    {
                        label: 'Discard Changes',
                        default: true,
                        type: ActionAlertActionType.PROCEED_WITH_CAUTION,
                        handler: () => {
                            flowResult(this.editorStore.switchModes(GRAPH_EDITOR_MODE.FORM, {
                                isCompilationFailure: true,
                            })).catch(this.editorStore.applicationStore.alertUnhandledError);
                        },
                    },
                    {
                        label: 'Stay',
                        default: true,
                        type: ActionAlertActionType.PROCEED,
                    },
                ],
            });
        }
    }
    *cleanupBeforeEntering(fallbackOptions) {
        if (fallbackOptions?.isGraphBuildFailure) {
            const editorGrammar = (yield this.editorStore.graphManagerState.graphManager.entitiesToPureCode(this.editorStore.changeDetectionState
                .workspaceLocalLatestRevisionState.entities, { pretty: true }));
            yield flowResult(this.grammarTextEditorState.setGraphGrammarText(editorGrammar));
        }
        else {
            //Exclude UnknownPackageableElements from GrammarText editor mode since they cant be tranformed to PureCode
            const graphGrammar = (yield this.editorStore.graphManagerState.graphManager.graphToPureCode(this.editorStore.graphManagerState.graph, { pretty: true, excludeUnknown: true }));
            yield flowResult(this.grammarTextEditorState.setGraphGrammarText(graphGrammar));
        }
        this.editorStore.applicationStore.alertService.setBlockingAlert(undefined);
    }
    openFileSystem_File(file) {
        this.setGeneratedFile(file);
    }
    openElement(element) {
        const sourceInformation = this.grammarTextEditorState.sourceInformationIndex.get(element.path);
        if (sourceInformation) {
            this.grammarTextEditorState.setForcedCursorPosition({
                lineNumber: sourceInformation.startLine,
                column: 0,
            });
        }
    }
}
//# sourceMappingURL=GraphEditGrammarModeState.js.map