"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.todo = exports.repeat = exports.isRepentance = exports.isMultiplayer = exports.inRange = exports.iRange = exports.getTraversalDescription = exports.eRange = exports.assertNotNull = exports.assertDefined = void 0;
const ReadonlySet_1 = require("../types/ReadonlySet");
const playerIndex_1 = require("./playerIndex");
const types_1 = require("./types");
/**
 * Helper function to throw an error (using the `error` Lua function) if the provided value is equal
 * to `undefined`.
 *
 * This is useful to have TypeScript narrow a `T | undefined` value to `T` in a concise way.
 */
function assertDefined(value, ...[msg]) {
    if (value === undefined) {
        error(msg);
    }
}
exports.assertDefined = assertDefined;
/**
 * Helper function to throw an error (using the `error` Lua function) if the provided value is equal
 * to `null`.
 *
 * This is useful to have TypeScript narrow a `T | null` value to `T` in a concise way.
 */
function assertNotNull(value, ...[msg]) {
    if (value === null) {
        error(msg);
    }
}
exports.assertNotNull = assertNotNull;
/**
 * Helper function to return an array of integers with the specified range, inclusive on the lower
 * end and exclusive on the high end. (The "e" in the function name stands for exclusive.) Thus,
 * this function works in a similar way as the built-in `range` function from Python.
 *
 * If the end is lower than the start, an empty array will be returned.
 *
 * For example:
 *
 * - `eRange(2)` returns `[0, 1]`.
 * - `eRange(3)` returns `[0, 1, 2]`.
 * - `eRange(-3)` returns `[0, -1, -2]`.
 * - `eRange(1, 3)` returns `[1, 2]`.
 * - `eRange(2, 5)` returns `[2, 3, 4]`.
 * - `eRange(5, 2)` returns `[]`.
 * - `eRange(3, 3)` returns `[]`.
 *
 * @param start The integer to start at.
 * @param end Optional. The integer to end at. If not specified, then the start will be 0 and the
 *            first argument will be the end.
 * @param increment Optional. The increment to use. Default is 1.
 */
function eRange(start, end, increment = 1) {
    if (end === undefined) {
        return eRange(0, start, increment);
    }
    const array = [];
    for (let i = start; i < end; i += increment) {
        array.push(i);
    }
    return array;
}
exports.eRange = eRange;
/**
 * Helper function to log what is happening in functions that recursively move through nested data
 * structures.
 */
function getTraversalDescription(key, traversalDescription) {
    if (traversalDescription !== "") {
        traversalDescription += " --> ";
    }
    traversalDescription += tostring(key);
    return traversalDescription;
}
exports.getTraversalDescription = getTraversalDescription;
/**
 * Helper function to return an array of integers with the specified range, inclusive on both ends.
 * (The "i" in the function name stands for inclusive.)
 *
 * If the end is lower than the start, an empty array will be returned.
 *
 * For example:
 *
 * - `iRange(2)` returns `[0, 1, 2]`.
 * - `iRange(3)` returns `[0, 1, 2, 3]`.
 * - `iRange(-3)` returns `[0, -1, -2, -3]`.
 * - `iRange(1, 3)` returns `[1, 2, 3]`.
 * - `iRange(2, 5)` returns `[2, 3, 4, 5]`.
 * - `iRange(5, 2)` returns `[]`.
 * - `iRange(3, 3)` returns `[3]`.
 *
 * @param start The integer to start at.
 * @param end Optional. The integer to end at. If not specified, then the start will be 0 and the
 *            first argument will be the end.
 * @param increment Optional. The increment to use. Default is 1.
 */
function iRange(start, end, increment = 1) {
    if (end === undefined) {
        return iRange(0, start, increment);
    }
    const exclusiveEnd = end + 1;
    return eRange(start, exclusiveEnd, increment);
}
exports.iRange = iRange;
/**
 * Helper function to check if a variable is within a certain range, inclusive on both ends.
 *
 * - For example, `inRange(1, 1, 3)` will return `true`.
 * - For example, `inRange(0, 1, 3)` will return `false`.
 *
 * @param num The number to check.
 * @param start The start of the range to check.
 * @param end The end of the range to check.
 */
function inRange(num, start, end) {
    return num >= start && num <= end;
}
exports.inRange = inRange;
/**
 * Helper function to detect if there is two or more players currently playing.
 *
 * Specifically, this function looks for unique `ControllerIndex` values across all players.
 *
 * This function is not safe to use in the `POST_PLAYER_INIT` callback, because the
 * `ControllerIndex` will not be set properly. As a workaround, you can use it in the
 * `POST_PLAYER_INIT_FIRST` callback (or some other callback like `POST_UPDATE`).
 */
function isMultiplayer() {
    const players = (0, playerIndex_1.getAllPlayers)();
    const controllerIndexes = players.map((player) => player.ControllerIndex);
    const controllerIndexesSet = new ReadonlySet_1.ReadonlySet(controllerIndexes);
    return controllerIndexesSet.size > 1;
}
exports.isMultiplayer = isMultiplayer;
/**
 * Helper function to check if the player is using Afterbirth+ or Repentance.
 *
 * This function should always be used over the `REPENTANCE` constant, since the latter is not safe.
 *
 * Specifically, this function checks for the `Sprite.GetAnimation` method:
 * https://bindingofisaacrebirth.fandom.com/wiki/V1.06.J818#Lua_Changes
 */
function isRepentance() {
    const metatable = getmetatable(Sprite);
    assertDefined(metatable, "Failed to get the metatable of the Sprite global table.");
    const classTable = metatable.get("__class");
    assertDefined(classTable, 'Failed to get the "__class" key of the Sprite metatable.');
    const getAnimation = classTable.get("GetAnimation");
    return (0, types_1.isFunction)(getAnimation);
}
exports.isRepentance = isRepentance;
/**
 * Helper function to repeat code N times. This is faster to type and cleaner than using a for loop.
 *
 * For example:
 *
 * ```ts
 * const player = Isaac.GetPlayer();
 * repeat(10, () => {
 *   player.AddCollectible(CollectibleType.STEVEN);
 * });
 * ```
 *
 * The repeated function is passed the index of the iteration, if needed:
 *
 * ```ts
 * repeat(3, (i) => {
 *   print(i); // Prints "0", "1", "2"
 * });
 * ```
 */
function repeat(num, func) {
    for (let i = 0; i < num; i++) {
        func(i);
    }
}
exports.repeat = repeat;
/**
 * Helper function to signify that the enclosing code block is not yet complete. Using this function
 * is similar to writing a "TODO" comment, but it has the benefit of preventing ESLint errors due to
 * unused variables or early returns.
 *
 * When you see this function, it simply means that the programmer intends to add in more code to
 * this spot later.
 *
 * This function is variadic, meaning that you can pass as many arguments as you want. (This is
 * useful as a means to prevent unused variables.)
 *
 * This function does not actually do anything. (It is an "empty" function.)
 *
 * @allowEmptyVariadic
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
function todo(...args) { }
exports.todo = todo;
