local ____lualib = require("lualib_bundle")
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
local Set = ____lualib.Set
local __TS__Spread = ____lualib.__TS__Spread
local __TS__ArraySome = ____lualib.__TS__ArraySome
local __TS__StringSplit = ____lualib.__TS__StringSplit
local __TS__ArrayFind = ____lualib.__TS__ArrayFind
local __TS__New = ____lualib.__TS__New
local ____exports = {}
local setPrimitiveEntityFields
local ____isaac_2Dtypescript_2Ddefinitions = require("isaac-typescript-definitions")
local EntityFlag = ____isaac_2Dtypescript_2Ddefinitions.EntityFlag
local EntityType = ____isaac_2Dtypescript_2Ddefinitions.EntityType
local MotherVariant = ____isaac_2Dtypescript_2Ddefinitions.MotherVariant
local NPCState = ____isaac_2Dtypescript_2Ddefinitions.NPCState
local UltraGreedState = ____isaac_2Dtypescript_2Ddefinitions.UltraGreedState
local UltraGreedVariant = ____isaac_2Dtypescript_2Ddefinitions.UltraGreedVariant
local UltraGreedierState = ____isaac_2Dtypescript_2Ddefinitions.UltraGreedierState
local ____cachedClasses = require("core.cachedClasses")
local game = ____cachedClasses.game
local ____constants = require("core.constants")
local VectorZero = ____constants.VectorZero
local ____entitiesWithArmorSet = require("sets.entitiesWithArmorSet")
local ENTITIES_WITH_ARMOR_SET = ____entitiesWithArmorSet.ENTITIES_WITH_ARMOR_SET
local ____isaacAPIClass = require("functions.isaacAPIClass")
local getIsaacAPIClassName = ____isaacAPIClass.getIsaacAPIClassName
local ____random = require("functions.random")
local getRandom = ____random.getRandom
local ____readOnly = require("functions.readOnly")
local newReadonlyColor = ____readOnly.newReadonlyColor
local ____rng = require("functions.rng")
local getRandomSeed = ____rng.getRandomSeed
local isRNG = ____rng.isRNG
local newRNG = ____rng.newRNG
local ____sprites = require("functions.sprites")
local setSpriteOpacity = ____sprites.setSpriteOpacity
local ____tstlClass = require("functions.tstlClass")
local isTSTLSet = ____tstlClass.isTSTLSet
local ____types = require("functions.types")
local asNPCState = ____types.asNPCState
local isPrimitive = ____types.isPrimitive
local parseIntSafe = ____types.parseIntSafe
local ____utils = require("functions.utils")
local assertDefined = ____utils.assertDefined
local ____vector = require("functions.vector")
local doesVectorHaveLength = ____vector.doesVectorHaveLength
local isVector = ____vector.isVector
local vectorToString = ____vector.vectorToString
--- Helper function to count the number of entities in room. Use this over the vanilla
-- `Isaac.CountEntities` method to avoid having to specify a spawner and to handle ignoring charmed
-- enemies.
-- 
-- @param entityType Optional. Default is -1, which matches every entity type.
-- @param variant Optional. Default is -1, which matches every variant.
-- @param subType Optional. Default is -1, which matches every sub-type.
-- @param ignoreFriendly Optional. Default is false. Will throw a runtime error if set to true and
-- the `entityType` is equal to -1.
function ____exports.countEntities(self, entityType, variant, subType, ignoreFriendly)
    if entityType == nil then
        entityType = -1
    end
    if variant == nil then
        variant = -1
    end
    if subType == nil then
        subType = -1
    end
    if ignoreFriendly == nil then
        ignoreFriendly = false
    end
    if entityType == -1 then
        local entities = Isaac.GetRoomEntities()
        if not ignoreFriendly then
            return #entities
        end
        local nonFriendlyEntities = __TS__ArrayFilter(
            entities,
            function(____, entity) return not entity:HasEntityFlags(EntityFlag.FRIENDLY) end
        )
        return #nonFriendlyEntities
    end
    if not ignoreFriendly then
        return Isaac.CountEntities(nil, entityType, variant, subType)
    end
    local entities = Isaac.FindByType(
        entityType,
        variant,
        subType,
        false,
        ignoreFriendly
    )
    return #entities
