"use strict";
/*!
 * 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 _AnchorSet_events;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnchorSet = exports.anchorSlot = void 0;
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const internal_1 = require("@fluidframework/core-utils/internal");
const client_utils_1 = require("@fluid-internal/client-utils");
const index_js_1 = require("../../util/index.js");
const types_js_1 = require("./types.js");
/**
 * A singleton which represents a permanently invalid location (i.e. there is never a node there)
 */
const NeverAnchor = (0, index_js_1.brand)(0);
/**
 * Define a strongly typed slot on anchors in which data can be stored.
 *
 * @remarks
 * This is mainly useful for caching data associated with a location in the tree.
 *
 * Example usage:
 * ```typescript
 * const counterSlot = anchorSlot<number>();
 *
 * function useSlot(anchor: AnchorNode): void {
 * 	anchor.slots.set(counterSlot, 1 + anchor.slots.get(counterSlot) ?? 0);
 * }
 * ```
 */
function anchorSlot() {
    return (0, index_js_1.brandedSlot)();
}
exports.anchorSlot = anchorSlot;
/**
 * Collection of Anchors at a specific revision.
 *
 * See `Rebaser` for how to update across revisions.
 *
 * TODO: this should not be package exported.
 * If it's needed outside the package an Interface should be used instead which can reduce its
 * API surface to a small subset.
 *
 * @sealed
 */
