"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.unboxedField = exports.LazyForbiddenField = exports.LazyOptionalField = exports.LazyValueField = exports.LazySequence = exports.LazyField = exports.makeField = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const core_1 = require("../../core");
const contextuallyTyped_1 = require("../contextuallyTyped");
const default_field_kinds_1 = require("../default-field-kinds");
const util_1 = require("../../util");
const typed_schema_1 = require("../typed-schema");
const editable_tree_1 = require("../editable-tree");
const lazyTree_1 = require("./lazyTree");
const lazyEntity_1 = require("./lazyEntity");
function makeField(context, schema, cursor) {
    const fieldAnchor = cursor.buildFieldAnchor();
    const field = new (kindToClass.get(schema.kind) ?? (0, util_1.fail)("missing field implementation"))(context, schema, cursor, fieldAnchor);
    // Fields currently live as long as their parent does.
    // For root fields, this means forever, but other cases can be cleaned up when their parent anchor is deleted.
    if (fieldAnchor.parent !== undefined) {
        const anchorNode = context.forest.anchors.locate(fieldAnchor.parent) ??
            (0, util_1.fail)("parent anchor node should always exist since field is under a node");
        anchorNode.on("afterDelete", () => {
            field[util_1.disposeSymbol]();
        });
    }
    return field;
}
exports.makeField = makeField;
/**
 * A Proxy target, which together with a `fieldProxyHandler` implements a basic access to
 * the nodes of {@link EditableField} by means of the cursors.
 */
class LazyField extends lazyEntity_1.LazyEntity {
    constructor(context, schema, cursor, fieldAnchor) {
        super(context, schema, cursor, fieldAnchor);
        (0, core_utils_1.assert)(cursor.mode === 1 /* Fields */, 0x77b /* must be in fields mode */);
        this.key = cursor.getFieldKey();
        (0, lazyEntity_1.makePropertyNotEnumerable)(this, "key");
    }
    is(schema) {
        (0, core_utils_1.assert)(this.context.schema.policy.fieldKinds.get(schema.kind.identifier) === schema.kind, 0x77c /* Narrowing must be done to a kind that exists in this context */);
        if (schema.kind !== this.schema.kind) {
            return false;
        }
        if (schema.types === undefined) {
            return this.schema.types === undefined;
        }
        if (this.schema.types === undefined) {
            return false;
        }
        return (0, util_1.compareSets)({ a: this.schema.types, b: schema.types });
    }
    isSameAs(other) {
        (0, core_utils_1.assert)(other.context === this.context, 0x77d /* Content from different editable trees should not be used together */);
        return this.key === other.key && this.parent === other.parent;
    }
    normalizeNewContent(content) {
        return (0, contextuallyTyped_1.normalizeNewFieldContent)(this.context, this.schema, content);
    }
    get parent() {
        if (this[lazyEntity_1.anchorSymbol].parent === undefined) {
            return undefined;
        }
        const cursor = this[lazyEntity_1.cursorSymbol];
        cursor.exitField();
        const output = (0, lazyTree_1.makeTree)(this.context, cursor);
        cursor.enterField(this.key);
        return output;
    }
    [lazyEntity_1.tryMoveCursorToAnchorSymbol](anchor, cursor) {
        return this.context.forest.tryMoveCursorToField(anchor, cursor);
    }
    [lazyEntity_1.forgetAnchorSymbol](anchor) {
        if (anchor.parent === undefined)
            return;
        this.context.forest.anchors.forget(anchor.parent);
    }
    get length() {
        return this[lazyEntity_1.cursorSymbol].getFieldLength();
    }
    at(index) {
        return (0, core_1.inCursorNode)(this[lazyEntity_1.cursorSymbol], index, (cursor) => unboxedUnion(this.context, this.schema, cursor));
    }
    boxedAt(index) {
        return (0, core_1.inCursorNode)(this[lazyEntity_1.cursorSymbol], index, (cursor) => (0, lazyTree_1.makeTree)(this.context, cursor));
    }
    map(callbackfn) {
        return (0, core_1.mapCursorField)(this[lazyEntity_1.cursorSymbol], (cursor) => callbackfn(unboxedUnion(this.context, this.schema, cursor), cursor.fieldIndex, this));
    }
    mapBoxed(callbackfn) {
        return (0, core_1.mapCursorField)(this[lazyEntity_1.cursorSymbol], (cursor) => callbackfn((0, lazyTree_1.makeTree)(this.context, cursor), cursor.fieldIndex, this));
    }
    [Symbol.iterator]() {
        return this.mapBoxed((x) => x)[Symbol.iterator]();
    }
    treeStatus() {
        if (this[lazyEntity_1.isFreedSymbol]()) {
            return editable_tree_1.TreeStatus.Deleted;
        }
        const fieldAnchor = this[lazyEntity_1.anchorSymbol];
        const parentAnchor = fieldAnchor.parent;
        // If the parentAnchor is undefined it is a detached field.
        if (parentAnchor === undefined) {
            return (0, core_1.keyAsDetachedField)(fieldAnchor.fieldKey) === core_1.rootField
                ? editable_tree_1.TreeStatus.InDocument
                : editable_tree_1.TreeStatus.Removed;
        }
        const parentAnchorNode = this.context.forest.anchors.locate(parentAnchor);
        // As the "parentAnchor === undefined" case is handled above, parentAnchorNode should exist.
        (0, core_utils_1.assert)(parentAnchorNode !== undefined, 0x77e /* parentAnchorNode must exist. */);
        return (0, editable_tree_1.treeStatusFromPath)(parentAnchorNode);
    }
    getFieldPath() {
        return this[lazyEntity_1.cursorSymbol].getFieldPath();
    }
    /**
     * Returns the path to this field to use for editing. Throws iff this path is not {@link TreeStatus#InDocument}.
     * This path is not valid to hold onto across edits: this must be recalled for each edit.
     */
    getFieldPathForEditing() {
        (0, core_utils_1.assert)(this.treeStatus() === editable_tree_1.TreeStatus.InDocument, 0x77f /* Editing only allowed on fields with TreeStatus.InDocument status */);
        return this.getFieldPath();
    }
}
exports.LazyField = LazyField;
class LazySequence extends LazyField {
    constructor(context, schema, cursor, fieldAnchor) {
        super(context, schema, cursor, fieldAnchor);
        (0, lazyEntity_1.makePropertyEnumerableOwn)(this, "asArray", LazySequence.prototype);
    }
    sequenceEditor() {
        const fieldPath = this.getFieldPathForEditing();
        const fieldEditor = this.context.editor.sequenceField(fieldPath);
        return fieldEditor;
    }
    replaceRange(index, count, newContent) {
        const fieldEditor = this.sequenceEditor();
        const content = this.normalizeNewContent([...newContent]);
        fieldEditor.delete(index, count);
        fieldEditor.insert(index, content);
    }
    get asArray() {
        return this.map((x) => x);
    }
}
exports.LazySequence = LazySequence;
class LazyValueField extends LazyField {
    constructor(context, schema, cursor, fieldAnchor) {
        super(context, schema, cursor, fieldAnchor);
        (0, lazyEntity_1.makePropertyEnumerableOwn)(this, "content", LazyValueField.prototype);
    }
    valueFieldEditor() {
        const fieldPath = this.getFieldPathForEditing();
        const fieldEditor = this.context.editor.valueField(fieldPath);
        return fieldEditor;
    }
    get content() {
        return this.at(0);
    }
    get boxedContent() {
        return this.boxedAt(0);
    }
    setContent(newContent) {
        const content = this.normalizeNewContent(newContent);
        const fieldEditor = this.valueFieldEditor();
        (0, core_utils_1.assert)(content.length === 1, 0x780 /* value field content should normalize to one item */);
        fieldEditor.set(content[0]);
    }
}
exports.LazyValueField = LazyValueField;
class LazyOptionalField extends LazyField {
    constructor(context, schema, cursor, fieldAnchor) {
        super(context, schema, cursor, fieldAnchor);
        (0, lazyEntity_1.makePropertyEnumerableOwn)(this, "content", LazyOptionalField.prototype);
    }
    optionalEditor() {
        const fieldPath = this.getFieldPathForEditing();
        const fieldEditor = this.context.editor.optionalField(fieldPath);
        return fieldEditor;
    }
    get content() {
        return this.length === 0 ? undefined : this.at(0);
    }
    get boxedContent() {
        return this.length === 0 ? undefined : this.boxedAt(0);
    }
    setContent(newContent) {
        const content = this.normalizeNewContent(newContent);
        const fieldEditor = this.optionalEditor();
        (0, core_utils_1.assert)(content.length <= 1, 0x781 /* optional field content should normalize at most one item */);
        fieldEditor.set(content.length === 0 ? undefined : content[0], this.length === 0);
    }
}
exports.LazyOptionalField = LazyOptionalField;
class LazyForbiddenField extends LazyField {
}
exports.LazyForbiddenField = LazyForbiddenField;
const builderList = [
    [default_field_kinds_1.FieldKinds.forbidden, LazyForbiddenField],
    [default_field_kinds_1.FieldKinds.nodeKey, LazyOptionalField],
    [default_field_kinds_1.FieldKinds.optional, LazyOptionalField],
    [default_field_kinds_1.FieldKinds.sequence, LazySequence],
    [default_field_kinds_1.FieldKinds.value, LazyValueField],
];
const kindToClass = new Map(builderList);
/**
 * See {@link UnboxNode} for documentation on what unwrapping this performs.
 */
