/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { Lazy } from "@fluidframework/core-utils/internal";
import { tryGetTreeNodeForField } from "../../getTreeNodeForField.js";
import { createFieldSchema, FieldKind, normalizeAllowedTypes, normalizeAnnotatedAllowedTypes, unannotateImplicitAllowedTypes, } from "../../schemaTypes.js";
import { getKernel, NodeKind, 
// eslint-disable-next-line import/no-deprecated
typeNameSymbol, typeSchemaSymbol, getOrCreateInnerNode, } from "../../core/index.js";
import { unhydratedFlexTreeFromInsertable, } from "../../unhydratedFlexTreeFromInsertable.js";
import { prepareForInsertion } from "../../prepareForInsertion.js";
import { brand, count, } from "../../../util/index.js";
import { TreeNodeValid } from "../../treeNodeValid.js";
import { getUnhydratedContext } from "../../createContext.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 TreeNodeValid {
    constructor(input) {
        super(input ?? []);
    }
    [Symbol.iterator]() {
        return this.entries();
    }
    get innerNode() {
        return getOrCreateInnerNode(this);
    }
    editor(key) {
        const field = this.innerNode.getBoxed(brand(key));
        return field.editor;
    }
    delete(key) {
        const field = this.innerNode.getBoxed(brand(key));
        this.editor(key).set(undefined, field.length === 0);
    }
    *entries() {
        const node = this.innerNode;
        for (const key of node.keys()) {
            yield [
                key,
                tryGetTreeNodeForField(node.getBoxed(key)),
            ];
        }
    }
    get(key) {
        const node = this.innerNode;
        const field = node.getBoxed(brand(key));
        return tryGetTreeNodeForField(field);
    }
    has(key) {
        return this.innerNode.tryGetField(brand(key)) !== undefined;
    }
    keys() {
        const node = this.innerNode;
        return node.keys();
    }
    set(key, value) {
        const kernel = getKernel(this);
        const node = this.innerNode;
        const mapTree = prepareForInsertion(value, createFieldSchema(FieldKind.Optional, kernel.schema.info), node.context);
        const field = node.getBoxed(brand(key));
        this.editor(key).set(mapTree, field.length === 0);
        return this;
    }
    get size() {
        return count(this.innerNode.keys());
    }
    *values() {
        for (const [, value] of this.entries()) {
            yield value;
        }
    }
    forEach(callbackFn, thisArg) {
        for (const field of getOrCreateInnerNode(this)) {
            const node = tryGetTreeNodeForField(field);
            callbackFn.call(thisArg, node, field.key, this);
        }
    }
}
CustomMapNodeBase.kind = 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
export function mapSchema(identifier, info, implicitlyConstructable, useMapPrototype, metadata, persistedMetadata) {
    const lazyChildTypes = new Lazy(() => normalizeAllowedTypes(unannotateImplicitAllowedTypes(info)));
    const lazyAnnotatedTypes = new Lazy(() => [normalizeAnnotatedAllowedTypes(info)]);
    const lazyAllowedTypesIdentifiers = new Lazy(() => new Set([...lazyChildTypes.value].map((type) => type.identifier)));
    let unhydratedContext;
    class Schema extends CustomMapNodeBase {
        static prepareInstance(instance, flexNode) {
            if (useMapPrototype) {
                return new Proxy(instance, handler);
            }
            return instance;
        }
        static buildRawNode(instance, input) {
            return unhydratedFlexTreeFromInsertable(input, this);
        }
        static get allowedTypesIdentifiers() {
            return lazyAllowedTypesIdentifiers.value;
        }
        static oneTimeSetup() {
            const schema = this;
            unhydratedContext = getUnhydratedContext(schema);
            return unhydratedContext;
        }
        static get childTypes() {
            return lazyChildTypes.value;
        }
        static get childAnnotatedAllowedTypes() {
            return lazyAnnotatedTypes.value;
        }
        // eslint-disable-next-line import/no-deprecated
        get [typeNameSymbol]() {
            return identifier;
        }
        get [typeSchemaSymbol]() {
            return Schema.constructorCached?.constructor;
        }
    }
    Schema.constructorCached = undefined;
    Schema.identifier = identifier;
    Schema.info = info;
    Schema.implicitlyConstructable = implicitlyConstructable;
    Schema.metadata = metadata ?? {};
    Schema.persistedMetadata = persistedMetadata;
    const schemaErased = Schema;
    return schemaErased;
}
//# sourceMappingURL=mapNode.js.map