/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _ObjectForest_roots;
import { assert } from "@fluidframework/core-utils/internal";
import { AnchorSet, ITreeSubscriptionCursorState, aboveRootPlaceholder, } from "../../core/index.js";
import { createEmitter } from "../../events/index.js";
import { assertNonNegativeSafeInteger, assertValidIndex, assertValidRange, brand, fail, mapIterable, } from "../../util/index.js";
import { cursorForMapTreeNode, mapTreeFromCursor } from "../mapTreeCursor.js";
import { SynchronousCursor } from "../treeCursorUtils.js";
/** Get a field from a `MutableMapTree`, optionally modifying the tree to create it if missing. */
function getOrCreateField(mapTree, key) {
    const field = mapTree.fields.get(key);
    if (field !== undefined) {
        return field;
    }
    const newField = [];
    mapTree.fields.set(key, newField);
    return newField;
}
function deepCopyMapTree(mapTree) {
    return {
        ...mapTree,
        fields: new Map(mapIterable(mapTree.fields.entries(), ([key, field]) => [
            key,
            field.map(deepCopyMapTree),
        ])),
    };
}
/**
 * Reference implementation of IEditableForest.
 *
 * This implementation focuses on correctness and simplicity, not performance.
 * It does not use compressed chunks: instead nodes are implemented using objects.
 */
