/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
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;
};
import { createEmitter } from "@fluid-internal/client-utils";
import { assert, fail } from "@fluidframework/core-utils/internal";
import { AnchorSet, ITreeSubscriptionCursorState, aboveRootPlaceholder, combineVisitors, deepCopyMapTree, rootFieldKey, } from "../../core/index.js";
import { assertNonNegativeSafeInteger, assertValidIndex, assertValidRange, brand, breakingMethod, } from "../../util/index.js";
import { chunkFieldSingle, defaultChunkPolicy } from "../chunked-forest/index.js";
import { cursorForMapTreeNode, mapTreeFromCursor } from "../mapTreeCursor.js";
import { SynchronousCursor } from "../treeCursorUtils.js";
import { defaultSchemaPolicy, FieldKinds, inSchemaOrThrow, isFieldInSchema, } from "../default-schema/index.js";
/** Get a field from a `MutableMapTree`, optionally modifying the tree to create it if missing. */
function getOrCreateField(mapTree, key) {
    const field = mapTree.fields.get(key);
    if (field !== undefined) {
        return field;
    }
    const newField = [];
    mapTree.fields.set(key, newField);
    return newField;
}
/**
 * Reference implementation of IEditableForest.
 *
 * This implementation focuses on correctness and simplicity, not performance.
 * It does not use compressed chunks: instead nodes are implemented using objects.
 */
