"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.balancedReduce = exports.copyPropertyIfDefined = exports.defineLazyCachedProperty = exports.comparePartialStrings = exports.compareStrings = exports.comparePartialNumbers = exports.compareNumbers = exports.capitalize = exports.disposeSymbol = exports.invertMap = exports.transformObjectMap = exports.objectToMap = exports.assertNonNegativeSafeInteger = exports.assertValidRange = exports.assertValidIndex = exports.assertValidRangeIndices = exports.isJsonObject = exports.JsonCompatibleReadOnlySchema = exports.count = exports.find = exports.filterIterable = exports.mapIterable = exports.getOrAddEmptyToMap = exports.getOrCreate = exports.getOrAddInMap = exports.compareSets = exports.hasSingle = exports.oneFromIterable = exports.iterableHasSome = exports.hasSome = exports.getLast = exports.makeArray = exports.isReadonlyArray = exports.asMutable = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const typebox_1 = require("@sinclair/typebox");
/**
 * Casts a readonly object to a mutable one.
 * Better than casting to `Mutable<Foo>` because it doesn't risk casting a non-`Foo` to a `Mutable<Foo>`.
 * @param readonly - The object with readonly fields.
 * @returns The same object but with a type that makes all fields mutable.
 */
function asMutable(readonly) {
    return readonly;
}
exports.asMutable = asMutable;
/**
 * Checks whether or not the given object is a `readonly` array.
 *
 * Note that this does NOT indicate if a given array should be treated as readonly.
 * This instead indicates if an object is an Array, and is typed to tolerate the readonly case.
 */
function isReadonlyArray(x) {
    // `Array.isArray()` does not properly narrow `readonly` array types by itself,
    // so we wrap it in this type guard. This may become unnecessary if/when
    // https://github.com/microsoft/TypeScript/issues/17002 is resolved.
    return Array.isArray(x);
}
exports.isReadonlyArray = isReadonlyArray;
/**
 * Creates and populates a new array.
 * @param size - The size of the array to be created.
 * @param filler - Callback for populating the array with a value for a given index
 */
function makeArray(size, filler) {
    const array = [];
    for (let i = 0; i < size; ++i) {
        array.push(filler(i));
    }
    return array;
}
exports.makeArray = makeArray;
function getLast(array) {
    return array[array.length - 1];
}
exports.getLast = getLast;
function hasSome(array) {
    return array.length > 0;
}
exports.hasSome = hasSome;
/**
 * Returns true if and only if the given iterable has at least one element.
 */
function iterableHasSome(iterable) {
    return iterable[Symbol.iterator]().next().done === false;
}
exports.iterableHasSome = iterableHasSome;
/**
 * Returns the value from the given iterable if it contains exactly one item, otherwise `undefined`.
 * @remarks
 * Note that if the iterable itself may contain `undefined` values,
 * it is not possible to use this to distinguish between an iterable that contains exactly one `undefined` value and an iterable that contains zero or multiple values.
 */
function oneFromIterable(items) {
    const iterator = items[Symbol.iterator]();
    const first = iterator.next();
    if (first.done === true) {
        // Empty iterable case.
        return undefined;
    }
    const second = iterator.next();
    if (second.done === true) {
        // Single item iterable case.
        return first.value;
    }
    // Multiple item iterable case.
    return undefined;
}
exports.oneFromIterable = oneFromIterable;
function hasSingle(array) {
    return array.length === 1;
}
exports.hasSingle = hasSingle;
/**
 * Compares two sets using callbacks.
 * Early returns on first false comparison.
 *
 * @param a - One Set.
 * @param b - The other Set.
 * @param aExtra - Called for items in `a` but not `b`.
 * @param bExtra - Called for items in `b` but not `a`.
 * @param same - Called for items in `a` and `b`.
 * @returns false iff any of the call backs returned false.
 */