end
--- Helper function to check if one or more of a specific kind of entity is present in the current
-- room. It uses the `countEntities` helper function to determine this.
-- 
-- @param entityType Optional. Default is -1, which matches every entity type.
-- @param variant Optional. Default is -1, which matches every variant.
-- @param subType Optional. Default is -1, which matches every sub-type.
-- @param ignoreFriendly Optional. Default is false.
function ____exports.doesEntityExist(self, entityType, variant, subType, ignoreFriendly)
    if entityType == nil then
        entityType = -1
    end
    if variant == nil then
        variant = -1
    end
    if subType == nil then
        subType = -1
    end
    if ignoreFriendly == nil then
        ignoreFriendly = false
    end
    local count = ____exports.countEntities(
        nil,
        entityType,
        variant,
        subType,
        ignoreFriendly
    )
    return count > 0
end
function setPrimitiveEntityFields(self, entity, metatable, entityFields)
    local propGetTable = metatable.__propget
    assertDefined(nil, propGetTable, "Failed to get the \"__propget\" table for an entity.")
    for key in pairs(propGetTable) do
        local indexKey = key
        local value = entity[indexKey]
        if isPrimitive(nil, value) then
            entityFields[indexKey] = value
        elseif isVector(nil, value) then
            entityFields[indexKey] = vectorToString(nil, value)
        end
    end