let ObjectForest = (() => {
    var _a, _ObjectForest_events, _ObjectForest_roots;
    let _instanceExtraInitializers = [];
    let _checkSchema_decorators;
    return _a = class ObjectForest {
            get roots() {
                return __classPrivateFieldGet(this, _ObjectForest_roots, "f");
            }
            constructor(breaker, 
            /**
             * If provided, may be used for validation, but otherwise unused.
             */
            schema, anchors = new AnchorSet(), additionalAsserts = false, roots) {
                this.breaker = (__runInitializers(this, _instanceExtraInitializers), breaker);
                this.schema = schema;
                this.anchors = anchors;
                this.additionalAsserts = additionalAsserts;
                // All cursors that are in the "Current" state. Must be empty when editing.
                this.currentCursors = new Set();
                this.deltaVisitors = new Set();
                _ObjectForest_events.set(this, createEmitter());
                this.events = __classPrivateFieldGet(this, _ObjectForest_events, "f");
                _ObjectForest_roots.set(this, void 0);
                this.nextRange = 0;
                __classPrivateFieldSet(this, _ObjectForest_roots, roots !== undefined
                    ? deepCopyMapTree(roots)
                    : {
                        type: aboveRootPlaceholder,
                        fields: new Map(),
                    }, "f");
                if (additionalAsserts) {
                    this.checkSchema();
                }
            }
            get isEmpty() {
                return this.roots.fields.size === 0;
            }
            clone(schema, anchors) {
                return new _a(this.breaker, schema, anchors, this.additionalAsserts, this.roots);
            }
            chunkField(cursor) {
                return chunkFieldSingle(cursor, { idCompressor: undefined, policy: defaultChunkPolicy });
            }
            forgetAnchor(anchor) {
                this.anchors.forget(anchor);
            }
            /**
             * Throw (and break) if content does not match schema.
             */
            checkSchema() {
                const schema = this.schema;
                if (schema !== undefined) {
                    const roots = new Set([...this.roots.fields.keys(), rootFieldKey]);
                    for (const key of roots) {
                        const documentRoot = this.roots.fields.get(key) ?? [];
                        const fieldSchema = key === rootFieldKey
                            ? schema.rootFieldSchema
                            : // TODO: someday we should have schema for detached fields, and use that instead of generating one here.
                                {
                                    // Some detached fields may have multiple nodes, so we must treat them as sequences:
                                    kind: FieldKinds.sequence.identifier,
                                    types: new Set(documentRoot.map((node) => node.type)),
                                    // Metadata is not used for schema checks
                                    persistedMetadata: undefined,
                                };
                        const maybeError = isFieldInSchema(documentRoot, fieldSchema, {
                            schema,
                            policy: defaultSchemaPolicy,
                        });
                        inSchemaOrThrow(maybeError);
                    }
                }
            }
            acquireVisitor() {
                assert(this.activeVisitor === undefined, 0x76c /* Must release existing visitor before acquiring another */);
                // Note: This code uses cursors, however it also modifies the tree.
                // In general this is not safe, but this code happens to only modify the tree below the current cursor location,
                // which happens to work.
                // This pattern could be generalized/formalized with a concept of an exclusive cursor,
                // which can edit, but is the only cursor allowed at the time.
                const cursor = this.allocateCursor("acquireVisitor");
                cursor.setToAboveDetachedSequences();
                /**
                 * Called before each edit to allow cursor cleanup to happen and ensure that there are no unexpected cursors.
                 * This is required for each change since there may be app facing change event handlers which create cursors.
                 */
                const preEdit = () => {
                    __classPrivateFieldGet(this, _ObjectForest_events, "f").emit("beforeChange");
                    assert(this.currentCursors.has(cursor), 0x995 /* missing visitor cursor while editing */);
                    if (this.currentCursors.size > 1) {
                        const unexpectedSources = [...this.currentCursors].flatMap((c) => c === cursor ? [] : (c.source ?? null));
                        throw new Error(`Found unexpected cursors when editing with the following annotations: ${JSON.stringify(unexpectedSources)}`);
                    }
                };
                class Visitor {
                    constructor(forest) {
                        this.forest = forest;
                    }
                    free() {
                        cursor.free();
                        assert(this.forest.activeVisitor !== undefined, 0x76d /* Multiple free calls for same visitor */);
                        this.forest.activeVisitor = undefined;
                        if (this.forest.additionalAsserts) {
                            // Schema validation:
                            // When doing "additionalAsserts", validate the content against the schema after every batch of edits.
                            this.forest.checkSchema();
                        }
                    }
                    destroy(detachedField, count) {
                        preEdit();
                        this.forest.delete(detachedField);
                    }
                    create(content, destination) {
                        preEdit();
                        this.forest.add(content, destination);
                        __classPrivateFieldGet(this.forest, _ObjectForest_events, "f").emit("afterRootFieldCreated", destination);
                    }
                    attach(source, count, destination) {
                        preEdit();
                        this.attachEdit(source, count, destination);
                    }
                    detach(source, destination, id) {
                        preEdit();
                        this.detachEdit(source, destination);
                    }
                    /**
                     * Attaches the nodes from the given source field into the current field.
                     * Does not invalidate dependents.
                     * @param source - The the range to be attached.
                     * @param count - The number of nodes being attached.
                     * Expected to match the number of nodes in the source detached field.
                     * @param destination - The index in the current field at which to attach the content.
                     */
                    attachEdit(source, count, destination) {
                        assertNonNegativeSafeInteger(count);
                        if (count === 0) {
                            return;
                        }
                        const [parent, key] = cursor.getParent();
                        assert(parent !== this.forest.roots || key !== source, 0x7b6 /* Attach source field must be different from current field */);
                        const currentField = getOrCreateField(parent, key);
                        assertValidIndex(destination, currentField, true);
                        const sourceField = __classPrivateFieldGet(this.forest, _ObjectForest_roots, "f").fields.get(source) ?? [];
                        assert(sourceField !== undefined, 0x7b7 /* Attach source field must exist */);
                        assert(sourceField.length === count, 0x7b8 /* Attach must consume all nodes in source field */);
                        // TODO: this will fail for very large insertions due to argument limits.
                        currentField.splice(destination, 0, ...sourceField);
                        this.forest.delete(source);
                    }
                    /**
                     * Detaches the range from the current field and transfers it to the given destination if any.
                     * Does not invalidate dependents.
                     * @param source - The bounds of the range to be detached from the current field.
                     * @param destination - If specified, the destination to transfer the detached range to.
                     * If not specified, the detached range is destroyed.
                     */
                    detachEdit(source, destination) {
                        const [parent, key] = cursor.getParent();
                        assert(destination === undefined || parent !== this.forest.roots || key !== destination, 0x7b9 /* Detach destination field must be different from current field */);
                        const currentField = getOrCreateField(parent, key);
                        assertValidRange(source, currentField);
                        const content = currentField.splice(source.start, source.end - source.start);
                        if (destination !== undefined) {
                            this.forest.addFieldAsDetached(content, destination);
                        }
                        // This check is performed after the transfer to ensure that the field is not removed in scenarios
                        // where the source and destination are the same.
                        if (currentField.length === 0) {
                            parent.fields.delete(key);
                        }
                    }
                    enterNode(index) {
                        cursor.enterNode(index);
                    }
                    exitNode(index) {
                        cursor.exitNode();
                    }
                    enterField(key) {
                        cursor.enterField(key);
                    }
                    exitField(key) {
                        cursor.exitField();
                    }
                }
                const forestVisitor = new Visitor(this);
                const announcedVisitors = [];
                this.deltaVisitors.forEach((getVisitor) => announcedVisitors.push(getVisitor()));
                const combinedVisitor = combineVisitors([forestVisitor, ...announcedVisitors]);
                this.activeVisitor = combinedVisitor;
                return combinedVisitor;
            }
            registerAnnouncedVisitor(visitor) {
                this.deltaVisitors.add(visitor);
            }
            deregisterAnnouncedVisitor(visitor) {
                this.deltaVisitors.delete(visitor);
            }
            newDetachedField() {
                const range = brand(String(this.nextRange));
                this.nextRange += 1;
                return range;
            }
            add(nodes, key) {
                const field = Array.from(nodes, mapTreeFromCursor);
                this.addFieldAsDetached(field, key);
            }
            addFieldAsDetached(field, key) {
                assert(!this.roots.fields.has(key), 0x370 /* new range must not already exist */);
                if (field.length > 0) {
                    __classPrivateFieldGet(this, _ObjectForest_roots, "f").fields.set(key, field);
                }
            }
            delete(field) {
                __classPrivateFieldGet(this, _ObjectForest_roots, "f").fields.delete(field);
            }
            allocateCursor(source) {
                return new Cursor(this, source);
            }
            tryMoveCursorToNode(destination, cursorToMove) {
                const path = this.anchors.locate(destination);
                if (path === undefined) {
                    return -1 /* TreeNavigationResult.NotFound */;
                }
                this.moveCursorToPath(path, cursorToMove);
                return 1 /* TreeNavigationResult.Ok */;
            }
            tryMoveCursorToField(destination, cursorToMove) {
                if (destination.parent === undefined) {
                    this.moveCursorToPath(undefined, cursorToMove);
                }
                else {
                    const result = this.tryMoveCursorToNode(destination.parent, cursorToMove);
                    if (result !== 1 /* TreeNavigationResult.Ok */) {
                        return result;
                    }
                }
                cursorToMove.enterField(destination.fieldKey);
                return 1 /* TreeNavigationResult.Ok */;
            }
            moveCursorToPath(destination, cursorToMove) {
                assert(cursorToMove instanceof Cursor, 0x337 /* ObjectForest must only be given its own Cursor type */);
                assert(cursorToMove.forest === this, 0x338 /* ObjectForest must only be given its own Cursor */);
                const indexStack = [];
                const keyStack = [];
                let path = destination;
                while (path !== undefined) {
                    indexStack.push(path.parentIndex);
                    keyStack.push(path.parentField);
                    path = path.parent;
                }
                cursorToMove.setToAboveDetachedSequences();
                while (keyStack.length > 0) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    cursorToMove.enterField(keyStack.pop());
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    cursorToMove.enterNode(indexStack.pop());
                }
                return;
            }
            getCursorAboveDetachedFields() {
                return cursorForMapTreeNode(this.roots);
            }
        },
        _ObjectForest_events = new WeakMap(),
        _ObjectForest_roots = new WeakMap(),
        (() => {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            _checkSchema_decorators = [breakingMethod];
            __esDecorate(_a, null, _checkSchema_decorators, { kind: "method", name: "checkSchema", static: false, private: false, access: { has: obj => "checkSchema" in obj, get: obj => obj.checkSchema }, metadata: _metadata }, null, _instanceExtraInitializers);
            if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        })(),
        _a;
})();
export { ObjectForest };
/**
 * Cursor implementation for ObjectForest.
 */
