local ____lualib = require("lualib_bundle")
local Set = ____lualib.Set
local __TS__Spread = ____lualib.__TS__Spread
local __TS__ArrayMap = ____lualib.__TS__ArrayMap
local __TS__ArrayFind = ____lualib.__TS__ArrayFind
local __TS__New = ____lualib.__TS__New
local __TS__Iterator = ____lualib.__TS__Iterator
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries
local __TS__ArraySome = ____lualib.__TS__ArraySome
local ____exports = {}
local ____isaac_2Dtypescript_2Ddefinitions = require("isaac-typescript-definitions")
local DoorSlot = ____isaac_2Dtypescript_2Ddefinitions.DoorSlot
local DoorState = ____isaac_2Dtypescript_2Ddefinitions.DoorState
local DoorVariant = ____isaac_2Dtypescript_2Ddefinitions.DoorVariant
local GridEntityType = ____isaac_2Dtypescript_2Ddefinitions.GridEntityType
local GridRoom = ____isaac_2Dtypescript_2Ddefinitions.GridRoom
local RoomType = ____isaac_2Dtypescript_2Ddefinitions.RoomType
local ____cachedEnumValues = require("cachedEnumValues")
local DOOR_SLOT_FLAG_VALUES = ____cachedEnumValues.DOOR_SLOT_FLAG_VALUES
local DOOR_SLOT_VALUES = ____cachedEnumValues.DOOR_SLOT_VALUES
local ____cachedClasses = require("core.cachedClasses")
local game = ____cachedClasses.game
local ____constants = require("core.constants")
local DISTANCE_OF_GRID_TILE = ____constants.DISTANCE_OF_GRID_TILE
local ____doorSlotFlagToDoorSlot = require("objects.doorSlotFlagToDoorSlot")
local DEFAULT_DOOR_SLOT = ____doorSlotFlagToDoorSlot.DEFAULT_DOOR_SLOT
local DOOR_SLOT_FLAG_TO_DOOR_SLOT = ____doorSlotFlagToDoorSlot.DOOR_SLOT_FLAG_TO_DOOR_SLOT
local ____doorSlotToDirection = require("objects.doorSlotToDirection")
local DOOR_SLOT_TO_DIRECTION = ____doorSlotToDirection.DOOR_SLOT_TO_DIRECTION
local ____doorSlotToDoorSlotFlag = require("objects.doorSlotToDoorSlotFlag")
local DOOR_SLOT_TO_DOOR_SLOT_FLAG = ____doorSlotToDoorSlotFlag.DOOR_SLOT_TO_DOOR_SLOT_FLAG
local ____oppositeDoorSlots = require("objects.oppositeDoorSlots")
local OPPOSITE_DOOR_SLOTS = ____oppositeDoorSlots.OPPOSITE_DOOR_SLOTS
local ____roomShapeToDoorSlotCoordinates = require("objects.roomShapeToDoorSlotCoordinates")
local ROOM_SHAPE_TO_DOOR_SLOT_COORDINATES = ____roomShapeToDoorSlotCoordinates.ROOM_SHAPE_TO_DOOR_SLOT_COORDINATES
local ____roomShapeToDoorSlots = require("objects.roomShapeToDoorSlots")
local ROOM_SHAPE_TO_DOOR_SLOTS = ____roomShapeToDoorSlots.ROOM_SHAPE_TO_DOOR_SLOTS
local ____ReadonlySet = require("types.ReadonlySet")
local ReadonlySet = ____ReadonlySet.ReadonlySet
local ____bitwise = require("functions.bitwise")
local arrayToBitFlags = ____bitwise.arrayToBitFlags
local ____direction = require("functions.direction")
local directionToVector = ____direction.directionToVector
local ____enums = require("functions.enums")
local isEnumValue = ____enums.isEnumValue
local ____flag = require("functions.flag")
local hasFlag = ____flag.hasFlag
local ____tstlClass = require("functions.tstlClass")
local isTSTLSet = ____tstlClass.isTSTLSet
local ____types = require("functions.types")
local parseIntSafe = ____types.parseIntSafe
function ____exports.doorSlotToDirection(self, doorSlot)
    return DOOR_SLOT_TO_DIRECTION[doorSlot]
end
--- Helper function to get the offset from a door position that a player will enter a room at.
-- 
-- When players enter a room, they do not appear exactly on the location of the door, because then
-- they would immediately collide with the loading zone. Instead, they appear on the grid tile next
-- to the door.
function ____exports.getDoorSlotEnterPositionOffset(self, doorSlot)
    local direction = ____exports.doorSlotToDirection(nil, doorSlot)
    local vector = directionToVector(nil, direction)
    local oppositeVector = vector * -1
    return oppositeVector * DISTANCE_OF_GRID_TILE
