local ____lualib = require("lualib_bundle")
local __TS__ArrayEvery = ____lualib.__TS__ArrayEvery
local __TS__New = ____lualib.__TS__New
local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf
local __TS__ArraySplice = ____lualib.__TS__ArraySplice
local __TS__ArrayPushArray = ____lualib.__TS__ArrayPushArray
local __TS__ArrayEntries = ____lualib.__TS__ArrayEntries
local __TS__Iterator = ____lualib.__TS__Iterator
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
local __TS__ArrayMap = ____lualib.__TS__ArrayMap
local __TS__ArrayUnshift = ____lualib.__TS__ArrayUnshift
local __TS__ArraySlice = ____lualib.__TS__ArraySlice
local __TS__SparseArrayNew = ____lualib.__TS__SparseArrayNew
local __TS__SparseArrayPush = ____lualib.__TS__SparseArrayPush
local __TS__SparseArraySpread = ____lualib.__TS__SparseArraySpread
local Set = ____lualib.Set
local __TS__Spread = ____lualib.__TS__Spread
local __TS__ArrayToSorted = ____lualib.__TS__ArrayToSorted
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys
local __TS__ArraySome = ____lualib.__TS__ArraySome
local __TS__ArrayReduce = ____lualib.__TS__ArrayReduce
local ____exports = {}
local addCombinations
local ____ReadonlySet = require("types.ReadonlySet")
local ReadonlySet = ____ReadonlySet.ReadonlySet
local ____random = require("functions.random")
local getRandomInt = ____random.getRandomInt
local ____rng = require("functions.rng")
local isRNG = ____rng.isRNG
local newRNG = ____rng.newRNG
local ____sort = require("functions.sort")
local sortNormal = ____sort.sortNormal
local ____types = require("functions.types")
local isNumber = ____types.isNumber
local isTable = ____types.isTable
local ____utils = require("functions.utils")
local assertDefined = ____utils.assertDefined
local eRange = ____utils.eRange
function addCombinations(self, n, src, got, all)
    if n == 0 then
        if #got > 0 then
            all[#all + 1] = got
        end
        return
    end
    for ____, ____value in __TS__Iterator(__TS__ArrayEntries(src)) do
        local i = ____value[1]
        local element = ____value[2]
        local ____addCombinations_3 = addCombinations
        local ____temp_1 = n - 1
        local ____TS__ArraySlice_result_2 = __TS__ArraySlice(src, i + 1)
        local ____array_0 = __TS__SparseArrayNew(table.unpack(got))
        __TS__SparseArrayPush(____array_0, element)
        ____addCombinations_3(
            nil,
            ____temp_1,
            ____TS__ArraySlice_result_2,
            {__TS__SparseArraySpread(____array_0)},
            all
        )
    end
end
--- Helper function to get a random index from the provided array.
-- 
-- If you want to get an unseeded index, you must explicitly pass `undefined` to the `seedOrRNG`
-- parameter.
-- 
-- @param array The array to get the index 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 indexes that will be skipped over when getting the random
-- index. Default is an empty array.
function ____exports.getRandomArrayIndex(self, array, seedOrRNG, exceptions)
    if exceptions == nil then
        exceptions = {}
    end
    if #array == 0 then
        error("Failed to get a random array index since the provided array is empty.")
    end
    return getRandomInt(
        nil,
        0,
        #array - 1,
        seedOrRNG,
        exceptions
    )
end
--- Shuffles the provided array in-place using the Fisher-Yates algorithm.
-- 
-- If you want an unseeded shuffle, you must explicitly pass `undefined` to the `seedOrRNG`
-- parameter.
-- 
-- From: https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
-- 
-- @param array The array to shuffle.
-- @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.
function ____exports.shuffleArrayInPlace(self, array, seedOrRNG)
    local currentIndex = #array
    local rng = isRNG(nil, seedOrRNG) and seedOrRNG or newRNG(nil, seedOrRNG)
    while currentIndex > 0 do
        currentIndex = currentIndex - 1
        local randomIndex = ____exports.getRandomArrayIndex(nil, array, rng)
        ____exports.swapArrayElements(nil, array, currentIndex, randomIndex)
    end
end
--- Helper function to swap two different array elements. (The elements will be swapped in-place.)
function ____exports.swapArrayElements(self, array, i, j)
    local value1 = array[i + 1]
    local value2 = array[j + 1]
    array[i + 1] = value2
    array[j + 1] = value1
end
--- Helper function for determining if two arrays contain the exact same elements. Note that this
-- only performs a shallow comparison.
function ____exports.arrayEquals(self, array1, array2)
    if #array1 ~= #array2 then
        return false
    end
    return __TS__ArrayEvery(
        array1,
        function(____, array1Element, i)
            local array2Element = array2[i + 1]
            return array1Element == array2Element
        end
    )
end
--- Builds a new array based on the original array without the specified element(s). Returns the new
-- array. If the specified element(s) are not found in the array, it will simply return a shallow
-- copy of the array.
-- 
-- If there is more than one matching element in the array, this function will remove all of them.
-- 
-- This function is variadic, meaning that you can specify N arguments to remove N elements.
function ____exports.arrayRemove(self, originalArray, ...)
    local elementsToRemove = {...}
    local elementsToRemoveSet = __TS__New(ReadonlySet, elementsToRemove)
    local array = {}
    for ____, element in ipairs(originalArray) do
        if not elementsToRemoveSet:has(element) then
            array[#array + 1] = element
        end
    end
    return array
end
--- Removes all of the specified element(s) from the array. If the specified element(s) are not found
-- in the array, this function will do nothing.
-- 
-- This function is variadic, meaning that you can specify N arguments to remove N elements.
-- 
-- If there is more than one matching element in the array, this function will remove every matching
-- element. If you want to only remove the first matching element, use the `arrayRemoveInPlace`
-- function instead.
-- 
-- @returns True if one or more elements were removed, false otherwise.
function ____exports.arrayRemoveAllInPlace(self, array, ...)
    local elementsToRemove = {...}
    local removedOneOrMoreElements = false
    for ____, element in ipairs(elementsToRemove) do
        local index
        repeat
            do
                index = __TS__ArrayIndexOf(array, element)
                if index > -1 then
                    removedOneOrMoreElements = true
                    __TS__ArraySplice(array, index, 1)
                end
            end
        until not (index > -1)
    end
    return removedOneOrMoreElements
end
--- Removes the specified element(s) from the array. If the specified element(s) are not found in the
-- array, this function will do nothing.
-- 
-- This function is variadic, meaning that you can specify N arguments to remove N elements.
-- 
-- If there is more than one matching element in the array, this function will only remove the first
-- matching element. If you want to remove all of the elements, use the `arrayRemoveAllInPlace`
-- function instead.
-- 
-- @returns The removed elements. This will be an empty array if no elements were removed.
function ____exports.arrayRemoveInPlace(self, array, ...)
    local elementsToRemove = {...}
    local removedElements = {}
    for ____, element in ipairs(elementsToRemove) do
        local index = __TS__ArrayIndexOf(array, element)
        if index ~= -1 then
            local removedElement = __TS__ArraySplice(array, index, 1)
            __TS__ArrayPushArray(removedElements, removedElement)
        end
    end
    return removedElements
end
--- Shallow copies and removes the elements at the specified indexes from the array. Returns the
-- copied array. If the specified indexes are not found in the array, it will simply return a
-- shallow copy of the array.
-- 
-- This function is variadic, meaning that you can specify N arguments to remove N elements.
function ____exports.arrayRemoveIndex(self, originalArray, ...)
    local indexesToRemove = {...}
    local indexesToRemoveSet = __TS__New(ReadonlySet, indexesToRemove)
    local array = {}
    for ____, ____value in __TS__Iterator(__TS__ArrayEntries(originalArray)) do
        local i = ____value[1]
        local element = ____value[2]
        if not indexesToRemoveSet:has(i) then
            array[#array + 1] = element
        end
    end
    return array
end
--- Removes the elements at the specified indexes from the array. If the specified indexes are not
-- found in the array, this function will do nothing.
-- 
-- This function is variadic, meaning that you can specify N arguments to remove N elements.
-- 
-- @returns The removed elements. This will be an empty array if no elements were removed.
function ____exports.arrayRemoveIndexInPlace(self, array, ...)
    local indexesToRemove = {...}
    local legalIndexes = __TS__ArrayFilter(
        indexesToRemove,
        function(____, i) return i >= 0 and i < #array end
    )
    if #legalIndexes == 0 then
        return {}
    end
    local legalIndexesSet = __TS__New(ReadonlySet, legalIndexes)
    local removedElements = {}
    do
        local i = #array - 1
        while i >= 0 do
            if legalIndexesSet:has(i) then
                local removedElement = __TS__ArraySplice(array, i, 1)
                __TS__ArrayPushArray(removedElements, removedElement)
            end
            i = i - 1
        end
    end
    return removedElements
end
function ____exports.arrayToString(self, array)
    if #array == 0 then
        return "[]"
    end
    local strings = __TS__ArrayMap(
        array,
        function(____, element) return tostring(element) end
    )
    local commaSeparatedStrings = table.concat(strings, ", ")
    return ("[" .. commaSeparatedStrings) .. "]"
end
--- Helper function to combine two or more arrays. Returns a new array that is the composition of all
-- of the specified arrays.
-- 
-- This function is variadic, meaning that you can specify N arguments to combine N arrays. Note
-- that this will only perform a shallow copy of the array elements.
function ____exports.combineArrays(self, ...)
    local arrays = {...}
    local elements = {}
    for ____, array in ipairs(arrays) do
        for ____, element in ipairs(array) do
            elements[#elements + 1] = element
        end
    end
    return elements
end
--- Helper function to perform a shallow copy.
-- 
-- @param oldArray The array to copy.
-- @param numElements Optional. If specified, will only copy the first N elements. By default, the
-- entire array will be copied.
function ____exports.copyArray(self, oldArray, numElements)
    if numElements == nil then
        return {table.unpack(oldArray)}
    end
    local newArrayWithFirstNElements = {}
    do
        local i = 0
        while i < numElements do
            newArrayWithFirstNElements[#newArrayWithFirstNElements + 1] = oldArray[i + 1]
            i = i + 1
        end
    end
    return newArrayWithFirstNElements
end
--- Helper function to remove all of the elements in an array in-place.
function ____exports.emptyArray(self, array)
    __TS__ArraySplice(array, 0)
end
--- Helper function to perform a filter and a map at the same time. Similar to `Array.map`, provide a
-- function that transforms a value, but return `undefined` if the value should be skipped. (Thus,
-- this function cannot be used in situations where `undefined` can be a valid array element.)
-- 
-- This function is useful because the `Array.map` method will always produce an array with the same
-- amount of elements as the original array.
-- 
-- This is named `filterMap` after the Rust function:
-- https://doc.rust-lang.org/std/iter/struct.FilterMap.html
function ____exports.filterMap(self, array, func)
    local filteredArray = {}
    for ____, element in ipairs(array) do
        local newElement = func(nil, element)
        if newElement ~= nil then
            filteredArray[#filteredArray + 1] = newElement
        end
    end
    return filteredArray
end
--- Helper function to get all possible combinations of the given array. This includes the
-- combination of an empty array.
-- 
-- For example, if this function is provided an array containing 1, 2, and 3, then it will return an
-- array containing the following arrays:
-- 
-- - [] (if `includeEmptyArray` is set to true)
-- - [1]
-- - [2]
-- - [3]
-- - [1, 2]
-- - [1, 3]
-- - [2, 3]
-- - [1, 2, 3]
-- 
-- From: https://github.com/firstandthird/combinations/blob/master/index.js
-- 
-- @param array The array to get the combinations of.
-- @param includeEmptyArray Whether to include an empty array in the combinations.
-- @param min Optional. The minimum number of elements to include in each combination. Default is 1.
-- @param max Optional. The maximum number of elements to include in each combination. Default is
-- the length of the array.
function ____exports.getArrayCombinations(self, array, includeEmptyArray, min, max)
    if min == nil or min <= 0 then
        min = 1
    end
    if max == nil or max <= 0 then
        max = #array
    end
    local all = {}
    do
        local i = min
        while i < #array do
            addCombinations(
                nil,
                i,
                array,
                {},
                all
            )
            i = i + 1
        end
    end
    if #array == max then
        all[#all + 1] = array
    end
    if includeEmptyArray then
        __TS__ArrayUnshift(all, {})
    end
    return all
end
--- Helper function to get the duplicate elements in an array. Only one element for each value will
-- be returned. The elements will be sorted before they are returned.
function ____exports.getArrayDuplicateElements(self, array)
    local duplicateElements = __TS__New(Set)
    local set = __TS__New(Set)
    for ____, element in ipairs(array) do
        if set:has(element) then
            duplicateElements:add(element)
        end
        set:add(element)
    end
    local values = {__TS__Spread(duplicateElements)}
    return __TS__ArrayToSorted(values, sortNormal)
end
--- Helper function to get an array containing the indexes of an array.
-- 
-- For example, an array of `["Apple", "Banana"]` would return an array of `[0, 1]`.
-- 
-- Note that normally, you would use the `Object.keys` method to get the indexes of an array, but
-- due to implementation details of TypeScriptToLua, this results in an array of 1 through N
-- (instead of an array of 0 through N -1).
function ____exports.getArrayIndexes(self, array)
    return eRange(nil, #array)
end
--- Helper function to get the highest value in an array. Returns undefined if there were no elements
-- in the array.
function ____exports.getHighestArrayElement(self, array)
    if #array == 0 then
        return nil
    end
    local highestValue
    for ____, element in ipairs(array) do
        if highestValue == nil or element > highestValue then
            highestValue = element
        end
    end
    return highestValue
end
--- Helper function to get the lowest value in an array. Returns undefined if there were no elements
-- in the array.
function ____exports.getLowestArrayElement(self, array)
    if #array == 0 then
        return nil
    end
    local lowestValue
    for ____, element in ipairs(array) do
        if lowestValue == nil or element < lowestValue then
            lowestValue = element
        end
    end
    return lowestValue
end
--- Helper function to get a random element from the provided array.
-- 
-- If you want to get an unseeded element, you must explicitly pass `undefined` to the `seedOrRNG`
-- parameter.
-- 
-- @param array The array to get an element 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 ____exports.getRandomArrayElement(self, array, seedOrRNG, exceptions)
    if exceptions == nil then
        exceptions = {}
    end
    if #array == 0 then
        error("Failed to get a random array element since the provided array is empty.")
    end
    local arrayToUse = #exceptions > 0 and ____exports.arrayRemove(
        nil,
        array,
        table.unpack(exceptions)
    ) or array
    local randomIndex = ____exports.getRandomArrayIndex(nil, arrayToUse, seedOrRNG)
    local randomElement = arrayToUse[randomIndex + 1]
    assertDefined(
        nil,
        randomElement,
        ("Failed to get a random array element since the random index of " .. tostring(randomIndex)) .. " was not valid."
    )
    return randomElement
end
--- Helper function to get a random element from the provided array. Once the random element is
-- decided, it is then removed from the array (in-place).
-- 
-- If you want to get an unseeded element, you must explicitly pass `undefined` to the `seedOrRNG`
-- parameter.
-- 
-- @param array The array to get an element 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 ____exports.getRandomArrayElementAndRemove(self, array, seedOrRNG, exceptions)
    if exceptions == nil then
        exceptions = {}
    end
    local randomArrayElement = ____exports.getRandomArrayElement(nil, array, seedOrRNG, exceptions)
    ____exports.arrayRemoveInPlace(nil, array, randomArrayElement)
    return randomArrayElement
end
--- Similar to the `Array.includes` method, but works on a widened version of the array.
-- 
-- This is useful when the normal `Array.includes` produces a type error from an array that uses an
-- `as const` assertion.
function ____exports.includes(self, array, searchElement)
    local widenedArray = array
    return __TS__ArrayIncludes(widenedArray, searchElement)
end
--- Since Lua uses tables for every non-primitive data structure, it is non-trivial to determine if a
-- particular table is being used as an array. `isArray` returns true if:
-- 
-- - the table contains all numerical indexes that are contiguous, starting at 1
-- - the table has no keys (i.e. an "empty" table)
-- 
-- @param object The object to analyze.
-- @param ensureContiguousValues Optional. Whether the Lua table has to have all contiguous keys in
-- order to be considered an array. Default is true.
function ____exports.isArray(self, object, ensureContiguousValues)
    if ensureContiguousValues == nil then
        ensureContiguousValues = true
    end
    if not isTable(nil, object) then
        return false
    end
    local metatable = getmetatable(object)
    if metatable ~= nil then
        return false
    end
    local keys = __TS__ObjectKeys(object)
    if #keys == 0 then
        return true
    end
    local hasAllNumberKeys = __TS__ArrayEvery(
        keys,
        function(____, key) return isNumber(nil, key) end
    )
    if not hasAllNumberKeys then
        return false
    end
    if ensureContiguousValues then
        do
            local i = 1
            while i <= #keys do
                local element = object[i]
                if element == nil then
                    return false
                end
                i = i + 1
            end
        end
    end
    return true
end
--- Helper function to see if every element in the array is N + 1.
-- 
-- For example, `[2, 3, 4]` would return true, and `[2, 3, 5]` would return false.
function ____exports.isArrayContiguous(self, array)
    local lastValue
    for ____, element in ipairs(array) do
        if lastValue == nil then
            lastValue = element - 1
        end
        if element ~= lastValue - 1 then
            return false
        end
    end
    return true
end
--- Helper function to check if all the elements of an array are unique within that array.
-- 
-- Under the hood, this is performed by converting the array to a set.
function ____exports.isArrayElementsUnique(self, array)
    local set = __TS__New(Set, array)
    return set.size == #array
end
--- Checks if an array is in the provided 2-dimensional array.
function ____exports.isArrayInArray(self, arrayToMatch, parentArray)
    return __TS__ArraySome(
        parentArray,
        function(____, element) return ____exports.arrayEquals(nil, element, arrayToMatch) end
    )
end
--- Helper function to set every element in an array to a specific value.
function ____exports.setAllArrayElements(self, array, value)
    do
        local i = 0
        while i < #array do
            array[i + 1] = value
            i = i + 1
        end
    end
end
--- Shallow copies and shuffles the array using the Fisher-Yates algorithm. Returns the copied array.
-- 
-- If you want an unseeded shuffle, you must explicitly pass `undefined` to the `seedOrRNG`
-- parameter.
-- 
-- From: https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
-- 
-- @param originalArray The array to shuffle.
-- @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.
function ____exports.shuffleArray(self, originalArray, seedOrRNG)
    local array = ____exports.copyArray(nil, originalArray)
    ____exports.shuffleArrayInPlace(nil, array, seedOrRNG)
    return array
end
--- Helper function to sum every value in an array together.
function ____exports.sumArray(self, array)
    return __TS__ArrayReduce(
        array,
        function(____, accumulator, element) return accumulator + element end,
        0
    )
end
return ____exports