class AnchorSet {
    constructor() {
        _AnchorSet_events.set(this, (0, client_utils_1.createEmitter)());
        this.events = __classPrivateFieldGet(this, _AnchorSet_events, "f");
        /**
         * Incrementing counter to give each anchor in this set a unique index for its identifier.
         * "0" is reserved for the `NeverAnchor`.
         */
        this.anchorCounter = 1;
        /**
         * Incrementing number that is bumped each time that the {@link AnchorSet} is changed.
         * This allows consumers to cache state associated with a particular generation number and later determine if that state may have been invalidated using a comparison with the current generation number.
         * For example, anchor slots can be used to cache the removal status of a node to memoize repeated walks up the tree.
         */
        this.generationNumber = 0;
        /**
         * Special root node under which all anchors in this anchor set are transitively parented.
         * This does not appear in the UpPaths (instead they use undefined for the root).
         * Immediate children of this root are in detached fields (which have their identifiers used as the field keys).
         *
         * This is allocated with refCount one, which is never freed so it is never cleaned up
         * (as long as this AnchorSet is not garbage collected).
         *
         * There should never be any children other than the special root detached field under this between transactions:
         * TODO: check for and enforce this.
         */
        this.root = new PathNode(this, types_js_1.EmptyKey, 0, undefined);
        // TODO: anchor system could be optimized a bit to avoid the maps (Anchor is ref to Path, path has ref count).
        // For now use this more encapsulated approach with maps.
        this.anchorToPath = new Map();
        this.events.on("treeChanging", () => {
            this.generationNumber += 1;
        });
    }
    /**
     * Allows access to data stored on the AnchorSet in "slots".
     * Use {@link anchorSlot} to create slots.
     *
     * @privateRemarks
     * This forwards to the slots of the special above root anchor which locate can't access.
     */
    // See note on BrandedKey.
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    get slots() {
        return this.root.slots;
    }
    *[(_AnchorSet_events = new WeakMap(), Symbol.iterator)]() {
        const stack = [];
        let node = this.root;
        while (node !== undefined) {
            yield node;
            for (const [_, children] of node.children) {
                for (const child of children) {
                    stack.push(child);
                }
            }
            node = stack.pop();
        }
    }
    /**
     * Check if there are currently no anchors tracked.
     * Mainly for testing anchor cleanup.
     */
    isEmpty() {
        return this.root.children.size === 0;
    }
    locate(anchor) {
        if (anchor === NeverAnchor) {
            return undefined;
        }
        const path = this.anchorToPath.get(anchor);
        (0, internal_1.assert)(path !== undefined, 0x3a6 /* Cannot locate anchor which is not in this AnchorSet */);
        return path.status === Status.Alive ? path : undefined;
    }
    forget(anchor) {
        if (anchor !== NeverAnchor) {
            const path = this.anchorToPath.get(anchor);
            (0, internal_1.assert)(path !== undefined, 0x351 /* cannot forget unknown Anchor */);
            path.removeRef();
            this.anchorToPath.delete(anchor);
        }
    }
    /**
     * TODO: Add APIs need to allow callers of this function to reduce copying here.
     * Ex: maybe return something extending UpPath here.
     * @param path - the path to the node to be tracked. If null, returns an anchor
     * which is permanently invalid.
     */
    // eslint-disable-next-line @rushstack/no-new-null
    track(path) {
        if (path === null) {
            return NeverAnchor;
        }
        const foundPath = this.trackInner(path);
        const anchor = (0, index_js_1.brand)(this.anchorCounter++);
        this.anchorToPath.set(anchor, foundPath);
        return anchor;
    }
    /**
     * Finds a path node, creating if needed, and adds a ref count to it.
     */
    trackInner(path) {
        if (path instanceof PathNode && path.anchorSet === this) {
            path.addRef();
            return path;
        }
        const parent = path.parent ?? this.root;
        const parentPath = this.trackInner(parent);
        const child = parentPath.getOrCreateChild(path.parentField, path.parentIndex);
        // Now that child is added (if needed), remove the extra ref that we added in the recursive call.
        parentPath.removeRef();
        return child;
    }
    /**
     * Finds a path node if it already exists.
     *
     * Does not add a ref!
     */
    find(path) {
        if (path instanceof PathNode) {
            if (path.anchorSet === this) {
                return path;
            }
        }
        const parent = path.parent ?? this.root;
        const parentPath = this.find(parent);
        return parentPath?.childIfAnchored(path.parentField, path.parentIndex);
    }
    /**
     * Returns an equivalent path making as much of it with PathNodes as possible.
     * This allows future operations (like find, track, locate) on this path (and derived ones) to be faster.
     * Note that the returned path may use AnchorNodes from this AnchorSet,
     * but does not have a tracked reference to them, so this should not be held onto across anything that might free an AnchorNode.
     *
     * @remarks
     * Also ensures that any PathNode in the path is from this AnchorSet.
     */
    internalizePath(originalPath) {
        let path = originalPath;
        const stack = [];
        while (path !== undefined) {
            if (path instanceof PathNode && path.anchorSet === this) {
                break;
            }
            stack.push(path);
            path = path.parent;
        }
        // Now `path` contains an internalized path.
        // It just needs the paths from stackOut to wrap it.
        let wrapWith;
        while ((wrapWith = stack.pop()) !== undefined) {
            if (path === undefined || path instanceof PathNode) {
                // If path already has an anchor, get an anchor for it's child if there is one:
                const child = (path ?? this.root).childIfAnchored(wrapWith.parentField, wrapWith.parentIndex);
                if (child !== undefined) {
                    path = child;
                    continue;
                }
            }
            // Replacing this if with a ternary makes the documentation harder to include and hurts readability.
            // eslint-disable-next-line unicorn/prefer-ternary
            if (path === wrapWith.parent && !(wrapWith instanceof PathNode)) {
                // path is safe to reuse from input path, so use it to avoid allocating another object.
                path = wrapWith;
            }
            else {
                path = {
                    parent: path,
                    parentField: wrapWith.parentField,
                    parentIndex: wrapWith.parentIndex,
                };
            }
        }
        return path ?? (0, index_js_1.fail)("internalize path must be a path");
    }
    /**
     * Recursively marks the given `nodes` and their descendants as disposed and pointing to a deleted node.
     * Note that this does NOT detach the nodes.
     */
    deepDelete(nodes) {
        const stack = [...nodes];
        while (stack.length > 0) {
            const node = stack.pop();
            (0, internal_1.assert)(node.status === Status.Alive, 0x408 /* PathNode must be alive */);
            node.status = Status.Dangling;
            node.events.emit("afterDestroy", node);
            for (const children of node.children.values()) {
                stack.push(...children);
            }
        }
    }
    /**
     * Decouple nodes from their parent.
     * This removes the reference from the parent to the decoupled children, and updates the indexes of the remaining children accordingly.
     * This does NOT update the decoupled children: both their index and parent are left at their existing values.
     * To decouple and fixup the children, see `removeChildren` and `moveChildren`.
     * @param startPath - The path to the first node that is being decoupled.
     * @param count - number of siblings that are decoupled from the original tree.
     *
     * TODO: tests
     */
    decoupleNodes(startPath, count) {
        (0, internal_1.assert)(count > 0, 0x681 /* count must be positive */);
        const sourceParent = this.find(startPath.parent ?? this.root);
        const sourceChildren = sourceParent?.children?.get(startPath.parentField);
        let nodes = [];
        if (sourceChildren !== undefined) {
            let numberBeforeDecouple = 0;
            let numberToDecouple = 0;
            let index = 0;
            while (index < sourceChildren.length &&
                sourceChildren[index].parentIndex < startPath.parentIndex) {
                numberBeforeDecouple++;
                index++;
            }
            while (index < sourceChildren.length &&
                sourceChildren[index].parentIndex < startPath.parentIndex + count) {
                numberToDecouple++;
                index++;
            }
            while (index < sourceChildren.length) {
                // Fix indexes in source after moved items (subtract count).
                sourceChildren[index].parentIndex -= count;
                index++;
            }
            // Sever the parent -> child connections
            nodes = sourceChildren.splice(numberBeforeDecouple, numberToDecouple);
            if (sourceChildren.length === 0) {
                sourceParent.afterEmptyField(startPath.parentField);
            }
        }
        return nodes;
    }
    /**
     * Couple nodes to a parent.
     * @param destination - where the siblings are coupled to.
     * @param count - number of siblings that are coupled in the original tree.
     * @param coupleInfo - this object contains the nodes to couple and the parent index of the first node that is coupled in the original tree.
     *
     * TODO: tests
     */
    coupleNodes(destination, count, coupleInfo) {
        (0, internal_1.assert)(coupleInfo.nodes.length > 0, 0x682 /* coupleInfo must have nodes to couple */);
        // The destination needs to be created if it does not exist yet.
        const destinationPath = this.trackInner(destination.parent ?? this.root);
        // Update nodes for new parent.
        for (const node of coupleInfo.nodes) {
            node.parentIndex += destination.parentIndex - coupleInfo.startParentIndex;
            node.parentPath = destinationPath;
            node.parentField = destination.parentField;
        }
        // Update new parent to add children
        const field = destinationPath.children.get(destination.parentField);
        if (field === undefined) {
            destinationPath.children.set(destination.parentField, coupleInfo.nodes);
        }
        else {
            // Update existing field contents
            const numberBeforeCouple = this.increaseParentIndexes(field, destination.parentIndex, count);
            // TODO: this will fail for very large numbers of anchors due to argument limits.
            field.splice(numberBeforeCouple, 0, ...coupleInfo.nodes);
        }
        destinationPath.removeRef();
    }
    /**
     * Updates the parent indexes within `field` to account for `count` children being inserted at `fromParentIndex`. Note that
     * `fromParentIndex` is the logical position within the field, not the index with the sparse PathNode array.
     *
     * @param field - the field to update.
     * @param fromParentIndex - the logical index within the field to start updating from.
     * @param count - the number to increase parent indexes.
     * @returns the number of items in the field that are not increased.
     *
     * TODO: tests
     */
    increaseParentIndexes(field, fromParentIndex, count) {
        let index = 0;
        while (index < field.length && field[index].parentIndex < fromParentIndex) {
            index++;
        }
        const numberBeforeIncrease = index;
        while (index < field.length) {
            field[index].parentIndex += count;
            index++;
        }
        return numberBeforeIncrease;
    }
    /**
     * Updates paths for a range move (including re-parenting path items and updating indexes).
     * @param sourceStart - where the siblings are removed from.
     * @param destination - where the siblings are moved to.
     * @param count - number of siblings to move.
     *
     * TODO:
     * How should anchors that become invalid, then valid again (ex: into content that was deleted, then undone) work?
     * Add an API to resurrect them? Store them in special detached fields? Store them in special non-detached fields?
     *
     * TODO:
     * How should custom anchors work (ex: ones not just tied to a specific Node)?
     * This design assumes they can be expressed in terms of a Node anchor + some extra stuff,
     * but we don't have an API for the extra stuff yet.
     *
     * TODO: tests
     */
    moveChildren(sourceStart, destination, count) {
        const nodes = this.decoupleNodes(sourceStart, count);
        if (nodes.length > 0) {
            this.coupleNodes(destination, count, {
                startParentIndex: sourceStart.parentIndex,
                nodes,
            });
        }
        else {
            // If there are no nodes to move, we still need to update the parent indexes of the nodes
            // affected in the move in.
            this.offsetChildren(destination, count);
        }
    }
    removeChildren(path, count) {
        const nodes = this.decoupleNodes(path, count);
        this.deepDelete(nodes);
    }
    /**
     * Updates the parent indexes of all the nodes located at right side of the given path by the given offset.
     * @param firstSiblingToOffset - the path to offset children of.
     * @param offset - the offset to apply to the children.
     *
     */
    offsetChildren(firstSiblingToOffset, offset) {
        const nodePath = this.find(firstSiblingToOffset.parent ?? this.root);
        const field = nodePath?.children.get(firstSiblingToOffset.parentField);
        if (field !== undefined) {
            this.increaseParentIndexes(field, firstSiblingToOffset.parentIndex, offset);
        }
    }
    /**
     * Provides a visitor that can be used to mutate this {@link AnchorSet}.
     *
     * @returns A visitor that can be used to mutate this {@link AnchorSet}.
     *
     * @remarks
     * Mutating the {@link AnchorSet} does NOT update the forest.
     * The visitor must be released after use by calling {@link DeltaVisitor.free} on it.
     * It is invalid to acquire a visitor without releasing the previous one,
     * and this method will throw an error if this is attempted.
     */
    acquireVisitor() {
        (0, internal_1.assert)(this.activeVisitor === undefined, 0x767 /* Must release existing visitor before acquiring another */);
        const referencedPathNodes = [];
        const visitor = {
            anchorSet: this,
            // Run `withNode` on anchorNode for parent if there is such an anchorNode.
            // If at root, run `withRoot` instead.
            maybeWithNode(withNode, withRoot) {
                if (this.parent === undefined && withRoot !== undefined) {
                    withRoot();
                }
                else {
                    (0, internal_1.assert)(this.parent !== undefined, 0x5b0 /* parent must exist */);
                    // TODO:Perf:
                    // When traversing to a depth D when there are not anchors in that subtree, this goes O(D^2).
                    // Delta traversal should early out in this case because no work is needed (and all move outs are known to not contain anchors).
                    this.parent = this.anchorSet.internalizePath(this.parent);
                    if (this.parent instanceof PathNode) {
                        this.parent.addRef();
                        referencedPathNodes.push(this.parent);
                        withNode(this.parent);
                    }
                }
            },
            // Lookup table for path visitors collected from {@link AnchorEvents.visitSubtreeChanging} emitted events.
            // The key is the path of the node that the visitor is registered on. The code ensures that the path visitor visits only the appropriate subtrees
            // by maintaining the mapping only during time between the {@link DeltaVisitor.enterNode} and {@link DeltaVisitor.exitNode} calls for a given anchorNode.
            pathVisitors: new Map(),
            parentField: undefined,
            parent: undefined,
            /**
             * Events collected during the visit which get sent as a batch during "free".
             */
            bufferedEvents: [],
            // 'currentDepth' and 'depthThresholdForSubtreeChanged' serve to keep track of when do we need to emit
            // subtreeChangedAfterBatch events.
            // The algorithm works as follows:
            // - Initialize both to 0.
            // - As we walk the tree from the root towards the leaves, when we enter a node increment currentDepth by 1.
            // - When we edit a node, set depthThresholdForSubtreeChanged = currentDepth.
            //   Intuitively, depthThresholdForSubtreeChanged means "as you walk the tree towards the root, when you exit a
            //   node at this depth you should emit a subtreeChangedAfterBatch event".
            // - When we exit a node, if d === currentDepth then emit a subtreeChangedAfterBatch and decrement d by 1.
            //   Then decrement currentDepth unconditionally.
            // Note that the event will be emitted when exiting a node that was edited (depthThresholdForSubtreeChanged will
            // have been set to the current depth when the edit happened), it will be emitted when exiting a node that is the
            // parent of a node that already emitted the event (because both depthThresholdForSubtreeChanged and currentDepth
            // get decremented when exiting a node so they stay in sync), and if we're already emitting the event but start
            // walking the tree back towards the leaves in a path where no edits happen, currentDepth will be increased again
            // as we walk that path, depthThresholdForSubtreeChanged will not, and thus no event will be emitted when walking
            // back up that path, until we get back to the depth where we were already emitting the event, and will continue
            // emitting it on the way to the root.
            currentDepth: 0,
            depthThresholdForSubtreeChanged: 0,
            free() {
                (0, internal_1.assert)(this.anchorSet.activeVisitor !== undefined, 0x768 /* Multiple free calls for same visitor */);
                for (const node of referencedPathNodes) {
                    node.removeRef();
                }
                this.anchorSet.activeVisitor = undefined;
                const alreadyEmitted = new Map();
                for (const { node, event } of this.bufferedEvents) {
                    if (!alreadyEmitted.has(node)) {
                        alreadyEmitted.set(node, []);
                    }
                    const emittedEvents = alreadyEmitted.get(node);
                    if (emittedEvents?.includes(event) ?? false) {
                        continue;
                    }
                    emittedEvents?.push(event);
                    if (event === "childrenChangedAfterBatch") {
                        const fieldKeys = this.bufferedEvents
                            .filter((e) => e.node === node && e.event === event)
                            .map((e) => e.changedField ??
                            (0, index_js_1.fail)("childrenChangedAfterBatch events should have a changedField"));
                        node.events.emit(event, { changedFields: new Set(fieldKeys) });
                    }
                    else {
                        node.events.emit(event);
                    }
                }
            },
            notifyChildrenChanging() {
                this.maybeWithNode((p) => p.events.emit("childrenChanging", p), () => __classPrivateFieldGet(this.anchorSet, _AnchorSet_events, "f").emit("childrenChanging", this.anchorSet));
            },
            notifyChildrenChanged() {
                this.maybeWithNode((p) => {
                    (0, internal_1.assert)(this.parentField !== undefined, 0xa24 /* Must be in a field to modify its contents */);
                    p.events.emit("childrenChanged", p);
                    this.bufferedEvents.push({
                        node: p,
                        event: "childrenChangedAfterBatch",
                        changedField: this.parentField,
                    });
                }, () => { });
            },
            beforeAttach(source, count, destination) {
                (0, internal_1.assert)(this.parentField !== undefined, 0x7a0 /* Must be in a field in order to attach */);
                const destinationPath = {
                    parent: this.parent,
                    field: this.parentField,
                    index: destination,
                };
                const sourcePath = (0, index_js_1.brand)({
                    field: source,
                    start: 0,
                    end: count,
                });
                for (const visitors of this.pathVisitors.values()) {
                    for (const pathVisitor of visitors) {
                        pathVisitor.beforeAttach(sourcePath, destinationPath);
                    }
                }
            },
            afterAttach(source, destination) {
                (0, internal_1.assert)(this.parentField !== undefined, 0x7a1 /* Must be in a field in order to attach */);
                const sourcePath = (0, index_js_1.brand)({
                    field: source,
                    index: 0,
                });
                const destinationPath = {
                    parent: this.parent,
                    field: this.parentField,
                    ...destination,
                };
                for (const visitors of this.pathVisitors.values()) {
                    for (const pathVisitor of visitors) {
                        pathVisitor.afterAttach(sourcePath, destinationPath);
                    }
                }
            },
            attach(source, count, destination) {
                this.notifyChildrenChanging();
                this.attachEdit(source, count, destination);
                this.notifyChildrenChanged();
            },
            attachEdit(source, count, destination) {
                (0, internal_1.assert)(this.parentField !== undefined, 0x7a2 /* Must be in a field in order to attach */);
                const sourcePath = {
                    parent: this.anchorSet.root,
                    parentField: source,
                    parentIndex: 0,
                };
                const destinationPath = {
                    parent: this.parent,
                    parentField: this.parentField,
                    parentIndex: destination,
                };
                this.anchorSet.moveChildren(sourcePath, destinationPath, count);
                this.depthThresholdForSubtreeChanged = this.currentDepth;
            },
            beforeDetach(source, destination) {
                (0, internal_1.assert)(this.parentField !== undefined, 0x7a3 /* Must be in a field in order to attach */);
                const sourcePath = {
                    parent: this.parent,
                    field: this.parentField,
                    ...source,
                };
                const destinationPath = (0, index_js_1.brand)({
                    field: destination,
                    index: 0,
                });
                for (const visitors of this.pathVisitors.values()) {
                    for (const pathVisitor of visitors) {
                        pathVisitor.beforeDetach(sourcePath, destinationPath);
                    }
                }
            },
            afterDetach(source, count, destination) {
                (0, internal_1.assert)(this.parentField !== undefined, 0x7a4 /* Must be in a field in order to attach */);
                const sourcePath = {
                    parent: this.parent,
                    field: this.parentField,
                    index: source,
                };
                const destinationPath = (0, index_js_1.brand)({
                    field: destination,
                    start: 0,
                    end: count,
                });
                for (const visitors of this.pathVisitors.values()) {
                    for (const pathVisitor of visitors) {
                        pathVisitor.afterDetach(sourcePath, destinationPath);
                    }
                }
            },
            detach(source, destination) {
                this.notifyChildrenChanging();
                this.detachEdit(source, destination);
                this.notifyChildrenChanged();
            },
            detachEdit(source, destination) {
                (0, internal_1.assert)(this.parentField !== undefined, 0x7a5 /* Must be in a field in order to detach */);
                const sourcePath = {
                    parent: this.parent,
                    parentField: this.parentField,
                    parentIndex: source.start,
                };
                const destinationPath = {
                    parent: this.anchorSet.root,
                    parentField: destination,
                    parentIndex: 0,
                };
                this.anchorSet.moveChildren(sourcePath, destinationPath, source.end - source.start);
                this.depthThresholdForSubtreeChanged = this.currentDepth;
            },
            beforeReplace(newContent, oldContent, destination) {
                (0, internal_1.assert)(this.parentField !== undefined, 0x7a6 /* Must be in a field in order to replace */);
                const oldContentPath = {
                    parent: this.parent,
                    field: this.parentField,
                    ...oldContent,
                };
                const newNodesSourcePath = (0, index_js_1.brand)({
                    field: newContent,
                    start: 0,
                    end: oldContent.end - oldContent.start,
                });
                const oldNodesDestinationPath = (0, index_js_1.brand)({
                    field: destination,
                    index: 0,
                });
                for (const visitors of this.pathVisitors.values()) {
                    for (const pathVisitor of visitors) {
                        pathVisitor.beforeReplace(newNodesSourcePath, oldContentPath, oldNodesDestinationPath);
                    }
                }
            },
            afterReplace(newContentSource, newContent, oldContent) {
                (0, internal_1.assert)(this.parentField !== undefined, 0x7a7 /* Must be in a field in order to replace */);
                const newContentPath = {
                    parent: this.parent,
                    field: this.parentField,
                    ...newContent,
                };
                const newNodesSourcePath = (0, index_js_1.brand)({
                    field: newContentSource,
                    index: 0,
                });
                const oldNodesDestinationPath = (0, index_js_1.brand)({
                    field: oldContent,
                    start: 0,
                    end: newContent.end - newContent.start,
                });
                for (const visitors of this.pathVisitors.values()) {
                    for (const pathVisitor of visitors) {
                        pathVisitor.afterReplace(newNodesSourcePath, newContentPath, oldNodesDestinationPath);
                    }
                }
            },
            replace(newContentSource, range, oldContentDestination) {
                this.notifyChildrenChanging();
                this.detachEdit(range, oldContentDestination);
                this.attachEdit(newContentSource, range.end - range.start, range.start);
                this.notifyChildrenChanged();
            },
            destroy(detachedField, count) {
                this.anchorSet.removeChildren({
                    parent: undefined,
                    parentField: detachedField,
                    parentIndex: 0,
                }, count);
            },
            beforeDestroy(detachedField, count) {
                const range = (0, index_js_1.brand)({
                    field: detachedField,
                    start: 0,
                    end: count,
                });
                for (const visitors of this.pathVisitors.values()) {
                    for (const pathVisitor of visitors) {
                        pathVisitor.beforeDestroy(range);
                    }
                }
            },
            create(content, destination) {
                // Nothing to do since content can only be created in a new detached field,
                // which cannot contain any anchors.
            },
            afterCreate(content, destination) {
                for (const visitors of this.pathVisitors.values()) {
                    for (const pathVisitor of visitors) {
                        const rangePath = (0, index_js_1.brand)({
                            field: destination,
                            start: 0,
                            end: content.length,
                        });
                        pathVisitor.afterCreate(rangePath);
                    }
                }
            },
            enterNode(index) {
                (0, internal_1.assert)(this.parentField !== undefined, 0x3ab /* Must be in a field to enter node */);
                this.parent = {
                    parent: this.parent,
                    parentField: this.parentField,
                    parentIndex: index,
                };
                this.parentField = undefined;
                this.maybeWithNode((p) => {
                    // avoid multiple pass side-effects
                    if (!this.pathVisitors.has(p)) {
                        const visitors = p.events.emitAndCollect("subtreeChanging", p);
                        if (visitors.length > 0) {
                            this.pathVisitors.set(p, new Set(visitors.filter((v) => v !== undefined)));
                        }
                    }
                });
                this.currentDepth++;
            },
            exitNode(index) {
                (0, internal_1.assert)(this.parent !== undefined, 0x3ac /* Must have parent node */);
                this.maybeWithNode((p) => {
                    p.events.emit("subtreeChanged", p);
                    if (this.depthThresholdForSubtreeChanged === this.currentDepth) {
                        this.bufferedEvents.push({
                            node: p,
                            event: "subtreeChangedAfterBatch",
                        });
                        this.depthThresholdForSubtreeChanged--;
                    }
                    // Remove subtree path visitors added at this node if there are any
                    this.pathVisitors.delete(p);
                });
                const parent = this.parent;
                this.parentField = parent.parentField;
                this.parent = parent.parent;
                this.currentDepth--;
            },
            enterField(key) {
                this.parentField = key;
            },
            exitField(key) {
                this.parentField = undefined;
            },
        };
        __classPrivateFieldGet(this, _AnchorSet_events, "f").emit("treeChanging", this);
        this.activeVisitor = visitor;
        return visitor;
    }
}
exports.AnchorSet = AnchorSet;
/**
 * Indicates the status of a `NodePath`.
 */
