"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SizedNestedMap = exports.mapNestedMap = exports.forEachInNestedMap = exports.nestedMapFromFlatList = exports.nestedMapToFlatList = exports.deleteFromNestedMap = exports.getOrDefaultInNestedMap = exports.getOrAddInNestedMap = exports.tryGetFromNestedMap = exports.getOrCreateInNestedMap = exports.setInNestedMap = exports.populateNestedMap = exports.tryAddToNestedMap = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const utils_js_1 = require("./utils.js");
/**
 * If (key1, key2) already has a value in the map, it is returned, otherwise value is added under (key1, key2) and undefined is returned.
 */
function tryAddToNestedMap(map, key1, key2, value) {
    let innerMap = map.get(key1);
    if (innerMap === undefined) {
        innerMap = new Map();
        map.set(key1, innerMap);
    }
    if (innerMap.has(key2)) {
        return innerMap.get(key2);
    }
    innerMap.set(key2, value);
    return undefined;
}
exports.tryAddToNestedMap = tryAddToNestedMap;
/**
 * Copies over all entries from the source map into the destination map.
 *
 * @param source - The map to copy data from. Not mutated.
 * @param destination - The map to copy data into. Both the outer and inner map may be mutated.
 * @param override - Whether existing entries in `destination` should be replaced by corresponding entries in `source`.
 *
 * @remarks This function performs deep copying when necessary.
 * This ensures that mutating `destination` after this call will not result in unexpected mutations to `source`.
 */
function populateNestedMap(source, destination, override) {
    for (const [key1, sourceInner] of source) {
        let destinationInner = destination.get(key1);
        if (destinationInner === undefined) {
            destinationInner = new Map(sourceInner);
            destination.set(key1, destinationInner);
        }
        else {
            for (const [key2, value] of sourceInner) {
                if (override || !destinationInner.has(key2)) {
                    destinationInner.set(key2, value);
                }
            }
        }
    }
}
exports.populateNestedMap = populateNestedMap;
/**
 * Sets the value at (key1, key2) in map to value.
 * If there already is a value for (key1, key2), it is replaced with the provided one.
 */
function setInNestedMap(map, key1, key2, value) {
    const innerMap = (0, utils_js_1.getOrAddInMap)(map, key1, new Map());
    innerMap.set(key2, value);
}
exports.setInNestedMap = setInNestedMap;
/**
 * {@link getOrCreate} for {@link NestedMap}.
 */
function getOrCreateInNestedMap(map, key1, key2, defaultValue) {
    const innerMap = (0, utils_js_1.getOrAddInMap)(map, key1, new Map());
    return (0, utils_js_1.getOrCreate)(innerMap, key2, () => defaultValue(key1, key2));
}
exports.getOrCreateInNestedMap = getOrCreateInNestedMap;
/**
 * Returns the value at (key1, key2) in map, or undefined if not present.
 */
function tryGetFromNestedMap(map, key1, key2) {
    const innerMap = map.get(key1);
    if (innerMap === undefined) {
        return undefined;
    }
    return innerMap.get(key2);
}
exports.tryGetFromNestedMap = tryGetFromNestedMap;
/**
 * If (key1, key2) is not in the map, add value to the map.
 * Returns whatever is at (key1, key2) in map (which will be value if it was empty before).
 */
function getOrAddInNestedMap(map, key1, key2, value) {
    const existing = tryAddToNestedMap(map, key1, key2, value);
    if (existing !== undefined) {
        return existing;
    }
    return value;
}
exports.getOrAddInNestedMap = getOrAddInNestedMap;
/**
 * Does not change map.
 * If (key1, key2) is not in map, returns value.
 * If (key1, key2) is in map, return its entry.
 */
function getOrDefaultInNestedMap(map, key1, key2, value) {
    const existing = tryGetFromNestedMap(map, key1, key2);
    if (existing !== undefined) {
        return existing;
    }
    return value;
}
exports.getOrDefaultInNestedMap = getOrDefaultInNestedMap;
/**
 * Removes the value at (key1, key2) from the map.
 *
 * @returns true iff found.
 */
function deleteFromNestedMap(map, key1, key2) {
    const innerMap = map.get(key1);
    if (innerMap === undefined) {
        return false;
    }
    const deleted = innerMap.delete(key2);
    if (innerMap.size === 0) {
        map.delete(key1);
    }
    return deleted;
}
exports.deleteFromNestedMap = deleteFromNestedMap;
/**
 * Converts a nested map to a flat list of triplets.
 */
