local ____lualib = require("lualib_bundle")
local __TS__ArraySome = ____lualib.__TS__ArraySome
local __TS__New = ____lualib.__TS__New
local __TS__ArrayMap = ____lualib.__TS__ArrayMap
local __TS__ArrayAt = ____lualib.__TS__ArrayAt
local __TS__ArrayFind = ____lualib.__TS__ArrayFind
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
local ____exports = {}
local isTaintedModded
local ____isaac_2Dtypescript_2Ddefinitions = require("isaac-typescript-definitions")
local Challenge = ____isaac_2Dtypescript_2Ddefinitions.Challenge
local CollectibleType = ____isaac_2Dtypescript_2Ddefinitions.CollectibleType
local ControllerIndex = ____isaac_2Dtypescript_2Ddefinitions.ControllerIndex
local NullItemID = ____isaac_2Dtypescript_2Ddefinitions.NullItemID
local PlayerForm = ____isaac_2Dtypescript_2Ddefinitions.PlayerForm
local PlayerType = ____isaac_2Dtypescript_2Ddefinitions.PlayerType
local TearFlag = ____isaac_2Dtypescript_2Ddefinitions.TearFlag
local ____cachedClasses = require("core.cachedClasses")
local game = ____cachedClasses.game
local ____ReadonlySet = require("types.ReadonlySet")
local ReadonlySet = ____ReadonlySet.ReadonlySet
local ____characters = require("functions.characters")
local getCharacterName = ____characters.getCharacterName
local isVanillaCharacter = ____characters.isVanillaCharacter
local ____flag = require("functions.flag")
local hasFlag = ____flag.hasFlag
local ____playerIndex = require("functions.playerIndex")
local getAllPlayers = ____playerIndex.getAllPlayers
local getPlayerIndexVanilla = ____playerIndex.getPlayerIndexVanilla
local getPlayers = ____playerIndex.getPlayers
local ____types = require("functions.types")
local isNumber = ____types.isNumber
local ____utils = require("functions.utils")
local assertDefined = ____utils.assertDefined
local ____repeat = ____utils["repeat"]
--- Helper function to get an array containing the characters of all of the current players.
function ____exports.getCharacters(self)
    local players = getPlayers(nil)
    return __TS__ArrayMap(
        players,
        function(____, player) return player:GetPlayerType() end
    )
end
function ____exports.isModdedPlayer(self, player)
    return not ____exports.isVanillaPlayer(nil, player)
end
function ____exports.isVanillaPlayer(self, player)
    local character = player:GetPlayerType()
    return isVanillaCharacter(nil, character)
end
function isTaintedModded(self, player)
    local character = player:GetPlayerType()
    local name = player:GetName()
    local taintedCharacter = Isaac.GetPlayerTypeByName(name, true)
    return character == taintedCharacter
end
--- Helper function to check to see if any player is holding up an item (from e.g. an active item
-- activation, a poop from IBS, etc.).
function ____exports.anyPlayerHoldingItem(self)
    local players = getAllPlayers(nil)
    return __TS__ArraySome(
        players,
        function(____, player) return player:IsHoldingItem() end
    )
end
--- Helper function to determine if the given character is present.
-- 
-- This function is variadic, meaning that you can supply as many characters as you want to check
-- for. Returns true if any of the characters supplied are present.
function ____exports.anyPlayerIs(self, ...)
    local matchingCharacters = {...}
    local matchingCharacterSet = __TS__New(ReadonlySet, matchingCharacters)
    local characters = ____exports.getCharacters(nil)
    return __TS__ArraySome(
        characters,
        function(____, character) return matchingCharacterSet:has(character) end
    )
end
--- Helper function to determine if a player will destroy a rock/pot/skull if they walk over it.
-- 
-- The following situations allow for this to be true:
-- - the player has Leo (collectible 302)
-- - the player has Thunder Thighs (collectible 314)
-- - the player is under the effects of Mega Mush (collectible 625)
-- - the player has Stompy (transformation 13)
function ____exports.canPlayerCrushRocks(self, player)
    local effects = player:GetEffects()
    return player:HasCollectible(CollectibleType.LEO) or player:HasCollectible(CollectibleType.THUNDER_THIGHS) or effects:HasCollectibleEffect(CollectibleType.MEGA_MUSH) or player:HasPlayerForm(PlayerForm.STOMPY)
