"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalCommitSource = exports.UndoRedoManager = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const rebase_1 = require("../rebase");
/**
 * Manages a branch of the undoable/redoable commit trees and repair data associated with the undoable and redoable commits.
 */
class UndoRedoManager {
    constructor(changeFamily, headUndoableCommit, headRedoableCommit, commitTypes = new Map()) {
        this.changeFamily = changeFamily;
        this.headUndoableCommit = headUndoableCommit;
        this.headRedoableCommit = headRedoableCommit;
        this.commitTypes = commitTypes;
    }
    /**
     * @param changeFamily - {@link ChangeFamily} used for inverting changes.
     * @param headUndoableCommit - Optional commit to set as the initial undoable commit.
     * @param headRedoableCommit - Optional commit to set as the initial redoable commit.
     */
    static create(changeFamily, headUndoableCommit, headRedoableCommit) {
        return new UndoRedoManager(changeFamily, headUndoableCommit, headRedoableCommit);
    }
    get headUndoable() {
        return this.headUndoableCommit;
    }
    get headRedoable() {
        return this.headRedoableCommit;
    }
    /**
     * Adds the provided commit to the undo or redo commit tree, depending on the type of commit it is.
     * Should be called for all commits on the relevant branch, including undo and redo commits.
     */
    trackCommit(commit, type) {
        this.commitTypes.set(commit.revision, type);
        const parent = type === LocalCommitSource.Undo ? this.headRedoableCommit : this.headUndoableCommit;
        const undoableOrRedoable = {
            commit,
            parent,
        };
        switch (type) {
            // Undo commits push a new head redoable commit to the redoable commit stack and pop from the
            // undoable commit stack.
            case LocalCommitSource.Undo:
                this.headUndoableCommit = this.headUndoableCommit?.parent;
                this.headRedoableCommit = undoableOrRedoable;
                break;
            // Redo commits pop from the redoable commit stack and all other commits push to the undoable commit stack.
            case LocalCommitSource.Redo:
                this.headRedoableCommit = this.headRedoableCommit?.parent;
            default:
                this.headUndoableCommit = undoableOrRedoable;
        }
    }
    /**
     * Returns the {@link LocalCommitSource} associated with the provided revision.
     */
    getCommitType(revision) {
        return this.commitTypes.get(revision);
    }
    /**
     * Removes the {@link LocalCommitSource} associated with the provided revision.
     */
    untrackCommitType(revision) {
        this.commitTypes.delete(revision);
    }
    /**
     * Inverts the head undoable commit and returns the inverted change.
     * This change can then be applied and tracked.
     * @param headCommit - The head commit of the branch to undo from.
     */
    undo(headCommit) {
        const undoableCommit = this.headUndoableCommit;
        if (undoableCommit === undefined) {
            // No undoable commits, exit early
            return undefined;
        }
        return this.createInvertedChange(undoableCommit, headCommit);
    }
    /**
     * Inverts the head redoable commit and returns the inverted change.
     * This change can then be applied and tracked.
     * @param headCommit - The head commit of the branch to redo from.
     */
    redo(headCommit) {
        const redoableCommit = this.headRedoableCommit;
        if (redoableCommit === undefined) {
            // No undoable commits, exit early
            return undefined;
        }
        return this.createInvertedChange(redoableCommit, headCommit);
    }
    createInvertedChange(undoableOrRedoable, headCommit) {
        const { commit } = undoableOrRedoable;
        let change = this.changeFamily.rebaser.invert((0, rebase_1.tagChange)(commit.change, commit.revision), false, commit.repairData);
        // Rebase the inverted change onto any commits that occurred after the undoable commits.
        if (commit.revision !== headCommit.revision) {
            const pathAfterUndoable = [];
            const ancestor = (0, rebase_1.findCommonAncestor)([commit], [headCommit, pathAfterUndoable]);
            (0, common_utils_1.assert)(ancestor === commit, 0x677 /* The head commit should be based off the undoable commit. */);
            change = pathAfterUndoable.reduce((a, b) => this.changeFamily.rebaser.rebase(a, b), change);
        }
        return change;
    }
    /**
     * Creates a copy of this `UndoRedoManager`.
     * @param headUndoableCommit - Optional head undoable commit, if one is not provided the head undoable commit
     * of this {@link UndoRedoManager} will be used.
     * @param headRedoableCommit - Optional head redoable commit, if one is not provided the head redoable commit
     * of this {@link UndoRedoManager} will be used.
     */
    clone(headUndoableCommit, headRedoableCommit) {
        return new UndoRedoManager(this.changeFamily, headUndoableCommit ?? this.headUndoableCommit, headRedoableCommit ?? this.headRedoableCommit, this.commitTypes);
    }
    /**
     * Updates the state of this {@link UndoRedoManager} to correctly reference commits that have been rebased after merging.
     * @param newCommits - all commits which were appended to the source branch.
     * @param mergedUndoRedoManager - the {@link UndoRedoManager} of the branch that was merged.
     */
    updateAfterMerge(newCommits, mergedUndoRedoManager) {
        this.updateBasedOnNewCommits(newCommits, this, mergedUndoRedoManager);
    }
    /**
     * Updates the state of this {@link UndoRedoManager} to correctly reference commits that have been rebased.
     * @param newCommits - all commits from the original branch that have rebased versions present on the new branch.
     * @param baseUndoRedoManager - the {@link UndoRedoManager} of the branch that was rebased onto
     */
    updateAfterRebase(newCommits, baseUndoRedoManager) {
        this.updateBasedOnNewCommits(newCommits, baseUndoRedoManager, this);
    }
    updateBasedOnNewCommits(newCommits, baseUndoRedoManager, originalUndoRedoManager) {
        if ((originalUndoRedoManager.headUndoable === undefined &&
            originalUndoRedoManager.headRedoable === undefined) ||
            newCommits.length === 0) {
            // The branch that was rebased had no undoable or redoable edits so the new undo redo manager
            // should be a copy of the undo redo manager from the base branch.
            this.headUndoableCommit = baseUndoRedoManager.headUndoable;
            this.headRedoableCommit = baseUndoRedoManager.headRedoable;
            return;
        }
        // Rebuild the reversible commit trees off of the undo redo manager of the branch
        // that was rebased onto.
        let newHeadUndoable = baseUndoRedoManager.headUndoable;
        let newHeadRedoable = baseUndoRedoManager.headRedoable;
        // Distinguish which reversible stack each commit is in and add it to the stack.
        for (const commit of newCommits) {
            const type = originalUndoRedoManager.commitTypes.get(commit.revision);
            if (type !== undefined) {
                if (type === LocalCommitSource.Undo) {
                    newHeadRedoable = {
                        commit,
                        parent: newHeadRedoable,
                    };
                }
                else {
                    newHeadUndoable = {
                        commit,
                        parent: newHeadUndoable,
                    };
                }
            }
        }
        this.headUndoableCommit = newHeadUndoable;
        this.headRedoableCommit = newHeadRedoable;
    }
}
exports.UndoRedoManager = UndoRedoManager;
/**
 * The source of a local commit.
 *
 * @alpha
 */
var LocalCommitSource;
(function (LocalCommitSource) {
    /** A typical local commit */
    LocalCommitSource[LocalCommitSource["Default"] = 0] = "Default";
    /** A commit that is the result of an undo. */
    LocalCommitSource[LocalCommitSource["Undo"] = 1] = "Undo";
    /** A commit that is the result of a redo. */
    LocalCommitSource[LocalCommitSource["Redo"] = 2] = "Redo";
})(LocalCommitSource = exports.LocalCommitSource || (exports.LocalCommitSource = {}));
//# sourceMappingURL=undoRedoManager.js.map