"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Context = exports.ContextSlot = 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 lazyField_js_1 = require("./lazyField.js");
/**
 * Creating multiple flex 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.
 */
exports.ContextSlot = (0, index_js_1.anchorSlot)();
/**
 * Implementation of `FlexTreeHydratedContext`.
 *
 * @remarks An editor is required to edit the FlexTree.
 *
 * A {@link FlexTreeContext} which is used to manage the cursors and anchors within the FlexTrees:
 * This is necessary for supporting using this tree across edits to the forest, and not leaking memory.
 *
 * This context and the trees it produces observe the kinds of fields from the schema
 */
class Context {
    constructor(schemaPolicy, checkout, 
    /**
     * An object which handles node key generation and conversion
     */
    nodeKeyManager) {
        this.schemaPolicy = schemaPolicy;
        this.checkout = checkout;
        this.nodeKeyManager = nodeKeyManager;
        this.withCursors = new Set();
        this.withAnchors = new Set();
        this.disposed = false;
        this.onDispose = [
            this.checkout.forest.events.on("beforeChange", () => {
                this.prepareForEdit();
            }),
        ];
        (0, internal_1.assert)(!this.checkout.forest.anchors.slots.has(exports.ContextSlot), 0x92b /* Cannot create second flex-tree from checkout */);
        this.checkout.forest.anchors.slots.set(exports.ContextSlot, this);
    }
    isHydrated() {
        (0, internal_1.debugAssert)(() => !this.disposed || "Disposed");
        return true;
    }
    isDisposed() {
        return this.disposed;
    }
    get schema() {
        return this.checkout.storedSchema;
    }
    /**
     * Called before editing.
     * Clears all cursors so editing can proceed.
     */
    prepareForEdit() {
        (0, internal_1.assert)(this.disposed === false, 0x802 /* use after dispose */);
        for (const target of this.withCursors) {
            target.prepareForEdit();
        }
        (0, internal_1.assert)(this.withCursors.size === 0, 0x773 /* prepareForEdit should remove all cursors */);
    }
    [index_js_2.disposeSymbol]() {
        (0, internal_1.assert)(this.disposed === false, 0x803 /* double dispose */);
        this.disposed = true;
        this.clear();
        for (const unregister of this.onDispose) {
            unregister();
        }
        this.onDispose.length = 0;
        const deleted = this.checkout.forest.anchors.slots.delete(exports.ContextSlot);
        (0, internal_1.assert)(deleted, 0x8c4 /* unexpected dispose */);
    }
    /**
     * Release any cursors and anchors held by tree entities created in this context.
     * @remarks
     * Ensures the cashed references to those entities on the Anchors are also cleared.
     * The tree entities are invalid to use after this, but the context may still be used
     * to create new trees starting from the root.
     */
    clear() {
        for (const target of this.withAnchors) {
            target[index_js_2.disposeSymbol]();
        }
        this.lazyRootCache = undefined;
        (0, internal_1.assert)(this.withCursors.size === 0, 0x774 /* free should remove all cursors */);
        (0, internal_1.assert)(this.withAnchors.size === 0, 0x775 /* free should remove all anchors */);
    }
    /**
     * Gets the root field of the tree.
     * @remarks
     * This is a cached value: it should not be disposed by the accessor.
     * Like all other fields from this context, it will become invalid to use after {@link clear} is called or the {@link disposeSymbol} method is called.
     */
    get root() {
        (0, internal_1.assert)(this.disposed === false, 0x804 /* use after dispose */);
        this.lazyRootCache ??= this.detachedField(index_js_1.rootField, this.schema.rootFieldSchema.kind);
        return this.lazyRootCache;
    }
    /**
     * Returns a new {@link FlexTreeField} that will live as long as the caller allows up to the next call to {@link clear} or disposal of the context.
     * @remarks
     * Due to limited support for detached fields, not all operations are supported.
     * Additionally if the detached field's content is deleted, the field will become out of schema if it is required: it must not be used after that point.
     */
    detachedField(key, schema) {
        (0, internal_1.assert)(this.disposed === false, 0xb9c /* use after dispose */);
        const cursor = this.checkout.forest.allocateCursor("root");
        (0, index_js_1.moveToDetachedField)(this.checkout.forest, cursor, key);
        const field = (0, lazyField_js_1.makeField)(this, schema, cursor);
        cursor.free();
        return field;
    }
    get events() {
        return this.checkout.forest.events;
    }
}
exports.Context = Context;
//# sourceMappingURL=context.js.map