"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.capitalize = exports.disposeSymbol = exports.oneFromSet = exports.objectToMap = exports.generateStableId = exports.useDeterministicStableId = exports.assertNonNegativeSafeInteger = exports.assertValidIndex = exports.isJsonObject = exports.JsonCompatibleReadOnlySchema = exports.zipIterables = exports.mapIterable = exports.getOrAddEmptyToMap = exports.getOrCreate = exports.compareSets = exports.makeArray = exports.isReadonlyArray = exports.fail = exports.clone = exports.asMutable = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const typebox_1 = require("@sinclair/typebox");
const structured_clone_1 = __importDefault(require("@ungap/structured-clone"));
const container_runtime_1 = require("@fluidframework/container-runtime");
/**
 * 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;
exports.clone = structured_clone_1.default;
/**
 * @public
 */
function fail(message) {
    throw new Error(message);
}
exports.fail = fail;
/**
 * 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;
/**
 * 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 (aExtra && !aExtra(item)) {
                return false;
            }
        }
        else {
            if (same && !same(item)) {
                return false;
            }
        }
    }
    for (const item of b.keys()) {
        if (!a.has(item)) {
            if (bExtra && !bExtra(item)) {
                return false;
            }
        }
    }
    return true;
}
exports.compareSets = compareSets;
/**
 * 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;
/**
 * Returns an iterable of tuples containing pairs of elements from the given iterables
 * @param iterableA - an iterable to zip together with `iterableB`
 * @param iterableB - an iterable to zip together with `iterableA`
 * @returns in iterable of tuples of elements zipped together from `iterableA` and `iterableB`.
 * If the input iterables are of different lengths, then the extra elements in the longer will be ignored.
 */
function* zipIterables(iterableA, iterableB) {
    const iteratorA = iterableA[Symbol.iterator]();
    const iteratorB = iterableB[Symbol.iterator]();
    for (let nextA = iteratorA.next(), nextB = iteratorB.next(); nextA.done !== true && nextB.done !== true; nextA = iteratorA.next(), nextB = iteratorB.next()) {
        yield [nextA.value, nextB.value];
    }
}
exports.zipIterables = zipIterables;
/**
 * @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;
function assertValidIndex(index, array, allowOnePastEnd = false) {
    assertNonNegativeSafeInteger(index);
    if (allowOnePastEnd) {
        (0, core_utils_1.assert)(index <= array.length, 0x378 /* index must be less than or equal to length */);
    }
    else {
        (0, core_utils_1.assert)(index < array.length, 0x379 /* index must be less than length */);
    }
}
exports.assertValidIndex = assertValidIndex;
function assertNonNegativeSafeInteger(index) {
    (0, core_utils_1.assert)(Number.isSafeInteger(index), 0x376 /* index must be an integer */);
    (0, core_utils_1.assert)(index >= 0, 0x377 /* index must be non-negative */);
}
exports.assertNonNegativeSafeInteger = assertNonNegativeSafeInteger;
/**
 * The counter used to generate deterministic stable ids for testing purposes.
 */
let deterministicStableIdCount;
/**
 * Runs `f` with {@link generateStableId} altered to return sequential StableIds starting as a fixed seed.
 * Used to make test logic that uses {@link generateStableId} deterministic.
 *
 * @remarks Only use this function for testing purposes.
 *
 * @example
 *
 * ```typescript
 * function f() {
 *    const id = generateStableId();
 *    ...
 * }
 * const result = useDeterministicStableId(f());
 * ```
 */
function useDeterministicStableId(f) {
    (0, core_utils_1.assert)(deterministicStableIdCount === undefined, 0x6ce /* useDeterministicStableId cannot be nested */);
    deterministicStableIdCount = 1;
    try {
        return f();
        // Since this is intended to be used by tests, and test runners often recover from exceptions to run more tests,
        // clean this up with a finally block to reduce risk of breaking unrelated tests after a failure.
    }
    finally {
        deterministicStableIdCount = undefined;
    }
}
exports.useDeterministicStableId = useDeterministicStableId;
/**
 * Generates a random StableId.
 *
 * For test usage desiring deterministic results, see {@link useDeterministicStableId}.
 */
function generateStableId() {
    if (deterministicStableIdCount !== undefined) {
        (0, core_utils_1.assert)(deterministicStableIdCount < 281474976710656, 0x6cf /* The maximum valid value for deterministicStableIdCount is 16^12 */);
        // Tried to generate a unique id prefixing it with the word 'beef'
        return (0, container_runtime_1.assertIsStableId)(`beefbeef-beef-4000-8000-${(deterministicStableIdCount++)
            .toString(16)
            .padStart(12, "0")}`);
    }
    return (0, container_runtime_1.generateStableId)();
}
exports.generateStableId = generateStableId;
/**
 * 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;
/**
 * Returns the value from `set` if it contains exactly one item, otherwise `undefined`.
 */
function oneFromSet(set) {
    if (set === undefined) {
        return undefined;
    }
    if (set.size !== 1) {
        return undefined;
    }
    for (const item of set) {
        return item;
    }
}
exports.oneFromSet = oneFromSet;
/**
 * Placeholder for `Symbol.dispose`.
 *
 * Replace this with `Symbol.dispose` when it is available.
 */
exports.disposeSymbol = Symbol("Symbol.dispose");
/**
 * 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;
//# sourceMappingURL=utils.js.map