local ____lualib = require("lualib_bundle")
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
local __TS__ArrayMap = ____lualib.__TS__ArrayMap
local __TS__Iterator = ____lualib.__TS__Iterator
local Map = ____lualib.Map
local __TS__New = ____lualib.__TS__New
local Set = ____lualib.Set
local ____exports = {}
local ADJACENT_ROOM_GRID_INDEX_DELTAS
local ____isaac_2Dtypescript_2Ddefinitions = require("isaac-typescript-definitions")
local DisplayFlag = ____isaac_2Dtypescript_2Ddefinitions.DisplayFlag
local LevelStateFlag = ____isaac_2Dtypescript_2Ddefinitions.LevelStateFlag
local RoomDescriptorFlag = ____isaac_2Dtypescript_2Ddefinitions.RoomDescriptorFlag
local RoomType = ____isaac_2Dtypescript_2Ddefinitions.RoomType
local ____cachedClasses = require("core.cachedClasses")
local game = ____cachedClasses.game
local ____constants = require("core.constants")
local ALL_DISPLAY_FLAGS = ____constants.ALL_DISPLAY_FLAGS
local LEVEL_GRID_ROW_WIDTH = ____constants.LEVEL_GRID_ROW_WIDTH
local MAX_LEVEL_GRID_INDEX = ____constants.MAX_LEVEL_GRID_INDEX
local ____roomShapeToDoorSlotsToGridIndexDelta = require("objects.roomShapeToDoorSlotsToGridIndexDelta")
local ROOM_SHAPE_TO_DOOR_SLOTS_TO_GRID_INDEX_DELTA = ____roomShapeToDoorSlotsToGridIndexDelta.ROOM_SHAPE_TO_DOOR_SLOTS_TO_GRID_INDEX_DELTA
local ____array = require("functions.array")
local getRandomArrayElement = ____array.getRandomArrayElement
local ____doors = require("functions.doors")
local doorSlotToDoorSlotFlag = ____doors.doorSlotToDoorSlotFlag
local ____flag = require("functions.flag")
local addFlag = ____flag.addFlag
local hasFlag = ____flag.hasFlag
local removeFlag = ____flag.removeFlag
local ____map = require("functions.map")
local copyMap = ____map.copyMap
local ____rng = require("functions.rng")
local isRNG = ____rng.isRNG
local newRNG = ____rng.newRNG
local ____roomData = require("functions.roomData")
local getRoomAllowedDoors = ____roomData.getRoomAllowedDoors
local getRoomData = ____roomData.getRoomData
local getRoomDescriptor = ____roomData.getRoomDescriptor
local getRoomGridIndex = ____roomData.getRoomGridIndex
local getRoomShape = ____roomData.getRoomShape
local ____roomShape = require("functions.roomShape")
local getGridIndexDelta = ____roomShape.getGridIndexDelta
local ____rooms = require("functions.rooms")
local getRooms = ____rooms.getRooms
local getRoomsInsideGrid = ____rooms.getRoomsInsideGrid
local isMineShaft = ____rooms.isMineShaft
local isMirrorRoom = ____rooms.isMirrorRoom
local isSecretRoomType = ____rooms.isSecretRoomType
--- Helper function to get only the adjacent room grid indexes that exist (i.e. have room data).
-- 
-- This is just a filtering of the results of the `getAdjacentExistingRoomGridIndexes` function. See
-- that function for more information.
-- 
-- @param roomGridIndex Optional. Default is the current room index.
function ____exports.getAdjacentExistingRoomGridIndexes(self, roomGridIndex)
    local adjacentRoomGridIndexes = ____exports.getAdjacentRoomGridIndexes(nil, roomGridIndex)
    return __TS__ArrayFilter(
        adjacentRoomGridIndexes,
        function(____, adjacentRoomGridIndex) return getRoomData(nil, adjacentRoomGridIndex) ~= nil end
    )
