"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
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 __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 _TreeNodeKernel_hydrationState, _TreeNodeKernel_eventBuffer, _KernelEventBuffer_instances, _KernelEventBuffer_disposed, _KernelEventBuffer_disposeOnFlushListener, _KernelEventBuffer_events, _KernelEventBuffer_eventSource, _KernelEventBuffer_disposeSourceListeners, _KernelEventBuffer_childrenChangedBuffer, _KernelEventBuffer_fieldMarksBuffer, _KernelEventBuffer_invalidatedFieldMarkKeys, _KernelEventBuffer_subTreeChangedBuffer, _KernelEventBuffer_emit, _KernelEventBuffer_handleChildrenChangedAfterBatch, _KernelEventBuffer_handleSubtreeChangedAfterBatch, _KernelEventBuffer_assertNotDisposed;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SimpleContextSlot = exports.createTreeNodeFromInner = exports.treeNodeFromAnchor = exports.getInnerNode = exports.getSimpleContextFromInnerNode = exports.getSimpleNodeSchemaFromInnerNode = exports.tryDisposeTreeNode = exports.simpleTreeNodeSlot = exports.splitInnerNodeType = exports.withBufferedTreeEvents = exports.TreeNodeKernel = exports.tryGetTreeNodeSchema = exports.isTreeNode = exports.getKernel = void 0;
const client_utils_1 = require("@fluid-internal/client-utils");
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("../../core/index.js");
const index_js_2 = require("../../feature-libraries/index.js");
const index_js_3 = require("../../feature-libraries/index.js");
const unhydratedFlexTree_js_1 = require("./unhydratedFlexTree.js");
const treeNodeToKernel = new WeakMap();
function getKernel(node) {
    const kernel = treeNodeToKernel.get(node);
    (0, internal_1.assert)(kernel !== undefined, 0x9b1 /* Expected tree node to have kernel */);
    return kernel;
}
exports.getKernel = getKernel;
/**
 * Detects if the given 'candidate' is a TreeNode.
 *
 * @remarks
 * Supports both Hydrated and {@link Unhydrated} TreeNodes, both of which return true.
 *
 * Because the common usage is to check if a value being inserted/set is a TreeNode,
 * this function permits calling with primitives as well as objects.
 *
 * Primitives will always return false (as they are copies of data, not references to nodes).
 *
 * @param candidate - Value which may be a TreeNode
 * @returns true if the given 'candidate' is a hydrated TreeNode.
 */
function isTreeNode(candidate) {
    return treeNodeToKernel.has(candidate);
}
exports.isTreeNode = isTreeNode;
/**
 * Returns a schema for a value if the value is a {@link TreeNode}.
 *
 * Returns undefined for other values.
 * @remarks
 * Does not give schema for a {@link TreeLeafValue}.
 */
function tryGetTreeNodeSchema(value) {
    const kernel = treeNodeToKernel.get(value);
    return kernel?.schema;
}
exports.tryGetTreeNodeSchema = tryGetTreeNodeSchema;
/** True if and only if the given {@link HydrationState} is post-hydration */
function isHydrated(state) {
    return state.innerNode.isHydrated();
}
/**
 * Contains state and an internal API for managing {@link TreeNode}s.
 * @remarks All {@link TreeNode}s have an associated kernel object.
 * The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states.
 */