end
--- Helper function to remove a collectible or trinket that is currently queued to go into a player's
-- inventory (i.e. the item is being held over their head).
-- 
-- If the player does not have an item currently queued, then this function will be a no-op.
-- 
-- Returns whether an item was actually dequeued.
-- 
-- Under the hood, this clones the `QueuedItemData`, since directly setting the `Item` field to
-- `undefined` does not work for some reason.
function ____exports.dequeueItem(self, player)
    if player.QueuedItem.Item == nil then
        return false
    end
    local queue = player.QueuedItem
    queue.Item = nil
    player.QueuedItem = queue
    return true
end
--- Helper function to get how long Azazel's Brimstone laser should be. You can pass either an
-- `EntityPlayer` object or a tear height stat.
-- 
-- The formula for calculating it is: 32 - 2.5 * tearHeight
function ____exports.getAzazelBrimstoneDistance(self, playerOrTearHeight)
    local tearHeight = isNumber(nil, playerOrTearHeight) and playerOrTearHeight or playerOrTearHeight.TearHeight
    return 32 - 2.5 * tearHeight
end
--- Helper function to get the closest player to a certain position. Note that this will never
-- include players with a non-undefined parent, since they are not real players (e.g. the Strawman
-- Keeper).
function ____exports.getClosestPlayer(self, position)
    local closestPlayer
    local closestDistance = math.huge
    for ____, player in ipairs(getPlayers(nil)) do
        local distance = position:Distance(player.Position)
        if distance < closestDistance then
            closestPlayer = player
            closestDistance = distance
        end
    end
    assertDefined(nil, closestPlayer, "Failed to find the closest player.")
    return closestPlayer
end
--- Helper function to return the player with the highest ID, according to the `Isaac.GetPlayer`
-- method.
function ____exports.getFinalPlayer(self)
    local players = getPlayers(nil)
    local lastPlayer = __TS__ArrayAt(players, -1)
    assertDefined(nil, lastPlayer, "Failed to get the final player since there were 0 players.")
    return lastPlayer
end
--- Helper function to get the first player with the lowest frame count. Useful to find a freshly
-- spawned player after using items like Esau Jr. Don't use this function if two or more players
-- will be spawned on the same frame.
function ____exports.getNewestPlayer(self)
    local newestPlayer
    local lowestFrame = math.huge
    for ____, player in ipairs(getPlayers(nil)) do
        if player.FrameCount < lowestFrame then
            newestPlayer = player
            lowestFrame = player.FrameCount
        end
    end
    assertDefined(nil, newestPlayer, "Failed to find the newest player.")
    return newestPlayer
end
--- Iterates over all players and checks if any are close enough to the specified position.
-- 
-- @returns The first player found when iterating upwards from index 0.
function ____exports.getPlayerCloserThan(self, position, distance)
    local players = getPlayers(nil)
    return __TS__ArrayFind(
        players,
        function(____, player) return player.Position:Distance(position) <= distance end
    )
end
--- Helper function to get the player from a tear, laser, bomb, etc. Returns undefined if the entity
-- does not correspond to any particular player.
-- 
-- This function works by looking at the `Parent` and the `SpawnerEntity` fields (in that order). As
-- a last resort, it will attempt to use the `Entity.ToPlayer` method on the entity itself.
function ____exports.getPlayerFromEntity(self, entity)
    if entity.Parent ~= nil then
        local player = entity.Parent:ToPlayer()
        if player ~= nil then
            return player
        end
        local familiar = entity.Parent:ToFamiliar()
        if familiar ~= nil then
            return familiar.Player
        end
    end
    if entity.SpawnerEntity ~= nil then
        local player = entity.SpawnerEntity:ToPlayer()
        if player ~= nil then
            return player
        end
        local familiar = entity.SpawnerEntity:ToFamiliar()
        if familiar ~= nil then
            return familiar.Player
        end
    end
    return entity:ToPlayer()
end
--- Helper function to get an `EntityPlayer` object from an `EntityPtr`. Returns undefined if the
-- entity has gone out of scope or if the associated entity is not a player.
function ____exports.getPlayerFromPtr(self, entityPtr)
    local entity = entityPtr.Ref
    if entity == nil then
        return nil
    end
    return entity:ToPlayer()
end
--- Helper function to get the proper name of the player. Use this instead of the
-- `EntityPlayer.GetName` method because it accounts for Blue Baby, Lazarus II, and Tainted
-- characters.
function ____exports.getPlayerName(self, player)
    local character = player:GetPlayerType()
    return ____exports.isModdedPlayer(nil, player) and player:GetName() or getCharacterName(nil, character)