end
--- Helper function to get all of the room grid indexes that are adjacent to a given room grid index
-- (even if those room grid indexes do not have any rooms in them).
-- 
-- Adjacent room grid indexes that are outside of the grid will not be included in the returned
-- array.
-- 
-- If a room grid index is provided that is outside of the grid, then an empty array will be
-- returned.
-- 
-- Note that this function does not take the shape of the room into account; it only looks at a
-- single room grid index.
-- 
-- @param roomGridIndex Optional. Default is the current room index.
function ____exports.getAdjacentRoomGridIndexes(self, roomGridIndex)
    local roomGridIndexToUse = roomGridIndex or getRoomGridIndex(nil)
    if not ____exports.isRoomInsideGrid(nil, roomGridIndexToUse) then
        return {}
    end
    local adjacentRoomGridIndexes = __TS__ArrayMap(
        ADJACENT_ROOM_GRID_INDEX_DELTAS,
        function(____, delta) return roomGridIndexToUse + delta end
    )
    return __TS__ArrayFilter(
        adjacentRoomGridIndexes,
        function(____, adjacentRoomGridIndex) return ____exports.isRoomInsideGrid(nil, adjacentRoomGridIndex) end
    )
end
--- Helper function to iterate through the possible doors for a room and see if any of them would be
-- a valid spot to insert a brand new room on the floor.
-- 
-- @param roomGridIndex Optional. Default is the current room index.
-- @param ensureDeadEnd Optional. Whether to only include doors that lead to a valid dead end
-- attached to a normal room. If false, the function will include all doors
-- that would have a red door.
-- @returns A array of tuples of `DoorSlot` and room grid index.
function ____exports.getNewRoomCandidatesBesideRoom(self, roomGridIndex, ensureDeadEnd)
    if ensureDeadEnd == nil then
        ensureDeadEnd = true
    end
    local roomDescriptor = getRoomDescriptor(nil, roomGridIndex)
    if not ____exports.isRoomInsideGrid(nil, roomDescriptor.SafeGridIndex) then
        return {}
    end
    local roomData = roomDescriptor.Data
    if roomData == nil then
        return {}
    end
    local doorSlotToRoomGridIndexes = ____exports.getRoomShapeAdjacentNonExistingGridIndexes(nil, roomDescriptor.SafeGridIndex, roomData.Shape)
    local roomCandidates = {}
    for ____, ____value in __TS__Iterator(doorSlotToRoomGridIndexes) do
        local doorSlot = ____value[1]
        local adjacentRoomGridIndex = ____value[2]
        do
            local doorSlotFlag = doorSlotToDoorSlotFlag(nil, doorSlot)
            if not hasFlag(nil, roomData.Doors, doorSlotFlag) then
                goto __continue17
            end
            if ensureDeadEnd and not ____exports.isDeadEnd(nil, adjacentRoomGridIndex) then
                goto __continue17
            end
            roomCandidates[#roomCandidates + 1] = {doorSlot = doorSlot, roomGridIndex = adjacentRoomGridIndex}
        end
        ::__continue17::
    end
    return roomCandidates
end
--- Helper function to get all of the spots on the floor to insert a brand new room.
-- 
-- @param ensureDeadEnd Optional. Whether to only include spots that are a valid dead end attached
-- to a normal room. If false, the function will include all valid spots that
-- have a red door.
-- @returns A array of tuples containing the adjacent room grid index, the `DoorSlot`, and the new
-- room grid index.
function ____exports.getNewRoomCandidatesForLevel(self, ensureDeadEnd)
    if ensureDeadEnd == nil then
        ensureDeadEnd = true
    end
    local roomsInsideGrid = getRoomsInsideGrid(nil)
    local normalRooms = __TS__ArrayFilter(
        roomsInsideGrid,
        function(____, room) return room.Data ~= nil and room.Data.Type == RoomType.DEFAULT and not isMirrorRoom(nil, room.Data) and not isMineShaft(nil, room.Data) end
    )
    local roomsToLookThrough = ensureDeadEnd and normalRooms or roomsInsideGrid
    local newRoomCandidates = {}
    for ____, room in ipairs(roomsToLookThrough) do
        local newRoomCandidatesBesideRoom = ____exports.getNewRoomCandidatesBesideRoom(nil, room.SafeGridIndex, ensureDeadEnd)
        for ____, ____value in ipairs(newRoomCandidatesBesideRoom) do
            local doorSlot = ____value.doorSlot
            local roomGridIndex = ____value.roomGridIndex
            newRoomCandidates[#newRoomCandidates + 1] = {adjacentRoomGridIndex = room.SafeGridIndex, doorSlot = doorSlot, newRoomGridIndex = roomGridIndex}
        end
    end
    return newRoomCandidates
end
--- Helper function to get only the adjacent room grid indexes for a room shape that exist (i.e. have
-- room data).
-- 
-- This is just a filtering of the results of the `getRoomShapeAdjacentGridIndexes` function. See
-- that function for more information.
function ____exports.getRoomShapeAdjacentExistingGridIndexes(self, safeRoomGridIndex, roomShape)
    local roomShapeAdjacentGridIndexes = copyMap(
        nil,
        ____exports.getRoomShapeAdjacentGridIndexes(nil, safeRoomGridIndex, roomShape)
    )
    for ____, ____value in __TS__Iterator(roomShapeAdjacentGridIndexes) do
        local doorSlot = ____value[1]
        local roomGridIndex = ____value[2]
        local roomData = getRoomData(nil, roomGridIndex)
        if roomData == nil then
            roomShapeAdjacentGridIndexes:delete(doorSlot)
        end
    end
    return roomShapeAdjacentGridIndexes
end
--- Helper function to get the room grid index delta that each hypothetical door in a given room
-- shape would go to.
-- 
-- This is used by the `getRoomShapeAdjacentGridIndexes` function.
-- 
-- @returns A map of `DoorSlot` to the corresponding room grid index delta.
function ____exports.getRoomShapeAdjacentGridIndexDeltas(self, roomShape)
    return ROOM_SHAPE_TO_DOOR_SLOTS_TO_GRID_INDEX_DELTA[roomShape]
end
--- Helper function to get the room grid index that each hypothetical door in a given room shape
-- would go to. (This will not include room grid indexes that are outside of the grid.)
-- 
-- @param safeRoomGridIndex This must be the room safe grid index (i.e. the top-left room grid index
-- for the respective room).
-- @param roomShape The shape of the hypothetical room.
-- @returns A map of `DoorSlot` to the corresponding room grid index.
function ____exports.getRoomShapeAdjacentGridIndexes(self, safeRoomGridIndex, roomShape)
    local roomShapeAdjacentGridIndexDeltas = ____exports.getRoomShapeAdjacentGridIndexDeltas(nil, roomShape)
    local adjacentGridIndexes = __TS__New(Map)
    for ____, ____value in __TS__Iterator(roomShapeAdjacentGridIndexDeltas) do
        local doorSlot = ____value[1]
        local delta = ____value[2]
        local roomGridIndex = safeRoomGridIndex + delta
        if ____exports.isRoomInsideGrid(nil, roomGridIndex) then
            adjacentGridIndexes:set(doorSlot, roomGridIndex)
        end
    end
    return adjacentGridIndexes
end
--- Helper function to get only the adjacent room grid indexes for a room shape that do not exist
-- (i.e. do not have room data).
-- 
-- This is just a filtering of the results of the `getRoomShapeAdjacentGridIndexes` function. See
-- that function for more information.
function ____exports.getRoomShapeAdjacentNonExistingGridIndexes(self, safeRoomGridIndex, roomShape)
    local roomShapeAdjacentGridIndexes = copyMap(
        nil,
        ____exports.getRoomShapeAdjacentGridIndexes(nil, safeRoomGridIndex, roomShape)
    )
    for ____, ____value in __TS__Iterator(roomShapeAdjacentGridIndexes) do
        local doorSlot = ____value[1]
        local roomGridIndex = ____value[2]
        local roomData = getRoomData(nil, roomGridIndex)
        if roomData ~= nil then
            roomShapeAdjacentGridIndexes:delete(doorSlot)
        end
    end
    return roomShapeAdjacentGridIndexes
end
--- Helper function to check if the given room grid index is a dead end. Specifically, this is
-- defined as having only one adjacent room that exists.
-- 
-- Note that this function does not take the shape of the room into account; it only looks at a
-- single room grid index.
-- 
-- This function does not care if the given room grid index actually exists, so you can use it to
-- check if a hypothetical room would be a dead end.
-- 
-- @param roomGridIndex Optional. Default is the current room index.
function ____exports.isDeadEnd(self, roomGridIndex)
    local adjacentExistingRoomGridIndexes = ____exports.getAdjacentExistingRoomGridIndexes(nil, roomGridIndex)
    return #adjacentExistingRoomGridIndexes == 1
end
--- Helper function to detect if the provided room was created by the Red Key item.
-- 
-- Under the hood, this checks for the `RoomDescriptorFlag.FLAG_RED_ROOM` flag.
-- 
-- @param roomGridIndex Optional. Default is the current room index.
function ____exports.isRedKeyRoom(self, roomGridIndex)
    local roomDescriptor = getRoomDescriptor(nil, roomGridIndex)
    return hasFlag(nil, roomDescriptor.Flags, RoomDescriptorFlag.RED_ROOM)
end
--- Helper function to determine if a given room grid index is inside of the normal 13x13 level grid.
-- 
-- For example, Devil Rooms and the Mega Satan room are not considered to be inside the grid.
-- 
-- @param roomGridIndex Optional. Default is the current room index.
function ____exports.isRoomInsideGrid(self, roomGridIndex)
    if roomGridIndex == nil then
        roomGridIndex = getRoomGridIndex(nil)
    end
    return roomGridIndex >= 0 and roomGridIndex <= MAX_LEVEL_GRID_INDEX
end
--- Helper function to check if a room exists at the given room grid index. (A room will exist if it
-- has non-undefined data in the room descriptor.)
function ____exports.roomExists(self, roomGridIndex)
    local roomData = getRoomData(nil, roomGridIndex)
    return roomData ~= nil
end
local LEFT = -1
local UP = -LEVEL_GRID_ROW_WIDTH
local RIGHT = 1
local DOWN = LEVEL_GRID_ROW_WIDTH
ADJACENT_ROOM_GRID_INDEX_DELTAS = {LEFT, UP, RIGHT, DOWN}
--- Helper function to get only the adjacent room grid indexes that do not exist (i.e. do not have
-- room data).
-- 
-- This is just a filtering of the results of the `getAdjacentExistingRoomGridIndexes` function. See
-- that function for more information.
function ____exports.getAdjacentNonExistingRoomGridIndexes(self, roomGridIndex)
    local adjacentRoomGridIndexes = ____exports.getAdjacentRoomGridIndexes(nil, roomGridIndex)
    return __TS__ArrayFilter(
        adjacentRoomGridIndexes,
        function(____, adjacentRoomGridIndex) return getRoomData(nil, adjacentRoomGridIndex) == nil end
    )
end
--- Helper function to get the room safe grid index for every room on the entire floor.
function ____exports.getAllRoomGridIndexes(self)
    local rooms = getRooms(nil)
    return __TS__ArrayMap(
        rooms,
        function(____, roomDescriptor) return roomDescriptor.SafeGridIndex end
    )
end
--- Helper function to pick a random valid spot on the floor to insert a brand new room. Note that
-- some floors will not have any valid spots. If this is the case, this function will return
-- undefined.
-- 
-- If you want to get an unseeded room, you must explicitly pass `undefined` to the `seedOrRNG`
-- parameter.
-- 
-- @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 ensureDeadEnd Optional. Whether to pick a valid dead end attached to a normal room. If
-- false, the function will randomly pick from any valid location that would
-- have a red door.
-- @returns Either a tuple of adjacent room grid index, `DoorSlot`, and new room grid index, or
-- undefined.
function ____exports.getNewRoomCandidate(self, seedOrRNG, ensureDeadEnd)
    if ensureDeadEnd == nil then
        ensureDeadEnd = true
    end
    local newRoomCandidatesForLevel = ____exports.getNewRoomCandidatesForLevel(nil, ensureDeadEnd)
    if #newRoomCandidatesForLevel == 0 then
        return nil
    end
    return getRandomArrayElement(nil, newRoomCandidatesForLevel, seedOrRNG)
end
--- Helper function to get the grid indexes of all the rooms connected to the given room index,
-- taking the shape of the room into account. (This will only include rooms with valid data.)
-- 
-- Returns an empty map if the provided room grid index is out of bounds or has no associated room
-- data.
-- 
-- @param roomGridIndex Optional. Default is the current room index.
-- @returns A map of `DoorSlot` to the corresponding room grid index.
function ____exports.getRoomAdjacentGridIndexes(self, roomGridIndex)
    local roomDescriptor = getRoomDescriptor(nil, roomGridIndex)
    if not ____exports.isRoomInsideGrid(nil, roomDescriptor.SafeGridIndex) then
        return __TS__New(Map)
    end
    local roomData = roomDescriptor.Data
    if roomData == nil then
        return __TS__New(Map)
    end
    return ____exports.getRoomShapeAdjacentExistingGridIndexes(nil, roomDescriptor.SafeGridIndex, roomData.Shape)
end
--- Helper function to get an array of all of the room descriptors for rooms that match the specified
-- room type.
-- 
-- This function only searches through rooms in the current dimension and rooms inside the grid.
-- 
-- This function is variadic, meaning that you can specify N arguments to get the combined room
-- descriptors for N room types.
function ____exports.getRoomDescriptorsForType(self, ...)
    local roomTypes = {...}
    local roomTypesSet = __TS__New(Set, roomTypes)
    local roomsInsideGrid = getRoomsInsideGrid(nil)
    return __TS__ArrayFilter(
        roomsInsideGrid,
        function(____, roomDescriptor) return roomDescriptor.Data ~= nil and roomTypesSet:has(roomDescriptor.Data.Type) end
    )
end
--- Helper function to get an array of all of the safe grid indexes for rooms that match the
-- specified room type.
-- 
-- This function only searches through rooms in the current dimension.
-- 
-- This function is variadic, meaning that you can specify N arguments to get the combined grid
-- indexes for N room types.
function ____exports.getRoomGridIndexesForType(self, ...)
    local roomDescriptors = ____exports.getRoomDescriptorsForType(nil, ...)
    return __TS__ArrayMap(
        roomDescriptors,
        function(____, roomDescriptor) return roomDescriptor.SafeGridIndex end
    )
end
--- Helper function to determine if the current room grid index is inside of the normal 13x13 level
-- grid.
-- 
-- For example, Devil Rooms and the Mega Satan room are not considered to be inside the grid.
function ____exports.inGrid(self)
    local roomGridIndex = getRoomGridIndex(nil)
    return ____exports.isRoomInsideGrid(nil, roomGridIndex)
end
--- Helper function to detect if the current room was created by the Red Key item.
-- 
-- Under the hood, this checks for the `RoomDescriptorFlag.FLAG_RED_ROOM` flag.
function ____exports.inRedKeyRoom(self)
    local roomGridIndex = getRoomGridIndex(nil)
    return ____exports.isRedKeyRoom(nil, roomGridIndex)
end
function ____exports.isDoorSlotValidAtGridIndex(self, doorSlot, roomGridIndex)
    local allowedDoors = getRoomAllowedDoors(nil, roomGridIndex)
    return allowedDoors:has(doorSlot)
end
function ____exports.isDoorSlotValidAtGridIndexForRedRoom(self, doorSlot, roomGridIndex)
    local doorSlotValidAtGridIndex = ____exports.isDoorSlotValidAtGridIndex(nil, doorSlot, roomGridIndex)
    if not doorSlotValidAtGridIndex then
        return false
    end
    local roomShape = getRoomShape(nil, roomGridIndex)
    if roomShape == nil then
        return false
    end
    local delta = getGridIndexDelta(nil, roomShape, doorSlot)
    if delta == nil then
        return false
    end
    local redRoomGridIndex = roomGridIndex + delta
    return not ____exports.roomExists(nil, redRoomGridIndex) and ____exports.isRoomInsideGrid(nil, redRoomGridIndex)
end
--- Helper function to generate a new room on the floor.
-- 
-- If you want to generate an unseeded room, you must explicitly pass `undefined` to the `seedOrRNG`
-- parameter.
-- 
-- Under the hood, this function uses the `Level.MakeRedRoomDoor` method to create the room.
-- 
-- @param seedOrRNG The `Seed` or `RNG` object to use. If an `RNG` object is provided, the
-- `RNG.Next` method will be called. Default is `Level.GetDungeonPlacementSeed`.
-- Note that the RNG is only used to select the random location to put the room on
-- the floor; it does not influence the randomly chosen room contents. (That is
-- performed by the game and can not be manipulated prior to its generation.)
-- @param ensureDeadEnd Optional. Whether to place the room at a valid dead end attached to a normal
-- room. If false, it will randomly appear at any valid location that would
-- have a red door.
-- @param customRoomData Optional. By default, the newly created room will have data corresponding
-- to the game's randomly generated red room. If you provide this function
-- with room data, it will be used to override the vanilla data.
-- @returns The room grid index of the new room or undefined if the floor had no valid dead ends to
-- place a room.
function ____exports.newRoom(self, seedOrRNG, ensureDeadEnd, customRoomData)
    if ensureDeadEnd == nil then
        ensureDeadEnd = true
    end
    local level = game:GetLevel()
    if seedOrRNG == nil then
        seedOrRNG = level:GetDungeonPlacementSeed()
    end
    local rng = isRNG(nil, seedOrRNG) and seedOrRNG or newRNG(nil, seedOrRNG)
    local newRoomCandidate = ____exports.getNewRoomCandidate(nil, rng, ensureDeadEnd)
    if newRoomCandidate == nil then
        return nil
    end
    local adjacentRoomGridIndex = newRoomCandidate.adjacentRoomGridIndex
    local doorSlot = newRoomCandidate.doorSlot
    local newRoomGridIndex = newRoomCandidate.newRoomGridIndex
    level:MakeRedRoomDoor(adjacentRoomGridIndex, doorSlot)
    local roomDescriptor = getRoomDescriptor(nil, newRoomGridIndex)
    roomDescriptor.Flags = removeFlag(nil, roomDescriptor.Flags, RoomDescriptorFlag.RED_ROOM)
    if customRoomData ~= nil then
        roomDescriptor.Data = customRoomData
    end
    local roomData = roomDescriptor.Data
    if roomData ~= nil then
        local hasFullMap = level:GetStateFlag(LevelStateFlag.FULL_MAP_EFFECT)
        local hasCompass = level:GetStateFlag(LevelStateFlag.COMPASS_EFFECT)
        local hasBlueMap = level:GetStateFlag(LevelStateFlag.BLUE_MAP_EFFECT)
        local roomType = roomData.Type
        local isSecretRoom = isSecretRoomType(nil, roomType)
        if hasFullMap then
            roomDescriptor.DisplayFlags = ALL_DISPLAY_FLAGS
        elseif not isSecretRoom and hasCompass then
            roomDescriptor.DisplayFlags = addFlag(nil, DisplayFlag.VISIBLE, DisplayFlag.SHOW_ICON)
        elseif isSecretRoom and hasBlueMap then
            roomDescriptor.DisplayFlags = addFlag(nil, DisplayFlag.VISIBLE, DisplayFlag.SHOW_ICON)
        end
    end
    return newRoomGridIndex
end
--- Helper function to get the coordinates of a given room grid index. The floor is represented by a
-- 13x13 grid.
-- 
-- - Since the starting room is in the center, the starting room grid index of 84 is equal to
--   coordinates of (6, 6).
-- - The top-left grid index of 0 is equal to coordinates of: (12, 0)
-- - The top-right grid index of 12 is equal to coordinates of: (0, 0)
-- - The bottom-left grid index of 156 is equal to coordinates of: (0, 12)
-- - The bottom-right grid index of 168 is equal to coordinates of: (12, 12)
function ____exports.roomGridIndexToVector(self, roomGridIndex)
    local x = roomGridIndex % LEVEL_GRID_ROW_WIDTH
    local y = math.floor(roomGridIndex / LEVEL_GRID_ROW_WIDTH)
    return Vector(x, y)
end
--- Helper function to convert a room grid index expressed as a vector back into an integer.
-- 
-- Also see the `roomGridIndexToVector` helper function.
function ____exports.vectorToRoomGridIndex(self, roomVector)
    return roomVector.Y * LEVEL_GRID_ROW_WIDTH + roomVector.X
end
return ____exports