function unboxedTree(context, schema, cursor) {
    if ((0, typed_schema_1.schemaIsLeaf)(schema)) {
        return cursor.value;
    }
    if ((0, typed_schema_1.schemaIsFieldNode)(schema)) {
        cursor.enterField(core_1.EmptyKey);
        const primaryField = makeField(context, schema.structFields.get(core_1.EmptyKey) ?? (0, util_1.fail)("invalid schema"), cursor);
        cursor.exitField();
        return primaryField;
    }
    return (0, lazyTree_1.makeTree)(context, cursor);
}
/**
 * See {@link UnboxNodeUnion} for documentation on what unwrapping this performs.
 */
function unboxedUnion(context, schema, cursor) {
    const type = (0, util_1.oneFromSet)(schema.types);
    if (type !== undefined) {
        return unboxedTree(context, context.schema.treeSchema.get(type) ?? (0, util_1.fail)("missing schema"), cursor);
    }
    return (0, lazyTree_1.makeTree)(context, cursor);
}
/**
 * @param context - the common context of the field.
 * @param schema - the FieldStoredSchema of the field.
 * @param cursor - the cursor, which must point to the field being proxified.
 */
function unboxedField(context, schema, cursor) {
    const kind = schema.kind;
    if (kind === default_field_kinds_1.FieldKinds.value) {
        return (0, core_1.inCursorNode)(cursor, 0, (innerCursor) => unboxedUnion(context, schema, innerCursor));
    }
    if (kind === default_field_kinds_1.FieldKinds.optional) {
        if (cursor.getFieldLength() === 0) {
            return undefined;
        }
        return (0, core_1.inCursorNode)(cursor, 0, (innerCursor) => unboxedUnion(context, schema, innerCursor));
    }
    // TODO: forbidden and nodeKey
    return makeField(context, schema, cursor);
}
exports.unboxedField = unboxedField;
//# sourceMappingURL=lazyField.js.map