class Cursor extends SynchronousCursor {
    /**
     * @param forest - forest this cursor navigates
     * @param innerCursor - underlying cursor implementation this wraps. `undefined` when state is not `Current`
     */
    constructor(forest, source, innerCursor) {
        super();
        this.forest = forest;
        this.source = source;
        this.innerCursor = innerCursor;
        if (innerCursor === undefined) {
            this.state = ITreeSubscriptionCursorState.Cleared;
        }
        else {
            this.state = ITreeSubscriptionCursorState.Current;
            this.forest.currentCursors.add(this);
        }
    }
    buildFieldAnchor() {
        const path = this.getFieldPath();
        const anchor = path.parent === undefined ? undefined : this.forest.anchors.track(path.parent);
        return { parent: anchor, fieldKey: path.field };
    }
    getFieldPath(prefix) {
        assert(this.innerCursor !== undefined, 0x45f /* Cursor must be current to be used */);
        return this.innerCursor.getFieldPath(prefix);
    }
    get mode() {
        assert(this.innerCursor !== undefined, 0x42e /* Cursor must be current to be used */);
        return this.innerCursor.mode;
    }
    nextField() {
        assert(this.innerCursor !== undefined, 0x42f /* Cursor must be current to be used */);
        return this.innerCursor.nextField();
    }
    exitField() {
        assert(this.innerCursor !== undefined, 0x430 /* Cursor must be current to be used */);
        return this.innerCursor.exitField();
    }
    skipPendingFields() {
        assert(this.innerCursor !== undefined, 0x431 /* Cursor must be current to be used */);
        return this.innerCursor.skipPendingFields();
    }
    getFieldKey() {
        assert(this.innerCursor !== undefined, 0x432 /* Cursor must be current to be used */);
        return this.innerCursor.getFieldKey();
    }
    getFieldLength() {
        assert(this.innerCursor !== undefined, 0x433 /* Cursor must be current to be used */);
        return this.innerCursor.getFieldLength();
    }
    firstNode() {
        assert(this.innerCursor !== undefined, 0x434 /* Cursor must be current to be used */);
        return this.innerCursor.firstNode();
    }
    enterNode(childIndex) {
        assert(this.innerCursor !== undefined, 0x435 /* Cursor must be current to be used */);
        return this.innerCursor.enterNode(childIndex);
    }
    getPath(prefix) {
        assert(this.innerCursor !== undefined, 0x436 /* Cursor must be current to be used */);
        return this.innerCursor.getPath(prefix) ?? fail(0xb27 /* no path when at root */);
    }
    get fieldIndex() {
        assert(this.innerCursor !== undefined, 0x437 /* Cursor must be current to be used */);
        return this.innerCursor.fieldIndex;
    }
    get chunkStart() {
        assert(this.innerCursor !== undefined, 0x438 /* Cursor must be current to be used */);
        return this.innerCursor.chunkStart;
    }
    get chunkLength() {
        assert(this.innerCursor !== undefined, 0x439 /* Cursor must be current to be used */);
        return this.innerCursor.chunkLength;
    }
    seekNodes(offset) {
        assert(this.innerCursor !== undefined, 0x43a /* Cursor must be current to be used */);
        return this.innerCursor.seekNodes(offset);
    }
    nextNode() {
        assert(this.innerCursor !== undefined, 0x43b /* Cursor must be current to be used */);
        return this.innerCursor.nextNode();
    }
    exitNode() {
        assert(this.innerCursor !== undefined, 0x43c /* Cursor must be current to be used */);
        return this.innerCursor.exitNode();
    }
    firstField() {
        assert(this.innerCursor !== undefined, 0x43d /* Cursor must be current to be used */);
        return this.innerCursor.firstField();
    }
    enterField(key) {
        assert(this.innerCursor !== undefined, 0x43e /* Cursor must be current to be used */);
        return this.innerCursor.enterField(key);
    }
    get type() {
        assert(this.innerCursor !== undefined, 0x43f /* Cursor must be current to be used */);
        return this.innerCursor.type;
    }
    get value() {
        assert(this.innerCursor !== undefined, 0x440 /* Cursor must be current to be used */);
        return this.innerCursor.value;
    }
    // TODO: tests for clear when not at root.
    clear() {
        assert(this.state !== ITreeSubscriptionCursorState.Freed, 0x33b /* Cursor must not be freed */);
        this.state = ITreeSubscriptionCursorState.Cleared;
        this.innerCursor = undefined;
        this.forest.currentCursors.delete(this);
    }
    /**
     * Move this cursor to the special dummy node above the detached sequences.
     * Can be used when cleared (but not freed).
     */
    setToAboveDetachedSequences() {
        assert(this.state !== ITreeSubscriptionCursorState.Freed, 0x33c /* Cursor must not be freed */);
        this.clear();
        this.state = ITreeSubscriptionCursorState.Current;
        // Cast to a cursor of _mutable_ trees to allow direct manipulation of the tree data which is more efficient than doing immutable copies.
        this.innerCursor = cursorForMapTreeNode(this.forest.roots);
        this.forest.currentCursors.add(this);
    }
    getNode() {
        assert(this.innerCursor !== undefined, 0x33e /* Cursor must be current to be used */);
        return this.innerCursor.getNode();
    }
    getParent() {
        assert(this.innerCursor !== undefined, 0x441 /* Cursor must be current to be used */);
        // This could be optimized to skip moving it accessing internals of cursor.
        const key = this.innerCursor.getFieldKey();
        this.innerCursor.exitField();
        const node = this.innerCursor.getNode();
        this.innerCursor.enterField(key);
        return [node, key];
    }
    fork(source) {
        assert(this.innerCursor !== undefined, 0x460 /* Cursor must be current to be used */);
        return new Cursor(this.forest, source ?? `fork: ${this.source}`, this.innerCursor.fork());
    }
    free() {
        assert(this.state !== ITreeSubscriptionCursorState.Freed, 0x33f /* Cursor must not be double freed */);
        this.forest.currentCursors.delete(this);
        this.state = ITreeSubscriptionCursorState.Freed;
    }
    buildAnchor() {
        assert(this.state === ITreeSubscriptionCursorState.Current, 0x37a /* Cursor must be current to be used */);
        return this.forest.anchors.track(this.getPath());
    }
}
// This function is the folder level export for objectForest, and hides all the implementation types.
// When other forest implementations are created (ex: optimized ones),
// this function should likely be moved and updated to (at least conditionally) use them.
/**
 * Returns an implementation of {@link IEditableForest} with no content.
 * @privateRemarks
 * TODO:
 * refactor to remove this function, and instead place `ForestTypeReference` and `ForestTypeExpensiveDebug` here as a better way to encapsulate ObjectForest.
 */
export function buildForest(breaker, schema, anchors, additionalAsserts = false) {
    return new ObjectForest(breaker, schema, anchors, additionalAsserts);
}
//# sourceMappingURL=objectForest.js.map