/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/common-utils";
import { lookupGlobalFieldSchema, rootFieldKey, moveToDetachedField, } from "../../core";
import { singleMapTreeCursor } from "../mapTreeCursor";
import { applyFieldTypesFromContext } from "../contextuallyTyped";
import { makeField, unwrappedField } from "./editableField";
/**
 * Implementation of `EditableTreeContext`.
 *
 * An editor is required to edit the EditableTrees.
 */
export 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();
        }
        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();
        }
        assert(this.withCursors.size === 0, 0x3c1 /* free should remove all cursors */);
        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 = applyFieldTypesFromContext(this.schema, rootField.fieldSchema, value);
        const cursors = mapTrees.map(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 = lookupGlobalFieldSchema(this.schema, rootFieldKey);
        const cursor = this.forest.allocateCursor();
        moveToDetachedField(this.forest, cursor);
        const proxifiedField = unwrap
            ? unwrappedField(this, rootSchema, cursor)
            : 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);
    }
}
/**
 * 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.
 */
export function getEditableTreeContext(forest, editor) {
    return new ProxyContext(forest, editor);
}
//# sourceMappingURL=editableTreeContext.js.map