end
--- Returns the combined value of all of the player's red hearts, soul/black hearts, and bone hearts,
-- minus the value of the player's rotten hearts.
-- 
-- This is equivalent to the number of hits that the player can currently take, but does not take
-- into account double damage from champion enemies and/or being on later floors. (For example, on
-- Womb 1, players who have 1 soul heart remaining would die in 1 hit to anything, even though this
-- function would report that they have 2 hits remaining.)
function ____exports.getPlayerNumHitsRemaining(self, player)
    local hearts = player:GetHearts()
    local soulHearts = player:GetSoulHearts()
    local boneHearts = player:GetBoneHearts()
    local eternalHearts = player:GetEternalHearts()
    local rottenHearts = player:GetRottenHearts()
    return hearts + soulHearts + boneHearts + eternalHearts - rottenHearts
end
--- Helper function to get all of the players that are a certain character.
-- 
-- This function is variadic, meaning that you can supply as many characters as you want to check
-- for. Returns true if any of the characters supplied are present.
function ____exports.getPlayersOfType(self, ...)
    local characters = {...}
    local charactersSet = __TS__New(ReadonlySet, characters)
    local players = getPlayers(nil)
    return __TS__ArrayFilter(
        players,
        function(____, player)
            local character = player:GetPlayerType()
            return charactersSet:has(character)
        end
    )
end
--- Helper function to get all of the players that are using keyboard (i.e.
-- `ControllerIndex.KEYBOARD`). This function returns an array of players because it is possible
-- that there is more than one player with the same controller index (e.g. Jacob & Esau).
-- 
-- Note that this function includes players with a non-undefined parent like e.g. the Strawman
-- Keeper.
function ____exports.getPlayersOnKeyboard(self)
    local players = getAllPlayers(nil)
    return __TS__ArrayFilter(
        players,
        function(____, player) return player.ControllerIndex == ControllerIndex.KEYBOARD end
    )
end
--- Helper function to get all of the players that match the provided controller index. This function
-- returns an array of players because it is possible that there is more than one player with the
-- same controller index (e.g. Jacob & Esau).
-- 
-- Note that this function includes players with a non-undefined parent like e.g. the Strawman
-- Keeper.
function ____exports.getPlayersWithControllerIndex(self, controllerIndex)
    local players = getAllPlayers(nil)
    return __TS__ArrayFilter(
        players,
        function(____, player) return player.ControllerIndex == controllerIndex end
    )
end
--- Helper function to check to see if a player has one or more transformations.
-- 
-- This function is variadic, meaning that you can supply as many transformations as you want to
-- check for. Returns true if the player has any of the supplied transformations.
function ____exports.hasForm(self, player, ...)
    local playerForms = {...}
    return __TS__ArraySome(
        playerForms,
        function(____, playerForm) return player:HasPlayerForm(playerForm) end
    )
end
--- Helper function to check if a player has homing tears.
-- 
-- Under the hood, this checks the `EntityPlayer.TearFlags` variable for `TearFlag.HOMING` (1 << 2).
function ____exports.hasHoming(self, player)
    return hasFlag(nil, player.TearFlags, TearFlag.HOMING)
end
--- After touching a white fire, a player will turn into The Lost until they clear a room.
function ____exports.hasLostCurse(self, player)
    local effects = player:GetEffects()
    return effects:HasNullEffect(NullItemID.LOST_CURSE)
end
--- Helper function to check if a player has piercing tears.
-- 
-- Under the hood, this checks the `EntityPlayer.TearFlags` variable for `TearFlag.PIERCING` (1 <<
-- 1).
function ____exports.hasPiercing(self, player)
    return hasFlag(nil, player.TearFlags, TearFlag.PIERCING)
end
--- Helper function to check if a player has spectral tears.
-- 
-- Under the hood, this checks the `EntityPlayer.TearFlags` variable for `TearFlag.SPECTRAL` (1 <<
-- 0).
function ____exports.hasSpectral(self, player)
    return hasFlag(nil, player.TearFlags, TearFlag.SPECTRAL)
end
--- Helper function for detecting when a player is Bethany or Tainted Bethany. This is useful if you
-- need to adjust UI elements to account for Bethany's soul charges or Tainted Bethany's blood
-- charges.
function ____exports.isBethany(self, player)
    local character = player:GetPlayerType()
    return character == PlayerType.BETHANY or character == PlayerType.BETHANY_B
end
--- Helper function to check if a player is a specific character (i.e. `PlayerType`).
-- 
-- This function is variadic, meaning that you can supply as many characters as you want to check
-- for. Returns true if the player is any of the supplied characters.
function ____exports.isCharacter(self, player, ...)
    local characters = {...}
    local characterSet = __TS__New(ReadonlySet, characters)
    local character = player:GetPlayerType()
    return characterSet:has(character)
