"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 _LazyTreeNode_removeDeleteCallback;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LazyTreeNode = exports.makeTree = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../core/index.js");
const index_js_2 = require("../../util/index.js");
const index_js_3 = require("../default-schema/index.js");
const flexTreeTypes_js_1 = require("./flexTreeTypes.js");
const lazyEntity_js_1 = require("./lazyEntity.js");
const lazyField_js_1 = require("./lazyField.js");
/**
 * This does not take ownership of this cursor: Node will fork it as needed.
 */
function makeTree(context, cursor) {
    const anchor = cursor.buildAnchor();
    const anchorNode = context.checkout.forest.anchors.locate(anchor) ??
        (0, internal_1.fail)(0xb12 /* cursor should point to a node that is not the root of the AnchorSet */);
    const cached = anchorNode.slots.get(flexTreeTypes_js_1.flexTreeSlot);
    if (cached !== undefined) {
        context.checkout.forest.anchors.forget(anchor);
        (0, internal_1.assert)(cached.context === context, 0x782 /* contexts must match */);
        (0, internal_1.assert)(cached instanceof LazyTreeNode, 0x92c /* Expected LazyTreeNode */);
        return cached;
    }
    return new LazyTreeNode(context, cursor.type, cursor, anchorNode, anchor);
}
exports.makeTree = makeTree;
function cleanupTree(anchor) {
    const cached = anchor.slots.get(flexTreeTypes_js_1.flexTreeSlot) ?? (0, internal_1.fail)(0xb13 /* tree should only be cleaned up once */);
    (0, internal_1.assert)(cached instanceof LazyTreeNode, 0x92d /* Expected LazyTreeNode */);
    cached[index_js_2.disposeSymbol]();
}
/**
 * Lazy implementation of {@link FlexTreeNode}.
 */
