/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, fail } from "@fluidframework/core-utils/internal";
import { newChangeAtomIdRangeMap, offsetChangeAtomId, } from "../../core/index.js";
import { brand, brandConst, newIntegerRangeMap, } from "../../util/index.js";
const offsetChangesetLocalId = (value, offset) => brand(value + offset);
export class DefaultRevisionReplacer {
    constructor(updatedRevision, obsoleteRevisions) {
        this.updatedRevision = updatedRevision;
        this.obsoleteRevisions = obsoleteRevisions;
        /**
         * Mapping from (obsolete revision tag, original local id) to the updated local id.
         */
        this.updatedLocalIds = newChangeAtomIdRangeMap(offsetChangesetLocalId);
        /**
         * The set of local IDs already used in the scope of the updated revision.
         */
        this.localIds = newIntegerRangeMap();
        /**
         * The maximum local ID seen so far in the scope of the updated revision.
         */
        this.maxSeen = brandConst(-1)();
    }
    isObsolete(revision) {
        return this.obsoleteRevisions.has(revision);
    }
    getUpdatedAtomId(id, count = 1) {
        assert(count >= 1, 0xcc9 /* Count must be at least 1 */);
        if (this.isObsolete(id.revision)) {
            const updated = { ...id, revision: this.updatedRevision };
            let continuingOutputId;
            let remainderCount = count;
            let remainderStart = id;
            while (remainderCount > 0) {
                const prior = this.updatedLocalIds.getFirst(remainderStart, remainderCount);
                if (prior.value === undefined) {
                    const defaultOutputId = continuingOutputId ?? remainderStart.localId;
                    const newLocalId = this.areAllUnallocated(defaultOutputId, prior.length)
                        ? // This change atom ID uses a local ID that has not yet been used in the scope of the updated revision.
                            // We reuse it as is to minimize the number of IDs that need to be updated.
                            defaultOutputId
                        : // Some of the IDs in this range have already been used in the scope of the updated revision.
                            // We need to allocate new local IDs.
                            brand(this.maxSeen + 1);
                    this.maxSeen = brand(Math.max(this.maxSeen, newLocalId + prior.length - 1));
                    this.localIds.set(newLocalId, prior.length, true);
                    this.updatedLocalIds.set(remainderStart, prior.length, newLocalId);
                    if (continuingOutputId === undefined) {
                        updated.localId = newLocalId;
                    }
                    else if (newLocalId !== continuingOutputId) {
                        fail(0xcca /* TODO: Handle non-contiguous ranges */);
                    }
                    continuingOutputId = offsetChangesetLocalId(newLocalId, prior.length);
                }
                else {
                    if (continuingOutputId === undefined) {
                        updated.localId = prior.value;
                    }
                    else if (prior.value !== continuingOutputId) {
                        fail(0xccb /* TODO: Handle non-contiguous ranges */);
                    }
                    continuingOutputId = offsetChangesetLocalId(prior.value, prior.length);
                }
                remainderStart = offsetChangeAtomId(remainderStart, prior.length);
                remainderCount -= prior.length;
            }
            return updated;
        }
        return id;
    }
    areAllUnallocated(id, count) {
        for (const entry of this.localIds.getAll(id, count)) {
            if (entry.value !== undefined) {
                return false;
            }
        }
        return true;
    }
}
//# sourceMappingURL=defaultRevisionReplacer.js.map