function nestedMapToFlatList(map) {
    const list = [];
    for (const [key1, innerMap] of map) {
        for (const [key2, val] of innerMap) {
            list.push([key1, key2, val]);
        }
    }
    return list;
}
exports.nestedMapToFlatList = nestedMapToFlatList;
/**
 * Builds a nested map from a flat list of triplets.
 */
function nestedMapFromFlatList(list) {
    const map = new Map();
    for (const [key1, key2, val] of list) {
        (0, utils_js_1.getOrAddInMap)(map, key1, new Map()).set(key2, val);
    }
    return map;
}
exports.nestedMapFromFlatList = nestedMapFromFlatList;
function forEachInNestedMap(map, delegate) {
    for (const [keyFirst, innerMap] of map) {
        for (const [keySecond, val] of innerMap) {
            delegate(val, keyFirst, keySecond);
        }
    }
}
exports.forEachInNestedMap = forEachInNestedMap;
/**
 * Maps the `input` map values using the provided `delegate`.
 *
 * @param input - The `NestedMap` whose contents are being mapped.
 * @param delegate - The delegate to use for mapping values,
 * @returns A new `NestedMap` with the same keys as `input`, but with the values produced by `delegate`.
 */
function mapNestedMap(input, delegate) {
    const output = new Map();
    for (const [keyFirst, inputInnerMap] of input) {
        const outputInnerMap = new Map();
        for (const [keySecond, val] of inputInnerMap) {
            const mappedValue = delegate(val, keyFirst, keySecond);
            outputInnerMap.set(keySecond, mappedValue);
        }
        output.set(keyFirst, outputInnerMap);
    }
    return output;
}
exports.mapNestedMap = mapNestedMap;
/**
 * Map with two keys; same semantics as NestedMap, but maintains a size count for the entire collection.
 * Note: undefined is not supported as a value, and will cause incorrect behavior.
 */
class SizedNestedMap {
    constructor() {
        this.nestedMap = new Map();
        this.count = 0;
    }
    /**
     * Returns the total number of elements in this nested map.
     */
    get size() {
        return this.count;
    }
    /**
     * If (key1, key2) already has a value in the map, it is returned, otherwise value is added under (key1, key2) and undefined is
     * returned.
     */
    tryGet(key1, key2) {
        return tryGetFromNestedMap(this.nestedMap, key1, key2);
    }
    /**
     * Does not change map.
     * If (key1, key2) is not in map, returns value.
     * If (key1, key2) is in map, return its entry.
     */
    getOrDefault(key1, key2, value) {
        return getOrDefaultInNestedMap(this.nestedMap, key1, key2, value);
    }
    /**
     * If (key1, key2) already has a value in the map, it is returned, otherwise value is added under (key1, key2) and undefined is
     * returned.
     */
    tryAdd(key1, key2, value) {
        const currentVal = tryAddToNestedMap(this.nestedMap, key1, key2, value);
        if (currentVal === undefined) {
            this.count++;
        }
        return currentVal;
    }
    /**
     * Sets the value at (key1, key2) in map to value.
     * If there already is a value for (key1, key2), it is replaced with the provided one.
     */
    set(key1, key2, value) {
        if (this.tryAdd(key1, key2, value) !== undefined) {
            setInNestedMap(this.nestedMap, key1, key2, value);
        }
    }
    /**
     * Removes the value at (key1, key2) from the map.
     * Returns true iff found.
     */
    delete(key1, key2) {
        const deleted = deleteFromNestedMap(this.nestedMap, key1, key2);
        if (deleted) {
            this.count--;
        }
        return deleted;
    }
    /**
     * Runs the supplied delegate for every (value, key1, key2).
     */
    forEach(delegate) {
        forEachInNestedMap(this.nestedMap, delegate);
    }
    /**
     * Clears the map.
     */
    clear() {
        this.count = 0;
        this.nestedMap.clear();
    }
    values() {
        return [...this.nestedMap.values()].flatMap((innerMap) => innerMap.values())[0] ?? (0, internal_1.oob)();
    }
    [Symbol.iterator]() {
        return this.nestedMap[Symbol.iterator]();
    }
}
exports.SizedNestedMap = SizedNestedMap;
//# sourceMappingURL=nestedMap.js.map