var Status;
(function (Status) {
    /**
     * Indicates the `NodePath` is being maintained and corresponds to a valid
     * (i.e., not removed) node in the document.
     */
    Status[Status["Alive"] = 0] = "Alive";
    /**
     * Indicates the `NodePath` is not being maintained by the `AnchorSet`.
     * The `NodePath` may or may not correspond to a valid node in the document.
     *
     * Accessing such a node is invalid.
     * Nodes in this state are retained to detect use-after-free bugs.
     */
    Status[Status["Disposed"] = 1] = "Disposed";
    /**
     * Indicates the `NodePath` corresponds to a removed node in the document.
     * Such `NodePath`s are not maintained by the `AnchorSet` (other than updating
     * their status to `Disposed` when appropriate).
     *
     * Accessing such a node is invalid.
     * Nodes in this state are retained to detect use-after-free bugs.
     */
    Status[Status["Dangling"] = 2] = "Dangling";
})(Status || (Status = {}));
/**
 * Tree of anchors.
 *
 * Contains both child and parent pointers, which are kept in sync.
 *
 * Each anchor is equivalent to a path through the tree.
 * This tree structure stores a collection of these paths, but deduplicating the common prefixes of the tree
 * prefix-tree style.
 *
 * These anchors are used instead of just holding onto the node objects in forests for several reasons:
 *
 * - Update policy might be more complex than just tracking a node object in the forest.
 *
 * - Not all forests will have node objects: some may use compressed binary formats with no objects to reference.
 *
 * - Anchors are needed even when not using forests, and for nodes that are outside the currently loaded part of the
 * forest.
 *
 * - Forest in general do not need to support up pointers, but they are needed for anchors.
 *
 * Thus this can be thought of as a sparse copy of the subset of trees which are used as anchors,
 * plus the parent paths for them.
 *
 * ReferenceCountedBase tracks the number of references to this from external sources (`Anchors` via `AnchorSet`.).
 * Kept alive as if any of the follow are true:
 * 1. there are children.
 * 2. refcount is non-zero.
 * 3. events are registered.
 */