end
--- Helper function to remove all of the entities in the supplied array.
-- 
-- @param entities The array of entities to remove.
-- @param cap Optional. If specified, will only remove the given amount of entities.
-- @returns An array of the entities that were removed.
function ____exports.removeEntities(self, entities, cap)
    if #entities == 0 then
        return {}
    end
    local entitiesRemoved = {}
    for ____, entity in ipairs(entities) do
        entity:Remove()
        entitiesRemoved[#entitiesRemoved + 1] = entity
        if cap ~= nil and #entitiesRemoved >= cap then
            break
        end
    end
    return entitiesRemoved
end
--- From DeadInfinity.
local DAMAGE_FLASH_COLOR = newReadonlyColor(
    nil,
    0.5,
    0.5,
    0.5,
    1,
    200 / 255,
    0 / 255,
    0 / 255
)
--- Helper function to check if one or more matching entities exist in the current room. It uses the
-- `doesEntityExist` helper function to determine this.
-- 
-- @param entityTypes An array or set of the entity types that you want to check for. Will return
-- true if any of the provided entity types exist.
-- @param ignoreFriendly Optional. Default is false.
function ____exports.doesAnyEntityExist(self, entityTypes, ignoreFriendly)
    if ignoreFriendly == nil then
        ignoreFriendly = false
    end
    local entityTypesMutable = entityTypes
    local entityTypesArray = isTSTLSet(nil, entityTypesMutable) and ({__TS__Spread(entityTypesMutable:values())}) or entityTypesMutable
    return __TS__ArraySome(
        entityTypesArray,
        function(____, entityType) return ____exports.doesEntityExist(
            nil,
            entityType,
            -1,
            -1,
            ignoreFriendly
        ) end
    )
end
--- Given an array of entities, this helper function returns the closest one to a provided reference
-- entity.
-- 
-- For example:
-- 
-- ```ts
-- const player = Isaac.GetPlayer();
-- const gapers = getEntities(EntityType.GAPER);
-- const closestGaper = getClosestEntityTo(player, gapers);
-- ```
-- 
-- @param referenceEntity The entity that is close by.
-- @param entities The array of entities to look through.
-- @param filterFunc Optional. A function to filter for a specific type of entity, like e.g. an
-- enemy with a certain amount of HP left.
function ____exports.getClosestEntityTo(self, referenceEntity, entities, filterFunc)
    local closestEntity
    local closestDistance = math.huge
    for ____, entity in ipairs(entities) do
        local distance = referenceEntity.Position:Distance(entity.Position)
        if distance < closestDistance and (filterFunc == nil or filterFunc(nil, entity)) then
            closestEntity = entity
            closestDistance = distance
        end
    end
    return closestEntity
end
--- Helper function to get the entity type, variant, and sub-type from an `EntityID`.
function ____exports.getConstituentsFromEntityID(self, entityID)
    local parts = __TS__StringSplit(entityID, ".")
    if #parts ~= 3 then
        error("Failed to get the constituents from entity ID: " .. entityID)
    end
    local entityTypeString, variantString, subTypeString = table.unpack(parts, 1, 3)
    assertDefined(nil, entityTypeString, "Failed to get the first constituent from an entity ID: " .. entityID)
    assertDefined(nil, variantString, "Failed to get the second constituent from an entity ID: " .. entityID)
    assertDefined(nil, subTypeString, "Failed to get the third constituent from an entity ID: " .. entityID)
    local entityType = parseIntSafe(nil, entityTypeString)
    assertDefined(nil, entityType, "Failed to convert the entity type to an integer: " .. entityTypeString)
    local variant = parseIntSafe(nil, variantString)
    assertDefined(nil, variant, "Failed to convert the entity variant to an integer: " .. variantString)
    local subType = parseIntSafe(nil, subTypeString)
    assertDefined(nil, subType, "Failed to convert the entity sub-type to an integer: " .. subTypeString)
    return {entityType, variant, subType}
end
--- Helper function to get all of the entities in the room or all of the entities that match a
-- specific entity type / variant / sub-type.
-- 
-- Due to bugs with `Isaac.FindInRadius`, this function uses `Isaac.GetRoomEntities`, which is more
-- expensive but also more robust. (If a matching entity type is provided, then `Isaac.FindByType`
-- will be used instead.)
-- 
-- For example:
-- 
-- ```ts
-- // Make all of the entities in the room invisible.
-- for (const entity of getEntities()) {
--   entity.Visible = false;
-- }
-- ```
-- 
-- @param entityType Optional. If specified, will only get the entities that match the type. Default
-- is -1, which matches every type.
-- @param variant Optional. If specified, will only get the entities that match the variant. Default
-- is -1, which matches every variant.
-- @param subType Optional. If specified, will only get the entities that match the sub-type.
-- Default is -1, which matches every sub-type.
-- @param ignoreFriendly Optional. If set to true, it will exclude friendly NPCs from being
-- returned. Default is false. Will only be taken into account if the
-- `entityType` is specified.
function ____exports.getEntities(self, entityType, variant, subType, ignoreFriendly)
    if entityType == nil then
        entityType = -1
    end
    if variant == nil then
        variant = -1
    end
    if subType == nil then
        subType = -1
    end
    if ignoreFriendly == nil then
        ignoreFriendly = false
    end
    if entityType == -1 then
        return Isaac.GetRoomEntities()
    end
    return Isaac.FindByType(entityType, variant, subType, ignoreFriendly)
end
--- Helper function to get all the fields on an entity. For example, this is useful for comparing it
-- to another entity later. (One option is to use the `logTableDifferences` function for this.)
-- 
-- This function will only get fields that are equal to booleans, numbers, or strings, or Vectors,
-- as comparing other types is non-trivial.
function ____exports.getEntityFields(self, entity)
    local entityFields = {}
    local metatable = getmetatable(entity)
    assertDefined(nil, metatable, "Failed to get the metatable for an entity.")
    setPrimitiveEntityFields(nil, entity, metatable, entityFields)
    local className = getIsaacAPIClassName(nil, entity)
    if className == "Entity" then
        return entityFields
    end
    local parentTable = metatable.__parent
    assertDefined(nil, parentTable, "Failed to get the \"__parent\" table for an entity.")
    setPrimitiveEntityFields(nil, entity, parentTable, entityFields)
    return entityFields
end
--- Helper function to get an entity from a `PtrHash`. Note that doing this is very expensive, so you
-- should only use this function when debugging. (Normally, if you need to work backwards from a
-- reference, you would use an `EntityPtr` instead of a `PtrHash`.
function ____exports.getEntityFromPtrHash(self, ptrHash)
    local entities = ____exports.getEntities(nil)
    return __TS__ArrayFind(
        entities,
        function(____, entity) return GetPtrHash(entity) == ptrHash end
    )
end
--- Helper function to get a string containing the entity's type, variant, and sub-type.
function ____exports.getEntityID(self, entity)
    return (((tostring(entity.Type) .. ".") .. tostring(entity.Variant)) .. ".") .. tostring(entity.SubType)
end
--- Helper function to get a formatted string in the format returned by the `getEntityID` function.
function ____exports.getEntityIDFromConstituents(self, entityType, variant, subType)
    return (((tostring(entityType) .. ".") .. tostring(variant)) .. ".") .. tostring(subType)
end
--- Helper function to compare two different arrays of entities. Returns the entities that are in the
-- second array but not in the first array.
function ____exports.getFilteredNewEntities(self, oldEntities, newEntities)
    local oldEntitiesSet = __TS__New(Set)
    for ____, entity in ipairs(oldEntities) do
        local ptrHash = GetPtrHash(entity)
        oldEntitiesSet:add(ptrHash)
    end
    return __TS__ArrayFilter(
        newEntities,
        function(____, entity)
            local ptrHash = GetPtrHash(entity)
            return not oldEntitiesSet:has(ptrHash)
        end
    )
end
--- Helper function to see if a particular entity has armor. In this context, armor refers to the
-- damage scaling mechanic. For example, Ultra Greed has armor, but a Gaper does not.
-- 
-- For more on armor, see the wiki: https://bindingofisaacrebirth.fandom.com/wiki/Damage_Scaling
function ____exports.hasArmor(self, entity)
    local typeVariantString = (tostring(entity.Type) .. ".") .. tostring(entity.Variant)
    return ENTITIES_WITH_ARMOR_SET:has(typeVariantString)
end
--- Helper function to detect if a particular entity is an active enemy. Use this over the
-- `Entity.IsActiveEnemy` method since it is bugged with friendly enemies, Grimaces, Ultra Greed,
-- and Mother.
function ____exports.isActiveEnemy(self, entity)
    if entity:HasEntityFlags(EntityFlag.FRIENDLY) then
        return false
    end
    local room = game:GetRoom()
    local isClear = room:IsClear()
    if isClear then
        repeat
            local ____switch36 = entity.Type
            local ____cond36 = ____switch36 == EntityType.GRIMACE
            if ____cond36 then
                do
                    return false
                end
            end
            ____cond36 = ____cond36 or ____switch36 == EntityType.ULTRA_DOOR
            if ____cond36 then
                do
                    return false
                end
            end
            ____cond36 = ____cond36 or ____switch36 == EntityType.ULTRA_GREED
            if ____cond36 then
                do
                    local npc = entity:ToNPC()
                    if npc ~= nil then
                        local ultraGreedVariant = npc.Variant
                        repeat
                            local ____switch41 = ultraGreedVariant
                            local ____cond41 = ____switch41 == UltraGreedVariant.ULTRA_GREED
                            if ____cond41 then
                                do
                                    if npc.State == asNPCState(nil, UltraGreedState.GOLD_STATUE) then
                                        return false
                                    end
                                    break
                                end
                            end
                            ____cond41 = ____cond41 or ____switch41 == UltraGreedVariant.ULTRA_GREEDIER
                            if ____cond41 then
                                do
                                    if npc.State == asNPCState(nil, UltraGreedierState.POST_EXPLOSION) then
                                        return false
                                    end
                                    break
                                end
                            end
                        until true
                    end
                    break
                end
            end
            ____cond36 = ____cond36 or ____switch36 == EntityType.MOTHER
            if ____cond36 then
                do
                    if entity.Variant == MotherVariant.MOTHER_1 then
                        local npc = entity:ToNPC()
                        if npc ~= nil and npc.State == NPCState.SPECIAL then
                            return false
                        end
                    end
                    break
                end
            end
            do
                do
                    break
                end
            end
        until true
    end
    return entity:IsActiveEnemy(false)
end
--- Helper function to measure an entity's velocity to see if it is moving.
-- 
-- Use this helper function over checking if the velocity length is equal to 0 because entities can
-- look like they are completely immobile but yet still have a non zero velocity. Thus, using a
-- threshold is needed.
-- 
-- @param entity The entity whose velocity to measure.
-- @param threshold Optional. The threshold from 0 to consider to be moving. Default is 0.01.
function ____exports.isEntityMoving(self, entity, threshold)
    if threshold == nil then
        threshold = 0.01
    end
    return doesVectorHaveLength(nil, entity.Velocity, threshold)
end
--- Helper function to parse a string that contains an entity type, a variant, and a sub-type,
-- separated by periods.
-- 
-- For example, passing "45.0.1" would return an array of [45, 0, 1].
-- 
-- Returns undefined if the string cannot be parsed.
function ____exports.parseEntityID(self, entityID)
    local entityIDArray = __TS__StringSplit(entityID, ".")
    if #entityIDArray ~= 3 then
        return nil
    end
    local entityTypeString, variantString, subTypeString = table.unpack(entityIDArray, 1, 3)
    if entityTypeString == nil or variantString == nil or subTypeString == nil then
        return nil
    end
    local entityType = parseIntSafe(nil, entityTypeString)
    local variant = parseIntSafe(nil, variantString)
    local subType = parseIntSafe(nil, subTypeString)
    if entityType == nil or variant == nil or subType == nil then
        return nil
    end
    return {entityType, variant, subType}
end
--- Helper function to parse a string that contains an entity type and a variant separated by a
-- period.
-- 
-- For example, passing "45.0" would return an array of [45, 0].
-- 
-- Returns undefined if the string cannot be parsed.
function ____exports.parseEntityTypeVariantString(self, entityTypeVariantString)
    local entityTypeVariantArray = __TS__StringSplit(entityTypeVariantString, ".")
    if #entityTypeVariantArray ~= 2 then
        return nil
    end
    local entityTypeString, variantString = table.unpack(entityTypeVariantArray, 1, 2)
    if entityTypeString == nil or variantString == nil then
        return nil
    end
    local entityType = parseIntSafe(nil, entityTypeString)
    local variant = parseIntSafe(nil, variantString)
    if entityType == nil or variant == nil then
        return nil
    end
    return {entityType, variant}
end
--- Helper function to remove all of the matching entities in the room.
-- 
-- @param entityType The entity type to match.
-- @param entityVariant Optional. The variant to match. Default is -1, which matches every variant.
-- @param entitySubType Optional. The sub-type to match. Default is -1, which matches every
-- sub-type.
-- @param cap Optional. If specified, will only remove the given amount of collectibles.
-- @returns An array of the entities that were removed.
function ____exports.removeAllMatchingEntities(self, entityType, entityVariant, entitySubType, cap)
    if entityVariant == nil then
        entityVariant = -1
    end
    if entitySubType == nil then
        entitySubType = -1
    end
    local entities = ____exports.getEntities(nil, entityType, entityVariant, entitySubType)
    return ____exports.removeEntities(nil, entities, cap)
end
--- Helper function to reroll an enemy. Use this instead of the vanilla "Game.RerollEnemy" function
-- if you want the rerolled enemy to be returned.
-- 
-- @param entity The entity to reroll.
-- @returns If the game failed to reroll the enemy, returns undefined. Otherwise, returns the
-- rerolled entity.
function ____exports.rerollEnemy(self, entity)
    local oldEntities = ____exports.getEntities(nil)
    local wasRerolled = game:RerollEnemy(entity)
    if not wasRerolled then
        return nil
    end
    local newEntities = ____exports.getEntities(nil)
    local filteredNewEntities = ____exports.getFilteredNewEntities(nil, oldEntities, newEntities)
    if #filteredNewEntities == 0 then
        error("Failed to find the new entity generated by the \"Game.RerollEnemy\" method.")
    end
    return filteredNewEntities[1]
end
--- Helper function to make an entity flash red like it is taking damage. This is useful when you
-- want to make it appear as if an entity is taking damage without actually dealing any damage to
-- it.
function ____exports.setEntityDamageFlash(self, entity)
    entity:SetColor(DAMAGE_FLASH_COLOR, 2, 0)
end
--- Helper function to keep an entity's color the same values as it already is but set the opacity to
-- a specific value.
-- 
-- @param entity The entity to set.
-- @param alpha A value between 0 and 1 that represents the fade amount.
function ____exports.setEntityOpacity(self, entity, alpha)
    local sprite = entity:GetSprite()
    setSpriteOpacity(nil, sprite, alpha)
end
function ____exports.setEntityRandomColor(self, entity)
    local seed = entity.InitSeed == 0 and getRandomSeed(nil) or entity.InitSeed
    local rng = newRNG(nil, seed)
    local r = getRandom(nil, rng)
    local g = getRandom(nil, rng)
    local b = getRandom(nil, rng)
    local color = Color(r, g, b)
    entity:SetColor(
        color,
        100000,
        100000,
        false,
        false
    )
end
--- Helper function to spawn an entity. Always use this instead of the `Isaac.Spawn` method, since
-- using that method can crash the game.
-- 
-- Also see the `spawnWithSeed` helper function.
-- 
-- @param entityType The `EntityType` of the entity to spawn.
-- @param variant The variant of the entity to spawn.
-- @param subType The sub-type of the entity to spawn.
-- @param positionOrGridIndex The position or grid index of the entity to spawn.
-- @param velocity Optional. The velocity of the entity to spawn. Default is `VectorZero`.
-- @param spawner Optional. The entity that will be the `SpawnerEntity`. Default is undefined.
-- @param seedOrRNG Optional. The seed or RNG object to use to generate the `InitSeed` of the
-- entity. Default is undefined, which will make the entity spawn with a random
-- seed.
function ____exports.spawn(self, entityType, variant, subType, positionOrGridIndex, velocity, spawner, seedOrRNG)
    if velocity == nil then
        velocity = VectorZero
    end
    local room = game:GetRoom()
    if positionOrGridIndex == nil then
        local entityID = ____exports.getEntityIDFromConstituents(nil, entityType, variant, subType)
        error(("Failed to spawn entity " .. entityID) .. " since an undefined position was passed to the \"spawn\" function.")
    end
    local position = isVector(nil, positionOrGridIndex) and positionOrGridIndex or room:GetGridPosition(positionOrGridIndex)
    if seedOrRNG == nil then
        seedOrRNG = newRNG(nil)
    end
    local seed = isRNG(nil, seedOrRNG) and seedOrRNG:Next() or seedOrRNG
    return game:Spawn(
        entityType,
        variant,
        position,
        velocity,
        spawner,
        subType,
        seed
    )
end
--- Helper function to spawn the entity corresponding to an `EntityID`.
-- 
-- @param entityID The `EntityID` of the entity to spawn.
-- @param positionOrGridIndex The position or grid index of the entity to spawn.
-- @param velocity Optional. The velocity of the entity to spawn. Default is `VectorZero`.
-- @param spawner Optional. The entity that will be the `SpawnerEntity`. Default is undefined.
-- @param seedOrRNG Optional. The seed or RNG object to use to generate the `InitSeed` of the
-- entity. Default is undefined, which will make the entity spawn with a random
-- seed using the `Isaac.Spawn` method.
function ____exports.spawnEntityID(self, entityID, positionOrGridIndex, velocity, spawner, seedOrRNG)
    if velocity == nil then
        velocity = VectorZero
    end
    local entityType, variant, subType = table.unpack(
        ____exports.getConstituentsFromEntityID(nil, entityID),
        1,
        3
    )
    return ____exports.spawn(
        nil,
        entityType,
        variant,
        subType,
        positionOrGridIndex,
        velocity,
        spawner,
        seedOrRNG
    )
end
--- Helper function to spawn an entity. Use this instead of the `Game.Spawn` method if you do not
-- need to specify the velocity or spawner.
function ____exports.spawnWithSeed(self, entityType, variant, subType, positionOrGridIndex, seedOrRNG, velocity, spawner)
    if velocity == nil then
        velocity = VectorZero
    end
    return ____exports.spawn(
        nil,
        entityType,
        variant,
        subType,
        positionOrGridIndex,
        velocity,
        spawner,
        seedOrRNG
    )
end
return ____exports
