"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 __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 _TreeNodeKernel_instances, _TreeNodeKernel_hydrated, _TreeNodeKernel_preHydrationEvents, _TreeNodeKernel_events_get;
Object.defineProperty(exports, "__esModule", { value: true });
exports.tryDisposeTreeNode = exports.tryGetCachedTreeNode = exports.TreeNodeKernel = exports.tryGetTreeNodeSchema = exports.isTreeNode = exports.getKernel = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../events/index.js");
const index_js_2 = require("../../core/index.js");
const index_js_3 = require("../../feature-libraries/index.js");
const index_js_4 = require("../../util/index.js");
// TODO: decide how to deal with dependencies on flex-tree implementation.
// eslint-disable-next-line import/no-internal-modules
const lazyNode_js_1 = require("../../feature-libraries/flex-tree/lazyNode.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;
/**
 * 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.
 * When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method.
 */
class TreeNodeKernel {
    /**
     * Create a TreeNodeKernel which can be looked up with {@link getKernel}.
     *
     * @param innerNode - When unhydrated/raw or marinated the MapTreeNode. FlexTreeNode when cooked.
     * @remarks
     * Exactly one kernel per TreeNode should be created.
     */
    constructor(node, schema, innerNode) {
        _TreeNodeKernel_instances.add(this);
        this.node = node;
        this.schema = schema;
        this.innerNode = innerNode;
        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_hydrated.set(this, void 0);
        /**
         * Events registered before hydration.
         * @remarks
         * As an optimization these are allocated lazily as they are usually unused.
         */
        _TreeNodeKernel_preHydrationEvents.set(this, void 0);
        (0, internal_1.assert)(!treeNodeToKernel.has(node), 0xa1a /* only one kernel per node can be made */);
        treeNodeToKernel.set(node, this);
        if ((0, index_js_3.isMapTreeNode)(innerNode)) {
            // Unhydrated case
            mapTreeNodeToProxy.set(innerNode, node);
        }
        else {
            // Hydrated case
            (0, internal_1.assert)(!innerNode.anchorNode.slots.has(proxySlot), 0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */);
            this.hydrate(innerNode.anchorNode);
        }
    }
    /**
     * Transition from {@link Unhydrated} to hydrated.
     * Bi-directionally associates the given hydrated TreeNode to the given anchor node.
     * @remarks
     * Happens at most once for any given node.
     * Cleans up mappings to {@link MapTreeNode} - it is assumed that they are no longer needed once the proxy has an anchor node.
     */
    hydrate(anchorNode) {
        (0, internal_1.assert)(!this.disposed, 0xa2a /* cannot hydrate a disposed node */);
        (0, internal_1.assert)(__classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f") === undefined, 0xa2b /* hydration should only happen once */);
        // If the this node is raw and thus has a MapTreeNode, forget it:
        if ((0, index_js_3.isMapTreeNode)(this.innerNode)) {
            mapTreeNodeToProxy.delete(this.innerNode);
        }
        // However, it's fine for an anchor node to rotate through different proxies when the content at that place in the tree is replaced.
        anchorNode.slots.set(proxySlot, this.node);
        __classPrivateFieldSet(this, _TreeNodeKernel_hydrated, {
            anchorNode,
            offAnchorNode: new Set([
                anchorNode.on("afterDestroy", () => this.dispose()),
                // TODO: this should be triggered on change even for unhydrated nodes.
                anchorNode.on("childrenChanging", () => {
                    this.generationNumber += 1;
                }),
            ]),
        }, "f");
        // If needed, register forwarding emitters for events from before hydration
        if (__classPrivateFieldGet(this, _TreeNodeKernel_preHydrationEvents, "f") !== undefined) {
            for (const eventName of kernelEvents) {
                if (__classPrivateFieldGet(this, _TreeNodeKernel_preHydrationEvents, "f").hasListeners(eventName)) {
                    __classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f").offAnchorNode.add(
                    // Argument is forwarded between matching events, so the type should be correct.
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    anchorNode.on(eventName, (arg) => __classPrivateFieldGet(this, _TreeNodeKernel_preHydrationEvents, "f")?.emit(eventName, arg)));
                }
            }
        }
    }
    isHydrated() {
        (0, internal_1.assert)(!this.disposed, 0xa2c /* cannot use a disposed node */);
        return __classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f") !== undefined;
    }
    getStatus() {
        if (this.disposed) {
            return index_js_3.TreeStatus.Deleted;
        }
        if (__classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f")?.anchorNode === undefined) {
            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_hydrated, "f").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[index_js_3.isFreedSymbol]()) {
                return index_js_3.TreeStatus.Deleted;
            }
        }
        return (0, index_js_3.treeStatusFromAnchorCache)(__classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f").anchorNode);
    }
    on(eventName, listener) {
        return __classPrivateFieldGet(this, _TreeNodeKernel_instances, "a", _TreeNodeKernel_events_get).on(eventName, listener);
    }
    dispose() {
        this.disposed = true;
        for (const off of __classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f")?.offAnchorNode ?? []) {
            off();
        }
        // TODO: go to the context and remove myself from withAnchors
    }
    /**
     * Retrieves the flex node associated with the given target via {@link setInnerNode}.
     * @remarks
     * For {@link Unhydrated} nodes, this returns the MapTreeNode.
     *
     * For hydrated nodes it returns a FlexTreeNode backed by the forest.
     * Note that for "marinated" nodes, this FlexTreeNode exists and returns it: it does not return the MapTreeNode which is the current InnerNode.
     */
    getOrCreateInnerNode(allowFreed = false) {
        if (!(0, index_js_3.isMapTreeNode)(this.innerNode)) {
            // Cooked case
            return this.innerNode;
        }
        if (__classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f") === undefined) {
            // Unhydrated case
            return this.innerNode;
        }
        // Marinated case -> cooked
        const anchorNode = __classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f").anchorNode;
        // The proxy is bound to an anchor node, but it may or may not have an actual flex node yet
        const flexNode = anchorNode.slots.get(index_js_3.flexTreeSlot);
        if (flexNode !== undefined) {
            this.innerNode = flexNode;
            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, index_js_4.fail)("missing context");
        const cursor = context.checkout.forest.allocateCursor("getFlexNode");
        context.checkout.forest.moveCursorToPath(anchorNode, cursor);
        const newFlexNode = (0, lazyNode_js_1.makeTree)(context, cursor);
        cursor.free();
        this.innerNode = newFlexNode;
        // Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode
        anchorForgetters?.get(this.node)?.();
        if (!allowFreed) {
            (0, index_js_3.assertFlexTreeEntityNotFreed)(newFlexNode);
        }
        return newFlexNode;
    }
    /**
     * Creates an anchor node and associates it with the given proxy.
     * @privateRemarks
     * Use `forgetters` to cleanup the anchor allocated by this function once the anchor is no longer needed.
     * In practice, this happens when either the anchor node is destroyed, or another anchor to the same node is created by a new flex node.
     *
     * The FlexTreeNode holds a reference to the same anchor, and has a lifetime at least as long as the simple-tree,
     * so this would be unnecessary except for the case of "marinated" nodes, which have an anchor,
     * but might not have a FlexTreeNode.
     * Handling this case is an optimization assuming that this extra anchor reference is cheaper than eagerly creating FlexTreeNodes.
     */
    anchorProxy(anchors, path) {
        (0, internal_1.assert)(!anchorForgetters.has(this.node), 0x91c /* Proxy anchor should not be set twice */);
        const anchor = anchors.track(path);
        const anchorNode = anchors.locate(anchor) ?? (0, index_js_4.fail)("Expected anchor node to be present");
        this.hydrate(anchorNode);
        const forget = () => {
            if (anchors.locate(anchor)) {
                anchors.forget(anchor);
            }
            anchorForgetters.delete(this.node);
            off();
        };
        anchorForgetters.set(this.node, forget);
        const off = anchorNode.on("afterDestroy", forget);
        return anchorNode;
    }
    /**
     * Retrieves the InnerNode associated with the given target via {@link setInnerNode}, if any.
     * @remarks
     * If `target` is a unhydrated node, returns its MapTreeNode.
     * If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode.
     * If the target is not a node, or a marinated node with no FlexTreeNode for its anchor, returns undefined.
     */
    tryGetInnerNode() {
        if ((0, index_js_3.isFlexTreeNode)(this.innerNode)) {
            // Cooked case
            return this.innerNode;
        }
        if (__classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f") === undefined) {
            // Unhydrated case
            return this.innerNode;
        }
        // Marinated case -> cooked
        const anchorNode = __classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f").anchorNode;
        // The proxy is bound to an anchor node, but it may or may not have an actual flex node yet
        return anchorNode.slots.get(index_js_3.flexTreeSlot);
    }
}
exports.TreeNodeKernel = TreeNodeKernel;
_TreeNodeKernel_hydrated = new WeakMap(), _TreeNodeKernel_preHydrationEvents = new WeakMap(), _TreeNodeKernel_instances = new WeakSet(), _TreeNodeKernel_events_get = function _TreeNodeKernel_events_get() {
    if (__classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f") === undefined) {
        __classPrivateFieldSet(this, _TreeNodeKernel_preHydrationEvents, __classPrivateFieldGet(this, _TreeNodeKernel_preHydrationEvents, "f") ?? (0, index_js_1.createEmitter)(), "f");
        return __classPrivateFieldGet(this, _TreeNodeKernel_preHydrationEvents, "f");
    }
    else {
        return __classPrivateFieldGet(this, _TreeNodeKernel_hydrated, "f").anchorNode;
    }
};
/**
 * Used by {@link anchorProxy} as an optimization to ensure that only one anchor is remembered at a time for a given anchor node
 */
const anchorForgetters = new WeakMap();
const kernelEvents = ["childrenChangedAfterBatch", "subtreeChangedAfterBatch"];
/**
 * {@inheritdoc proxyToMapTreeNode}
 */
const mapTreeNodeToProxy = new WeakMap();
/**
 * An anchor slot which associates an anchor with its corresponding 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.
 */
const proxySlot = (0, index_js_2.anchorSlot)();
/**
 * Retrieves the proxy associated with the given flex node via {@link setInnerNode}, if any.
 */
function tryGetCachedTreeNode(flexNode) {
    if ((0, index_js_3.isMapTreeNode)(flexNode)) {
        // Unhydrated case
        return mapTreeNodeToProxy.get(flexNode);
    }
    // Hydrated case
    return flexNode.anchorNode.slots.get(proxySlot);
}
exports.tryGetCachedTreeNode = tryGetCachedTreeNode;
function tryDisposeTreeNode(anchorNode) {
    const treeNode = anchorNode.slots.get(proxySlot);
    if (treeNode !== undefined) {
        const kernel = getKernel(treeNode);
        kernel.dispose();
    }
}
exports.tryDisposeTreeNode = tryDisposeTreeNode;
//# sourceMappingURL=treeNodeKernel.js.map