class TreeNodeKernel {
    /**
     * Create a TreeNodeKernel which can be looked up with {@link getKernel}.
     *
     * @param initialContext - context from when this node was originally created. Only used when unhydrated.
     * @param innerNode - When unhydrated the MapTreeNode. Otherwise HydratedFlexTreeNode.
     * @remarks
     * Exactly one kernel per TreeNode should be created.
     */
    constructor(node, schema, innerNode, initialContext) {
        this.node = node;
        this.schema = schema;
        this.initialContext = initialContext;
        this.disposed = false;
        /**
         * Generation number which is incremented any time we have an edit on the node.
         * Used during iteration to make sure there has been no edits that were concurrently made.
         * @remarks
         * This is updated monotonically by this class when edits are applied.
         * TODO: update this when applying edits to unhydrated trees.
         *
         * If TypeScript supported making this immutable from outside the class without making it readonly from inside, that would be used here,
         * but they only way to do that is add a separate public accessor and make it private, which was deemed not worth the boilerplate, runtime overhead and bundle size.
         */
        this.generationNumber = 0;
        _TreeNodeKernel_hydrationState.set(this, void 0);
        /**
         * Events registered before hydration.
         * @remarks
         * Since these are usually not used, they are allocated lazily as an optimization.
         * The laziness also avoids extra forwarding overhead for events from this kernel's anchor node and also avoids registering for events that are unneeded.
         * This means optimizations like skipping processing data in subtrees where no subtreeChanged events are subscribed to would be able to work,
         * since the kernel does not unconditionally subscribe to those events (like a design which simply forwards all events would).
         */
        _TreeNodeKernel_eventBuffer.set(this, void 0);
        splitInnerNodeType(innerNode);
        (0, internal_1.assert)(!treeNodeToKernel.has(node), 0xa1a /* only one kernel per node can be made */);
        treeNodeToKernel.set(node, this);
        if (innerNode instanceof unhydratedFlexTree_js_1.UnhydratedFlexTreeNode) {
            // Unhydrated case
            (0, internal_1.debugAssert)(() => innerNode.treeNode === undefined);
            innerNode.treeNode = node;
            __classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, {
                innerNode,
            }, "f");
            __classPrivateFieldSet(this, _TreeNodeKernel_eventBuffer, new KernelEventBuffer(innerNode.events), "f");
        }
        else {
            // Hydrated case
            __classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, this.createHydratedState(innerNode), "f");
            __classPrivateFieldSet(this, _TreeNodeKernel_eventBuffer, new KernelEventBuffer(innerNode.anchorNode.events), "f");
        }
    }
    get context() {
        if (isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) {
            // This can't be cached on this.#hydrated during hydration since initial tree is hydrated before the context is cached on the anchorSet.
            return (__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode.anchorNode.anchorSet.slots.get(exports.SimpleContextSlot) ??
                (0, internal_1.fail)(0xb40 /* missing simple-tree context */));
        }
        return this.initialContext;
    }
    /**
     * Transition from {@link Unhydrated} to hydrated.
     * Bi-directionally associates the given hydrated TreeNode to the HydratedFlexTreeNode.
     * @remarks
     * Happens at most once for any given node.
     * Cleans up mappings to {@link UnhydratedFlexTreeNode} - it is assumed that they are no longer needed once this node has an anchor node.
     */
    hydrate(inner) {
        (0, internal_1.assert)(!this.disposed, 0xa2a /* cannot hydrate a disposed node */);
        (0, internal_1.assert)(!isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f")), 0xa2b /* hydration should only happen once */);
        __classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, this.createHydratedState(inner), "f");
        // Lazily migrate existing event listeners to the anchor node
        __classPrivateFieldGet(this, _TreeNodeKernel_eventBuffer, "f").migrateEventSource(inner.anchorNode.events);
    }
    createHydratedState(innerNode) {
        (0, internal_1.assert)(!innerNode.anchorNode.slots.has(exports.simpleTreeNodeSlot), 0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */);
        innerNode.anchorNode.slots.set(exports.simpleTreeNodeSlot, this.node);
        return {
            innerNode,
            offAnchorNode: new Set([
                innerNode.anchorNode.events.on("afterDestroy", () => this.dispose()),
                // TODO: this should be triggered on change even for unhydrated nodes.
                innerNode.anchorNode.events.on("childrenChanging", () => {
                    this.generationNumber += 1;
                }),
            ]),
        };
    }
    getStatus() {
        if (this.disposed) {
            return index_js_3.TreeStatus.Deleted;
        }
        if (!isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) {
            return index_js_3.TreeStatus.New;
        }
        // TODO: Replace this check with the proper check against the cursor state when the cursor becomes part of the kernel
        const flex = __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode.anchorNode.slots.get(index_js_3.flexTreeSlot);
        if (flex !== undefined) {
            (0, internal_1.assert)(flex instanceof index_js_3.LazyEntity, 0x9b4 /* Unexpected flex node implementation */);
            if (flex.isFreed()) {
                return index_js_3.TreeStatus.Deleted;
            }
        }
        return (0, index_js_3.treeStatusFromAnchorCache)(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode.anchorNode);
    }
    get events() {
        return __classPrivateFieldGet(this, _TreeNodeKernel_eventBuffer, "f");
    }
    dispose() {
        (0, internal_1.debugAssert)(() => !this.disposed || "Cannot dispose a disposed node");
        this.disposed = true;
        if (isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) {
            for (const off of __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").offAnchorNode) {
                off();
            }
        }
        __classPrivateFieldGet(this, _TreeNodeKernel_eventBuffer, "f").dispose();
        // TODO: go to the context and remove myself from withAnchors
    }
    isHydrated() {
        return isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"));
    }
    get anchorNode() {
        return isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))
            ? __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode.anchorNode
            : undefined;
    }
    /**
     * Retrieves the flex node associated with the given target.
     * @remarks
     * For {@link Unhydrated} nodes, this returns the MapTreeNode.
     *
     * For hydrated nodes it returns a FlexTreeNode backed by the forest.
     *
     * @throws A {@link @fluidframework/telemetry-utils#UsageError} if the node has been deleted.
     */
    getInnerNode() {
        if (!isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) {
            (0, internal_1.debugAssert)(() => __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode?.context.isDisposed() === false ||
                "Unhydrated node should never be disposed");
            return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode; // Unhydrated case
        }
        if (this.disposed) {
            throw new internal_2.UsageError("Cannot access a deleted node.");
        }
        return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode;
    }
    /**
     * Retrieves the {@link UnhydratedFlexTreeNode} if unhydrated. otherwise undefined.
     */
    getInnerNodeIfUnhydrated() {
        if (isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) {
            return undefined;
        }
        return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode;
    }
}
exports.TreeNodeKernel = TreeNodeKernel;
_TreeNodeKernel_hydrationState = new WeakMap(), _TreeNodeKernel_eventBuffer = new WeakMap();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const kernelEvents = ["childrenChangedAfterBatch", "subtreeChangedAfterBatch"];
// #region TreeNodeEventBuffer
/**
 * Whether or not events from {@link TreeNodeKernel} should be buffered instead of emitted immediately.
 */