class LazyTreeNode extends lazyEntity_js_1.LazyEntity {
    get [(_LazyTreeNode_removeDeleteCallback = new WeakMap(), flexTreeTypes_js_1.flexTreeMarker)]() {
        return flexTreeTypes_js_1.FlexTreeEntityKind.Node;
    }
    constructor(context, type, cursor, anchorNode, anchor) {
        super(context, cursor, anchor);
        this.type = type;
        this.anchorNode = anchorNode;
        // Using JS private here prevents it from showing up as a enumerable own property, or conflicting with struct fields.
        _LazyTreeNode_removeDeleteCallback.set(this, void 0);
        this.fields = {
            get: (key) => this.tryGetField(key),
            [Symbol.iterator]: () => (0, index_js_1.mapCursorFields)(this.cursor, (cursor) => {
                const key = cursor.getFieldKey();
                const pair = [
                    key,
                    (0, lazyField_js_1.makeField)(this.context, this.storedSchema.getFieldSchema(key).kind, cursor),
                ];
                return pair;
            }).values(),
        };
        this.storedSchema =
            context.schema.nodeSchema.get(this.type) ?? (0, internal_1.fail)(0xb14 /* missing schema */);
        (0, internal_1.assert)(cursor.mode === 0 /* CursorLocationType.Nodes */, 0x783 /* must be in nodes mode */);
        anchorNode.slots.set(flexTreeTypes_js_1.flexTreeSlot, this);
        __classPrivateFieldSet(this, _LazyTreeNode_removeDeleteCallback, anchorNode.events.on("afterDestroy", cleanupTree), "f");
    }
    isHydrated() {
        return true;
    }
    tryMoveCursorToAnchor(cursor) {
        return this.context.checkout.forest.tryMoveCursorToNode(this.anchor, cursor);
    }
    forgetAnchor() {
        // This type unconditionally has an anchor, so `forgetAnchor` is always called and cleanup can be done here:
        // After this point this node will not be usable,
        // so remove it from the anchor incase a different context (or the same context later) uses this AnchorSet.
        this.anchorNode.slots.delete(flexTreeTypes_js_1.flexTreeSlot);
        __classPrivateFieldGet(this, _LazyTreeNode_removeDeleteCallback, "f").call(this);
        this.context.checkout.forest.anchors.forget(this.anchor);
    }
    get value() {
        return this.cursor.value;
    }
    tryGetField(fieldKey) {
        const schema = this.storedSchema.getFieldSchema(fieldKey);
        return (0, index_js_1.inCursorField)(this.cursor, fieldKey, (cursor) => {
            if (cursor.getFieldLength() === 0) {
                return undefined;
            }
            return (0, lazyField_js_1.makeField)(this.context, schema.kind, cursor);
        });
    }
    getBoxed(key) {
        const fieldSchema = this.storedSchema.getFieldSchema(key);
        return (0, index_js_1.inCursorField)(this.cursor, key, (cursor) => {
            return (0, lazyField_js_1.makeField)(this.context, fieldSchema.kind, cursor);
        });
    }
    [Symbol.iterator]() {
        return (0, index_js_1.mapCursorFields)(this.cursor, (cursor) => (0, lazyField_js_1.makeField)(this.context, this.storedSchema.getFieldSchema(cursor.getFieldKey()).kind, cursor)).values();
    }
    get parentField() {
        const cursor = this.cursor;
        const index = this.anchorNode.parentIndex;
        (0, internal_1.assert)(cursor.fieldIndex === index, 0x786 /* mismatched indexes */);
        const key = this.anchorNode.parentField;
        cursor.exitNode();
        (0, internal_1.assert)(key === cursor.getFieldKey(), 0x787 /* mismatched keys */);
        let fieldSchema;
        // Check if the current node is in a detached sequence.
        if (this.anchorNode.parent === undefined) {
            // Parent field is a detached sequence, and thus needs special handling for its schema.
            // eslint-disable-next-line unicorn/prefer-ternary
            if (key === index_js_1.rootFieldKey) {
                fieldSchema = this.context.schema.rootFieldSchema.kind;
            }
            else {
                // All fields (in the flex tree API) have a schema.
                // Since currently there is no known schema for detached field other than the special default root:
                // give all other detached fields a schema of sequence of anything.
                // That schema is the only one that is safe since its the only field schema that allows any possible field content.
                //
                // TODO:
                // if any of the following are done this schema will need to be more specific:
                // 1. Editing APIs start exposing user created detached sequences.
                // 2. Remove (and its inverse) start working on subsequences or fields contents (like everything in a sequence or optional field) and not just single nodes.
                // 3. Possibly other unknown cases.
                // Additionally this approach makes it possible for a user to take a FlexTree node, get its parent, check its schema, down cast based on that, then edit that detached field (ex: removing the node in it).
                // This MIGHT work properly with existing merge resolution logic (it must keep client in sync and be unable to violate schema), but this either needs robust testing or to be explicitly banned (error before sending the op).
                // Issues like replacing a node in the a removed sequenced then undoing the remove could easily violate schema if not everything works exactly right!
                fieldSchema = index_js_3.FieldKinds.sequence.identifier;
            }
        }
        else {
            cursor.exitField();
            const parentType = cursor.type;
            cursor.enterField(key);
            const nodeSchema = this.context.schema.nodeSchema.get(parentType) ??
                (0, internal_1.fail)(0xb15 /* requested schema that does not exist */);
            fieldSchema = nodeSchema.getFieldSchema(key).kind;
        }
        const proxifiedField = (0, lazyField_js_1.makeField)(this.context, fieldSchema, cursor);
        cursor.enterNode(index);
        return { parent: proxifiedField, index };
    }
    keys() {
        return (0, index_js_1.mapCursorFields)(this.cursor, (cursor) => cursor.getFieldKey()).values();
    }
}
exports.LazyTreeNode = LazyTreeNode;
//# sourceMappingURL=lazyNode.js.map