/**
 * 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, flow, computed, makeObservable, flowResult, } from 'mobx';
import { EntityDiffViewerState, } from './EntityDiffEditorState.js';
import { assertErrorThrown, UnsupportedOperationError, } from '@finos/legend-shared';
import { mergeDiff3 } from 'node-diff3';
import { extractEntityNameFromPath } from '@finos/legend-storage';
import { EntityChangeConflictResolution } from '@finos/legend-server-sdlc';
import { ParserError } from '@finos/legend-graph';
const START_HEADER_MARKER = '<<<<<<<';
const COMMON_BASE_MARKER = '|||||||';
const SPLITTER_MARKER = '=======';
const END_FOOTER_MARKER = '>>>>>>>';
const scanMergeConflict = (text) => {
    const lines = text.split('\n');
    const conflicts = [];
    let currentConflict = null;
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const lineNumber = i + 1;
        // ignore empty lines
        if (!line?.trim()) {
            continue;
        }
        // Is this a start line? <<<<<<<
        if (line.startsWith(START_HEADER_MARKER)) {
            if (currentConflict !== null) {
                // Give up parsing, anything matched up this to this point will be decorated
                // anything after will not
                break;
            }
            // Create a new conflict starting at this line
            currentConflict = { startHeader: lineNumber };
        }
        else if (currentConflict &&
            !currentConflict.splitter &&
            line.startsWith(COMMON_BASE_MARKER)) {
            // Are we within a conflict block and is this a common ancestors marker? |||||||
            currentConflict.commonBase = lineNumber;
        }
        else if (currentConflict &&
            !currentConflict.splitter &&
            line.startsWith(SPLITTER_MARKER)) {
            // Are we within a conflict block and is this a splitter? =======
            currentConflict.splitter = lineNumber;
        }
        else if (currentConflict && line.startsWith(END_FOOTER_MARKER)) {
            // Are we within a conflict block and is this a footer? >>>>>>>
            currentConflict.endFooter = lineNumber;
            if (currentConflict.splitter !== undefined) {
                conflicts.push(currentConflict);
            }
            // Reset the current conflict to be empty, so we can match the next
            // starting header marker.
            currentConflict = null;
        }
    }
    return conflicts;
};
export var ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE;
(function (ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE) {
    ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE["MERGE_VIEW"] = "MERGE_VIEW";
    ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE["BASE_CURRENT"] = "BASE_CURRENT";
    ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE["BASE_INCOMING"] = "BASE_INCOMING";
    ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE["CURRENT_INCOMING"] = "CURRENT_INCOMING";
})(ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE || (ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE = {}));
export class EntityChangeConflictEditorState extends EntityDiffViewerState {
    entityPath;
    // revision
    baseRevision;
    currentChangeRevision;
    incomingChangeRevision;
    // entity
    baseEntity;
    currentChangeEntity;
    incomingChangeEntity;
    // grammar
    baseGrammarText;
    currentChangeGrammarText;
    incomingChangeGrammarText;
    // entity getter/updater function
    baseEntityGetter;
    currentChangeEntityGetter;
    incomingChangeEntityGetter;
    // editor
    mergedText;
    mergeSucceeded = true;
    mergeConflicts = [];
    isReadOnly = false;
    currentMergeEditorConflict;
    currentMergeEditorLine;
    mergeEditorParserError;
    currentMode = ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE.MERGE_VIEW;
    conflictResolutionState;
    constructor(editorStore, conflictResolutionState, entityPath, baseRevision, currentChangeRevision, incomingChangeRevision, baseEntity, currentChangeEntity, incomingChangeEntity, baseEntityGetter, currentChangeEntityGetter, incomingChangeEntityGetter) {
        super(baseRevision, currentChangeRevision, editorStore);
        makeObservable(this, {
            entityPath: observable,
            baseRevision: observable,
            currentChangeRevision: observable,
            incomingChangeRevision: observable,
            baseEntity: observable.ref,
            currentChangeEntity: observable.ref,
            incomingChangeEntity: observable.ref,
            baseGrammarText: observable,
            currentChangeGrammarText: observable,
            incomingChangeGrammarText: observable,
            baseEntityGetter: observable,
            currentChangeEntityGetter: observable,
            incomingChangeEntityGetter: observable,
            mergedText: observable,
            mergeSucceeded: observable,
            mergeConflicts: observable,
            isReadOnly: observable,
            currentMergeEditorConflict: observable,
            currentMergeEditorLine: observable,
            mergeEditorParserError: observable,
            currentMode: observable,
            label: computed,
            sortedMergedConflicts: computed,
            canUseTheirs: computed,
            canUseYours: computed,
            canMarkAsResolved: computed,
            previousConflict: computed,
            nextConflict: computed,
            setReadOnly: action,
            setMergedText: action,
            setCurrentMode: action,
            setCurrentMergeEditorLine: action,
            setCurrentMergeEditorConflict: action,
            clearMergeEditorError: action,
            refreshMergeConflict: action,
            resetMergeEditorStateOnLeave: action,
            acceptCurrentChange: action,
            acceptIncomingChange: action,
            acceptBothChanges: action,
            rejectBothChanges: action,
            refresh: flow,
            getMergedText: flow,
            markAsResolved: flow,
            useCurrentChanges: flow,
            useIncomingChanges: flow,
            getGrammarForEntity: flow,
        });
        this.entityPath = entityPath;
        // revision
        this.baseRevision = baseRevision;
        this.currentChangeRevision = currentChangeRevision;
        this.incomingChangeRevision = incomingChangeRevision;
        // entity
        this.baseEntity = baseEntity;
        this.currentChangeEntity = currentChangeEntity;
        this.incomingChangeEntity = incomingChangeEntity;
        // entity getter/updater function
        this.baseEntityGetter = baseEntityGetter;
        this.currentChangeEntityGetter = currentChangeEntityGetter;
        this.incomingChangeEntityGetter = incomingChangeEntityGetter;
        this.conflictResolutionState = conflictResolutionState;
    }
    setReadOnly(val) {
        this.isReadOnly = val;
    }
    setMergedText(val) {
        this.mergedText = val;
    }
    setCurrentMode(mode) {
        this.currentMode = mode;
    }
    setCurrentMergeEditorLine(val) {
        this.currentMergeEditorLine = val;
    }
    setCurrentMergeEditorConflict(conflict) {
        this.currentMergeEditorConflict = conflict;
    }
    clearMergeEditorError() {
        this.mergeEditorParserError = undefined;
    }
    refreshMergeConflict() {
        if (this.mergedText !== undefined) {
            this.mergeConflicts = scanMergeConflict(this.mergedText);
        }
    }
    get label() {
        return extractEntityNameFromPath(this.entityPath);
    }
    get sortedMergedConflicts() {
        return this.mergeConflicts.toSorted((a, b) => a.startHeader - b.startHeader);
    }
    get canUseTheirs() {
        return (this.currentMode !== ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE.BASE_CURRENT);
    }
    get canUseYours() {
        return (this.currentMode !== ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE.BASE_INCOMING);
    }
    get canMarkAsResolved() {
        return Boolean(!this.mergeConflicts.length &&
            !this.mergeEditorParserError &&
            this.currentMode === ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE.MERGE_VIEW);
    }
    get previousConflict() {
        const currentLine = this.currentMergeEditorLine ?? 0;
        return this.sortedMergedConflicts
            .slice()
            .reverse()
            .find((conflict) => conflict.endFooter && conflict.endFooter < currentLine);
    }
    get nextConflict() {
        const currentLine = this.currentMergeEditorLine ?? 0;
        return this.sortedMergedConflicts.find((conflict) => conflict.startHeader > currentLine);
    }
    match(tab) {
        return (tab instanceof EntityChangeConflictEditorState &&
            tab.entityPath === this.entityPath);
    }
    getModeComparisonViewInfo(mode) {
        switch (mode) {
            case ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE.MERGE_VIEW:
                return {
                    label: 'Merged changes',
                    fromRevision: this.currentChangeRevision,
                    toRevision: this.incomingChangeRevision,
                };
            case ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE.BASE_CURRENT:
                return {
                    label: 'Your changes',
                    fromGrammarText: this.baseGrammarText ?? '',
                    toGrammarText: this.currentChangeGrammarText ?? '',
                    fromRevision: this.baseRevision,
                    toRevision: this.currentChangeRevision,
                };
            case ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE.BASE_INCOMING:
                return {
                    label: 'Their changes',
                    fromGrammarText: this.baseGrammarText ?? '',
                    toGrammarText: this.incomingChangeGrammarText ?? '',
                    fromRevision: this.baseRevision,
                    toRevision: this.incomingChangeRevision,
                };
            case ENTITY_CHANGE_CONFLICT_EDITOR_VIEW_MODE.CURRENT_INCOMING:
                return {
                    label: 'Both changes',
                    fromGrammarText: this.currentChangeGrammarText ?? '',
                    toGrammarText: this.incomingChangeGrammarText ?? '',
                    fromRevision: this.currentChangeRevision,
                    toRevision: this.incomingChangeRevision,
                };
            default:
                throw new UnsupportedOperationError();
        }
    }
    resetMergeEditorStateOnLeave() {
        this.clearMergeEditorError();
        this.currentMergeEditorLine = undefined;
        this.currentMergeEditorConflict = undefined;
    }
    *refresh() {
        this.baseEntity = this.baseEntityGetter
            ? this.baseEntityGetter(this.entityPath)
            : this.baseEntity;
        this.currentChangeEntity = this.currentChangeEntityGetter
            ? this.currentChangeEntityGetter(this.entityPath)
            : this.currentChangeEntity;
        this.incomingChangeEntity = this.incomingChangeEntityGetter
            ? this.incomingChangeEntityGetter(this.entityPath)
            : this.incomingChangeEntity;
        if (this.isReadOnly || this.mergedText === undefined) {
            yield flowResult(this.getMergedText());
        }
    }
    *getMergedText() {
        this.baseGrammarText = (yield flowResult(this.getGrammarForEntity(this.baseEntity)));
        this.currentChangeGrammarText = (yield flowResult(this.getGrammarForEntity(this.currentChangeEntity)));
        this.incomingChangeGrammarText = (yield flowResult(this.getGrammarForEntity(this.incomingChangeEntity)));
        const result = mergeDiff3(this.currentChangeGrammarText, this.baseGrammarText, this.incomingChangeGrammarText, {
            stringSeparator: '\n',
            label: { a: 'Your Change', o: 'BASE', b: 'Their Change' },
        });
        this.mergedText = result.result.join('\n');
        this.refreshMergeConflict();
        this.mergeSucceeded = !this.mergeConflicts.length;
    }
    *getGrammarForEntity(entity) {
        if (entity) {
            const elementGrammar = (yield this.editorStore.graphManagerState.graphManager.entitiesToPureCode([entity], { pretty: true }));
            return elementGrammar;
        }
        return '';
    }
    *markAsResolved() {
        try {
            const entities = (yield this.editorStore.graphManagerState.graphManager.pureCodeToEntities(this.mergedText ?? ''));
            if (!entities.length) {
                this.conflictResolutionState.resolveConflict(new EntityChangeConflictResolution(this.entityPath, undefined));
            }
            else if (entities.length === 1) {
                this.conflictResolutionState.resolveConflict(new EntityChangeConflictResolution(this.entityPath, entities[0]));
            }
            else {
                this.editorStore.applicationStore.notificationService.notifyWarning(`Can't mark conflict as resolved: more than one element found in parsed text`);
                return;
            }
        }
        catch (error) {
            assertErrorThrown(error);
            if (error instanceof ParserError) {
                this.mergeEditorParserError = error;
                this.editorStore.applicationStore.notificationService.notifyWarning(`Can't mark conflict as resolved. Parsing error: ${this.mergeEditorParserError.message}`);
            }
        }
        yield flowResult(this.conflictResolutionState.markConflictAsResolved(this));
    }
    *useCurrentChanges() {
        this.conflictResolutionState.resolveConflict(new EntityChangeConflictResolution(this.entityPath, this.currentChangeEntity));
        yield flowResult(this.conflictResolutionState.markConflictAsResolved(this));
    }
    *useIncomingChanges() {
        this.conflictResolutionState.resolveConflict(new EntityChangeConflictResolution(this.entityPath, this.incomingChangeEntity));
        yield flowResult(this.conflictResolutionState.markConflictAsResolved(this));
    }
    acceptCurrentChange(conflict) {
        if (this.mergedText === undefined) {
            return;
        }
        const lines = this.mergedText.split('\n');
        this.setMergedText(lines
            .slice(0, conflict.startHeader - 1)
            .concat(lines.slice(conflict.startHeader, (conflict.commonBase ?? conflict.splitter) - 1)) // current change
            .concat(lines.slice(conflict.endFooter, lines.length))
            .join('\n'));
        this.refreshMergeConflict();
    }
    acceptIncomingChange(conflict) {
        if (this.mergedText === undefined) {
            return;
        }
        const lines = this.mergedText.split('\n');
        this.setMergedText(lines
            .slice(0, conflict.startHeader - 1)
            .concat(lines.slice(conflict.splitter, conflict.endFooter - 1)) // incoming change
            .concat(lines.slice(conflict.endFooter, lines.length))
            .join('\n'));
        this.refreshMergeConflict();
    }
    acceptBothChanges(conflict) {
        if (this.mergedText === undefined) {
            return;
        }
        const lines = this.mergedText.split('\n');
        this.setMergedText(lines
            .slice(0, conflict.startHeader - 1)
            .concat(lines.slice(conflict.startHeader, (conflict.commonBase ?? conflict.splitter) - 1)) // current change
            .concat(lines.slice(conflict.splitter, conflict.endFooter - 1)) // incoming change
            .concat(lines.slice(conflict.endFooter, lines.length))
            .join('\n'));
        this.refreshMergeConflict();
    }
    rejectBothChanges(conflict) {
        if (this.mergedText === undefined) {
            return;
        }
        const lines = this.mergedText.split('\n');
        this.setMergedText(lines
            .slice(0, conflict.startHeader - 1)
            .concat(conflict.commonBase
            ? lines.slice(conflict.commonBase, conflict.splitter - 1)
            : []) // base
            .concat(lines.slice(conflict.endFooter, lines.length))
            .join('\n'));
        this.refreshMergeConflict();
    }
}
//# sourceMappingURL=EntityChangeConflictEditorState.js.map