export class ObjectForest {
    get roots() {
        return __classPrivateFieldGet(this, _ObjectForest_roots, "f");
    }
    constructor(anchors = new AnchorSet(), additionalAsserts = false, roots) {
        this.anchors = anchors;
        this.additionalAsserts = additionalAsserts;
        // All cursors that are in the "Current" state. Must be empty when editing.
        this.currentCursors = new Set();
        this.events = createEmitter();
        _ObjectForest_roots.set(this, void 0);
        this.nextRange = 0;
        __classPrivateFieldSet(this, _ObjectForest_roots, roots !== undefined
            ? deepCopyMapTree(roots)
            : {
                type: aboveRootPlaceholder,
                fields: new Map(),
            }, "f");
    }
    get isEmpty() {
        return this.roots.fields.size === 0;
    }
    on(eventName, listener) {
        return this.events.on(eventName, listener);
    }
    clone(_, anchors) {
        return new ObjectForest(anchors, this.additionalAsserts, this.roots);
    }
    forgetAnchor(anchor) {
        this.anchors.forget(anchor);
    }
    acquireVisitor() {
        assert(this.activeVisitor === undefined, 0x76c /* Must release existing visitor before acquiring another */);
        // Note: This code uses cursors, however it also modifies the tree.
        // In general this is not safe, but this code happens to only modify the tree below the current cursor location,
        // which happens to work.
        // This pattern could be generalized/formalized with a concept of an exclusive cursor,
        // which can edit, but is the only cursor allowed at the time.
        const cursor = this.allocateCursor("acquireVisitor");
        cursor.setToAboveDetachedSequences();
        /**
         * Called before each edit to allow cursor cleanup to happen and ensure that there are no unexpected cursors.
         * This is required for each change since there may be app facing change event handlers which create cursors.
         */
        const preEdit = () => {
            this.events.emit("beforeChange");
            assert(this.currentCursors.has(cursor), 0x995 /* missing visitor cursor while editing */);
            if (this.currentCursors.size > 1) {
                const unexpectedSources = [...this.currentCursors].flatMap((c) => c === cursor ? [] : c.source ?? null);
                throw new Error(`Found unexpected cursors when editing with the following annotations: ${JSON.stringify(unexpectedSources)}`);
            }
        };
        class Visitor {
            constructor(forest) {
                this.forest = forest;
            }
            free() {
                cursor.free();
                assert(this.forest.activeVisitor !== undefined, 0x76d /* Multiple free calls for same visitor */);
                this.forest.activeVisitor = undefined;
            }
            destroy(detachedField, count) {
                preEdit();
                this.forest.delete(detachedField);
            }
            create(content, destination) {
                preEdit();
                this.forest.add(content, destination);
                this.forest.events.emit("afterRootFieldCreated", destination);
            }
            attach(source, count, destination) {
                preEdit();
                this.attachEdit(source, count, destination);
            }
            detach(source, destination) {
                preEdit();
                this.detachEdit(source, destination);
            }
            /**
             * Attaches the nodes from the given source field into the current field.
             * Does not invalidate dependents.
             * @param source - The the range to be attached.
             * @param count - The number of nodes being attached.
             * Expected to match the number of nodes in the source detached field.
             * @param destination - The index in the current field at which to attach the content.
             */
            attachEdit(source, count, destination) {
                assertNonNegativeSafeInteger(count);
                if (count === 0) {
                    return;
                }
                const [parent, key] = cursor.getParent();
                assert(parent !== this.forest.roots || key !== source, 0x7b6 /* Attach source field must be different from current field */);
                const currentField = getOrCreateField(parent, key);
                assertValidIndex(destination, currentField, true);
                const sourceField = __classPrivateFieldGet(this.forest, _ObjectForest_roots, "f").fields.get(source) ?? [];
                assert(sourceField !== undefined, 0x7b7 /* Attach source field must exist */);
                assert(sourceField.length === count, 0x7b8 /* Attach must consume all nodes in source field */);
                // TODO: this will fail for very large insertions due to argument limits.
                currentField.splice(destination, 0, ...sourceField);
                this.forest.delete(source);
            }
            /**
             * Detaches the range from the current field and transfers it to the given destination if any.
             * Does not invalidate dependents.
             * @param source - The bounds of the range to be detached from the current field.
             * @param destination - If specified, the destination to transfer the detached range to.
             * If not specified, the detached range is destroyed.
             */
            detachEdit(source, destination) {
                const [parent, key] = cursor.getParent();
                assert(destination === undefined || parent !== this.forest.roots || key !== destination, 0x7b9 /* Detach destination field must be different from current field */);
                const currentField = getOrCreateField(parent, key);
                assertValidRange(source, currentField);
                const content = currentField.splice(source.start, source.end - source.start);
                if (destination !== undefined) {
                    this.forest.addFieldAsDetached(content, destination);
                }
                // This check is performed after the transfer to ensure that the field is not removed in scenarios
                // where the source and destination are the same.
                if (currentField.length === 0) {
                    parent.fields.delete(key);
                }
            }
            replace(newContentSource, range, oldContentDestination) {
                preEdit();
                assert(newContentSource !== oldContentDestination, 0x7ba /* Replace detached source field and detached destination field must be different */);
                this.detachEdit(range, oldContentDestination);
                this.attachEdit(newContentSource, range.end - range.start, range.start);
            }
            enterNode(index) {
                cursor.enterNode(index);
            }
            exitNode(index) {
                cursor.exitNode();
            }
            enterField(key) {
                cursor.enterField(key);
            }
            exitField(key) {
                cursor.exitField();
            }
        }
        const visitor = new Visitor(this);
        this.activeVisitor = visitor;
        return visitor;
    }
    newDetachedField() {
        const range = brand(String(this.nextRange));
        this.nextRange += 1;
        return range;
    }
    add(nodes, key) {
        const field = Array.from(nodes, mapTreeFromCursor);
        this.addFieldAsDetached(field, key);
    }
    addFieldAsDetached(field, key) {
        assert(!this.roots.fields.has(key), 0x370 /* new range must not already exist */);
        if (field.length > 0) {
            __classPrivateFieldGet(this, _ObjectForest_roots, "f").fields.set(key, field);
        }
    }
    delete(field) {
        __classPrivateFieldGet(this, _ObjectForest_roots, "f").fields.delete(field);
    }
    allocateCursor(source) {
        return new Cursor(this, source);
    }
    tryMoveCursorToNode(destination, cursorToMove) {
        const path = this.anchors.locate(destination);
        if (path === undefined) {
            return -1 /* TreeNavigationResult.NotFound */;
        }
        this.moveCursorToPath(path, cursorToMove);
        return 1 /* TreeNavigationResult.Ok */;
    }
    tryMoveCursorToField(destination, cursorToMove) {
        if (destination.parent === undefined) {
            this.moveCursorToPath(undefined, cursorToMove);
        }
        else {
            const result = this.tryMoveCursorToNode(destination.parent, cursorToMove);
            if (result !== 1 /* TreeNavigationResult.Ok */) {
                return result;
            }
        }
        cursorToMove.enterField(destination.fieldKey);
        return 1 /* TreeNavigationResult.Ok */;
    }
    moveCursorToPath(destination, cursorToMove) {
        assert(cursorToMove instanceof Cursor, 0x337 /* ObjectForest must only be given its own Cursor type */);
        assert(cursorToMove.forest === this, 0x338 /* ObjectForest must only be given its own Cursor */);
        const indexStack = [];
        const keyStack = [];
        let path = destination;
        while (path !== undefined) {
            indexStack.push(path.parentIndex);
            keyStack.push(path.parentField);
            path = path.parent;
        }
        cursorToMove.setToAboveDetachedSequences();
        while (keyStack.length > 0) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            cursorToMove.enterField(keyStack.pop());
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            cursorToMove.enterNode(indexStack.pop());
        }
        return;
    }
    getCursorAboveDetachedFields() {
        return cursorForMapTreeNode(this.roots);
    }
}
_ObjectForest_roots = new WeakMap();
/**
 * Cursor implementation for ObjectForest.
 */
