/**
 * 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 { assertErrorThrown, LogEvent, guaranteeNonNullable, ActionState, assertNonEmptyString, isNonNullable, } from '@finos/legend-shared';
import { makeObservable, action, observable, flow, computed, flowResult, } from 'mobx';
import { ACTIVITY_MODE } from '../editor/EditorConfig.js';
import { EntityDiff, Comparison, reprocessEntityDiffs, Project, Review, ReviewApproval, } from '@finos/legend-server-sdlc';
import { LEGEND_STUDIO_APP_EVENT } from '../../__lib__/LegendStudioEvent.js';
import { LegendStudioUserDataHelper } from '../../__lib__/LegendStudioUserDataHelper.js';
import { DEFAULT_TAB_SIZE } from '@finos/legend-application';
import { EntityDiffViewState } from '../editor/editor-state/entity-diff-editor-state/EntityDiffViewState.js';
import { SPECIAL_REVISION_ALIAS } from '../editor/editor-state/entity-diff-editor-state/EntityDiffEditorState.js';
export class ProjectReviewReport {
    diffs;
    fromEntities = [];
    toEntities = [];
    fromConfig;
    toConfig;
    fromToProjectConfig;
    constructor(diffs) {
        this.diffs = diffs;
        makeObservable(this, {
            fromEntities: observable,
            toEntities: observable,
            fromToProjectConfig: observable,
            addFromConfig: action,
            addToConfig: action,
        });
    }
    addFromConfig(config) {
        this.fromConfig = config;
        this.createFromToConfigIfPossible();
    }
    addToConfig(config) {
        this.toConfig = config;
        this.createFromToConfigIfPossible();
    }
    createFromToConfigIfPossible() {
        if (this.fromConfig && this.toConfig) {
            this.fromToProjectConfig = [this.fromConfig, this.toConfig];
        }
    }
    findFromEntity(entityPath) {
        return this.fromEntities.find((e) => e.path === entityPath);
    }
    addFromEntity(entity) {
        this.fromEntities.push(entity);
        return this;
    }
    addToEntity(entity) {
        this.toEntities.push(entity);
        return this;
    }
    findToEntity(entityPath) {
        return this.toEntities.find((e) => e.path === entityPath);
    }
}
export class ProjectReviewerStore {
    editorStore;
    currentProjectId;
    currentProject;
    currentPatch;
    currentReviewId;
    currentReview;
    // comparison
    buildReviewReportState = ActionState.create();
    reviewReport;
    fetchCurrentReviewState = ActionState.create();
    approveState = ActionState.create();
    reviewApproval;
    closeState = ActionState.create();
    commitState = ActionState.create();
    reOpenState = ActionState.create();
    constructor(editorStore) {
        makeObservable(this, {
            currentProjectId: observable,
            currentProject: observable,
            currentPatch: observable,
            currentReviewId: observable,
            currentReview: observable,
            fetchCurrentReviewState: observable,
            buildReviewReportState: observable,
            approveState: observable,
            closeState: observable,
            commitState: observable,
            reOpenState: observable,
            reviewReport: observable,
            reviewApproval: observable,
            projectId: computed,
            patchReleaseVersionId: computed,
            reviewId: computed,
            review: computed,
            setProjectIdAndReviewId: action,
            refresh: action,
            initializeEngine: flow,
            fetchReviewComparison: flow,
            fetchProject: flow,
            fetchReviewApprovals: flow,
            fetchReview: flow,
            approveReview: flow,
            commitReview: flow,
            reOpenReview: flow,
            closeReview: flow,
        });
        this.editorStore = editorStore;
        this.editorStore.activeActivity = ACTIVITY_MODE.REVIEW;
    }
    get projectId() {
        return guaranteeNonNullable(this.currentProjectId, 'Project ID must exist');
    }
    get patchReleaseVersionId() {
        return this.currentPatch?.patchReleaseVersionId.id;
    }
    get reviewId() {
        return guaranteeNonNullable(this.currentReviewId, 'Review ID must exist');
    }
    get review() {
        return guaranteeNonNullable(this.currentReview, 'Review must exist');
    }
    get approvalString() {
        const approvals = this.reviewApproval?.approvedBy;
        if (approvals?.length) {
            return `Approved by ${approvals.map((e) => e.name).join(',')}.`;
        }
        return undefined;
    }
    setProjectIdAndReviewId(projectId, reviewId) {
        this.currentProjectId = projectId;
        this.currentReviewId = reviewId;
    }
    initialize() {
        flowResult(this.initializeEngine()).catch(this.editorStore.applicationStore.alertUnhandledError);
        flowResult(this.fetchReview()).catch(this.editorStore.applicationStore.alertUnhandledError);
        flowResult(this.fetchProject()).catch(this.editorStore.applicationStore.alertUnhandledError);
        flowResult(this.fetchReviewApprovals()).catch(this.editorStore.applicationStore.alertUnhandledError);
        flowResult(this.fetchReviewComparison()).catch(this.editorStore.applicationStore.alertUnhandledError);
    }
    *initializeEngine() {
        try {
            // setup engine used for to/from grammar transfomation
            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,
                },
            }, {
                tracerService: this.editorStore.applicationStore.tracerService,
            });
        }
        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);
        }
    }
    openReviewChange(diff) {
        const fromEntityGetter = (entityPath) => entityPath ? this.reviewReport?.findFromEntity(entityPath) : undefined;
        const toEntityGetter = (entityPath) => entityPath ? this.reviewReport?.findToEntity(entityPath) : undefined;
        const fromEntity = EntityDiff.shouldOldEntityExist(diff)
            ? guaranteeNonNullable(fromEntityGetter(diff.getValidatedOldPath()), `Can't find entity with path  '${diff.oldPath}'`)
            : undefined;
        const toEntity = EntityDiff.shouldNewEntityExist(diff)
            ? guaranteeNonNullable(toEntityGetter(diff.getValidatedNewPath()), `Can't find entity with path  '${diff.newPath}'`)
            : undefined;
        const diffState = new EntityDiffViewState(this.editorStore, SPECIAL_REVISION_ALIAS.WORKSPACE_BASE, SPECIAL_REVISION_ALIAS.WORKSPACE_HEAD, diff.oldPath, diff.newPath, fromEntity, toEntity, fromEntityGetter, toEntityGetter);
        this.editorStore.tabManagerState.openTab(this.editorStore.tabManagerState.tabs.find((t) => t.match(diffState)) ??
            diffState);
    }
    refresh() {
        this.editorStore.tabManagerState.closeAllTabs();
        this.reviewReport = undefined;
        flowResult(this.fetchReviewComparison()).catch(this.editorStore.applicationStore.alertUnhandledError);
    }
    /**
     * To save load time, this function will levergae the reviewId coming from the URl and doesn't
     * assume the review has completed been fetched
     */
    *fetchReviewComparison() {
        this.buildReviewReportState.inProgress();
        try {
            const comparison = Comparison.serialization.fromJson((yield this.editorStore.sdlcServerClient.getReviewComparision(this.projectId, this.patchReleaseVersionId, this.reviewId)));
            const report = new ProjectReviewReport(reprocessEntityDiffs(comparison.entityDiffs));
            this.reviewReport = report;
            const fromToRequests = Promise.all([
                ...report.diffs
                    .map((diff) => diff.oldPath)
                    .filter(isNonNullable)
                    .map((fromDiff) => this.editorStore.sdlcServerClient
                    .getReviewFromEntity(this.projectId, this.patchReleaseVersionId, this.reviewId, fromDiff)
                    .then((fromEntity) => report.addFromEntity(fromEntity))),
                ...report.diffs
                    .map((diff) => diff.newPath)
                    .filter(isNonNullable)
                    .map((toDiff) => this.editorStore.sdlcServerClient
                    .getReviewToEntity(this.projectId, this.patchReleaseVersionId, this.reviewId, toDiff)
                    .then((toEntity) => report.addToEntity(toEntity))),
            ]);
            if (comparison.projectConfigurationUpdated) {
                yield Promise.all([
                    this.editorStore.sdlcServerClient
                        .getReviewFromConfiguration(this.projectId, this.patchReleaseVersionId, this.reviewId)
                        .then((config) => report.addFromConfig(config)),
                    this.editorStore.sdlcServerClient
                        .getReviewToConfiguration(this.projectId, this.patchReleaseVersionId, this.reviewId)
                        .then((config) => report.addToConfig(config)),
                ]);
            }
            yield fromToRequests;
        }
        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);
        }
        finally {
            this.buildReviewReportState.complete();
        }
    }
    *fetchProject() {
        try {
            this.currentProject = Project.serialization.fromJson((yield this.editorStore.sdlcServerClient.getProject(this.projectId)));
        }
        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);
        }
    }
    *fetchReviewApprovals() {
        try {
            this.reviewApproval = ReviewApproval.serialization.fromJson((yield this.editorStore.sdlcServerClient.getReviewApprovals(this.projectId, this.patchReleaseVersionId, this.reviewId)));
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE), error);
        }
    }
    *fetchReview() {
        try {
            // TODO: can we assume review also an integer ?
            assertNonEmptyString(this.currentReviewId, 'Review ID provided must be a valid non empty string');
            this.fetchCurrentReviewState.inProgress();
            this.currentReview = Review.serialization.fromJson((yield this.editorStore.sdlcServerClient.getReview(this.projectId, this.patchReleaseVersionId, this.reviewId)));
        }
        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);
        }
        finally {
            this.fetchCurrentReviewState.complete();
        }
    }
    *approveReview() {
        this.approveState.inProgress();
        try {
            this.currentReview = Review.serialization.fromJson((yield this.editorStore.sdlcServerClient.approveReview(this.projectId, this.patchReleaseVersionId, this.review.id)));
        }
        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);
        }
        finally {
            this.approveState.complete();
        }
    }
    *commitReview() {
        this.commitState.inProgress();
        try {
            this.currentReview = Review.serialization.fromJson((yield this.editorStore.sdlcServerClient.commitReview(this.projectId, this.patchReleaseVersionId, this.review.id, { message: `${this.review.title} [review]` })));
            // Committing a review deletes its workspace on SDLC. Drop the
            // matching entry from the local recents cache (no-op if it wasn't
            // there, e.g., the reviewer isn't the workspace author). Patch
            // workspaces are never cached.
            if (this.patchReleaseVersionId === undefined) {
                LegendStudioUserDataHelper.workspaceSetup_removeRecentWorkspace(this.editorStore.applicationStore.userDataService, {
                    projectId: this.projectId,
                    workspaceId: this.review.workspaceId,
                    workspaceType: this.review.workspaceType,
                });
            }
        }
        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);
        }
        finally {
            this.commitState.complete();
        }
    }
    *reOpenReview() {
        this.reOpenState.inProgress();
        try {
            this.currentReview = Review.serialization.fromJson((yield this.editorStore.sdlcServerClient.reopenReview(this.projectId, this.patchReleaseVersionId, this.review.id)));
        }
        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);
        }
        finally {
            this.reOpenState.complete();
        }
    }
    *closeReview() {
        this.closeState.inProgress();
        try {
            this.currentReview = Review.serialization.fromJson((yield this.editorStore.sdlcServerClient.closeReview(this.projectId, this.patchReleaseVersionId, this.review.id)));
        }
        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);
        }
        finally {
            this.closeState.complete();
        }
    }
}
//# sourceMappingURL=ProjectReviewerStore.js.map