"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.mapSchema = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("../../../feature-libraries/index.js");
const getTreeNodeForField_js_1 = require("../../getTreeNodeForField.js");
const fieldSchema_js_1 = require("../../fieldSchema.js");
const index_js_2 = require("../../core/index.js");
const unhydratedFlexTreeFromInsertable_js_1 = require("../../unhydratedFlexTreeFromInsertable.js");
const prepareForInsertion_js_1 = require("../../prepareForInsertion.js");
const index_js_3 = require("../../../util/index.js");
const createContext_js_1 = require("../../createContext.js");
const common_js_1 = require("../common.js");
const index_js_4 = require("../../../core/index.js");
// TreeMapNode is invariant over schema type, so for this handler to work with all schema, the only possible type for the schema is `any`.
// This is not ideal, but no alternatives are possible.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handler = {
    getPrototypeOf: () => {
        return Map.prototype;
    },
};
class CustomMapNodeBase extends index_js_2.TreeNodeValid {
    constructor(input) {
        super(input ?? []);
    }
    [Symbol.iterator]() {
        return this.entries();
    }
    get innerNode() {
        return (0, index_js_2.getOrCreateInnerNode)(this);
    }
    editor(key) {
        const field = this.innerNode.getBoxed((0, index_js_3.brand)(key));
        return field.editor;
    }
    delete(key) {
        const field = this.innerNode.getBoxed((0, index_js_3.brand)(key));
        this.editor(key).set(undefined, field.length === 0);
    }
    *entries() {
        const node = this.innerNode;
        for (const key of node.keys()) {
            yield [
                key,
                (0, getTreeNodeForField_js_1.tryGetTreeNodeForField)(node.getBoxed(key)),
            ];
        }
    }
    get(key) {
        const node = this.innerNode;
        const field = node.getBoxed((0, index_js_3.brand)(key));
        return (0, getTreeNodeForField_js_1.tryGetTreeNodeForField)(field);
    }
    has(key) {
        return this.innerNode.tryGetField((0, index_js_3.brand)(key)) !== undefined;
    }
    keys() {
        const node = this.innerNode;
        return node.keys();
    }
    set(key, value) {
        const kernel = (0, index_js_2.getKernel)(this);
        const node = this.innerNode;
        const innerSchema = this.innerNode.context.schema.nodeSchema.get((0, index_js_3.brand)(kernel.schema.identifier));
        (0, internal_1.assert)(innerSchema instanceof index_js_4.MapNodeStoredSchema, 0xc17 /* Expected MapNodeStoredSchema */);
        const mapTree = (0, prepareForInsertion_js_1.prepareForInsertion)(value, (0, fieldSchema_js_1.createFieldSchema)(fieldSchema_js_1.FieldKind.Optional, kernel.schema.info), node.context, innerSchema.mapFields);
        const field = node.getBoxed((0, index_js_3.brand)(key));
        this.editor(key).set(mapTree, field.length === 0);
        return this;
    }
    get size() {
        return (0, index_js_3.count)(this.innerNode.keys());
    }
    *values() {
        for (const [, value] of this.entries()) {
            yield value;
        }
    }
    forEach(callbackFn, thisArg) {
        for (const field of (0, index_js_2.getOrCreateInnerNode)(this)) {
            const node = (0, getTreeNodeForField_js_1.tryGetTreeNodeForField)(field);
            callbackFn.call(thisArg, node, field.key, this);
        }
    }
}
CustomMapNodeBase.kind = index_js_2.NodeKind.Map;
/**
 * Define a {@link TreeNodeSchema} for a {@link (TreeArrayNode:interface)}.
 *
 * @param base - base schema type to extend.
 * @param useMapPrototype - should this type emulate a ES6 Map object (by faking its prototype with a proxy).
 * @param persistedMetadata - Optional persisted metadata for the object node schema.
 */
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function mapSchema(identifier, info, implicitlyConstructable, useMapPrototype, metadata, persistedMetadata) {
    const lazyChildTypes = new internal_1.Lazy(() => (0, index_js_2.normalizeAllowedTypes)((0, index_js_2.unannotateImplicitAllowedTypes)(info)));
    const lazyAllowedTypesIdentifiers = new internal_1.Lazy(() => new Set([...lazyChildTypes.value].map((type) => type.identifier)));
    let privateData;
    class Schema extends CustomMapNodeBase {
        static prepareInstance(instance, flexNode) {
            if (useMapPrototype) {
                return new Proxy(instance, handler);
            }
            return instance;
        }
        static buildRawNode(instance, input) {
            return (0, unhydratedFlexTreeFromInsertable_js_1.unhydratedFlexTreeFromInsertable)(input, this);
        }
        static get allowedTypesIdentifiers() {
            return lazyAllowedTypesIdentifiers.value;
        }
        static oneTimeSetup() {
            const schema = this;
            return (0, createContext_js_1.getTreeNodeSchemaInitializedData)(this, {
                shallowCompatibilityTest,
                toFlexContent: (data) => mapToFlexContent(data, schema),
            });
        }
        static get childTypes() {
            return lazyChildTypes.value;
        }
        // eslint-disable-next-line import/no-deprecated
        get [index_js_2.typeNameSymbol]() {
            return identifier;
        }
        get [index_js_2.typeSchemaSymbol]() {
            return Schema.constructorCached?.constructor;
        }
        static get [index_js_2.privateDataSymbol]() {
            return (privateData ??= (0, index_js_2.createTreeNodeSchemaPrivateData)(this, [info], (storedOptions) => new index_js_4.MapNodeStoredSchema({
                kind: index_js_1.FieldKinds.optional.identifier,
                types: (0, index_js_2.convertAllowedTypes)(info, storedOptions),
                persistedMetadata,
            }, persistedMetadata)));
        }
    }
    Schema.constructorCached = undefined;
    Schema.identifier = identifier;
    Schema.info = info;
    Schema.implicitlyConstructable = implicitlyConstructable;
    Schema.metadata = metadata ?? {};
    Schema.persistedMetadata = persistedMetadata;
    const schemaErased = Schema;
    return schemaErased;
}
exports.mapSchema = mapSchema;
/**
 * {@link TreeNodeSchemaInitializedData.shallowCompatibilityTest} for Map nodes.
 */