function compareSets({ a, b, aExtra, bExtra, same, }) {
    for (const item of a.keys()) {
        if (b.has(item)) {
            if (same !== undefined && !same(item)) {
                return false;
            }
        }
        else {
            if (aExtra === undefined) {
                return false;
            }
            else {
                if (!aExtra(item)) {
                    return false;
                }
            }
        }
    }
    for (const item of b.keys()) {
        if (a.has(item)) {
            continue;
        }
        if (bExtra === undefined) {
            return false;
        }
        else {
            if (!bExtra(item)) {
                return false;
            }
        }
    }
    return true;
}
exports.compareSets = compareSets;
/**
 * Sets the value at `key` in map to value if not already present.
 * Returns the value at `key` after setting it.
 * This is equivalent to a get or default that adds the default to the map.
 */
function getOrAddInMap(map, key, value) {
    const currentValue = map.get(key);
    if (currentValue !== undefined) {
        return currentValue;
    }
    map.set(key, value);
    return value;
}
exports.getOrAddInMap = getOrAddInMap;
/**
 * Retrieve a value from a map with the given key, or create a new entry if the key is not in the map.
 * @param map - The map to query/update
 * @param key - The key to lookup in the map
 * @param defaultValue - a function which returns a default value. This is called and used to set an initial value for the given key in the map if none exists
 * @returns either the existing value for the given key, or the newly-created value (the result of `defaultValue`)
 */
function getOrCreate(map, key, defaultValue) {
    let value = map.get(key);
    if (value === undefined) {
        value = defaultValue(key);
        map.set(key, value);
    }
    return value;
}
exports.getOrCreate = getOrCreate;
/**
 * Utility for dictionaries whose values are lists.
 * Gets the list associated with the provided key, if it exists.
 * Otherwise, creates an entry with an empty list, and returns that list.
 */
function getOrAddEmptyToMap(map, key) {
    let collection = map.get(key);
    if (collection === undefined) {
        collection = [];
        map.set(key, collection);
    }
    return collection;
}
exports.getOrAddEmptyToMap = getOrAddEmptyToMap;
/**
 * Map one iterable to another by transforming each element one at a time
 * @param iterable - the iterable to transform
 * @param map - the transformation function to run on each element of the iterable
 * @returns a new iterable of elements which have been transformed by the `map` function
 */
function* mapIterable(iterable, map) {
    for (const t of iterable) {
        yield map(t);
    }
}
exports.mapIterable = mapIterable;
/**
 * Filter one iterable into another
 * @param iterable - the iterable to filter
 * @param filter - the predicate function to run on each element of the iterable
 * @returns a new iterable including only the elements that passed the filter predicate
 */
function* filterIterable(iterable, filter) {
    for (const t of iterable) {
        if (filter(t)) {
            yield t;
        }
    }
}
exports.filterIterable = filterIterable;
/**
 * Finds the first element in the given iterable that satisfies a predicate.
 * @param iterable - The iterable to search for an eligible element
 * @param predicate - The predicate to run against each element
 * @returns The first element in the iterable that satisfies the predicate, or undefined if the iterable contains no such element
 */
function find(iterable, predicate) {
    for (const t of iterable) {
        if (predicate(t)) {
            return t;
        }
    }
}
exports.find = find;
/**
 * Counts the number of elements in the given iterable.
 * @param iterable - the iterable to enumerate
 * @returns the number of elements that were iterated after exhausting the iterable
 */
function count(iterable) {
    let n = 0;
    for (const _ of iterable) {
        n += 1;
    }
    return n;
}
exports.count = count;
/**
 * @remarks TODO: Audit usage of this type in schemas, evaluating whether it is necessary and performance
 * of alternatives.
 *
 * True "arbitrary serializable data" is probably fine, but some persisted types declarations might be better
 * expressed using composition of schemas for runtime validation, even if we don't think making the types
 * generic is worth the maintenance cost.
 */
exports.JsonCompatibleReadOnlySchema = typebox_1.Type.Any();
/**
 * Returns if a particular json compatible value is an object.
 * Does not include `null` or arrays.
 */
function isJsonObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
exports.isJsonObject = isJsonObject;
/**
 * Verifies that the supplied indices are valid within the supplied array.
 * @param startIndex - The starting index in the range. Must be in [0, length).
 * @param endIndex - The ending index in the range. Must be within (start, length].
 * @param array - The array the indices refer to
 */
