/**
 * 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, flow, flowResult, makeObservable, observable } from 'mobx';
import { assertErrorThrown, AssertionError, LogEvent, IllegalStateError, StopWatch, } from '@finos/legend-shared';
import { generateViewProjectByGAVRoute, generateViewVersionRoute, generateViewRevisionRoute, generateViewProjectRoute, } from '../../__lib__/LegendStudioNavigation.js';
import { parseGAVCoordinates, } from '@finos/legend-storage';
import { ProjectConfiguration, Revision, RevisionAlias, Version, Workspace, } from '@finos/legend-server-sdlc';
import { LEGEND_STUDIO_APP_EVENT } from '../../__lib__/LegendStudioEvent.js';
import { DEFAULT_TAB_SIZE } from '@finos/legend-application';
import { resolveVersion, StoreProjectData } from '@finos/legend-server-depot';
import { ProjectVersionWorkflowManagerState, ProjectWorkflowManagerState, } from '../editor/sidebar-state/WorkflowManagerState.js';
import { GRAPH_MANAGER_EVENT, DependencyGraphBuilderError, GraphDataDeserializationError, GraphBuilderError, createGraphBuilderReport, LegendSDLC, } from '@finos/legend-graph';
import { GRAPH_EDITOR_MODE } from '../editor/EditorConfig.js';
import { LegendStudioTelemetryHelper } from '../../__lib__/LegendStudioTelemetryHelper.js';
import { payloadDebugger } from '../editor/panel-group/DevToolPanelState.js';
export class ProjectViewerStore {
    editorStore;
    currentRevision;
    latestVersion;
    revision;
    version;
    initialEntityPath;
    projectGAVCoordinates;
    workflowManagerState;
    constructor(editorStore) {
        makeObservable(this, {
            currentRevision: observable,
            latestVersion: observable,
            revision: observable,
            version: observable,
            projectGAVCoordinates: observable.ref,
            workflowManagerState: observable.ref,
            onLatestVersion: computed,
            onCurrentRevision: computed,
            initializeWithProjectInformation: flow,
            initializeWithGAV: flow,
            buildGraph: flow,
            initialize: flow,
        });
        this.editorStore = editorStore;
    }
    get onLatestVersion() {
        return Boolean(this.latestVersion && this.version && this.latestVersion === this.version);
    }
    get onCurrentRevision() {
        return Boolean(this.currentRevision &&
            this.revision &&
            this.currentRevision === this.revision);
    }
    /**
     * Since we don't dynamically change the route based on the currently opened element
     * We have to handle the following cases:
     *  1. if the element is found and then the user opens another element
     *  2. if the elemnt is not found
     * in either case, the most suitable behavior at the moment is to internalize/swallow up the entity path param
     */
    internalizeEntityPath(params) {
        const { gav, projectId, revisionId, versionId, entityPath } = params;
        if (entityPath) {
            this.initialEntityPath = entityPath;
            if (projectId) {
                this.editorStore.applicationStore.navigationService.navigator.updateCurrentLocation(versionId
                    ? generateViewVersionRoute(projectId, versionId)
                    : revisionId
                        ? generateViewRevisionRoute(projectId, revisionId)
                        : generateViewProjectRoute(projectId));
            }
            else if (gav) {
                const { groupId, artifactId, versionId: _versionId, } = parseGAVCoordinates(gav);
                this.editorStore.applicationStore.navigationService.navigator.updateCurrentLocation(generateViewProjectByGAVRoute(groupId, artifactId, _versionId));
            }
        }
    }
    /**
     * Initialize the graph by fetching project information from the SDLC server.
     */
    *initializeWithProjectInformation(projectId, versionId, revisionId) {
        const stopWatch = new StopWatch();
        // fetch project informations
        this.editorStore.initState.setMessage(`Fetching project information...`);
        yield flowResult(this.editorStore.sdlcState.fetchCurrentProject(projectId));
        const stubWorkspace = new Workspace();
        stubWorkspace.projectId = projectId;
        stubWorkspace.workspaceId = '';
        this.editorStore.sdlcState.setCurrentWorkspace(stubWorkspace);
        // get current revision so we can show how "outdated" the `current view` of the project is
        this.currentRevision = Revision.serialization.fromJson((yield this.editorStore.sdlcServerClient.getRevision(this.editorStore.sdlcState.activeProject.projectId, undefined, RevisionAlias.CURRENT)));
        this.latestVersion = Version.serialization.fromJson((yield this.editorStore.sdlcServerClient.getLatestVersion(this.editorStore.sdlcState.activeProject.projectId)));
        // ensure only either version or revision is specified
        if (versionId && revisionId) {
            throw new IllegalStateError(`Can't have both version ID and revision ID specified for viewer mode`);
        }
        let graphBuildingMaterial;
        this.editorStore.initState.setMessage(undefined);
        // fetch entities
        stopWatch.record();
        this.editorStore.initState.setMessage(`Fetching entities...`);
        if (versionId && !revisionId) {
            // get version info if a version is specified
            this.version =
                versionId !== this.latestVersion.id.id
                    ? Version.serialization.fromJson((yield this.editorStore.sdlcServerClient.getVersion(this.editorStore.sdlcState.activeProject.projectId, versionId)))
                    : this.latestVersion;
            graphBuildingMaterial = (yield Promise.all([
                this.editorStore.sdlcServerClient.getEntitiesByVersion(this.editorStore.sdlcState.activeProject.projectId, versionId),
                this.editorStore.sdlcServerClient.getConfigurationByVersion(this.editorStore.sdlcState.activeProject.projectId, versionId),
            ]));
        }
        else if (revisionId && !versionId) {
            // get revision info if a revision is specified
            this.revision =
                revisionId !== this.currentRevision.id
                    ? Revision.serialization.fromJson((yield this.editorStore.sdlcServerClient.getRevision(this.editorStore.sdlcState.activeProject.projectId, undefined, revisionId)))
                    : this.currentRevision;
            graphBuildingMaterial = (yield Promise.all([
                this.editorStore.sdlcServerClient.getEntitiesByRevision(this.editorStore.sdlcState.activeProject.projectId, undefined, revisionId),
                this.editorStore.sdlcServerClient.getConfigurationByRevision(this.editorStore.sdlcState.activeProject.projectId, undefined, revisionId),
            ]));
        }
        // if no revision ID or version ID is specified, we will just get the project HEAD
        else if (!revisionId && !versionId) {
            graphBuildingMaterial = (yield Promise.all([
                this.editorStore.sdlcServerClient.getEntities(this.editorStore.sdlcState.activeProject.projectId, undefined),
                this.editorStore.sdlcServerClient.getConfiguration(this.editorStore.sdlcState.activeProject.projectId, undefined),
            ]));
        }
        else {
            throw new IllegalStateError(`Can't initialize viewer when both 'verisonId' and 'revisionId' are provided`);
        }
        this.editorStore.initState.setMessage(undefined);
        stopWatch.record(GRAPH_MANAGER_EVENT.FETCH_GRAPH_ENTITIES__SUCCESS);
        // fetch project configuration
        const projectConfiguration = ProjectConfiguration.serialization.fromJson(graphBuildingMaterial[1]);
        this.editorStore.projectConfigurationEditorState.setProjectConfiguration(projectConfiguration);
        this.editorStore.projectConfigurationEditorState.setOriginalProjectConfiguration(projectConfiguration);
        // fetch project versions
        yield Promise.all([
            this.editorStore.sdlcState.fetchProjectVersions(),
            this.editorStore.sdlcState.fetchPublishedProjectVersions(),
            this.editorStore.sdlcState.fetchAuthorizedActions(),
        ]);
        // fetch entities
        const entities = graphBuildingMaterial[0];
        this.editorStore.changeDetectionState.workspaceLocalLatestRevisionState.setEntities(entities);
        // fetch dependencies
        this.editorStore.graphManagerState.dependenciesBuildState.setMessage(`Fetching dependencies...`);
        const dependencyEntitiesIndex = (yield this.editorStore.graphState.getIndexedDependencyEntities());
        stopWatch.record(GRAPH_MANAGER_EVENT.FETCH_GRAPH_DEPENDENCIES__SUCCESS);
        return {
            entities,
            dependencyEntitiesIndex,
        };
    }
    /**
     * Initialize the viewer store given GAV coordinate of a project.
     * This flow is different than the SDLC flow as we need to fetch the project
     * from Depot server here, where SDLC objects like project configurations
     * are not available.
     */
    *initializeWithGAV(groupId, artifactId, versionId) {
        const stopWatch = new StopWatch();
        // fetch project data
        this.editorStore.initState.setMessage(`Fetching project data...`);
        const project = StoreProjectData.serialization.fromJson((yield flowResult(this.editorStore.depotServerClient.getProject(groupId, artifactId))));
        this.editorStore.initState.setMessage(undefined);
        // fetch entities
        stopWatch.record();
        this.editorStore.initState.setMessage(`Fetching entities...`);
        const entities = (yield this.editorStore.depotServerClient.getEntities(project, versionId));
        this.editorStore.initState.setMessage(undefined);
        stopWatch.record(GRAPH_MANAGER_EVENT.FETCH_GRAPH_ENTITIES__SUCCESS);
        // fetch dependencies
        this.editorStore.graphManagerState.dependenciesBuildState.setMessage(`Fetching dependencies...`);
        const dependencyEntitiesIndex = (yield flowResult(this.editorStore.depotServerClient.getIndexedDependencyEntities(project, versionId)));
        stopWatch.record(GRAPH_MANAGER_EVENT.FETCH_GRAPH_DEPENDENCIES__SUCCESS);
        return {
            entities,
            dependencyEntitiesIndex: dependencyEntitiesIndex,
        };
    }
    *buildGraph(entities, dependencyEntitiesIndex) {
        try {
            const stopWatch = new StopWatch();
            // initialize graph manager
            yield this.editorStore.graphManagerState.graphManager.initialize({
                env: this.editorStore.applicationStore.config.env,
                tabSize: DEFAULT_TAB_SIZE,
                clientConfig: {
                    baseUrl: this.editorStore.applicationStore.config.engineServerUrl,
                    queryBaseUrl: this.editorStore.applicationStore.config.engineQueryServerUrl,
                    enableCompression: true,
                    payloadDebugger,
                },
            }, {
                tracerService: this.editorStore.applicationStore.tracerService,
            });
            yield this.editorStore.graphManagerState.initializeSystem();
            // reset
            this.editorStore.graphManagerState.resetGraph();
            // build dependencies
            stopWatch.record();
            const dependencyManager = this.editorStore.graphManagerState.graphManager.createDependencyManager();
            this.editorStore.graphManagerState.graph.dependencyManager =
                dependencyManager;
            const dependency_buildReport = createGraphBuilderReport();
            yield this.editorStore.graphManagerState.graphManager.buildDependencies(this.editorStore.graphManagerState.coreModel, this.editorStore.graphManagerState.systemModel, dependencyManager, dependencyEntitiesIndex, this.editorStore.graphManagerState.dependenciesBuildState, {}, dependency_buildReport);
            // build graph
            const graph_buildReport = createGraphBuilderReport();
            yield this.editorStore.graphManagerState.graphManager.buildGraph(this.editorStore.graphManagerState.graph, entities, this.editorStore.graphManagerState.graphBuildState, {
                origin: this.projectGAVCoordinates
                    ? new LegendSDLC(this.projectGAVCoordinates.groupId, this.projectGAVCoordinates.artifactId, resolveVersion(this.projectGAVCoordinates.versionId))
                    : undefined,
            }, graph_buildReport);
            // report
            stopWatch.record(GRAPH_MANAGER_EVENT.INITIALIZE_GRAPH__SUCCESS);
            const graphBuilderReportData = {
                timings: this.editorStore.applicationStore.timeService.finalizeTimingsRecord(stopWatch),
                dependencies: dependency_buildReport,
                dependenciesCount: this.editorStore.graphManagerState.graph.dependencyManager
                    .numberOfDependencies,
                graph: graph_buildReport,
            };
            LegendStudioTelemetryHelper.logEvent_GraphInitializationSucceeded(this.editorStore.applicationStore.telemetryService, graphBuilderReportData);
            this.editorStore.applicationStore.logService.info(LogEvent.create(GRAPH_MANAGER_EVENT.INITIALIZE_GRAPH__SUCCESS), graphBuilderReportData);
            // fetch available editor configurations
            yield Promise.all([
                this.editorStore.graphState.graphGenerationState.globalFileGenerationState.fetchAvailableFileGenerationDescriptions(),
                this.editorStore.graphState.graphGenerationState.externalFormatState.fetchExternalFormatDescriptions(),
                this.editorStore.graphState.graphGenerationState.externalFormatState.fetchExternalFormatDescriptions(),
                this.editorStore.graphState.fetchAvailableFunctionActivatorConfigurations(),
            ]);
            return true;
        }
        catch (error) {
            assertErrorThrown(error);
            // if graph builder fails, we fall back to text-mode
            this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.GRAPH_BUILDER_FAILURE), error);
            if (error instanceof DependencyGraphBuilderError) {
                // no recovery if dependency models cannot be built, this makes assumption that all dependencies models are compiled successfully
                // TODO: we might want to handle this more gracefully when we can show people the dependency model element in the future
                this.editorStore.applicationStore.notificationService.notifyError(`Can't initialize dependency models. Error: ${error.message}`);
                this.editorStore.applicationStore.alertService.setBlockingAlert({
                    message: `Can't initialize dependencies`,
                    prompt: 'Please use editor to better invesigate the issue',
                });
            }
            else if (error instanceof GraphDataDeserializationError) {
                // if something goes wrong with de-serialization, we can't really do anything but to alert
                this.editorStore.applicationStore.notificationService.notifyError(`Can't deserialize graph. Error: ${error.message}`);
                this.editorStore.applicationStore.alertService.setBlockingAlert({
                    message: `Can't deserialize graph`,
                    prompt: 'Please use editor to better invesigate the issue',
                });
            }
            else if (error instanceof GraphBuilderError) {
                // TODO: we should split this into 2 notifications when we support multiple notifications
                this.editorStore.applicationStore.notificationService.notifyError(`Can't build graph. Redirected to text mode for debugging. Error: ${error.message}`);
                this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.GRAPH_BUILDER_FAILURE), error);
                yield flowResult(this.editorStore.switchModes(GRAPH_EDITOR_MODE.GRAMMAR_TEXT, {
                    isGraphBuildFailure: true,
                }));
                if (this.editorStore.graphEditorMode.mode === GRAPH_EDITOR_MODE.FORM) {
                    // nothing we can do here so we will just block the user
                    this.editorStore.applicationStore.alertService.setBlockingAlert({
                        message: `Can't compose Pure code from graph models`,
                        prompt: 'Please use editor to better invesigate the issue',
                    });
                    return false;
                }
            }
            else {
                this.editorStore.applicationStore.notificationService.notifyError(error);
            }
            return false;
        }
    }
    *initialize(params) {
        if (!this.editorStore.initState.isInInitialState) {
            return;
        }
        const { gav, projectId } = params;
        this.editorStore.initState.inProgress();
        const onLeave = (hasBuildSucceeded) => {
            this.editorStore.initState.complete(hasBuildSucceeded);
        };
        try {
            let graphBuilderMaterial;
            if (projectId) {
                graphBuilderMaterial = (yield flowResult(this.initializeWithProjectInformation(projectId, params.versionId, params.revisionId)));
            }
            else if (gav) {
                this.projectGAVCoordinates = parseGAVCoordinates(gav);
                const { groupId, artifactId, versionId } = this.projectGAVCoordinates;
                graphBuilderMaterial = (yield flowResult(this.initializeWithGAV(groupId, artifactId, versionId)));
            }
            else {
                throw new IllegalStateError(`Can't initialize viewer when neither 'projectId' nor 'gav' is provided`);
            }
            const graphBuilderResult = (yield flowResult(this.buildGraph(graphBuilderMaterial.entities, graphBuilderMaterial.dependencyEntitiesIndex)));
            if (!graphBuilderResult) {
                onLeave(false);
                return;
            }
            // generate
            // NOTE: if we fetch the entities from a published project
            // there is no need to generate since the generated elements are already included
            if (!gav) {
                this.editorStore.initState.setMessage(`Generating elements...`);
                if (this.editorStore.graphManagerState.graph.ownGenerationSpecifications
                    .length) {
                    yield flowResult(this.editorStore.graphState.graphGenerationState.globalGenerate());
                }
                this.editorStore.initState.setMessage(undefined);
            }
            // build explorer tree
            this.editorStore.explorerTreeState.buildImmutableModelTrees();
            this.editorStore.explorerTreeState.build();
            // open element if provided an element path
            if (this.editorStore.graphManagerState.graphBuildState.hasSucceeded &&
                this.editorStore.explorerTreeState.buildState.hasCompleted &&
                this.initialEntityPath) {
                try {
                    this.editorStore.graphEditorMode.openElement(this.editorStore.graphManagerState.graph.getElement(this.initialEntityPath));
                }
                catch {
                    const elementPath = this.initialEntityPath;
                    this.initialEntityPath = undefined;
                    throw new AssertionError(`Can't find element with path '${elementPath}'`);
                }
            }
            // initialize workflow manager
            // NOTE: We will not show workflow viewer when `GAV` coordinates are provided
            // as we don't know which sdlc instance to fetch from.
            // Revision will be supported once `SDLC` adds the workflow apis.
            if (this.version) {
                this.workflowManagerState = new ProjectVersionWorkflowManagerState(this.editorStore, this.editorStore.sdlcState, this.version);
            }
            else if (!this.projectGAVCoordinates && !this.revision) {
                this.workflowManagerState = new ProjectWorkflowManagerState(this.editorStore, this.editorStore.sdlcState);
            }
            onLeave(true);
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE), error);
            this.editorStore.applicationStore.notificationService.notifyError(error);
            onLeave(false);
        }
    }
}
//# sourceMappingURL=ProjectViewerStore.js.map