class PathNode extends index_js_1.ReferenceCountedBase {
    /**
     * Construct a PathNode with refcount 1.
     * @param anchorSet - used to determine if this PathNode is already part of a specific anchorSet
     * to early out UpPath walking.
     */
    constructor(anchorSet, parentField, parentIndex, 
    /**
     * The parent of this `PathNode` (an up pointer in the `PathNode` tree).
     * If the status of this node is `Alive`, then there must be a corresponding down pointer from the
     * `parentPath` node to this node.
     * When undefined, this node is the {@link AnchorSet.root} for `this.anchorSet` and thus has no parent.
     *
     * When updating the tree, `AnchorSet` may transiently leave the up and down pointers inconsistent
     * (updating down pointers first), but must ensure they are consistent before the editing operation returns
     * to non-`AnchorSet` code.
     * This consistency guarantee only applies to nodes that are `Alive`.
     */
    parentPath) {
        super(1);
        this.anchorSet = anchorSet;
        this.parentField = parentField;
        this.parentIndex = parentIndex;
        this.parentPath = parentPath;
        this.status = Status.Alive;
        /**
         * Event emitter for this anchor.
         */
        this.events = (0, client_utils_1.createEmitter)(() => this.considerDispose());
        /**
         * PathNode arrays are kept sorted the PathNode's parentIndex for efficient search.
         * Users of this field must take care to maintain invariants (correct parent pointers, not empty child arrays etc.)
         *
         * Performance Note:
         * Large child lists could be updated more efficiently here using a data-structure optimized
         * for efficient prefix sum updates, such as a Fenwick tree or Finger tree.
         * This would be complicated by the need for parent pointers (including indexes),
         * but is possible to do.
         */
        this.children = new Map();
        // See note on BrandedKey.
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.slots = new Map();
    }
    child(key, index) {
        // Fast path: if child exists, return it.
        return (this.childIfAnchored(key, index) ?? {
            parent: this,
            parentField: key,
            parentIndex: index,
        });
    }
    getOrCreateChildRef(key, index) {
        const anchor = this.anchorSet.track(this.child(key, index));
        const node = this.anchorSet.locate(anchor) ?? (0, index_js_1.fail)("cannot reference child that does not exist");
        return [anchor, node];
    }
    /**
     * @returns true iff this PathNode is the special root node that sits above all the detached fields.
     * In this case, the fields are detached sequences.
     * Note that the special root node should never appear in an UpPath
     * since UpPaths represent this root as `undefined`.
     */
    isRoot() {
        return this.parentPath === undefined;
    }
    get parent() {
        (0, internal_1.assert)(this.status !== Status.Disposed, 0x409 /* PathNode must not be disposed */);
        (0, internal_1.assert)(this.parentPath !== undefined, 0x355 /* PathNode.parent is an UpPath API and thus should never be called on the root PathNode. */);
        // Root PathNode corresponds to the undefined root for UpPath API.
        if (this.parentPath.isRoot()) {
            return undefined;
        }
        return this.parentPath;
    }
    addRef(count = 1) {
        (0, internal_1.assert)(this.status === Status.Alive, 0x40a /* PathNode must be alive */);
        this.referenceAdded(count);
    }
    removeRef(count = 1) {
        (0, internal_1.assert)(this.status !== Status.Disposed, 0x40b /* PathNode must not be disposed */);
        this.referenceRemoved(count);
    }
    // Called when refcount is set to 0.
    // Node may be kept alive by children or events after this point.
    onUnreferenced() {
        this.considerDispose();
    }
    /**
     * Gets a child, adding a ref to it.
     * Creates child (with 1 ref) if needed.
     */
    getOrCreateChild(key, index) {
        (0, internal_1.assert)(this.status === Status.Alive, 0x40c /* PathNode must be alive */);
        let field = this.children.get(key);
        if (field === undefined) {
            field = [];
            this.children.set(key, field);
        }
        let child = binaryFind(field, index);
        if (child === undefined) {
            child = new PathNode(this.anchorSet, key, index, this);
            field.push(child);
            // Keep list sorted by index.
            field.sort((a, b) => a.parentIndex - b.parentIndex);
        }
        else {
            child.addRef();
        }
        return child;
    }
    childIfAnchored(key, index) {
        (0, internal_1.assert)(this.status === Status.Alive, 0x40d /* PathNode must be alive */);
        const field = this.children.get(key);
        return field === undefined ? undefined : binaryFind(field, index);
    }
    /**
     * Removes reference from this to `child`.
     * Since PathNodes are doubly linked,
     * the caller must ensure that the reference from child to parent is also removed (or the child is no longer used).
     */
    removeChild(child) {
        (0, internal_1.assert)(this.status === Status.Alive, 0x40e /* PathNode must be alive */);
        const key = child.parentField;
        const field = this.children.get(key);
        // TODO: should do more optimized search (ex: binary search or better) using child.parentIndex()
        // Note that this is the index in the list of child paths, not the index within the field
        const childIndex = field?.indexOf(child) ?? -1;
        (0, internal_1.assert)(childIndex !== -1, 0x35c /* child must be parented to be removed */);
        field?.splice(childIndex, 1);
        if (field?.length === 0) {
            this.afterEmptyField(key);
        }
    }
    /**
     * Call this after directly editing the child array for a field to be empty.
     * Handles cleaning up unneeded data
     * (like the field in the map, and possibly this entire PathNode and its parents if they are no longer needed.)
     */
    afterEmptyField(key) {
        (0, internal_1.assert)(this.status === Status.Alive, 0x40f /* PathNode must be alive */);
        this.children.delete(key);
        this.considerDispose();
    }
    /**
     * If node is no longer needed (has no references, no children and no events):
     * removes this from parent if alive, and sets this to disposed.
     * Must only be called when .
     *
     * Allowed when dangling (but not when disposed).
     */
    considerDispose() {
        (0, internal_1.assert)(this.status !== Status.Disposed, 0x41d /* PathNode must not be disposed */);
        if (this.isUnreferenced() && this.children.size === 0 && !this.events.hasListeners()) {
            if (this.status === Status.Alive) {
                this.parentPath?.removeChild(this);
            }
            this.status = Status.Disposed;
        }
    }
}
/**
 * Find a child PathNode by index using a binary search.
 * @param sorted - array of PathNode's sorted by parentIndex.
 * @param index - index being looked for.
 * @returns child with the requested parentIndex, or undefined.
 * @privateRemarks
 * This function is very commonly used with small arrays (length 0 or one for all non sequence fields),
 * and is currently a hot path due to how flex tree leaves to excessive cursor to anchor and anchor to cursor translations,
 * both of which walk paths down the AnchorSet.
 * Additionally current usages tends to fully populate the anchor tree leading the correct array index to be the requested parent index.
 * This makes the performance of this performance both important in small cases and easy to overly tune to the current usage patterns.
 * This lead to not implementing a general purpose reusable binary search.
 * Once this function is not so heavily overused due to inefficient patterns in flex-tree,
 * replacing it with a standard binary search is likely fine.
 * Until then, care and benchmarking should be used when messing with this function.
 */
function binaryFind(sorted, index) {
    // Try guessing the list is not sparse as a starter:
    const guess = sorted[index];
    if (guess !== undefined && guess.parentIndex === index) {
        return guess;
    }
    // inclusive
    let min = 0;
    // exclusive
    let max = sorted.length;
    while (min !== max) {
        const mid = Math.floor((min + max) / 2);
        const item = sorted[mid];
        const found = item.parentIndex;
        if (found === index) {
            return item; // Found the target, return it.
        }
        else if (found > index) {
            max = mid; // Continue search on lower half.
        }
        else {
            min = mid + 1; // Continue search on left half.
        }
    }
    return undefined; // If we reach here, target is not in array (or array was not sorted)
}
//# sourceMappingURL=anchorSet.js.map