"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateInterfaceMatchesEnum = exports.validateEnumContiguous = exports.validateCustomEnum = exports.isEnumValue = exports.getRandomEnumValue = exports.getLowestEnumValue = exports.getHighestEnumValue = exports.getEnumValues = exports.getEnumNames = exports.getEnumLength = exports.getEnumKeys = exports.getEnumEntries = void 0;
const ReadonlySet_1 = require("../types/ReadonlySet");
const array_1 = require("./array");
const types_1 = require("./types");
const utils_1 = require("./utils");
/**
 * TypeScriptToLua will transpile TypeScript number enums to Lua tables that have a double mapping.
 * Thus, when you iterate over them, you will get both the names of the enums and the values of the
 * enums, in a random order. Use this helper function to get the entries of the enum with the
 * reverse mappings filtered out.
 *
 * This function will return the enum values in a sorted order, which may not necessarily be the
 * same order as which they were declared in. (It is impossible to get the declaration order at
 * run-time.)
 *
 * This function will work properly for both number enums and string enums. (Reverse mappings are
 * not created for string enums.)
 *
 * Also see the `getEnumKeys` and `getEnumValues` helper functions.
 *
 * For a more in depth explanation, see:
 * https://isaacscript.github.io/main/gotchas#iterating-over-enums
 */
function getEnumEntries(transpiledEnum) {
    // The values cannot simply be type `T` due to the special construction of bit flag enums.
    const enumEntries = [];
    for (const [key, value] of pairs(transpiledEnum)) {
        // Ignore the reverse mappings created by TypeScriptToLua. Note that reverse mappings are not
        // created for string enums.
        if ((0, types_1.isString)(key)) {
            enumEntries.push([key, value]);
        }
    }
    // The enums will be in a random order (because of "pairs"), so sort them based on the values.
    // https://stackoverflow.com/questions/5199901/how-to-sort-an-associative-array-by-its-values-in-javascript
    enumEntries.sort(([_key1, value1], [_key2, value2]) => value1 < value2 ? -1 : value1 > value2 ? 1 : 0);
    return enumEntries;
}
exports.getEnumEntries = getEnumEntries;
/**
 * TypeScriptToLua will transpile TypeScript number enums to Lua tables that have a double mapping.
 * Thus, when you iterate over them, you will get both the names of the enums and the values of the
 * enums, in a random order. If all you need are the keys of an enum, use this helper function.
 *
 * This function will return the enum keys in a sorted order, which may not necessarily be the same
 * order as which they were declared in. (It is impossible to get the declaration order at
 * run-time.)
 *
 * This function will work properly for both number enums and string enums. (Reverse mappings are
 * not created for string enums.)
 *
 * Also see the `getEnumEntries` and `getEnumValues` helper functions.
 *
 * For a more in depth explanation, see:
 * https://isaacscript.github.io/main/gotchas#iterating-over-enums
 */
function getEnumKeys(transpiledEnum) {
    const enumEntries = getEnumEntries(transpiledEnum);
    return enumEntries.map(([key, _value]) => key);
}
exports.getEnumKeys = getEnumKeys;
/** Helper function to get the amount of entries inside of an enum. */
function getEnumLength(transpiledEnum) {
    const enumEntries = getEnumEntries(transpiledEnum);
    return enumEntries.length;
}
exports.getEnumLength = getEnumLength;
/**
 * TypeScriptToLua will transpile TypeScript number enums to Lua tables that have a double mapping.
 * Thus, when you iterate over them, you will get both the names of the enums and the values of the
 * enums, in a random order. If all you need are the names of an enum from the reverse mapping, use
 * this helper function.
 *
 * This function will return the enum names in a sorted order, which may not necessarily be the same
 * order as which they were declared in. (It is impossible to get the declaration order at
 * run-time.)
 *
 * This function will work properly for both number enums and string enums. (Reverse mappings are
 * not created for string enums, so their names would be equivalent to what would be returned by the
 * `getEnumKeys` function.)
 *
 * For a more in depth explanation, see:
 * https://isaacscript.github.io/main/gotchas#iterating-over-enums
 */
function getEnumNames(transpiledEnum) {
    const enumNames = [];
    for (const [key, _value] of pairs(transpiledEnum)) {
        if ((0, types_1.isString)(key)) {
            enumNames.push(key);
        }
    }
    // The enum names will be in a random order (because of "pairs"), so sort them.
    enumNames.sort();
    return enumNames;
}
exports.getEnumNames = getEnumNames;
/**
 * TypeScriptToLua will transpile TypeScript number enums to Lua tables that have a double mapping.
 * Thus, when you iterate over them, you will get both the names of the enums and the values of the
 * enums, in a random order. If all you need are the values of an enum, use this helper function.
 *
 * This function will return the enum values in a sorted order, which may not necessarily be the
 * same order as which they were declared in. (It is impossible to get the declaration order at
 * run-time.)
 *
 * This function will work properly for both number enums and string enums. (Reverse mappings are
 * not created for string enums.)
 *
 * Also see the `getEnumEntries` and `getEnumKeys` helper functions.
 *
 * For a more in depth explanation, see:
 * https://isaacscript.github.io/main/gotchas#iterating-over-enums
 */