function assertValidRangeIndices(startIndex, endIndex, array) {
    (0, internal_1.assert)(endIndex >= startIndex, 0x79c /* Range indices are malformed. */);
    assertValidIndex(startIndex, array, false);
    assertValidIndex(endIndex, array, true);
}
exports.assertValidRangeIndices = assertValidRangeIndices;
function assertValidIndex(index, array, allowOnePastEnd = false) {
    assertNonNegativeSafeInteger(index);
    if (allowOnePastEnd) {
        (0, internal_1.assert)(index <= array.length, 0x378 /* index must be less than or equal to length */);
    }
    else {
        (0, internal_1.assert)(index < array.length, 0x379 /* index must be less than length */);
    }
}
exports.assertValidIndex = assertValidIndex;
function assertValidRange({ start, end }, array) {
    assertNonNegativeSafeInteger(start);
    assertNonNegativeSafeInteger(end);
    (0, internal_1.assert)(end <= array.length, 0x79d /* Range end must be less than or equal to length */);
    (0, internal_1.assert)(start <= end, 0x79e /* Range start must be less than or equal to range start */);
}
exports.assertValidRange = assertValidRange;
function assertNonNegativeSafeInteger(index) {
    (0, internal_1.assert)(Number.isSafeInteger(index), 0x376 /* index must be an integer */);
    (0, internal_1.assert)(index >= 0, 0x377 /* index must be non-negative */);
}
exports.assertNonNegativeSafeInteger = assertNonNegativeSafeInteger;
/**
 * Convert an object into a Map.
 *
 * This function must only be used with objects specifically intended to encode map like information.
 * The only time such objects should be used is for encoding maps as object literals to allow for developer ergonomics or JSON compatibility.
 * Even those two use-cases need to be carefully considered as using objects as maps can have a lot of issues
 * (including but not limited to unintended access to __proto__ and other non-owned keys).
 * This function helps these few cases get into using an actual map in as safe of was as is practical.
 */
function objectToMap(objectMap) {
    const map = new Map();
    // This function must only be used with objects specifically intended to encode map like information.
    for (const key of Object.keys(objectMap)) {
        const element = objectMap[key];
        map.set(key, element);
    }
    return map;
}
exports.objectToMap = objectToMap;
/**
 * Convert an object used as a map into a new object used like a map.
 *
 * @remarks
 * This function must only be used with objects specifically intended to encode map like information.
 * The only time such objects should be used is for encoding maps as object literals to allow for developer ergonomics or JSON compatibility.
 * Even those two use-cases need to be carefully considered as using objects as maps can have a lot of issues
 * (including but not limited to unintended access to __proto__ and other non-owned keys).
 * {@link objectToMap} helps these few cases get into using an actual map in as safe of a way as is practical.
 */
function transformObjectMap(objectMap, transformer) {
    const output = Object.create(null);
    // This function must only be used with objects specifically intended to encode map like information.
    for (const key of Object.keys(objectMap)) {
        const element = objectMap[key];
        Object.defineProperty(output, key, {
            enumerable: true,
            configurable: true,
            writable: true,
            value: transformer(element, key),
        });
    }
    return output;
}
exports.transformObjectMap = transformObjectMap;
/**
 * Make an inverted copy of a map.
 *
 * @returns a map which can look up the keys from the values of the original map.
 */
function invertMap(input) {
    const result = new Map(mapIterable(input, ([key, value]) => [value, key]));
    (0, internal_1.assert)(result.size === input.size, 0x88a /* all values in a map must be unique to invert it */);
    return result;
}
exports.invertMap = invertMap;
/**
 * Placeholder for `Symbol.dispose`.
 * @privateRemarks
 * TODO: replace this with `Symbol.dispose` when it is available or make it a valid polyfill.
 */
exports.disposeSymbol = Symbol("Symbol.dispose placeholder");
/**
 * Capitalize a string.
 */
function capitalize(s) {
    // To avoid splitting characters which are made of multiple UTF-16 code units,
    // use iteration instead of indexing to separate the first character.
    const iterated = s[Symbol.iterator]().next();
    if (iterated.done === true) {
        // Empty string case.
        return "";
    }
    return (iterated.value.toUpperCase() + s.slice(iterated.value.length));
}
exports.capitalize = capitalize;
/**
 * Compares two numbers to form a strict total ordering.
 * @remarks NaN is treated as less than all other numbers and equal to itself.
 * Infinity is considered equal to itself.
 */
