"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.RawMapNode = exports.mapSchema = exports.createMapProxy = exports.mapStaticDispatchMap = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../feature-libraries/index.js");
const proxies_js_1 = require("./proxies.js");
const proxyBinding_js_1 = require("./proxyBinding.js");
const schemaCaching_js_1 = require("./schemaCaching.js");
const toMapTree_js_1 = require("./toMapTree.js");
const toFlexSchema_js_1 = require("./toFlexSchema.js");
const rawNode_js_1 = require("./rawNode.js");
// #region Create dispatch map for maps
exports.mapStaticDispatchMap = {
    [Symbol.iterator]: {
        value() {
            return this.entries();
        },
    },
    delete: {
        value(key) {
            const node = (0, proxyBinding_js_1.getFlexNode)(this);
            node.delete(key);
        },
    },
    entries: {
        *value() {
            const node = (0, proxyBinding_js_1.getFlexNode)(this);
            for (const key of node.keys()) {
                yield [key, (0, proxies_js_1.getProxyForField)(node.getBoxed(key))];
            }
        },
    },
    get: {
        value(key) {
            const node = (0, proxyBinding_js_1.getFlexNode)(this);
            const field = node.getBoxed(key);
            return (0, proxies_js_1.getProxyForField)(field);
        },
    },
    has: {
        value(key) {
            const node = (0, proxyBinding_js_1.getFlexNode)(this);
            return node.has(key);
        },
    },
    keys: {
        value() {
            const node = (0, proxyBinding_js_1.getFlexNode)(this);
            return node.keys();
        },
    },
    set: {
        value(key, value) {
            const node = (0, proxyBinding_js_1.getFlexNode)(this);
            const content = (0, proxies_js_1.prepareContentForInsert)(value, node.context.forest);
            const classSchema = (0, schemaCaching_js_1.getSimpleNodeSchema)(node.schema);
            const cursor = (0, toMapTree_js_1.cursorFromNodeData)(content, classSchema.info);
            node.set(key, cursor);
            return this;
        },
    },
    size: {
        get() {
            return (0, proxyBinding_js_1.getFlexNode)(this).size;
        },
    },
    values: {
        *value() {
            for (const [, value] of this.entries()) {
                yield value;
            }
        },
    },
    forEach: {
        value(callbackFn, thisArg) {
            if (thisArg === undefined) {
                // We can't pass `callbackFn` to `FlexTreeMapNode` directly, or else the third argument ("map") will be a flex node instead of the proxy.
                (0, proxyBinding_js_1.getFlexNode)(this).forEach((v, k, _) => callbackFn(v, k, this), thisArg);
            }
            else {
                const boundCallbackFn = callbackFn.bind(thisArg);
                (0, proxyBinding_js_1.getFlexNode)(this).forEach((v, k, _) => boundCallbackFn(v, k, this), thisArg);
            }
        },
    },
    // TODO: add `clear` once we have established merge semantics for it.
};
const mapPrototype = Object.create(Object.prototype, exports.mapStaticDispatchMap);
// #endregion
/**
 * @param allowAdditionalProperties - If true, setting of unexpected properties will be forwarded to the target object.
 * Otherwise setting of unexpected properties will error.
 * @param customTargetObject - Target object of the proxy.
 * If not provided `new Map()` is used for the target and a separate object created to dispatch map methods.
 * If provided, the customTargetObject will be used as both the dispatch object and the proxy target, and therefor must provide the map functionality from {@link mapPrototype}.
 */
function createMapProxy(allowAdditionalProperties, customTargetObject) {
    // Create a 'dispatch' object that this Proxy forwards to instead of the proxy target.
    const dispatch = customTargetObject ??
        Object.create(mapPrototype, {
        // Empty - JavaScript Maps do not expose any "own" properties.
        });
    const targetObject = customTargetObject ?? new Map();
    // TODO: Although the target is an object literal, it's still worthwhile to try experimenting with
    // a dispatch object to see if it improves performance.
    const proxy = new Proxy(targetObject, {
        get: (target, key, receiver) => {
            // Pass the proxy as the receiver here, so that any methods on the prototype receive `proxy` as `this`.
            return Reflect.get(dispatch, key, proxy);
        },
        getOwnPropertyDescriptor: (target, key) => {
            return Reflect.getOwnPropertyDescriptor(dispatch, key);
        },
        has: (target, key) => {
            return Reflect.has(dispatch, key);
        },
        set: (target, key, newValue) => {
            return allowAdditionalProperties ? Reflect.set(dispatch, key, newValue) : false;
        },
        ownKeys: (target) => {
            // All of Map's properties are inherited via its prototype, so there is nothing to return here,
            return [];
        },
    });
    return proxy;
}
exports.createMapProxy = createMapProxy;
/**
 * Define a {@link TreeNodeSchema} for a {@link (TreeArrayNode:interface)}.
 *
 * @param name - Unique identifier for this schema including the factory's scope.
 */
function mapSchema(base, customizable) {
    class schema extends base {
        constructor(input) {
            super(input);
            const proxyTarget = customizable ? this : undefined;
            const flexSchema = (0, toFlexSchema_js_1.getFlexSchema)(this.constructor);
            (0, internal_1.assert)(flexSchema instanceof index_js_1.FlexMapNodeSchema, "invalid flex schema");
            const flexNode = (0, index_js_1.isFlexTreeNode)(input)
                ? input
                : new RawMapNode(flexSchema, copyContent(flexSchema.name, input));
            const proxy = createMapProxy(customizable, proxyTarget);
            (0, proxyBinding_js_1.setFlexNode)(proxy, flexNode);
            return proxy;
        }
    }
    // Setup map functionality
    Object.defineProperties(schema.prototype, exports.mapStaticDispatchMap);
    return schema;
}
exports.mapSchema = mapSchema;
/**
 * The implementation of a map node created by {@link createRawNode}.
 */
class RawMapNode extends rawNode_js_1.RawTreeNode {
    get size() {
        return this[rawNode_js_1.nodeContent].size;
    }
    has(key) {
        return this[rawNode_js_1.nodeContent].has(key);
    }
    get(key) {
        return this[rawNode_js_1.nodeContent].get(key);
    }
    keys() {
        return this[rawNode_js_1.nodeContent].keys();
    }
    values() {
        throw (0, rawNode_js_1.rawError)("Iterating map values");
    }
    entries() {
        throw (0, rawNode_js_1.rawError)("Iterating map entries");
    }
    forEach(callbackFn, thisArg) {
        throw (0, rawNode_js_1.rawError)("Iterating maps with forEach");
    }
    set(key, value) {
        throw (0, rawNode_js_1.rawError)("Setting a map entry");
    }
    delete(key) {
        throw (0, rawNode_js_1.rawError)("Deleting a map entry");
    }
    get asObject() {
        throw (0, rawNode_js_1.rawError)("Converting a map to an object");
    }
    [Symbol.iterator]() {
        return this.entries();
    }
    boxedIterator() {
        throw (0, rawNode_js_1.rawError)("Boxed iteration");
    }
}
exports.RawMapNode = RawMapNode;
function copyContent(typeName, content) {
    const copy = new Map(content);
    (0, proxies_js_1.markContentType)(typeName, copy);
    return copy;
}
//# sourceMappingURL=mapNode.js.map