/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils";
import { Type } from "@sinclair/typebox";
import structuredClone from "@ungap/structured-clone";
import { generateStableId as runtimeGenerateStableId, assertIsStableId, } from "@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.
 */
export function asMutable(readonly) {
    return readonly;
}
export const clone = structuredClone;
/**
 * @alpha
 */
export function fail(message) {
    throw new Error(message);
}
/**
 * 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.
 */
export 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);
}
/**
 * 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
 */
export function makeArray(size, filler) {
    const array = [];
    for (let i = 0; i < size; ++i) {
        array.push(filler(i));
    }
    return array;
}
/**
 * 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.
 */
export 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;
}
/**
 * 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`)
 */
export function getOrCreate(map, key, defaultValue) {
    let value = map.get(key);
    if (value === undefined) {
        value = defaultValue(key);
        map.set(key, value);
    }
    return value;
}
/**
 * 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.
 */
export function getOrAddEmptyToMap(map, key) {
    let collection = map.get(key);
    if (collection === undefined) {
        collection = [];
        map.set(key, collection);
    }
    return collection;
}
/**
 * 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
 */
export function* mapIterable(iterable, map) {
    for (const t of iterable) {
        yield map(t);
    }
}
/**
 * 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.
 */
export 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];
    }
}
/**
 * @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.
 */
export const JsonCompatibleReadOnlySchema = Type.Any();
/**
 * Returns if a particular json compatible value is an object.
 * Does not include `null` or arrays.
 */
export function isJsonObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
/**
 * 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
 */
export function assertValidRangeIndices(startIndex, endIndex, array) {
    assert(endIndex >= startIndex, 0x79c /* Range indices are malformed. */);
    assertValidIndex(startIndex, array, false);
    assertValidIndex(endIndex, array, true);
}
export function assertValidIndex(index, array, allowOnePastEnd = false) {
    assertNonNegativeSafeInteger(index);
    if (allowOnePastEnd) {
        assert(index <= array.length, 0x378 /* index must be less than or equal to length */);
    }
    else {
        assert(index < array.length, 0x379 /* index must be less than length */);
    }
}
export function assertValidRange({ start, end }, array) {
    assertNonNegativeSafeInteger(start);
    assertNonNegativeSafeInteger(end);
    assert(end <= array.length, 0x79d /* Range end must be less than or equal to length */);
    assert(start <= end, 0x79e /* Range start must be less than or equal to range start */);
}
export function assertNonNegativeSafeInteger(index) {
    assert(Number.isSafeInteger(index), 0x376 /* index must be an integer */);
    assert(index >= 0, 0x377 /* index must be non-negative */);
}
/**
 * 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());
 * ```
 */
export function useDeterministicStableId(f) {
    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;
    }
}
export async function useAsyncDeterministicStableId(f) {
    assert(deterministicStableIdCount === undefined, 0x79f /* useAsyncDeterministicStableId cannot be nested */);
    deterministicStableIdCount = 1;
    try {
        return await 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;
    }
}
/**
 * Generates a random StableId.
 *
 * For test usage desiring deterministic results, see {@link useDeterministicStableId}.
 */
export function generateStableId() {
    if (deterministicStableIdCount !== undefined) {
        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 assertIsStableId(`beefbeef-beef-4000-8000-${(deterministicStableIdCount++)
            .toString(16)
            .padStart(12, "0")}`);
    }
    return runtimeGenerateStableId();
}
/**
 * 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.
 */
export 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;
}
/**
 * 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.
 */
export 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;
}
/**
 * Returns the value from `set` if it contains exactly one item, otherwise `undefined`.
 * @alpha
 */
export function oneFromSet(set) {
    if (set === undefined) {
        return undefined;
    }
    if (set.size !== 1) {
        return undefined;
    }
    for (const item of set) {
        return item;
    }
}
/**
 * Placeholder for `Symbol.dispose`.
 *
 * Replace this with `Symbol.dispose` when it is available.
 * @beta
 */
export const disposeSymbol = Symbol("Symbol.dispose placeholder");
/**
 * Capitalize a string.
 */
export 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));
}
//# sourceMappingURL=utils.js.map