function compareNumbers(a, b) {
    if (Number.isNaN(a)) {
        return Number.isNaN(b) ? 0 : -1;
    }
    else if (Number.isNaN(b)) {
        return 1;
    }
    else if (a === b) {
        return 0; // Infinity - Infinity = NaN (not 0), so use explicit comparison
    }
    return a - b;
}
exports.compareNumbers = compareNumbers;
/**
 * Compares two numbers to form a strict total ordering while allowing for `undefined` values.
 * @remarks `undefined` is considered less than any number and equal to itself.
 * NaN is treated as less than all other numbers (but greater than undefined) and equal to itself.
 */
function comparePartialNumbers(a, b) {
    if (a === undefined) {
        return b === undefined ? 0 : -1;
    }
    else if (b === undefined) {
        return 1;
    }
    return compareNumbers(a, b);
}
exports.comparePartialNumbers = comparePartialNumbers;
/** Compares two strings lexically to form a strict total ordering. */
function compareStrings(a, b) {
    return a > b ? 1 : a === b ? 0 : -1;
}
exports.compareStrings = compareStrings;
/**
 * Compares two strings lexically to form a strict total ordering while allowing for `undefined` values.
 * @remarks `undefined` is considered less than any string and equal to itself.
 */
function comparePartialStrings(a, b) {
    if (a === undefined) {
        return b === undefined ? 0 : -1;
    }
    else if (b === undefined) {
        return 1;
    }
    return compareStrings(a, b);
}
exports.comparePartialStrings = comparePartialStrings;
/**
 * Defines a property on an object that is lazily initialized and cached.
 * @remarks This is useful for properties that are expensive to compute and it is not guaranteed that they will be accessed.
 * This function initially defines a getter on the object, but after first read it replaces the getter with a value property.
 * @param obj - The object on which to define the property.
 * @param key - The key of the property to define.
 * @param get - The function (called either once or not at all) to compute the value of the property.
 * @returns `obj`, typed such that it has the new property.
 * This allows for the new property to be read off of `obj` in a type-safe manner after calling this function.
 */
function defineLazyCachedProperty(obj, key, get) {
    Reflect.defineProperty(obj, key, {
        get() {
            const value = get();
            Reflect.defineProperty(obj, key, { value });
            return value;
        },
        configurable: true,
    });
    return obj;
}
exports.defineLazyCachedProperty = defineLazyCachedProperty;
function copyPropertyIfDefined(source, property, destination) {
    if (source !== undefined) {
        const value = source[property];
        if (value !== undefined) {
            destination[property] = value;
        }
    }
}
exports.copyPropertyIfDefined = copyPropertyIfDefined;
/**
 * Reduces an array of values into a single value.
 * This is similar to `Array.prototype.reduce`,
 * except that it recursively reduces the left and right halves of the input before reducing their respective reductions.
 *
 * When compared with an approach like reducing all the values left-to-right,
 * this balanced approach is beneficial when the cost of invoking `callbackFn` is proportional to the number reduced values that its parameters collectively represent.
 * For example, if `T` is an array, and `callbackFn` concatenates its inputs,
 * then `balancedReduce` will have O(N*log(N)) time complexity instead of `Array.prototype.reduce`'s O(N²).
 * However, if `callbackFn` is O(1) then both `balancedReduce` and `Array.prototype.reduce` will have O(N) complexity.
 *
 * @param array - The array to reduce.
 * @param callbackFn - The function to execute for each pairwise reduction.
 * @param emptyCase - A factory function that provides the value to return if the input array is empty.
 */
function balancedReduce(array, callbackFn, emptyCase) {
    if (hasSingle(array)) {
        return array[0];
    }
    if (!hasSome(array)) {
        return emptyCase();
    }
    const mid = Math.floor(array.length / 2);
    const left = balancedReduce(array.slice(0, mid), callbackFn, emptyCase);
    const right = balancedReduce(array.slice(mid), callbackFn, emptyCase);
    return callbackFn(left, right);
}
exports.balancedReduce = balancedReduce;
//# sourceMappingURL=utils.js.map