/**
 * 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 { observable, action, flowResult, makeObservable, flow } from 'mobx';
import { LEGEND_STUDIO_APP_EVENT } from '../../__lib__/LegendStudioEvent.js';
import { assertErrorThrown, LogEvent, ActionState, IllegalStateError, UnsupportedOperationError, guaranteeNonNullable, guaranteeType, exactSearch, } from '@finos/legend-shared';
import { generateSetupRoute } from '../../__lib__/LegendStudioNavigation.js';
import { SANDBOX_SDLC_TAG, WorkspaceType, ImportReport, Project, Review, Workspace, Patch, isProjectSandbox, } from '@finos/legend-server-sdlc';
import { DEFAULT_TAB_SIZE, DEFAULT_TYPEAHEAD_SEARCH_LIMIT, DEFAULT_TYPEAHEAD_SEARCH_MINIMUM_SEARCH_LENGTH, } from '@finos/legend-application';
import { fetchProjectConfigurationStatus, ProjectConfigurationStatus, } from './ProjectConfigurationStatus.js';
import { GraphManagerState } from '@finos/legend-graph';
import { LegendStudioUserDataHelper, } from '../../__lib__/LegendStudioUserDataHelper.js';
export class WorkspaceSetupStore {
    applicationStore;
    sdlcServerClient;
    initState = ActionState.create();
    projects = [];
    currentProject;
    currentProjectConfigurationStatus;
    loadProjectsState = ActionState.create();
    createOrImportProjectState = ActionState.create();
    importProjectSuccessReport;
    showCreateProjectModal = false;
    engineInitializeState = ActionState.create();
    enginePromise;
    createSandboxProjectState = ActionState.create();
    sandboxProject = false;
    hasSandboxAccess;
    sandboxModal = false;
    loadSandboxState = ActionState.create();
    patches = [];
    loadPatchesState = ActionState.create();
    workspaces = [];
    currentWorkspace;
    loadWorkspacesState = ActionState.create();
    createWorkspaceState = ActionState.create();
    showCreateWorkspaceModal = false;
    graphManagerState;
    // Cached recents to make re-opening a project/workspace instantaneous.
    // NOTE: patch-based workspaces are intentionally excluded from this cache.
    recentProjects = [];
    recentWorkspaces = [];
    selectRecentProjectState = ActionState.create();
    constructor(applicationStore, sdlcServerClient) {
        makeObservable(this, {
            projects: observable,
            currentProject: observable,
            currentProjectConfigurationStatus: observable,
            importProjectSuccessReport: observable,
            showCreateProjectModal: observable,
            workspaces: observable,
            currentWorkspace: observable,
            loadSandboxState: observable,
            showCreateWorkspaceModal: observable,
            sandboxProject: observable,
            createSandboxProjectState: observable,
            engineInitializeState: observable,
            enginePromise: observable,
            sandboxModal: observable,
            hasSandboxAccess: observable,
            recentProjects: observable,
            recentWorkspaces: observable,
            setShowCreateProjectModal: action,
            setShowCreateWorkspaceModal: action,
            setImportProjectSuccessReport: action,
            setSandboxModal: action,
            changeWorkspace: action,
            resetProject: action,
            resetWorkspace: action,
            removeRecentProject: action,
            removeRecentWorkspace: action,
            clearRecents: action,
            initialize: flow,
            loadProjects: flow,
            loadSandboxProject: flow,
            changeProject: flow,
            createProject: flow,
            importProject: flow,
            createSandboxProject: flow,
            createWorkspace: flow,
            initializeEngine: flow,
            selectRecentProject: flow,
        });
        this.applicationStore = applicationStore;
        this.sdlcServerClient = sdlcServerClient;
        this.graphManagerState = new GraphManagerState(applicationStore.pluginManager, applicationStore.logService);
        this.recentProjects =
            LegendStudioUserDataHelper.workspaceSetup_getRecentProjects(applicationStore.userDataService);
        this.recentWorkspaces =
            LegendStudioUserDataHelper.workspaceSetup_getRecentWorkspaces(applicationStore.userDataService);
        if (this.supportsCreatingSandboxProject) {
            flowResult(this.initializeEngine()).catch(applicationStore.alertUnhandledError);
        }
    }
    get supportsCreatingSandboxProject() {
        return this.applicationStore.config.options
            .TEMPORARY__enableCreationOfSandboxProjects;
    }
    setShowCreateProjectModal(val) {
        this.showCreateProjectModal = val;
    }
    setShowCreateWorkspaceModal(val) {
        this.showCreateWorkspaceModal = val;
    }
    setImportProjectSuccessReport(importProjectSuccessReport) {
        this.importProjectSuccessReport = importProjectSuccessReport;
    }
    resetProject() {
        this.currentProject = undefined;
        this.patches = [];
        this.workspaces = [];
        this.currentWorkspace = undefined;
        this.applicationStore.navigationService.navigator.updateCurrentLocation(generateSetupRoute(undefined, undefined, undefined, undefined));
        this.currentProjectConfigurationStatus = undefined;
    }
    resetWorkspace() {
        this.currentWorkspace = undefined;
        if (this.currentProject) {
            this.applicationStore.navigationService.navigator.updateCurrentLocation(generateSetupRoute(this.currentProject.projectId, undefined, undefined, undefined));
        }
    }
    setSandboxModal(val) {
        this.sandboxModal = val;
    }
    // --- Recents -------------------------------------------------------------
    // NOTE: writes to the recents cache happen from the editor (see
    // `EditorStore.initialize`) at the moment a workspace is actually opened.
    // This store only reads the cache (to seed dropdowns) and prunes entries
    // that are discovered to be stale.
    removeRecentProject(projectId) {
        const updated = LegendStudioUserDataHelper.workspaceSetup_removeRecentProject(this.applicationStore.userDataService, projectId);
        this.recentProjects = updated.projects;
        this.recentWorkspaces = updated.workspaces;
    }
    removeRecentWorkspace(entry) {
        this.recentWorkspaces =
            LegendStudioUserDataHelper.workspaceSetup_removeRecentWorkspace(this.applicationStore.userDataService, entry);
    }
    clearRecents() {
        LegendStudioUserDataHelper.workspaceSetup_clearRecents(this.applicationStore.userDataService);
        this.recentProjects = [];
        this.recentWorkspaces = [];
    }
    /**
     * Fetches a project by id (used when the user picks a cached "recent"
     * project that may not be in the current search results) and switches to
     * it. If the project no longer exists, the entry is pruned from recents.
     *
     * NOTE: we deliberately don't short-circuit using the cached recent entry
     * here. Going through `getProject` keeps the prune-on-404 path intact, and
     * the cached metadata is already used elsewhere to make the UI feel fast
     * (dropdown stubs in `WorkspaceSetup.tsx` and tile labels in
     * `RecentWorkspacesPanel.tsx`).
     */
    *selectRecentProject(projectId) {
        this.selectRecentProjectState.inProgress();
        try {
            const project = Project.serialization.fromJson((yield this.sdlcServerClient.getProject(projectId)));
            yield flowResult(this.changeProject(project));
            this.selectRecentProjectState.pass();
        }
        catch (error) {
            assertErrorThrown(error);
            this.removeRecentProject(projectId);
            this.applicationStore.notificationService.notifyWarning(`Recent project could not be opened and was removed from recents`);
            this.selectRecentProjectState.fail();
        }
    }
    *createSandboxProject() {
        try {
            if (!this.hasSandboxAccess) {
                this.setSandboxModal(true);
                return;
            }
            // create sandbox project and pilot workspace
            this.applicationStore.alertService.setBlockingAlert({
                message: 'Creating sandbox project...',
                showLoading: true,
            });
            const sandboxProject = (yield this.graphManagerState.graphManager.createSandboxProject());
            this.applicationStore.alertService.setBlockingAlert({
                message: `Sandbox project ${sandboxProject.projectId} created. Creating default workspace...`,
                showLoading: true,
            });
            // Invalidate the cached sandbox info so loadSandboxProject re-fetches
            // and persists the newly-created project id instead of reusing the
            // stale "no sandbox yet" cache entry.
            LegendStudioUserDataHelper.workspaceSetup_clearSandboxInfo(this.applicationStore.userDataService);
            yield flowResult(this.loadSandboxProject());
            const sandbox = guaranteeType(this.sandboxProject, Project, 'Error retrieving sandbox project');
            const initialWorkspace = Workspace.serialization.fromJson((yield this.sdlcServerClient.createWorkspace(sandbox.projectId, undefined, 'myWorkspace', WorkspaceType.GROUP)));
            yield flowResult(this.changeProject(sandbox, {
                workspaceId: initialWorkspace.workspaceId,
                workspaceType: WorkspaceType.GROUP,
            }));
            this.applicationStore.alertService.setBlockingAlert(undefined);
            this.applicationStore.notificationService.notifySuccess(`Sandbox project with workspace created`);
        }
        catch (error) {
            assertErrorThrown(error);
            this.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.ENGINE_MANAGER_FAILURE), error);
            this.applicationStore.notificationService.notifyError(error);
        }
        finally {
            this.applicationStore.alertService.setBlockingAlert(undefined);
        }
    }
    *initialize(projectId, workspaceId, groupWorkspaceId) {
        if (!this.initState.isInInitialState) {
            return;
        }
        this.initState.inProgress();
        // TODO: when we genericize the way to initialize an application page
        this.applicationStore.assistantService.setIsHidden(false);
        try {
            if (projectId) {
                let project;
                try {
                    project = Project.serialization.fromJson((yield this.sdlcServerClient.getProject(projectId)));
                }
                catch {
                    this.removeRecentProject(projectId);
                    this.applicationStore.navigationService.navigator.updateCurrentLocation(generateSetupRoute(undefined, undefined));
                    this.initState.pass();
                    return;
                }
                yield flowResult(this.changeProject(project, workspaceId
                    ? { workspaceId: workspaceId, workspaceType: WorkspaceType.USER }
                    : groupWorkspaceId
                        ? {
                            workspaceId: groupWorkspaceId,
                            workspaceType: WorkspaceType.GROUP,
                        }
                        : undefined));
            }
            this.initState.pass();
        }
        catch (error) {
            assertErrorThrown(error);
            this.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE), error);
            this.applicationStore.notificationService.notifyError(error);
            this.initState.fail();
        }
    }
    *initializeEngine() {
        this.engineInitializeState.inProgress();
        try {
            const initPromise = this.graphManagerState.graphManager.initialize({
                env: this.applicationStore.config.env,
                tabSize: DEFAULT_TAB_SIZE,
                clientConfig: {
                    baseUrl: this.applicationStore.config.engineServerUrl,
                },
            }, {
                tracerService: this.applicationStore.tracerService,
                disableGraphConfiguration: true,
            });
            this.enginePromise = initPromise;
            yield initPromise;
            this.enginePromise = undefined;
            this.engineInitializeState.complete();
        }
        catch (error) {
            assertErrorThrown(error);
            this.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.ENGINE_MANAGER_FAILURE), error);
            this.engineInitializeState.complete();
        }
    }
    *loadProjects(searchText) {
        const isValidSearchString = searchText.length >= DEFAULT_TYPEAHEAD_SEARCH_MINIMUM_SEARCH_LENGTH;
        this.loadProjectsState.inProgress();
        try {
            this.projects = (yield this.sdlcServerClient.getProjects(undefined, 
            // We apply an exact search on the input text because we show exact searches with
            // custom selector. This avoids losing some results with the additional filtering.
            isValidSearchString ? exactSearch(searchText) : undefined, undefined, 
            // Sandbox projects are loaded via a separate, dedicated call (see
            // `loadSandboxProject`); exclude them here so they do not appear
            // in the main workspace setup project picker.
            [SANDBOX_SDLC_TAG], DEFAULT_TYPEAHEAD_SEARCH_LIMIT)).map((v) => Project.serialization.fromJson(v));
            this.loadProjectsState.pass();
        }
        catch (error) {
            assertErrorThrown(error);
            this.applicationStore.notificationService.notifyError(error);
            this.loadProjectsState.fail();
        }
    }
    *loadSandboxProject() {
        if (!this.supportsCreatingSandboxProject) {
            return;
        }
        this.sandboxProject = false;
        try {
            this.loadSandboxState.inProgress();
            if (this.enginePromise) {
                yield this.enginePromise;
            }
            const userId = this.sdlcServerClient.currentUser?.userId;
            // Fast path — if we have a recent, user-matching cache entry, use it to
            // avoid the `userHasPrototypeProjectAccess` graph manager call and the
            // sandbox-tag project search. We still hit SDLC once to confirm the
            // cached projectId is alive, but `getProject(id)` is cheaper than the
            // tagged search and self-invalidates on 404.
            if (userId) {
                const cached = LegendStudioUserDataHelper.workspaceSetup_getCachedSandboxInfo(this.applicationStore.userDataService, userId);
                if (cached) {
                    this.hasSandboxAccess = cached.hasAccess;
                    if (!cached.hasAccess) {
                        // No access, no project — nothing else to do.
                        this.sandboxProject = true;
                        this.loadSandboxState.pass();
                        return;
                    }
                    if (cached.projectId) {
                        try {
                            this.sandboxProject = Project.serialization.fromJson((yield this.sdlcServerClient.getProject(cached.projectId)));
                            this.loadSandboxState.pass();
                            return;
                        }
                        catch {
                            // Cached sandbox project no longer exists on the server; drop
                            // the cache and fall through to the full refresh.
                            LegendStudioUserDataHelper.workspaceSetup_clearSandboxInfo(this.applicationStore.userDataService);
                        }
                    }
                    else {
                        // User has access but hasn't created a sandbox yet.
                        this.sandboxProject = true;
                        this.loadSandboxState.pass();
                        return;
                    }
                }
            }
            // Slow path — original flow.
            const sandboxProject = (yield this.sdlcServerClient.getProjects(undefined, userId, [SANDBOX_SDLC_TAG], undefined, 1)).map((v) => Project.serialization.fromJson(v));
            if (this.hasSandboxAccess === undefined) {
                this.hasSandboxAccess =
                    (yield this.graphManagerState.graphManager.userHasPrototypeProjectAccess(userId ?? ''));
            }
            this.sandboxProject = true;
            if (sandboxProject.length > 1) {
                throw new UnsupportedOperationError('Only one sandbox project is supported per user.');
            }
            else if (sandboxProject.length === 1) {
                this.sandboxProject = guaranteeNonNullable(sandboxProject[0]);
            }
            // Persist the fresh result for next time. We only cache when we have
            // a userId (cache is scoped per user); anonymous sessions skip this.
            if (userId) {
                LegendStudioUserDataHelper.workspaceSetup_recordSandboxInfo(this.applicationStore.userDataService, {
                    userId,
                    hasAccess: this.hasSandboxAccess,
                    projectId: this.sandboxProject instanceof Project
                        ? this.sandboxProject.projectId
                        : undefined,
                });
            }
            this.loadSandboxState.pass();
        }
        catch (error) {
            this.sandboxProject = true;
            assertErrorThrown(error);
            this.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.WORKSPACE_SETUP_FAILURE), error);
            this.loadSandboxState.fail();
        }
        finally {
            this.loadSandboxState.complete();
        }
    }
    *changeProject(project, workspaceInfo) {
        this.currentProject = project;
        this.currentProjectConfigurationStatus = undefined;
        this.loadPatchesState.inProgress();
        try {
            if (isProjectSandbox(project)) {
                this.patches = [];
            }
            else {
                this.patches = (yield this.sdlcServerClient.getPatches(project.projectId)).map((v) => Patch.serialization.fromJson(v));
            }
            this.loadPatchesState.pass();
        }
        catch (error) {
            assertErrorThrown(error);
            this.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.DEPOT_MANAGER_FAILURE), error);
            this.applicationStore.notificationService.notifyError(error);
            this.loadPatchesState.fail();
        }
        this.loadWorkspacesState.inProgress();
        try {
            if (isProjectSandbox(project)) {
                const result = new ProjectConfigurationStatus();
                result.projectId = project.projectId;
                result.isConfigured = true;
                this.currentProjectConfigurationStatus = result;
            }
            else {
                this.currentProjectConfigurationStatus =
                    (yield fetchProjectConfigurationStatus(project.projectId, undefined, this.applicationStore, this.sdlcServerClient));
            }
            const workspacesInConflictResolutionIds = isProjectSandbox(project)
                ? []
                : (yield this.sdlcServerClient.getWorkspacesInConflictResolutionMode(project.projectId, undefined)).map((workspace) => workspace.workspaceId);
            this.workspaces = (yield this.sdlcServerClient.getWorkspaces(project.projectId))
                .map((v) => Workspace.serialization.fromJson(v))
                .filter(
            // NOTE we don't handle workspaces that only have conflict resolution but no standard workspace
            // since that indicates bad state of the SDLC server
            (workspace) => !workspacesInConflictResolutionIds.includes(workspace.workspaceId));
            for (const patch of this.patches) {
                this.workspaces = this.workspaces.concat((yield this.sdlcServerClient.getWorkspaces(project.projectId, patch.patchReleaseVersionId.id))
                    .map((v) => {
                    const w = Workspace.serialization.fromJson(v);
                    w.source = patch.patchReleaseVersionId.id;
                    return w;
                })
                    .filter(
                // NOTE we don't handle workspaces that only have conflict resolution but no standard workspace
                // since that indicates bad state of the SDLC server
                (workspace) => !workspacesInConflictResolutionIds.includes(workspace.workspaceId)));
            }
            if (workspaceInfo) {
                const matchingWorkspace = this.workspaces.find((workspace) => workspace.workspaceType === workspaceInfo.workspaceType &&
                    workspace.workspaceId === workspaceInfo.workspaceId);
                if (matchingWorkspace) {
                    this.changeWorkspace(matchingWorkspace);
                }
                else {
                    // Workspace no longer exists — prune from recents.
                    this.removeRecentWorkspace({
                        projectId: project.projectId,
                        workspaceId: workspaceInfo.workspaceId,
                        workspaceType: workspaceInfo.workspaceType,
                    });
                    this.applicationStore.navigationService.navigator.updateCurrentLocation(generateSetupRoute(project.projectId, undefined));
                }
            }
            else {
                this.currentWorkspace = undefined;
                this.applicationStore.navigationService.navigator.updateCurrentLocation(generateSetupRoute(project.projectId, undefined));
            }
            this.loadWorkspacesState.pass();
        }
        catch (error) {
            assertErrorThrown(error);
            this.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.DEPOT_MANAGER_FAILURE), error);
            this.applicationStore.notificationService.notifyError(error);
            this.loadWorkspacesState.fail();
        }
    }
    changeWorkspace(workspace) {
        if (!this.currentProject) {
            throw new IllegalStateError(`Can't change workspace: project is not specified`);
        }
        this.currentWorkspace = workspace;
        this.applicationStore.navigationService.navigator.updateCurrentLocation(generateSetupRoute(this.currentProject.projectId, workspace.source, workspace.workspaceId, workspace.workspaceType));
    }
    *createProject(name, description, groupId, artifactId, tags = []) {
        this.createOrImportProjectState.inProgress();
        try {
            const createdProject = Project.serialization.fromJson((yield this.sdlcServerClient.createProject({
                name,
                description,
                groupId,
                artifactId,
                tags,
            })));
            this.applicationStore.notificationService.notifySuccess(`Project '${name}' is succesfully created`);
            yield flowResult(this.changeProject(createdProject));
            this.setShowCreateProjectModal(false);
        }
        catch (error) {
            assertErrorThrown(error);
            this.applicationStore.notificationService.notifyError(error);
        }
        finally {
            this.createOrImportProjectState.reset();
        }
    }
    *importProject(id, groupId, artifactId) {
        this.createOrImportProjectState.inProgress();
        try {
            const report = ImportReport.serialization.fromJson((yield this.sdlcServerClient.importProject({
                id,
                groupId,
                artifactId,
            })));
            const importReview = Review.serialization.fromJson((yield this.sdlcServerClient.getReview(report.project.projectId, undefined, report.reviewId)));
            this.setImportProjectSuccessReport({
                projectName: report.project.name,
                projectId: report.project.projectId,
                reviewUrl: importReview.webURL,
            });
            yield flowResult(this.changeProject(report.project));
        }
        catch (error) {
            assertErrorThrown(error);
            this.applicationStore.notificationService.notifyError(error);
        }
        finally {
            this.createOrImportProjectState.reset();
        }
    }
    *createWorkspace(projectId, patchReleaseVersionId, workspaceId, workspaceType) {
        this.createWorkspaceState.inProgress();
        try {
            const newWorkspace = Workspace.serialization.fromJson((yield this.sdlcServerClient.createWorkspace(projectId, patchReleaseVersionId, workspaceId, workspaceType)));
            newWorkspace.source = patchReleaseVersionId;
            this.applicationStore.notificationService.notifySuccess(`Workspace '${newWorkspace.workspaceId}' is succesfully created`);
            const matchingWorkspace = this.workspaces.find((workspace) => workspace.workspaceId === newWorkspace.workspaceId &&
                workspace.workspaceType === newWorkspace.workspaceType);
            const newWorkspaceToSelect = matchingWorkspace ?? newWorkspace;
            this.changeWorkspace(newWorkspaceToSelect);
            this.setShowCreateWorkspaceModal(false);
            // NOTE: do this after closing the modal to not interfere
            // with validation of existing workspaces in create workspace modal
            if (!matchingWorkspace) {
                this.workspaces.push(newWorkspace);
            }
        }
        catch (error) {
            assertErrorThrown(error);
            this.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.WORKSPACE_SETUP_FAILURE), error);
            this.applicationStore.notificationService.notifyError(error);
        }
        finally {
            this.createWorkspaceState.reset();
        }
    }
}
//# sourceMappingURL=WorkspaceSetupStore.js.map