end
--- Helper function to see if a damage source is from a player. Use this instead of comparing to the
-- entity directly because it takes familiars into account.
function ____exports.isDamageFromPlayer(self, damageSource)
    local player = damageSource:ToPlayer()
    if player ~= nil then
        return true
    end
    local indirectPlayer = ____exports.getPlayerFromEntity(nil, damageSource)
    return indirectPlayer ~= nil
end
--- Helper function for detecting when a player is Eden or Tainted Eden. Useful for situations where
-- you want to know if the starting stats were randomized, for example.
function ____exports.isEden(self, player)
    local character = player:GetPlayerType()
    return character == PlayerType.EDEN or character == PlayerType.EDEN_B
end
function ____exports.isFirstPlayer(self, player)
    return getPlayerIndexVanilla(nil, player) == 0
end
--- Helper function for detecting when a player is Jacob or Esau. This will only match the
-- non-tainted versions of these characters.
function ____exports.isJacobOrEsau(self, player)
    local character = player:GetPlayerType()
    return character == PlayerType.JACOB or character == PlayerType.ESAU
end
--- Helper function for detecting when a player is Keeper or Tainted Keeper. Useful for situations
-- where you want to know if the health is coin hearts, for example.
function ____exports.isKeeper(self, player)
    local character = player:GetPlayerType()
    return character == PlayerType.KEEPER or character == PlayerType.KEEPER_B
end
--- Helper function for detecting when a player is The Lost or Tainted Lost.
function ____exports.isLost(self, player)
    local character = player:GetPlayerType()
    return character == PlayerType.LOST or character == PlayerType.LOST_B
end
--- Helper function for determining if a player is able to turn their head by pressing the shooting
-- buttons.
-- 
-- Under the hood, this function uses the `EntityPlayer.IsExtraAnimationFinished` method.
function ____exports.isPlayerAbleToAim(self, player)
    return player:IsExtraAnimationFinished()
end
--- Helper function for detecting if a player is one of the Tainted characters.
function ____exports.isTainted(self, player)
    local character = player:GetPlayerType()
    local ____isVanillaPlayer_result_0
    if ____exports.isVanillaPlayer(nil, player) then
        ____isVanillaPlayer_result_0 = character >= PlayerType.ISAAC_B
    else
        ____isVanillaPlayer_result_0 = isTaintedModded(nil, player)
    end
    return ____isVanillaPlayer_result_0
end
--- Helper function for detecting when a player is Tainted Lazarus or Dead Tainted Lazarus.
function ____exports.isTaintedLazarus(self, player)
    local character = player:GetPlayerType()
    return character == PlayerType.LAZARUS_B or character == PlayerType.LAZARUS_2_B
end
--- Helper function to remove the Dead Eye multiplier from a player.
-- 
-- Note that each time the `EntityPlayer.ClearDeadEyeCharge` method is called, it only has a chance
-- of working, so this function calls it 100 times to be safe.
function ____exports.removeDeadEyeMultiplier(self, player)
    ____repeat(
        nil,
        100,
        function()
            player:ClearDeadEyeCharge()
        end
    )
end
--- Helper function to blindfold the player by using a hack with the challenge variable.
-- 
-- Note that if the player dies and respawns (from e.g. Dead Cat), the blindfold will have to be
-- reapplied.
-- 
-- Under the hood, this function sets the challenge to one with a blindfold, changes the player to
-- the same character that they currently are, and then changes the challenge back. This method was
-- discovered by im_tem.
-- 
-- @param player The player to apply or remove the blindfold state from.
-- @param enabled Whether to apply or remove the blindfold.
-- @param modifyCostume Optional. Whether to add or remove the blindfold costume. Default is true.
function ____exports.setBlindfold(self, player, enabled, modifyCostume)
    if modifyCostume == nil then
        modifyCostume = true
    end
    local character = player:GetPlayerType()
    local challenge = Isaac.GetChallenge()
    if enabled then
        game.Challenge = Challenge.SOLAR_SYSTEM
        player:ChangePlayerType(character)
        game.Challenge = challenge
        if not modifyCostume then
            player:TryRemoveNullCostume(NullItemID.BLINDFOLD)
        end
    else
        game.Challenge = Challenge.NULL
        player:ChangePlayerType(character)
        game.Challenge = challenge
        if modifyCostume then
            player:TryRemoveNullCostume(NullItemID.BLINDFOLD)
        end
    end
end
return ____exports
