"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultResubmitMachine = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../util/index.js");
/**
 * Default implementation of {@link ResubmitMachine}.
 */
class DefaultResubmitMachine {
    constructor(
    /**
     * A function that can create a rollback for a given change.
     */
    makeRollback, 
    /**
     * Change enricher that represent the tip of the top-level local branch (i.e., the branch on which in-flight
     * commits are applied and automatically rebased).
     */
    tip) {
        this.makeRollback = makeRollback;
        this.tip = tip;
        /**
         * Maps from revision of submitted commit to the pending change for that commit.
         */
        this.pendingChanges = new Map();
        /**
         * The current enrichment version for in-flight commits.
         * Incremented when a peer commit is sequenced.
         */
        this.currentEnrichment = 0;
    }
    onCommitSubmitted(commit) {
        this.pendingChanges.set(commit.revision, {
            commit,
            lastEnrichment: this.currentEnrichment,
        });
    }
    onCommitRollback(commit) {
        this.pendingChanges.delete(commit.revision);
    }
    updateEnrichments(revision, getLocalCommits) {
        const pendingChange = this.pendingChanges.get(revision);
        if (pendingChange === undefined ||
            pendingChange.lastEnrichment === this.currentEnrichment) {
            // The first commit to resubmit has a valid enrichment, so all pending commits must be valid.
            return;
        }
        const localCommits = getLocalCommits();
        (0, internal_1.assert)(localCommits[0]?.revision === revision, 0xc79 /* Expected local commits to start with specified revision */);
        // Some in-flight commits have stale enrichments, so we recompute them.
        const checkout = this.tip.fork();
        // Roll back the checkout to the state before the oldest commit
        for (let iCommit = localCommits.length - 1; iCommit >= 0; iCommit -= 1) {
            const commit = localCommits[iCommit] ?? (0, internal_1.oob)();
            const rollback = this.makeRollback(commit);
            // WARNING: it's not currently possible to roll back past a schema change (see AB#7265).
            // Either we have to make it possible to do so, or this logic will have to change to work
            // forwards from an earlier fork instead of backwards.
            checkout.applyTipChange(rollback);
        }
        // Update the enrichments of the stale commits.
        for (const [iCommit, commit] of localCommits.entries()) {
            const current = this.getPendingChange(commit.revision);
            (0, internal_1.assert)(current !== undefined, 0xbda /* there must be an inflight commit for each resubmit commit */);
            if (current.lastEnrichment < this.currentEnrichment) {
                const enrichedChange = checkout.updateChangeEnrichments(commit.change, commit.revision);
                const enrichedCommit = { ...commit, change: enrichedChange };
                // Optimization: only apply the enriched change if the next commit also needs enrichment.
                const nextCommit = localCommits[iCommit + 1];
                if (nextCommit !== undefined &&
                    this.getPendingChange(nextCommit.revision).lastEnrichment < this.currentEnrichment) {
                    checkout.applyTipChange(enrichedChange, commit.revision);
                }
                current.commit = enrichedCommit;
                current.lastEnrichment = this.currentEnrichment;
            }
        }
        checkout[index_js_1.disposeSymbol]();
    }
    getEnrichedCommit(revision, getLocalCommitsSince) {
        this.updateEnrichments(revision, getLocalCommitsSince);
        const pendingChange = this.pendingChanges.get(revision);
        return pendingChange?.commit;
    }
    getPendingChange(revision) {
        return (this.pendingChanges.get(revision) ??
            (0, internal_1.fail)(0xc7a /* No pending change stored for this revision */));
    }
    onSequencedCommitApplied(revision, isLocal) {
        // We no longer need to track enrichment for the commit with this revision.
        // Note that we may have a commit for this revision even if this is not a local change,
        // as this client and another peer may have merged the same commit from a shared branch.
        this.pendingChanges.delete(revision);
        if (!isLocal) {
            // A peer commit has been sequenced, invalidating the enrichment of our in-flight commits.
            this.currentEnrichment++;
        }
    }
}
exports.DefaultResubmitMachine = DefaultResubmitMachine;
//# sourceMappingURL=defaultResubmitMachine.js.map