function getEnumValues(transpiledEnum) {
    const enumEntries = getEnumEntries(transpiledEnum);
    return enumEntries.map(([_key, value]) => value);
}
exports.getEnumValues = getEnumValues;
/**
 * Helper function to get the enum value with the highest value.
 *
 * Note that this is not necessarily the enum value that is declared last in the code, since there
 * is no way to infer that at run-time.
 *
 * Throws an error if the provided enum is empty.
 */
function getHighestEnumValue(transpiledEnum) {
    const enumValues = getEnumValues(transpiledEnum);
    const lastElement = enumValues.at(-1);
    (0, utils_1.assertDefined)(lastElement, "Failed to get the last value from an enum since the enum was empty.");
    return lastElement;
}
exports.getHighestEnumValue = getHighestEnumValue;
/**
 * Helper function to get the enum value with the lowest value.
 *
 * Note that this is not necessarily the enum value that is declared first in the code, since there
 * is no way to infer that at run-time.
 *
 * Throws an error if the provided enum is empty.
 */
function getLowestEnumValue(transpiledEnum) {
    const enumValues = getEnumValues(transpiledEnum);
    const firstElement = enumValues[0];
    (0, utils_1.assertDefined)(firstElement, "Failed to get the first value from an enum since the enum was empty.");
    return firstElement;
}
exports.getLowestEnumValue = getLowestEnumValue;
/**
 * Helper function to get a random value from the provided enum.
 *
 * If you want an unseeded value, you must explicitly pass `undefined` to the `seedOrRNG` parameter.
 *
 * @param transpiledEnum The enum to get the value from.
 * @param seedOrRNG The `Seed` or `RNG` object to use. If an `RNG` object is provided, the
 *                  `RNG.Next` method will be called. If `undefined` is provided, it will default to
 *                  a random seed.
 * @param exceptions Optional. An array of elements to skip over if selected.
 */
function getRandomEnumValue(transpiledEnum, seedOrRNG, exceptions = []) {
    const enumValues = getEnumValues(transpiledEnum);
    return (0, array_1.getRandomArrayElement)(enumValues, seedOrRNG, exceptions);
}
exports.getRandomEnumValue = getRandomEnumValue;
/** Helper function to validate that a particular value exists inside of an enum. */
function isEnumValue(value, transpiledEnum) {
    const enumValues = getEnumValues(transpiledEnum);
    return enumValues.includes(value);
}
exports.isEnumValue = isEnumValue;
/**
 * Helper function to check every value of a custom enum for -1. Will throw an run-time error if any
 * -1 values are found. This is helpful because many methods of the Isaac class return -1 if they
 * fail.
 *
 * For example:
 *
 * ```ts
 * enum EntityTypeCustom {
 *   FOO = Isaac.GetEntityTypeByName("Foo"),
 * }
 *
 * validateCustomEnum("EntityTypeCustom", EntityTypeCustom);
 * ```
 */
function validateCustomEnum(transpiledEnumName, transpiledEnum) {
    for (const [key, value] of getEnumEntries(transpiledEnum)) {
        if (value === -1) {
            error(`Failed to find the custom enum value: ${transpiledEnumName}.${key}`);
        }
    }
}
exports.validateCustomEnum = validateCustomEnum;
/**
 * Helper function to validate if every value in a number enum is contiguous, starting at 0.
 *
 * This is useful to automate checking large enums for typos.
 */
function validateEnumContiguous(transpiledEnumName, transpiledEnum) {
    const values = getEnumValues(transpiledEnum);
    const lastValue = values.at(-1);
    (0, utils_1.assertDefined)(lastValue, "Failed to validate that an enum was contiguous, since the last value was undefined.");
    if (!(0, types_1.isNumber)(lastValue)) {
        error("Failed to validate that an enum was contiguous, since the last value was not a number.");
    }
    const valuesSet = new ReadonlySet_1.ReadonlySet(values);
    for (const value of (0, utils_1.iRange)(lastValue)) {
        if (!valuesSet.has(value)) {
            error(`Failed to find a custom enum value of ${value} for: ${transpiledEnumName}`);
        }
    }
}
exports.validateEnumContiguous = validateEnumContiguous;
/**
 * Helper function to validate that an interface contains all of the keys of an enum. You must
 * specify both generic parameters in order for this to work properly (i.e. the interface and then
 * the enum).
 *
 * For example:
 *
 * ```ts
 * enum MyEnum {
 *   Value1,
 *   Value2,
 *   Value3,
 * }
 *
 * interface MyEnumToType {
 *   [MyEnum.Value1]: boolean;
 *   [MyEnum.Value2]: number;
 *   [MyEnum.Value3]: string;
 * }
 *
 * validateInterfaceMatchesEnum<MyEnumToType, MyEnum>();
 * ```
 *
 * This function is only meant to be used with interfaces (i.e. types that will not exist at
 * run-time). If you are generating an object that will contain all of the keys of an enum, use the
 * `satisfies` operator with the `Record` type instead.
 */
function validateInterfaceMatchesEnum() { } // eslint-disable-line @typescript-eslint/no-empty-function
exports.validateInterfaceMatchesEnum = validateInterfaceMatchesEnum;