end
--- Helper function to get the possible door slots that can exist for a given room shape.
function ____exports.getDoorSlotsForRoomShape(self, roomShape)
    return ROOM_SHAPE_TO_DOOR_SLOTS[roomShape]
end
--- Helper function to get all of the doors in the room. By default, it will return every door.
-- 
-- You can optionally specify one or more room types to return only the doors that match the
-- specified room types.
-- 
-- @allowEmptyVariadic
function ____exports.getDoors(self, ...)
    local roomTypes = {...}
    local room = game:GetRoom()
    local roomShape = room:GetRoomShape()
    local roomTypesSet = __TS__New(ReadonlySet, roomTypes)
    local possibleDoorSlots = ____exports.getDoorSlotsForRoomShape(nil, roomShape)
    local doors = {}
    for ____, doorSlot in __TS__Iterator(possibleDoorSlots) do
        do
            local door = room:GetDoor(doorSlot)
            if door == nil then
                goto __continue27
            end
            local gridEntityType = door:GetType()
            if gridEntityType ~= GridEntityType.DOOR then
                goto __continue27
            end
            if roomTypesSet.size == 0 or roomTypesSet:has(door.TargetRoomType) then
                doors[#doors + 1] = door
            end
        end
        ::__continue27::
    end
    return doors
end
--- Helper function to check if the provided door is the one that leads to the off-grid room that
-- contains the hole to the Blue Womb. (In vanilla, the door will only appear in the It Lives Boss
-- Room.)
function ____exports.isBlueWombDoor(self, door)
    return door.TargetRoomIndex == GridRoom.BLUE_WOMB
end
--- Helper function to check if the provided door is the one that leads to the Boss Rush room. (In
-- vanilla, the door will only appear in the Boss Room of the sixth floor.)
function ____exports.isBossRushDoor(self, door)
    return door.TargetRoomIndex == GridRoom.BOSS_RUSH
end
--- Helper function to check if the provided door is the one that leads to the Mega Satan Boss Room.
-- (In vanilla, the door will only appear in the starting room of The Chest / Dark Room.)
function ____exports.isMegaSatanDoor(self, door)
    return door.TargetRoomIndex == GridRoom.MEGA_SATAN
end
--- Helper function to check if the provided door leads to the "secret exit" off-grid room that takes
-- you to the Repentance floor.
function ____exports.isRepentanceDoor(self, door)
    return door.TargetRoomIndex == GridRoom.SECRET_EXIT
end
--- This refers to the hole in the wall that appears after bombing the entrance to a secret room.
-- Note that the door still exists before it has been bombed open. It has a sprite filename of
-- "gfx/grid/door_08_holeinwall.anm2".
-- 
-- Note that since Ultra Secret Rooms do not use holes, this function will not detect an Ultra
-- Secret Room door.
function ____exports.isSecretRoomDoor(self, door)
    local sprite = door:GetSprite()
    local fileName = sprite:GetFilename()
    return string.lower(fileName) == "gfx/grid/door_08_holeinwall.anm2"
end
--- Helper function to check if the provided door is the one that leads to the off-grid room that
-- contains the portal to The Void. (In vanilla, the door will only appear in the Hush Boss Room.)
function ____exports.isVoidDoor(self, door)
    return door.TargetRoomIndex == GridRoom.VOID
end
--- Helper function to remove a single door.
function ____exports.removeDoor(self, door)
    local room = game:GetRoom()
    room:RemoveDoor(door.Slot)
end
--- Helper function to remove the doors provided.
-- 
-- This function is variadic, meaning that you can specify as many doors as you want to remove.
function ____exports.removeDoors(self, ...)
    local doors = {...}
    for ____, door in ipairs(doors) do
        ____exports.removeDoor(nil, door)
    end
end
function ____exports.closeAllDoors(self)
    for ____, door in ipairs(____exports.getDoors(nil)) do
        door:Close(true)
    end
end
--- Use this instead of the `GridEntityDoor.Close` method if you want the door to immediately close
-- without an animation.
function ____exports.closeDoorFast(self, door)
    door.State = DoorState.CLOSED
    local sprite = door:GetSprite()
    sprite:Play("Closed", true)
end
function ____exports.doorSlotFlagToDoorSlot(self, doorSlotFlag)
    local doorSlot = DOOR_SLOT_FLAG_TO_DOOR_SLOT[doorSlotFlag]
    return doorSlot or DEFAULT_DOOR_SLOT
end
function ____exports.doorSlotFlagsToDoorSlots(self, doorSlotFlags)
    local doorSlots = {}
    for ____, doorSlotFlag in ipairs(DOOR_SLOT_FLAG_VALUES) do
        if hasFlag(nil, doorSlotFlags, doorSlotFlag) then
            local doorSlot = ____exports.doorSlotFlagToDoorSlot(nil, doorSlotFlag)
            doorSlots[#doorSlots + 1] = doorSlot
        end
    end
    return doorSlots
end
function ____exports.doorSlotToDoorSlotFlag(self, doorSlot)
    return DOOR_SLOT_TO_DOOR_SLOT_FLAG[doorSlot]
end
--- Helper function to convert an array of door slots or a set of door slots to the resulting bit
-- flag number.
function ____exports.doorSlotsToDoorSlotFlags(self, doorSlots)
    local doorSlotsMutable = doorSlots
    local doorSlotArray = isTSTLSet(nil, doorSlotsMutable) and ({__TS__Spread(doorSlotsMutable:values())}) or doorSlotsMutable
    local doorSlotFlagArray = __TS__ArrayMap(
        doorSlotArray,
        function(____, doorSlot) return ____exports.doorSlotToDoorSlotFlag(nil, doorSlot) end
    )
    return arrayToBitFlags(nil, doorSlotFlagArray)
end
function ____exports.getAngelRoomDoor(self)
    local angelRoomDoors = ____exports.getDoors(nil, RoomType.ANGEL)
    local ____temp_0
    if #angelRoomDoors == 0 then
        ____temp_0 = nil
    else
        ____temp_0 = angelRoomDoors[1]
    end
    return ____temp_0
end
--- Helper function to get the door that leads to the off-grid room that contains the hole to the
-- Blue Womb. (In vanilla, the door will only appear in the It Lives Boss Room.)
-- 
-- Returns undefined if the room has no Blue Womb doors.
function ____exports.getBlueWombDoor(self)
    local doors = ____exports.getDoors(nil)
    return __TS__ArrayFind(
        doors,
        function(____, door) return ____exports.isBlueWombDoor(nil, door) end
    )
end
--- Helper function to get the door that leads to the Boss Rush. (In vanilla, the door will only
-- appear in the Boss Room of the sixth floor.)
-- 
-- Returns undefined if the room has no Boss Rush doors.
function ____exports.getBossRushDoor(self)
    local doors = ____exports.getDoors(nil)
    return __TS__ArrayFind(
        doors,
        function(____, door) return ____exports.isBossRushDoor(nil, door) end
    )
end
function ____exports.getDevilRoomDoor(self)
    local devilRoomDoors = ____exports.getDoors(nil, RoomType.DEVIL)
    local ____temp_1
    if #devilRoomDoors == 0 then
        ____temp_1 = nil
    else
        ____temp_1 = devilRoomDoors[1]
    end
    return ____temp_1
end
--- If there is both a Devil Room and an Angel Room door, this function will return door with the
-- lowest slot number.
function ____exports.getDevilRoomOrAngelRoomDoor(self)
    local devilRoomOrAngelRoomDoors = ____exports.getDoors(nil, RoomType.DEVIL, RoomType.ANGEL)
    local ____temp_2
    if #devilRoomOrAngelRoomDoors == 0 then
        ____temp_2 = nil
    else
        ____temp_2 = devilRoomOrAngelRoomDoors[1]
    end
    return ____temp_2
end
--- Helper function to get the position that a player will enter a room at corresponding to a door.
-- 
-- When players enter a room, they do not appear exactly on the location of the door, because then
-- they would immediately collide with the loading zone. Instead, they appear on the grid tile next
-- to the door.
function ____exports.getDoorEnterPosition(self, door)
    local offset = ____exports.getDoorSlotEnterPositionOffset(nil, door.Slot)
    return door.Position + offset
end
--- Helper function to get the position that a player will enter a room at corresponding to a door
-- slot.
-- 
-- When players enter a room, they do not appear exactly on the location of the door, because then
-- they would immediately collide with the loading zone. Instead, they appear on the grid tile next
-- to the door.
function ____exports.getDoorSlotEnterPosition(self, doorSlot)
    local room = game:GetRoom()
    local position = room:GetDoorSlotPosition(doorSlot)
    local offset = ____exports.getDoorSlotEnterPositionOffset(nil, doorSlot)
    return position + offset
end
--- Helper function to get all of the doors in the room that lead to the provided room index.
-- 
-- This function is variadic, meaning that you can specify N arguments to return all of the doors
-- that match any of the N room grid indexes.
function ____exports.getDoorsToRoomIndex(self, ...)
    local roomGridIndex = {...}
    local roomGridIndexesSet = __TS__New(ReadonlySet, roomGridIndex)
    local doors = ____exports.getDoors(nil)
    return __TS__ArrayFilter(
        doors,
        function(____, door) return roomGridIndexesSet:has(door.TargetRoomIndex) end
    )
end
--- Helper function to get the door that leads to the Mega Satan Boss Room. (In vanilla, the door
-- will only appear in the starting room of The Chest / Dark Room.)
-- 
-- Returns undefined if the room has no Mega Satan doors.
function ____exports.getMegaSatanDoor(self)
    local doors = ____exports.getDoors(nil)
    return __TS__ArrayFind(
        doors,
        function(____, door) return ____exports.isMegaSatanDoor(nil, door) end
    )
end
function ____exports.getOppositeDoorSlot(self, doorSlot)
    return OPPOSITE_DOOR_SLOTS[doorSlot]
end
--- Helper function to get the door that leads to the "secret exit" off-grid room that takes you to
-- the Repentance floor or to the version of Depths 2 that has Dad's Key.
-- 
-- Returns undefined if the room has no Repentance doors.
function ____exports.getRepentanceDoor(self)
    local doors = ____exports.getDoors(nil)
    return __TS__ArrayFind(
        doors,
        function(____, door) return ____exports.isRepentanceDoor(nil, door) end
    )
end
--- Helper function to get the corresponding door slot for a given room shape and grid coordinates.
function ____exports.getRoomShapeDoorSlot(self, roomShape, x, y)
    local doorSlotCoordinates = ROOM_SHAPE_TO_DOOR_SLOT_COORDINATES[roomShape]
    for ____, ____value in ipairs(__TS__ObjectEntries(doorSlotCoordinates)) do
        local doorSlotString = ____value[1]
        local coordinates = ____value[2]
        do
            local doorSlot = parseIntSafe(nil, doorSlotString)
            if doorSlot == nil or not isEnumValue(nil, doorSlot, DoorSlot) then
                goto __continue40
            end
            local doorX, doorY = table.unpack(coordinates, 1, 2)
            if x == doorX and y == doorY then
                return doorSlot
            end
        end
        ::__continue40::
    end
    return nil
end
--- Helper function to get the room grid coordinates for a specific room shape and door slot
-- combination.
function ____exports.getRoomShapeDoorSlotCoordinates(self, roomShape, doorSlot)
    local doorSlotCoordinates = ROOM_SHAPE_TO_DOOR_SLOT_COORDINATES[roomShape]
    return doorSlotCoordinates[doorSlot]
end
--- Helper function to find unused door slots in the current room that can be used to make custom
-- doors.
function ____exports.getUnusedDoorSlots(self)
    local room = game:GetRoom()
    return __TS__ArrayFilter(
        DOOR_SLOT_VALUES,
        function(____, doorSlot) return doorSlot ~= DoorSlot.NO_DOOR_SLOT and room:IsDoorSlotAllowed(doorSlot) and room:GetDoor(doorSlot) == nil end
    )
end
--- Helper function to get the door that leads to the off-grid room that contains the portal to The
-- Void. (In vanilla, the door will only appear in the Hush Boss Room.)
-- 
-- Returns undefined if the room has no Void doors.
function ____exports.getVoidDoor(self)
    local doors = ____exports.getDoors(nil)
    return __TS__ArrayFind(
        doors,
        function(____, door) return ____exports.isVoidDoor(nil, door) end
    )
end
--- Helper function to check if the current room has one or more doors that lead to the given room
-- type.
-- 
-- This function is variadic, meaning that you can supply as many door types as you want to check
-- for. This function will return true if one or more room types match.
function ____exports.hasDoorType(self, ...)
    local roomTypes = {...}
    local doors = ____exports.getDoors(nil)
    local doorsOfThisRoomType = __TS__ArrayFilter(
        doors,
        function(____, door) return __TS__ArraySome(
            roomTypes,
            function(____, roomType) return door:IsRoomType(roomType) end
        ) end
    )
    return #doorsOfThisRoomType > 0
end
--- Helper function to check if the current room has one or more open door slots that can be used to
-- make custom doors.
function ____exports.hasUnusedDoorSlot(self)
    local unusedDoorSlots = ____exports.getUnusedDoorSlots(nil)
    return #unusedDoorSlots > 0
end
function ____exports.isAngelRoomDoor(self, door)
    return door.TargetRoomType == RoomType.ANGEL
end
function ____exports.isDevilRoomDoor(self, door)
    return door.TargetRoomType == RoomType.DEVIL
end
--- Helper function to see if a door slot could exist for a given room shape.
function ____exports.isDoorSlotInRoomShape(self, doorSlot, roomShape)
    local doorSlots = ____exports.getDoorSlotsForRoomShape(nil, roomShape)
    return doorSlots:has(doorSlot)
end
--- This refers to the Repentance door that spawns in a boss room after defeating the boss. You have
-- to spend one key to open it. It has a sprite filename of "gfx/grid/door_downpour.anm2".
function ____exports.isDoorToDownpour(self, door)
    if not ____exports.isRepentanceDoor(nil, door) then
        return false
    end
    local sprite = door:GetSprite()
    local fileName = sprite:GetFilename()
    return string.lower(fileName) == "gfx/grid/door_downpour.anm2"
end
--- This refers to the Repentance door that spawns in a boss room after defeating the boss. You have
-- to spend two hearts to open it. It has a sprite filename of "gfx/grid/door_mausoleum.anm2".
function ____exports.isDoorToMausoleum(self, door)
    if not ____exports.isRepentanceDoor(nil, door) then
        return false
    end
    local sprite = door:GetSprite()
    local fileName = sprite:GetFilename()
    return string.lower(fileName) == "gfx/grid/door_mausoleum.anm2"
end
--- This refers to the "strange door" located on the first room of Depths 2. You open it with either
-- a Polaroid or a Negative. It has a sprite filename of "gfx/grid/door_mausoleum_alt.anm2".
function ____exports.isDoorToMausoleumAscent(self, door)
    if not ____exports.isRepentanceDoor(nil, door) then
        return false
    end
    local sprite = door:GetSprite()
    local fileName = sprite:GetFilename()
    return string.lower(fileName) == "gfx/grid/door_mausoleum_alt.anm2"
end
--- This refers to the Repentance door that spawns in a boss room after defeating the boss. You have
-- to spend two bombs to open it. It has a sprite filename of "gfx/grid/door_mines.anm2".
function ____exports.isDoorToMines(self, door)
    if not ____exports.isRepentanceDoor(nil, door) then
        return false
    end
    local sprite = door:GetSprite()
    local fileName = sprite:GetFilename()
    return string.lower(fileName) == "gfx/grid/door_mines.anm2"
end
--- This refers to the Repentance door that spawns after defeating Mom. You open it with the
-- completed knife. It has a sprite filename of "gfx/grid/door_momsheart.anm2".
function ____exports.isDoorToMomsHeart(self, door)
    if not ____exports.isRepentanceDoor(nil, door) then
        return false
    end
    local sprite = door:GetSprite()
    local fileName = sprite:GetFilename()
    return string.lower(fileName) == "gfx/grid/door_momsheart.anm2"
end
function ____exports.isHiddenSecretRoomDoor(self, door)
    local sprite = door:GetSprite()
    local animation = sprite:GetAnimation()
    return ____exports.isSecretRoomDoor(nil, door) and animation == "Hidden"
end
--- Helper function to reset an unlocked door back to a locked state. Doing this is non-trivial
-- because in addition to calling the `GridEntityDoor.SetLocked` method, you must also:
-- 
-- - Set the `VisitedCount` of the room's `RoomDescription` to 0.
-- - Set the variant to `DoorVariant.DOOR_LOCKED`.
-- - Close the door.
function ____exports.lockDoor(self, door)
    local level = game:GetLevel()
    local roomDescriptor = level:GetRoomByIdx(door.TargetRoomIndex)
    roomDescriptor.VisitedCount = 0
    door:SetVariant(DoorVariant.LOCKED)
    door:SetLocked(true)
    door:Close(true)
end
--- For the purposes of this function, doors to Secret Rooms or Super Secret Rooms that have not been
-- discovered yet will not be opened.
function ____exports.openAllDoors(self)
    for ____, door in ipairs(____exports.getDoors(nil)) do
        door:Open()
    end
end
--- Use this instead of the `GridEntityDoor.Open` method if you want the door to immediately open
-- without an animation.
function ____exports.openDoorFast(self, door)
    door.State = DoorState.OPEN
    local sprite = door:GetSprite()
    sprite:Play("Opened", true)
end
--- Helper function to remove all of the doors in the room. By default, it will remove every door.
-- You can optionally specify one or more room types to remove only the doors that match the
-- specified room types.
-- 
-- @returns The number of doors removed.
-- @allowEmptyVariadic
function ____exports.removeAllDoors(self, ...)
    local doors = ____exports.getDoors(nil, ...)
    ____exports.removeDoors(
        nil,
        table.unpack(doors)
    )
    return #doors
end
return ____exports
