"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getEditableTreeContext = exports.ProxyContext = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const core_1 = require("../../core");
const mapTreeCursor_1 = require("../mapTreeCursor");
const contextuallyTyped_1 = require("../contextuallyTyped");
const editableField_1 = require("./editableField");
/**
 * Implementation of `EditableTreeContext`.
 *
 * An editor is required to edit the EditableTrees.
 */
class ProxyContext {
    /**
     * @param forest - the Forest
     * @param editor - an editor that makes changes to the forest.
     */
    constructor(forest, editor) {
        this.forest = forest;
        this.editor = editor;
        this.withCursors = new Set();
        this.withAnchors = new Set();
        this.eventUnregister = [
            this.forest.on("beforeDelta", () => {
                this.prepareForEdit();
            }),
        ];
    }
    prepareForEdit() {
        for (const target of this.withCursors) {
            target.prepareForEdit();
        }
        (0, common_utils_1.assert)(this.withCursors.size === 0, 0x3c0 /* prepareForEdit should remove all cursors */);
    }
    free() {
        this.clear();
        for (const unregister of this.eventUnregister) {
            unregister();
        }
        this.eventUnregister.length = 0;
    }
    clear() {
        for (const target of this.withCursors) {
            target.free();
        }
        for (const target of this.withAnchors) {
            target.free();
        }
        (0, common_utils_1.assert)(this.withCursors.size === 0, 0x3c1 /* free should remove all cursors */);
        (0, common_utils_1.assert)(this.withAnchors.size === 0, 0x3c2 /* free should remove all anchors */);
    }
    get unwrappedRoot() {
        return this.getRoot(true);
    }
    set unwrappedRoot(value) {
        // Note that an implementation of `set root` might change in the future,
        // see a comment in there regarding the `replaceNodes` and `replaceField` semantics.
        // This setter might want to keep the `replaceNodes` semantics for the cases when the root is unwrapped,
        // and use `replaceField` only if the root is a sequence field i.e.
        // it's unwrapped to the field itself.
        // TODO: update implementation once the low-level editing API is available.
        this.root = value;
    }
    get root() {
        return this.getRoot(false);
    }
    set root(value) {
        const rootField = this.getRoot(false);
        const mapTrees = (0, contextuallyTyped_1.applyFieldTypesFromContext)(this.schema, rootField.fieldSchema, value);
        const cursors = mapTrees.map(mapTreeCursor_1.singleMapTreeCursor);
        // `replaceNodes` has different merge semantics than the `replaceField` would ideally offer:
        // `replaceNodes` should not overwrite concurrently inserted content while `replaceField` should.
        // We currently use `replaceNodes` here because the low-level editing API
        // for the desired `replaceField` semantics is not yet avaialble.
        // TODO: update implementation once the low-level editing API is available.
        rootField.replaceNodes(0, cursors);
    }
    getRoot(unwrap) {
        const rootSchema = (0, core_1.lookupGlobalFieldSchema)(this.schema, core_1.rootFieldKey);
        const cursor = this.forest.allocateCursor();
        (0, core_1.moveToDetachedField)(this.forest, cursor);
        const proxifiedField = unwrap
            ? (0, editableField_1.unwrappedField)(this, rootSchema, cursor)
            : (0, editableField_1.makeField)(this, rootSchema, cursor);
        cursor.free();
        return proxifiedField;
    }
    get schema() {
        return this.forest.schema;
    }
    setNodeValue(path, value) {
        this.editor.setValue(path, value);
    }
    setValueField(field, newContent) {
        const fieldEditor = this.editor.valueField(field);
        fieldEditor.set(newContent);
    }
    setOptionalField(field, newContent, wasEmpty) {
        const fieldEditor = this.editor.optionalField(field);
        fieldEditor.set(newContent, wasEmpty);
    }
    insertNodes(field, index, newContent) {
        const fieldEditor = this.editor.sequenceField(field);
        fieldEditor.insert(index, newContent);
    }
    moveNodes(sourceField, sourceIndex, count, destinationField, destinationIndex) {
        this.editor.move(sourceField, sourceIndex, count, destinationField, destinationIndex);
    }
    deleteNodes(field, index, count) {
        const fieldEditor = this.editor.sequenceField(field);
        fieldEditor.delete(index, count);
    }
    replaceNodes(field, index, count, newContent) {
        const fieldEditor = this.editor.sequenceField(field);
        fieldEditor.delete(index, count);
        if (!Array.isArray(newContent) || newContent.length > 0) {
            fieldEditor.insert(index, newContent);
        }
    }
    on(eventName, listener) {
        return this.forest.on(eventName, listener);
    }
}
exports.ProxyContext = ProxyContext;
/**
 * A simple API for a Forest to interact with the tree.
 *
 * @param forest - the Forest
 * @param editor - an editor that makes changes to the forest.
 * @returns {@link EditableTreeContext} which is used to manage the cursors and anchors within the EditableTrees:
 * This is necessary for supporting using this tree across edits to the forest, and not leaking memory.
 */
function getEditableTreeContext(forest, editor) {
    return new ProxyContext(forest, editor);
}
exports.getEditableTreeContext = getEditableTreeContext;
//# sourceMappingURL=editableTreeContext.js.map