class Cursor extends SynchronousCursor {
    /**
     * @param forest - forest this cursor navigates
     * @param innerCursor - underlying cursor implementation this wraps. `undefined` when state is not `Current`
     */
    constructor(forest, source, innerCursor) {
        super();
        this.forest = forest;
        this.source = source;
        this.innerCursor = innerCursor;
        if (innerCursor === undefined) {
            this.state = ITreeSubscriptionCursorState.Cleared;
        }
        else {
            this.state = ITreeSubscriptionCursorState.Current;
            this.forest.currentCursors.add(this);
        }
    }
    buildFieldAnchor() {
        const path = this.getFieldPath();
        const anchor = path.parent === undefined ? undefined : this.forest.anchors.track(path.parent);
        return { parent: anchor, fieldKey: path.field };
    }
    getFieldPath(prefix) {
        assert(this.innerCursor !== undefined, 0x45f /* Cursor must be current to be used */);
        return this.innerCursor.getFieldPath(prefix);
    }
    get mode() {
        assert(this.innerCursor !== undefined, 0x42e /* Cursor must be current to be used */);
        return this.innerCursor.mode;
    }
    nextField() {
        assert(this.innerCursor !== undefined, 0x42f /* Cursor must be current to be used */);
        return this.innerCursor.nextField();
    }
    exitField() {
        assert(this.innerCursor !== undefined, 0x430 /* Cursor must be current to be used */);
        return this.innerCursor.exitField();
    }
    skipPendingFields() {
        assert(this.innerCursor !== undefined, 0x431 /* Cursor must be current to be used */);
        return this.innerCursor.skipPendingFields();
    }
    getFieldKey() {
        assert(this.innerCursor !== undefined, 0x432 /* Cursor must be current to be used */);
        return this.innerCursor.getFieldKey();
    }
    getFieldLength() {
        assert(this.innerCursor !== undefined, 0x433 /* Cursor must be current to be used */);
        return this.innerCursor.getFieldLength();
    }
    firstNode() {
        assert(this.innerCursor !== undefined, 0x434 /* Cursor must be current to be used */);
        return this.innerCursor.firstNode();
    }
    enterNode(childIndex) {
        assert(this.innerCursor !== undefined, 0x435 /* Cursor must be current to be used */);
        return this.innerCursor.enterNode(childIndex);
    }
    getPath(prefix) {
        assert(this.innerCursor !== undefined, 0x436 /* Cursor must be current to be used */);
        return this.innerCursor.getPath(prefix) ?? fail("no path when at root");
    }
    get fieldIndex() {
        assert(this.innerCursor !== undefined, 0x437 /* Cursor must be current to be used */);
        return this.innerCursor.fieldIndex;
    }
    get chunkStart() {
        assert(this.innerCursor !== undefined, 0x438 /* Cursor must be current to be used */);
        return this.innerCursor.chunkStart;
    }
    get chunkLength() {
        assert(this.innerCursor !== undefined, 0x439 /* Cursor must be current to be used */);
        return this.innerCursor.chunkLength;
    }
    seekNodes(offset) {
        assert(this.innerCursor !== undefined, 0x43a /* Cursor must be current to be used */);
        return this.innerCursor.seekNodes(offset);
    }
    nextNode() {
        assert(this.innerCursor !== undefined, 0x43b /* Cursor must be current to be used */);
        return this.innerCursor.nextNode();
    }
    exitNode() {
        assert(this.innerCursor !== undefined, 0x43c /* Cursor must be current to be used */);
        return this.innerCursor.exitNode();
    }
    firstField() {
        assert(this.innerCursor !== undefined, 0x43d /* Cursor must be current to be used */);
        return this.innerCursor.firstField();
    }
    enterField(key) {
        assert(this.innerCursor !== undefined, 0x43e /* Cursor must be current to be used */);
        return this.innerCursor.enterField(key);
    }
    get type() {
        assert(this.innerCursor !== undefined, 0x43f /* Cursor must be current to be used */);
        return this.innerCursor.type;
    }
    get value() {
        assert(this.innerCursor !== undefined, 0x440 /* Cursor must be current to be used */);
        return this.innerCursor.value;
    }
    // TODO: tests for clear when not at root.
    clear() {
        assert(this.state !== ITreeSubscriptionCursorState.Freed, 0x33b /* Cursor must not be freed */);
        this.state = ITreeSubscriptionCursorState.Cleared;
        this.innerCursor = undefined;
        this.forest.currentCursors.delete(this);
    }
    /**
     * Move this cursor to the special dummy node above the detached sequences.
     * Can be used when cleared (but not freed).
     */
    setToAboveDetachedSequences() {
        assert(this.state !== ITreeSubscriptionCursorState.Freed, 0x33c /* Cursor must not be freed */);
        this.clear();
        this.state = ITreeSubscriptionCursorState.Current;
        // Cast to a cursor of _mutable_ trees to allow direct manipulation of the tree data which is more efficient than doing immutable copies.
        this.innerCursor = cursorForMapTreeNode(this.forest.roots);
        this.forest.currentCursors.add(this);
    }
    getNode() {
        assert(this.innerCursor !== undefined, 0x33e /* Cursor must be current to be used */);
        return this.innerCursor.getNode();
    }
    getParent() {
        assert(this.innerCursor !== undefined, 0x441 /* Cursor must be current to be used */);
        // This could be optimized to skip moving it accessing internals of cursor.
        const key = this.innerCursor.getFieldKey();
        this.innerCursor.exitField();
        const node = this.innerCursor.getNode();
        this.innerCursor.enterField(key);
        return [node, key];
    }
    fork(source) {
        assert(this.innerCursor !== undefined, 0x460 /* Cursor must be current to be used */);
        return new Cursor(this.forest, source ?? `fork: ${this.source}`, this.innerCursor.fork());
    }
    free() {
        assert(this.state !== ITreeSubscriptionCursorState.Freed, 0x33f /* Cursor must not be double freed */);
        this.forest.currentCursors.delete(this);
        this.state = ITreeSubscriptionCursorState.Freed;
    }
    buildAnchor() {
        assert(this.state === ITreeSubscriptionCursorState.Current, 0x37a /* Cursor must be current to be used */);
        return this.forest.anchors.track(this.getPath());
    }
}
// This function is the folder level export for objectForest, and hides all the implementation types.
// When other forest implementations are created (ex: optimized ones),
// this function should likely be moved and updated to (at least conditionally) use them.
/**
 * @returns an implementation of {@link IEditableForest} with no data or schema.
 */
export function buildForest(anchors, additionalAsserts = false) {
    return new ObjectForest(anchors, additionalAsserts);
}
//# sourceMappingURL=objectForest.js.map