let bufferTreeEvents = false;
/**
 * Call the provided callback with {@link TreeNode}s' events paused until after the callback's completion.
 *
 * Events that would otherwise have been emitted immediately are merged and buffered until after the
 * provided callback has been completed.
 *
 * @remarks
 * Note: this should be used with caution. User application behaviors are implicitly coupled to event timing.
 * Disrupting this timing can lead to unexpected behavior.
 */
function withBufferedTreeEvents(callback) {
    if (bufferTreeEvents) {
        // Already buffering - just run the callback
        callback();
    }
    else {
        bufferTreeEvents = true;
        try {
            callback();
        }
        finally {
            bufferTreeEvents = false;
            flushEventsEmitter.emit("flush");
        }
    }
}
exports.withBufferedTreeEvents = withBufferedTreeEvents;
/**
 * Event emitter to notify subscribers when tree events buffered due to {@link withBufferedTreeEvents} should be flushed.
 */
const flushEventsEmitter = (0, client_utils_1.createEmitter)();
/**
 * Event emitter for {@link TreeNodeKernel}, which optionally buffers events based on {@link bufferTreeEvents}.
 * @remarks Listens to {@link flushEventsEmitter} to know when to flush any buffered events.
 */
class KernelEventBuffer {
    constructor(
    /**
     * Source of the kernel events.
     * Subscriptions will be created on-demand when listeners are added to this.events,
     * and those subscriptions will be cleaned up when all corresponding listeners have been removed.
     */
    eventSource) {
        _KernelEventBuffer_instances.add(this);
        _KernelEventBuffer_disposed.set(this, false);
        /**
         * Listen to {@link flushEventsEmitter} to know when to flush buffered events.
         */
        _KernelEventBuffer_disposeOnFlushListener.set(this, flushEventsEmitter.on("flush", this.flush.bind(this)));
        _KernelEventBuffer_events.set(this, (0, client_utils_1.createEmitter)());
        _KernelEventBuffer_eventSource.set(this, void 0);
        _KernelEventBuffer_disposeSourceListeners.set(this, new Map());
        /**
         * Buffer of fields that have changed since events were paused.
         * When events are flushed, a single {@link AnchorEvents.childrenChangedAfterBatch} event will be emitted
         * containing the accumulated set of changed fields.
         */
        _KernelEventBuffer_childrenChangedBuffer.set(this, new Set());
        /**
         * Buffer of field marks accumulated since events were paused.
         * Emitted alongside the buffered changed-fields set when flushed.
         */
        _KernelEventBuffer_fieldMarksBuffer.set(this, new Map());
        /**
         * Fields whose marks have been permanently invalidated within the current buffer window due to
         * two or more separate delta batches touching the same field.
         * Once a key is in this set it must never be re-added to the marks buffer, even if
         * a third (or later) batch arrives for that field.
         */
        _KernelEventBuffer_invalidatedFieldMarkKeys.set(this, new Set());
        /**
         * Whether or not the subtree has changed since events were paused.
         * When events are flushed, a single {@link AnchorEvents.subTreeChanged} event will be emitted if and only
         * if the subtree has changed.
         */
        _KernelEventBuffer_subTreeChangedBuffer.set(this, false);
        __classPrivateFieldSet(this, _KernelEventBuffer_eventSource, eventSource, "f");
    }
    /**
     * Migrate this event buffer to a new event source.
     *
     * @remarks
     * Cleans up any existing event subscriptions from the old source.
     * Binds events to the new source for each event with active listeners.
     */
    migrateEventSource(newSource) {
        // Unsubscribe from the old source
        for (const off of __classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").values()) {
            off();
        }
        __classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").clear();
        __classPrivateFieldSet(this, _KernelEventBuffer_eventSource, newSource, "f");
        if (__classPrivateFieldGet(this, _KernelEventBuffer_events, "f").hasListeners("childrenChangedAfterBatch")) {
            const off = __classPrivateFieldGet(this, _KernelEventBuffer_eventSource, "f").on("childrenChangedAfterBatch", ({ changedFields, fieldMarks }) => __classPrivateFieldGet(this, _KernelEventBuffer_instances, "m", _KernelEventBuffer_emit).call(this, "childrenChangedAfterBatch", { changedFields, fieldMarks }));
            __classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").set("childrenChangedAfterBatch", off);
        }
        if (__classPrivateFieldGet(this, _KernelEventBuffer_events, "f").hasListeners("subtreeChangedAfterBatch")) {
            const off = __classPrivateFieldGet(this, _KernelEventBuffer_eventSource, "f").on("subtreeChangedAfterBatch", () => __classPrivateFieldGet(this, _KernelEventBuffer_instances, "m", _KernelEventBuffer_emit).call(this, "subtreeChangedAfterBatch"));
            __classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").set("subtreeChangedAfterBatch", off);
        }
    }
    on(eventName, listener) {
        // Lazily bind event listeners to the source.
        // If we do not have any existing listeners for this event, then we need to bind to the source.
        if (!__classPrivateFieldGet(this, _KernelEventBuffer_events, "f").hasListeners(eventName)) {
            (0, internal_1.assert)(!__classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").has(eventName), 0xc4f /* Should not have a dispose function without listeners */);
            const off = eventName === "childrenChangedAfterBatch"
                ? __classPrivateFieldGet(this, _KernelEventBuffer_eventSource, "f").on(eventName, (args) => __classPrivateFieldGet(this, _KernelEventBuffer_instances, "m", _KernelEventBuffer_emit).call(this, eventName, args))
                : __classPrivateFieldGet(this, _KernelEventBuffer_eventSource, "f").on(eventName, () => __classPrivateFieldGet(this, _KernelEventBuffer_instances, "m", _KernelEventBuffer_emit).call(this, eventName));
            __classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").set(eventName, off);
        }
        __classPrivateFieldGet(this, _KernelEventBuffer_events, "f").on(eventName, listener);
        return () => this.off(eventName, listener);
    }
    off(eventName, listener) {
        __classPrivateFieldGet(this, _KernelEventBuffer_events, "f").off(eventName, listener);
        // If there are no remaining listeners for the event, unbind from the source
        if (!__classPrivateFieldGet(this, _KernelEventBuffer_events, "f").hasListeners(eventName)) {
            const off = __classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").get(eventName);
            off?.();
            __classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").delete(eventName);
        }
    }
    /**
     * Flushes any events buffered due to {@link withBufferedTreeEvents}.
     */
    flush() {
        __classPrivateFieldGet(this, _KernelEventBuffer_instances, "m", _KernelEventBuffer_assertNotDisposed).call(this);
        if (__classPrivateFieldGet(this, _KernelEventBuffer_childrenChangedBuffer, "f").size > 0) {
            __classPrivateFieldGet(this, _KernelEventBuffer_events, "f").emit("childrenChangedAfterBatch", {
                changedFields: __classPrivateFieldGet(this, _KernelEventBuffer_childrenChangedBuffer, "f"),
                fieldMarks: __classPrivateFieldGet(this, _KernelEventBuffer_fieldMarksBuffer, "f"),
            });
            __classPrivateFieldGet(this, _KernelEventBuffer_childrenChangedBuffer, "f").clear();
            __classPrivateFieldGet(this, _KernelEventBuffer_fieldMarksBuffer, "f").clear();
            __classPrivateFieldGet(this, _KernelEventBuffer_invalidatedFieldMarkKeys, "f").clear();
        }
        if (__classPrivateFieldGet(this, _KernelEventBuffer_subTreeChangedBuffer, "f")) {
            __classPrivateFieldGet(this, _KernelEventBuffer_events, "f").emit("subtreeChangedAfterBatch");
            __classPrivateFieldSet(this, _KernelEventBuffer_subTreeChangedBuffer, false, "f");
        }
    }
    dispose() {
        if (__classPrivateFieldGet(this, _KernelEventBuffer_disposed, "f")) {
            return;
        }
        (0, internal_1.assert)(__classPrivateFieldGet(this, _KernelEventBuffer_childrenChangedBuffer, "f").size === 0 && !__classPrivateFieldGet(this, _KernelEventBuffer_subTreeChangedBuffer, "f"), 0xc52 /* Buffered kernel events should have been flushed before disposing. */);
        __classPrivateFieldGet(this, _KernelEventBuffer_disposeOnFlushListener, "f").call(this);
        for (const off of __classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").values()) {
            off();
        }
        __classPrivateFieldGet(this, _KernelEventBuffer_disposeSourceListeners, "f").clear();
        __classPrivateFieldGet(this, _KernelEventBuffer_childrenChangedBuffer, "f").clear();
        __classPrivateFieldGet(this, _KernelEventBuffer_fieldMarksBuffer, "f").clear();
        __classPrivateFieldGet(this, _KernelEventBuffer_invalidatedFieldMarkKeys, "f").clear();
        __classPrivateFieldSet(this, _KernelEventBuffer_subTreeChangedBuffer, false, "f");
        __classPrivateFieldSet(this, _KernelEventBuffer_disposed, true, "f");
    }
}
_KernelEventBuffer_disposed = new WeakMap(), _KernelEventBuffer_disposeOnFlushListener = new WeakMap(), _KernelEventBuffer_events = new WeakMap(), _KernelEventBuffer_eventSource = new WeakMap(), _KernelEventBuffer_disposeSourceListeners = new WeakMap(), _KernelEventBuffer_childrenChangedBuffer = new WeakMap(), _KernelEventBuffer_fieldMarksBuffer = new WeakMap(), _KernelEventBuffer_invalidatedFieldMarkKeys = new WeakMap(), _KernelEventBuffer_subTreeChangedBuffer = new WeakMap(), _KernelEventBuffer_instances = new WeakSet(), _KernelEventBuffer_emit = function _KernelEventBuffer_emit(eventName, arg) {
    __classPrivateFieldGet(this, _KernelEventBuffer_instances, "m", _KernelEventBuffer_assertNotDisposed).call(this);
    switch (eventName) {
        case "childrenChangedAfterBatch": {
            (0, internal_1.assert)(arg !== undefined, 0xcea /* childrenChangedAfterBatch requires arg */);
            return __classPrivateFieldGet(this, _KernelEventBuffer_instances, "m", _KernelEventBuffer_handleChildrenChangedAfterBatch).call(this, arg.changedFields, arg.fieldMarks);
        }
        case "subtreeChangedAfterBatch": {
            return __classPrivateFieldGet(this, _KernelEventBuffer_instances, "m", _KernelEventBuffer_handleSubtreeChangedAfterBatch).call(this);
        }
        default: {
            (0, internal_1.unreachableCase)(eventName);
        }
    }
}, _KernelEventBuffer_handleChildrenChangedAfterBatch = function _KernelEventBuffer_handleChildrenChangedAfterBatch(changedFields, fieldMarks) {
    if (bufferTreeEvents) {
        for (const fieldKey of changedFields) {
            __classPrivateFieldGet(this, _KernelEventBuffer_childrenChangedBuffer, "f").add(fieldKey);
        }
        for (const [key, marks] of fieldMarks) {
            if (__classPrivateFieldGet(this, _KernelEventBuffer_invalidatedFieldMarkKeys, "f").has(key)) {
                // Already permanently invalidated by an earlier collision; ignore this batch too.
                // TODO: Once the eventing stack is rewritten to walk the composed delta at flush
                // time, this collision path will be unreachable and can be removed entirely.
                continue;
            }
            if (__classPrivateFieldGet(this, _KernelEventBuffer_fieldMarksBuffer, "f").has(key)) {
                // A second batch of marks arrived for the same field before the buffer was flushed.
                // We have no delta composition logic, so permanently invalidate this field so that
                // any further batches are also discarded rather than incorrectly surfaced.
                __classPrivateFieldGet(this, _KernelEventBuffer_fieldMarksBuffer, "f").delete(key);
                __classPrivateFieldGet(this, _KernelEventBuffer_invalidatedFieldMarkKeys, "f").add(key);
            }
            else {
                __classPrivateFieldGet(this, _KernelEventBuffer_fieldMarksBuffer, "f").set(key, marks);
            }
        }
    }
    else {
        __classPrivateFieldGet(this, _KernelEventBuffer_events, "f").emit("childrenChangedAfterBatch", { changedFields, fieldMarks });
    }
}, _KernelEventBuffer_handleSubtreeChangedAfterBatch = function _KernelEventBuffer_handleSubtreeChangedAfterBatch() {
    if (bufferTreeEvents) {
        __classPrivateFieldSet(this, _KernelEventBuffer_subTreeChangedBuffer, true, "f");
    }
    else {
        __classPrivateFieldGet(this, _KernelEventBuffer_events, "f").emit("subtreeChangedAfterBatch");
    }
}, _KernelEventBuffer_assertNotDisposed = function _KernelEventBuffer_assertNotDisposed() {
    (0, internal_1.assert)(!__classPrivateFieldGet(this, _KernelEventBuffer_disposed, "f"), 0xc51 /* Event handler disposed. */);
};
/**
 * Narrows innerNode to either {@link UnhydratedFlexTreeNode} or {@link HydratedFlexTreeNode}.
 */
function splitInnerNodeType(innerNode) {
    (0, internal_1.assert)(innerNode instanceof unhydratedFlexTree_js_1.UnhydratedFlexTreeNode || innerNode.isHydrated(), 0xbc8 /* Invalid inner node type */);
}
exports.splitInnerNodeType = splitInnerNodeType;
/**
 * An anchor slot which associates an anchor with its corresponding {@link TreeNode}, if there is one.
 * @remarks
 * For this to work, we have to require that there is at most a single view using a given AnchorSet.
 * FlexTree already has this assumption, and we also assume there is a single simple-tree per FlexTree, so this is valid.
 */
exports.simpleTreeNodeSlot = (0, index_js_1.anchorSlot)();
/**
 * Dispose a TreeNode (if any) for an existing anchor without disposing the anchor.
 */
function tryDisposeTreeNode(anchorNode) {
    const treeNode = anchorNode.slots.get(exports.simpleTreeNodeSlot);
    if (treeNode !== undefined) {
        const kernel = getKernel(treeNode);
        kernel.dispose();
        anchorNode.slots.delete(exports.simpleTreeNodeSlot);
    }
}
exports.tryDisposeTreeNode = tryDisposeTreeNode;
/**
 * Gets the {@link TreeNodeSchema} for the {@link InnerNode}.
 */
function getSimpleNodeSchemaFromInnerNode(innerNode) {
    const context = getSimpleContextFromInnerNode(innerNode);
    return context.schema.get(innerNode.type) ?? (0, internal_1.fail)(0xb3f /* missing schema from context */);
}
exports.getSimpleNodeSchemaFromInnerNode = getSimpleNodeSchemaFromInnerNode;
/**
 * Gets the {@link Context} for the {@link InnerNode}.
 */
function getSimpleContextFromInnerNode(innerNode) {
    splitInnerNodeType(innerNode);
    if (innerNode instanceof unhydratedFlexTree_js_1.UnhydratedFlexTreeNode) {
        return innerNode.simpleContext;
    }
    const context = innerNode.anchorNode.anchorSet.slots.get(exports.SimpleContextSlot);
    (0, internal_1.assert)(context !== undefined, 0xa55 /* missing simple tree context */);
    return context;
}
exports.getSimpleContextFromInnerNode = getSimpleContextFromInnerNode;
/**
 * Retrieves the flex node associated with the given target.
 * @remarks
 * For {@link Unhydrated} nodes, this returns the MapTreeNode.
 *
 * For hydrated nodes it returns a FlexTreeNode backed by the forest.
 *
 * @throws A {@link @fluidframework/telemetry-utils#UsageError} if the node has been deleted.
 */
function getInnerNode(treeNode) {
    const kernel = getKernel(treeNode);
    return kernel.getInnerNode();
}
exports.getInnerNode = getInnerNode;
/**
 * Gets a flex node from an anchor node
 */
function flexNodeFromAnchor(anchorNode) {
    const flexNode = anchorNode.slots.get(index_js_3.flexTreeSlot);
    if (flexNode !== undefined) {
        return flexNode; // If it does have a flex node, return it...
    } // ...otherwise, the flex node must be created
    const context = anchorNode.anchorSet.slots.get(index_js_3.ContextSlot) ?? (0, internal_1.fail)(0xb45 /* missing context */);
    const cursor = context.checkout.forest.allocateCursor("getFlexNode");
    context.checkout.forest.moveCursorToPath(anchorNode, cursor);
    const newFlexNode = (0, index_js_2.getOrCreateHydratedFlexTreeNode)(context, cursor);
    cursor.free();
    return newFlexNode;
}
/**
 * Gets a tree node from an anchor node
 */
function treeNodeFromAnchor(anchorNode) {
    const cached = anchorNode.slots.get(exports.simpleTreeNodeSlot);
    if (cached !== undefined) {
        return cached;
    }
    const flexNode = flexNodeFromAnchor(anchorNode);
    return createTreeNodeFromInner(flexNode);
}
exports.treeNodeFromAnchor = treeNodeFromAnchor;
/**
 * Constructs a TreeNode from an InnerNode.
 * @remarks
 * This does not do caching or validation: caller must ensure duplicate nodes for a given inner node are not created, and that the inner node is valid.
 */
function createTreeNodeFromInner(innerNode) {
    const classSchema = getSimpleNodeSchemaFromInnerNode(innerNode);
    const internal = innerNode;
    return typeof classSchema === "function"
        ? new classSchema(internal)
        : classSchema.create(internal);
}
exports.createTreeNodeFromInner = createTreeNodeFromInner;
/**
 * Creating multiple simple tree contexts for the same branch, and thus with the same underlying AnchorSet does not work due to how TreeNode caching works.
 * This slot is used to detect if one already exists and error if creating a second.
 * @remarks
 * See also {@link ContextSlot} in which the flex-tree context is stored.
 */
exports.SimpleContextSlot = (0, index_js_1.anchorSlot)();
//# sourceMappingURL=treeNodeKernel.js.map