function shallowCompatibilityTest(data) {
    if ((0, index_js_1.isTreeValue)(data)) {
        return index_js_2.CompatibilityLevel.None;
    }
    if ((0, index_js_3.isReadonlyArray)(data)) {
        // Arrays are iterable, so type checking does allow constructing a MapNode from an array if the array's type is key values pairs for the map.
        return index_js_2.CompatibilityLevel.Low;
    }
    if (Symbol.iterator in data) {
        return index_js_2.CompatibilityLevel.Normal;
    }
    // When not unioned with an ObjectNode, allow objects to be used to create maps.
    return index_js_2.CompatibilityLevel.Low;
}
/**
 * {@link TreeNodeSchemaInitializedData.toFlexContent} for Map nodes.
 *
 * Transforms data under a Map schema.
 * @param data - The tree data to be transformed. Must be an iterable or Record like object.
 * @param schema - The schema to comply with.
 */
function mapToFlexContent(data, schema) {
    if (!(typeof data === "object" && data !== null)) {
        throw new internal_2.UsageError(`Input data is incompatible with Map schema: ${data}`);
    }
    const fieldsIterator = (Symbol.iterator in data
        ? // Support iterables of key value pairs (including Map objects)
            data
        : // Support record objects for JSON style Map data
            Object.entries(data));
    return (0, common_js_1.recordLikeDataToFlexContent)(fieldsIterator, schema);
}
//# sourceMappingURL=mapNode.js.map