"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeepMap = void 0;
const errors_1 = require("./errors");
const normalizer_1 = require("./normalizer");
/**
 * A Map implementation that supports deep equality for object keys.
 */
class DeepMap extends Map {
    // NOTE: This is actually a thin wrapper. We're not using super other than to drive the (typed) API contract.
    /**
     * @param entries optional list of key-value pairs to initialize the map
     * @param options configuration options
     */
    constructor(entries, options = {}) {
        super();
        this.options = options;
        this.normalizer = new normalizer_1.Normalizer(options);
        const transformedEntries = entries
            ? entries.map(([key, val]) => [this.normalizeKey(key), { key, val }])
            : null;
        this.map = new Map(transformedEntries);
    }
    /**
     * Getter for number of kev-value pairs in the map.
     * @inheritdoc
     */
    get size() {
        return this.map.size;
    }
    /**
     * Returns true if the given key is present in the map.
     * @inheritdoc
     */
    has(key) {
        return this.map.has(this.normalizeKey(key));
    }
    /**
     * @inheritdoc
     */
    set(key, val) {
        this.map.set(this.normalizeKey(key), { key, val });
        return this;
    }
    /**
     * @inheritdoc
     */
    get(key) {
        var _a;
        return (_a = this.map.get(this.normalizeKey(key))) === null || _a === void 0 ? void 0 : _a.val;
    }
    /**
     * @inheritdoc
     */
    delete(key) {
        return this.map.delete(this.normalizeKey(key));
    }
    /**
     * Clear all key-value pairs from the map.
     * @inheritdoc
     */
    clear() {
        this.map.clear();
    }
    /**
     * @inheritdoc
     */
    forEach(callbackfn) {
        this.map.forEach((pair, _key, _internalMap) => {
            callbackfn(pair.val, pair.key, this);
        });
    }
    /**
     * @yields the next key-value pair in the map
     * @inheritdoc
     */
    *[Symbol.iterator]() {
        for (const [_hashStr, pair] of this.map[Symbol.iterator]()) {
            yield [pair.key, pair.val];
        }
    }
    /**
     * @yields the next key-value pair in the map
     * @inheritdoc
     */
    *entries() {
        for (const entry of this[Symbol.iterator]()) {
            yield entry;
        }
    }
    /**
     * @inheritdoc
     */
    *keys() {
        for (const [key, _val] of this[Symbol.iterator]()) {
            yield key;
        }
    }
    /**
     * @inheritdoc
     */
    *values() {
        for (const [_key, val] of this[Symbol.iterator]()) {
            yield val;
        }
    }
    /**
     * @param other the map to compare against
     * @returns true if the entries of `other` are the same as this map
     */
    equals(other) {
        this.validateUsingSameOptionsAs(other);
        return this.size === other.size && this.contains(other);
    }
    /**
     * @param other the map to compare against
     * @returns true if the entries of `other` are all contained in this map
     */
    contains(other) {
        this.validateUsingSameOptionsAs(other);
        return [...other.entries()].every(([key, val]) => this.keyValuePairIsPresentIn(key, val, this));
    }
    /**
     * @param other the map to compare against
     * @returns a new map whose keys are the union of keys between `this` and `other` maps.
     *
     * NOTE: If both maps prescribe the same key, the key-value pair from `this` will be retained.
     */
    union(other) {
        this.validateUsingSameOptionsAs(other);
        return new DeepMap([...other.entries(), ...this.entries()], this.options);
    }
    /**
     * @param other the map to compare against
     * @returns a new map containing all key-value pairs in `this` that are also present in `other`.
     */
    intersection(other) {
        this.validateUsingSameOptionsAs(other);
        const intersectingPairs = [...this.entries()].filter(([key, val]) => this.keyValuePairIsPresentIn(key, val, other));
        return new DeepMap(intersectingPairs, this.options);
    }
    /**
     * @param other the map to compare against
     * @returns a new map containing all key-value pairs in `this` that are not present in `other`.
     */
    difference(other) {
        this.validateUsingSameOptionsAs(other);
        const differencePairs = [...this.entries()].filter(([key, val]) => !this.keyValuePairIsPresentIn(key, val, other));
        return new DeepMap(differencePairs, this.options);
    }
    // PRIVATE/PROTECTED METHODS FOLLOW...
    normalizeKey(input) {
        return this.normalizer.normalizeKey(input);
    }
    normalizeValue(input) {
        return this.normalizer.normalizeValue(input);
    }
    validateUsingSameOptionsAs(other) {
        if (this.normalizer.getOptionsChecksum() !== other['normalizer'].getOptionsChecksum()) {
            throw new errors_1.DeepEqualityDataStructuresError('Structures must use same options for Comparable interface operations');
        }
    }
    /**
     * @returns true if the key is present in the provided map w/ the specified value
     */
    keyValuePairIsPresentIn(key, val, mapToCheck) {
        const checkVal = mapToCheck.get(key);
        return checkVal !== undefined && this.normalizeValue(checkVal) === this.normalizeValue(val);
    }
}
exports.DeepMap = DeepMap;
