/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils/internal";
import { BTree } from "@tylerbu/sorted-btree-es6";
import { Multiplicity, replaceAtomRevisions, } from "../../core/index.js";
import { FieldKindWithEditor } from "./fieldKindWithEditor.js";
import { makeGenericChangeCodec } from "./genericFieldKindCodecs.js";
import { newGenericChangeset } from "./genericFieldKindTypes.js";
/**
 * {@link FieldChangeHandler} implementation for {@link GenericChangeset}.
 */
export const genericChangeHandler = {
    rebaser: {
        compose,
        invert: (change) => change,
        rebase: rebaseGenericChange,
        prune: pruneGenericChange,
        replaceRevisions,
        mute: (change) => change,
    },
    codecsFactory: makeGenericChangeCodec,
    editor: {
        buildChildChanges(changes) {
            return newGenericChangeset(Array.from(changes));
        },
    },
    intoDelta: (change, deltaFromChild) => {
        let nodeIndex = 0;
        const markList = [];
        for (const [index, nodeChange] of change.entries()) {
            if (nodeIndex < index) {
                const offset = index - nodeIndex;
                markList.push({ count: offset });
                nodeIndex = index;
            }
            markList.push({ count: 1, fields: deltaFromChild(nodeChange) });
            nodeIndex += 1;
        }
        return { local: markList };
    },
    relevantRemovedRoots,
    isEmpty: (change) => change.length === 0,
    getNestedChanges,
    createEmpty: newGenericChangeset,
    getCrossFieldKeys: (_change) => [],
};
function compose(change1, change2, composeChildren) {
    const composed = change1.clone();
    for (const [index, id2] of change2.entries()) {
        const id1 = composed.get(index);
        const idComposed = id1 !== undefined ? composeChildren(id1, id2) : id2;
        composed.set(index, idComposed);
    }
    return composed;
}
function getNestedChanges(change) {
    // For generic changeset, the indices in the input and output contexts are the same.
    return change.toArray().map(([index, nodeChange]) => [nodeChange, index, index]);
}
function rebaseGenericChange(change, over, rebaseChild) {
    const rebased = new BTree();
    let nextIndex = 0;
    // eslint-disable-next-line no-constant-condition
    while (true) {
        const newEntry = change.getPairOrNextHigher(nextIndex);
        const baseEntry = over.getPairOrNextHigher(nextIndex);
        if (baseEntry === undefined && newEntry === undefined) {
            break;
        }
        const newIndex = newEntry?.[0] ?? Number.POSITIVE_INFINITY;
        const baseIndex = baseEntry?.[0] ?? Number.POSITIVE_INFINITY;
        let newNodeChange;
        let baseNodeChange;
        let index;
        if (newIndex === baseIndex) {
            assert(newEntry !== undefined && baseEntry !== undefined, 0xa0d /* Entries should be defined */);
            index = newIndex;
            newNodeChange = newEntry[1];
            baseNodeChange = baseEntry[1];
        }
        else if (newIndex < baseIndex) {
            assert(newEntry !== undefined, 0xa0e /* Entry should be defined */);
            index = newIndex;
            newNodeChange = newEntry[1];
        }
        else {
            assert(baseEntry !== undefined, 0xa0f /* Entry should be defined */);
            index = baseIndex;
            baseNodeChange = baseEntry[1];
        }
        const nodeChange = rebaseChild(newNodeChange, baseNodeChange);
        if (nodeChange !== undefined) {
            rebased.set(index, nodeChange);
        }
        nextIndex = index + 1;
    }
    return rebased;
}
function pruneGenericChange(changeset, pruneChild) {
    const pruned = new BTree();
    for (const [index, node] of changeset.entries()) {
        const prunedNode = pruneChild(node);
        if (prunedNode !== undefined) {
            pruned.set(index, node);
        }
    }
    return pruned;
}
function replaceRevisions(changeset, oldRevisions, newRevision) {
    return changeset.mapValues((node) => replaceAtomRevisions(node, oldRevisions, newRevision));
}
/**
 * {@link FieldKind} used to represent changes to elements of a field in a field-kind-agnostic format.
 */
export const genericFieldKind = new FieldKindWithEditor("ModularEditBuilder.Generic", Multiplicity.Sequence, genericChangeHandler, (types, other) => false, new Set());
/**
 * Converts a {@link GenericChangeset} into a field-kind-specific `TChange`.
 * @param changeset - The generic changeset to convert.
 * @param target - The {@link FieldChangeHandler} for the `FieldKind` that the returned change should target.
 * @param composeChild - A delegate to compose {@link NodeChangeset}s.
 * @returns An equivalent changeset as represented by the `target` field-kind.
 */
export function convertGenericChange(changeset, target) {
    return target.editor.buildChildChanges(changeset.entries());
}
function* relevantRemovedRoots(change, relevantRemovedRootsFromChild) {
    for (const nodeChange of change.values()) {
        yield* relevantRemovedRootsFromChild(nodeChange);
    }
}
//# sourceMappingURL=genericFieldKind.js.map