local ____lualib = require("lualib_bundle")
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
local ____exports = {}
local ____isaac_2Dtypescript_2Ddefinitions = require("isaac-typescript-definitions")
local GameStateFlag = ____isaac_2Dtypescript_2Ddefinitions.GameStateFlag
local LevelStage = ____isaac_2Dtypescript_2Ddefinitions.LevelStage
local RoomType = ____isaac_2Dtypescript_2Ddefinitions.RoomType
local StageType = ____isaac_2Dtypescript_2Ddefinitions.StageType
local ____cachedClasses = require("core.cachedClasses")
local game = ____cachedClasses.game
local ____levelNames = require("objects.levelNames")
local LEVEL_NAMES = ____levelNames.LEVEL_NAMES
local ____roomTypeSpecialGotoPrefixes = require("objects.roomTypeSpecialGotoPrefixes")
local ROOM_TYPE_SPECIAL_GOTO_PREFIXES = ____roomTypeSpecialGotoPrefixes.ROOM_TYPE_SPECIAL_GOTO_PREFIXES
local ____stageToStageID = require("objects.stageToStageID")
local STAGE_TO_STAGE_ID = ____stageToStageID.STAGE_TO_STAGE_ID
local ____stageTypeSuffixes = require("objects.stageTypeSuffixes")
local STAGE_TYPE_SUFFIXES = ____stageTypeSuffixes.STAGE_TYPE_SUFFIXES
local ____log = require("functions.log")
local log = ____log.log
local ____types = require("functions.types")
local asLevelStage = ____types.asLevelStage
local ____utils = require("functions.utils")
local inRange = ____utils.inRange
--- Helper function to check if the provided stage type is equal to `StageType.REPENTANCE` or
-- `StageType.REPENTANCE_B`.
function ____exports.isRepentanceStage(self, stageType)
    return stageType == StageType.REPENTANCE or stageType == StageType.REPENTANCE_B
end
--- Helper function to check if the provided stage is one with a story boss. Specifically, this is
-- Depths 2 (Mom), Womb 2 (Mom's Heart / It Lives), Blue Womb (Hush), Sheol (Satan), Cathedral
-- (Isaac), Dark Room (Lamb), The Chest (Blue Baby), The Void (Delirium), and Home (Dogma / The
-- Beast).
function ____exports.isStageWithStoryBoss(self, stage)
    return stage == LevelStage.DEPTHS_2 or stage >= LevelStage.WOMB_2
end
--- Helper function to check if the current stage type is equal to `StageType.REPENTANCE` or
-- `StageType.REPENTANCE_B`.
function ____exports.onRepentanceStage(self)
    local level = game:GetLevel()
    local stageType = level:GetStageType()
    return ____exports.isRepentanceStage(nil, stageType)
end
--- Helper function that calculates what the stage type should be for the provided stage. This
-- emulates what the game's internal code does.
function ____exports.calculateStageType(self, stage)
    local seeds = game:GetSeeds()
    local stageSeed = seeds:GetStageSeed(stage)
    if stageSeed % 2 == 0 then
        return StageType.WRATH_OF_THE_LAMB
    end
    if stageSeed % 3 == 0 then
        return StageType.AFTERBIRTH
    end
    return StageType.ORIGINAL
end
--- Helper function that calculates what the Repentance stage type should be for the provided stage.
-- This emulates what the game's internal code does.
function ____exports.calculateStageTypeRepentance(self, stage)
    if stage == LevelStage.WOMB_1 or stage == LevelStage.WOMB_2 then
        return StageType.REPENTANCE
    end
    local seeds = game:GetSeeds()
    local adjustedStage = asLevelStage(nil, stage + 1)
    local stageSeed = seeds:GetStageSeed(adjustedStage)
    local halfStageSeed = math.floor(stageSeed / 2)
    if halfStageSeed % 2 == 0 then
        return StageType.REPENTANCE_B
    end
    return StageType.REPENTANCE
end
--- Helper function to account for Repentance floors being offset by 1. For example, Downpour 2 is
-- the third level of the run, but the game considers it to have a stage of 2. This function will
-- consider Downpour 2 to have a stage of 3.
function ____exports.getEffectiveStage(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    if ____exports.onRepentanceStage(nil) then
        return stage + 1
    end
    return stage
end
--- Helper function to get the corresponding "goto" console command that would correspond to the
-- provided room type and room variant.
-- 
-- @param roomType The `RoomType` of the destination room.
-- @param roomVariant The variant of the destination room.
-- @param useSpecialRoomsForRoomTypeDefault Optional. Whether to use `s.default` as the prefix for
-- the `goto` command (instead of `d`) if the room type is
-- `RoomType.DEFAULT` (1). False by default.
function ____exports.getGotoCommand(self, roomType, roomVariant, useSpecialRoomsForRoomTypeDefault)
    if useSpecialRoomsForRoomTypeDefault == nil then
        useSpecialRoomsForRoomTypeDefault = false
    end
    local isNormalRoom = roomType == RoomType.DEFAULT and not useSpecialRoomsForRoomTypeDefault
    local roomTypeSpecialGotoPrefix = ROOM_TYPE_SPECIAL_GOTO_PREFIXES[roomType]
    local prefix = isNormalRoom and "d" or "s." .. roomTypeSpecialGotoPrefix
    return (("goto " .. prefix) .. ".") .. tostring(roomVariant)
end
--- Helper function to get the English name of the level. For example, "Caves 1".
-- 
-- This is useful because the `Level.GetName` method returns a localized version of the level name,
-- which will not display correctly on some fonts.
-- 
-- Note that this returns "Blue Womb" instead of "???" for stage 9.
-- 
-- @param stage Optional. If not specified, the current stage will be used.
-- @param stageType Optional. If not specified, the current stage type will be used.
function ____exports.getLevelName(self, stage, stageType)
    local level = game:GetLevel()
    if stage == nil then
        stage = level:GetStage()
    end
    if stageType == nil then
        stageType = level:GetStageType()
    end
    local stageNames = LEVEL_NAMES[stage]
    return stageNames[stageType]
end
--- Alias for the `Level.GetStage` method.
function ____exports.getStage(self)
    local level = game:GetLevel()
    return level:GetStage()
end
--- Helper function to get the stage ID that corresponds to a particular floor. It does this by
-- manually converting `LevelStage` and `StageType` into `StageID`. This is useful because
-- `getRoomStageID` will not correctly return the `StageID` if the player is in a special room.
-- 
-- @param stage Optional. If not specified, the stage corresponding to the current floor will be
-- used.
-- @param stageType Optional. If not specified, the stage type corresponding to the current floor
-- will be used.
function ____exports.getStageID(self, stage, stageType)
    local level = game:GetLevel()
    if stage == nil then
        stage = level:GetStage()
    end
    if stageType == nil then
        stageType = level:GetStageType()
    end
    local stageTypeToStageID = STAGE_TO_STAGE_ID[stage]
    return stageTypeToStageID[stageType]
end
--- Alias for the `Level.GetStageType` method.
function ____exports.getStageType(self)
    local level = game:GetLevel()
    return level:GetStageType()
end
--- Helper function to convert a numerical `StageType` into the letter suffix supplied to the "stage"
-- console command. For example, `StageType.REPENTANCE` is the stage type for Downpour, and the
-- console command to go to Downpour is "stage 1c", so this function converts `StageType.REPENTANCE`
-- to "c".
function ____exports.getStageTypeSuffix(self, stageType)
    return STAGE_TYPE_SUFFIXES[stageType]
end
--- Returns whether the provided stage and stage type represent a "final floor". This is defined as a
-- floor that prevents the player from entering the I AM ERROR room on.
-- 
-- For example, when using Undefined on The Chest, it has a 50% chance of teleporting the player to
-- the Secret Room and a 50% chance of teleporting the player to the Super Secret Room, because the
-- I AM ERROR room is never entered into the list of possibilities.
function ____exports.isFinalFloor(self, stage, stageType)
    return stage == LevelStage.DARK_ROOM_CHEST or stage == LevelStage.VOID or stage == LevelStage.HOME or stage == LevelStage.WOMB_2 and ____exports.isRepentanceStage(nil, stageType)
end
--- Helper function to check if the provided effective stage is one that has the possibility to grant
-- a natural Devil Room or Angel Room after killing the boss.
-- 
-- Note that in order for this function to work properly, you must provide it with the effective
-- stage (e.g. from the `getEffectiveStage` helper function) and not the absolute stage (e.g. from
-- the `Level.GetStage` method).
function ____exports.isStageWithNaturalDevilRoom(self, effectiveStage)
    return inRange(nil, effectiveStage, LevelStage.BASEMENT_2, LevelStage.WOMB_2) and effectiveStage ~= LevelStage.BLUE_WOMB
end
--- Helper function to check if the provided stage is one that will have a random collectible drop
-- upon defeating the boss of the floor.
-- 
-- This happens on most stages but will not happen on Depths 2, Womb 2, Sheol, Cathedral, Dark Room,
-- The Chest, and Home (due to the presence of a story boss).
-- 
-- Note that even though Delirium does not drop a random boss collectible, The Void is still
-- considered to be a stage that has a random boss collectible since all of the non-Delirium Boss
-- Rooms will drop random boss collectibles.
function ____exports.isStageWithRandomBossCollectible(self, stage)
    return not ____exports.isStageWithStoryBoss(nil, stage) or stage == LevelStage.VOID
end
--- Helper function to check if the provided stage will spawn a locked door to Downpour/Dross after
-- defeating the boss.
function ____exports.isStageWithSecretExitToDownpour(self, stage)
    return stage == LevelStage.BASEMENT_1 or stage == LevelStage.BASEMENT_2
end
--- Helper function to check if the provided stage and stage type will spawn a spiked door to
-- Mausoleum/Gehenna after defeating the boss.
function ____exports.isStageWithSecretExitToMausoleum(self, stage, stageType)
    local repentanceStage = ____exports.isRepentanceStage(nil, stageType)
    return stage == LevelStage.DEPTHS_1 and not repentanceStage or stage == LevelStage.CAVES_2 and repentanceStage
end
--- Helper function to check if the provided stage and stage type will spawn a wooden door to
-- Mines/Ashpit after defeating the boss.
function ____exports.isStageWithSecretExitToMines(self, stage, stageType)
    local repentanceStage = ____exports.isRepentanceStage(nil, stageType)
    return stage == LevelStage.CAVES_1 and not repentanceStage or stage == LevelStage.BASEMENT_2 and repentanceStage
end
--- Helper function to check if the current stage is one that would create a trapdoor if We Need to
-- Go Deeper was used.
function ____exports.isStageWithShovelTrapdoors(self, stage, stageType)
    local repentanceStage = ____exports.isRepentanceStage(nil, stageType)
    return stage < LevelStage.WOMB_2 or stage == LevelStage.WOMB_2 and not repentanceStage
end
--- Helper function to check if the player has taken Dad's Note. This sets the game state flag of
-- `GameStateFlag.BACKWARDS_PATH` and causes floor generation to change.
function ____exports.onAscent(self)
    return game:GetStateFlag(GameStateFlag.BACKWARDS_PATH)
end
function ____exports.onCathedral(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    local stageType = level:GetStageType()
    return stage == LevelStage.SHEOL_CATHEDRAL and stageType == StageType.WRATH_OF_THE_LAMB
end
function ____exports.onChest(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    local stageType = level:GetStageType()
    return stage == LevelStage.DARK_ROOM_CHEST and stageType == StageType.WRATH_OF_THE_LAMB
end
function ____exports.onDarkRoom(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    local stageType = level:GetStageType()
    return stage == LevelStage.DARK_ROOM_CHEST and stageType == StageType.ORIGINAL
end
--- Helper function to check if the current stage matches one of the given stages. This uses the
-- `getEffectiveStage` helper function so that the Repentance floors are correctly adjusted.
-- 
-- This function is variadic, which means you can pass as many stages as you want to match for.
function ____exports.onEffectiveStage(self, ...)
    local effectiveStages = {...}
    local thisEffectiveStage = ____exports.getEffectiveStage(nil)
    return __TS__ArrayIncludes(effectiveStages, thisEffectiveStage)
end
--- Returns whether the player is on the "final floor" of the particular run. The final floor is
-- defined as one that prevents the player from entering the I AM ERROR room on.
-- 
-- For example, when using Undefined on The Chest, it has a 50% chance of teleporting the player to
-- the Secret Room and a 50% chance of teleporting the player to the Super Secret Room, because the
-- I AM ERROR room is never entered into the list of possibilities.
function ____exports.onFinalFloor(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    local stageType = level:GetStageType()
    return ____exports.isFinalFloor(nil, stage, stageType)
end
--- Returns whether the player is on the first floor of the particular run.
-- 
-- This is tricky to determine because we have to handle the cases of Downpour/Dross 1 not being the
-- first floor and The Ascent.
function ____exports.onFirstFloor(self)
    local effectiveStage = ____exports.getEffectiveStage(nil)
    local isOnAscent = ____exports.onAscent(nil)
    return effectiveStage == LevelStage.BASEMENT_1 and not isOnAscent
end
function ____exports.onSheol(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    local stageType = level:GetStageType()
    return stage == LevelStage.SHEOL_CATHEDRAL and stageType == StageType.ORIGINAL
end
--- Helper function to check if the current stage matches one of the given stages.
-- 
-- This function is variadic, which means you can pass as many stages as you want to match for.
function ____exports.onStage(self, ...)
    local stages = {...}
    local level = game:GetLevel()
    local thisStage = level:GetStage()
    return __TS__ArrayIncludes(stages, thisStage)
end
--- Helper function to check if the current stage is equal to or higher than the given stage.
function ____exports.onStageOrHigher(self, stage)
    local level = game:GetLevel()
    local thisStage = level:GetStage()
    return thisStage >= stage
end
--- Helper function to check if the current stage is equal to or higher than the given stage.
function ____exports.onStageOrLower(self, stage)
    local level = game:GetLevel()
    local thisStage = level:GetStage()
    return thisStage <= stage
end
--- Helper function to check if the current stage matches one of the given stage types.
-- 
-- This function is variadic, which means you can pass as many room types as you want to match for.
function ____exports.onStageType(self, ...)
    local stageTypes = {...}
    local level = game:GetLevel()
    local thisStageType = level:GetStageType()
    return __TS__ArrayIncludes(stageTypes, thisStageType)
end
--- Helper function to check if the current stage is one that has the possibility to grant a natural
-- Devil Room or Angel Room after killing the boss.
function ____exports.onStageWithNaturalDevilRoom(self)
    local effectiveStage = ____exports.getEffectiveStage(nil)
    return ____exports.isStageWithNaturalDevilRoom(nil, effectiveStage)
end
--- Helper function to check if the current stage is one that will have a random collectible drop
-- upon defeating the boss of the floor.
-- 
-- This happens on most stages but will not happen on Depths 2, Womb 2, Sheol, Cathedral, Dark Room,
-- The Chest, and Home (due to the presence of a story boss).
-- 
-- Note that even though Delirium does not drop a random boss collectible, The Void is still
-- considered to be a stage that has a random boss collectible since all of the non-Delirium Boss
-- Rooms will drop random boss collectibles.
function ____exports.onStageWithRandomBossCollectible(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    return ____exports.isStageWithRandomBossCollectible(nil, stage)
end
--- Helper function to check if the current stage will spawn a locked door to Downpour/Dross after
-- defeating the boss.
function ____exports.onStageWithSecretExitToDownpour(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    return ____exports.isStageWithSecretExitToDownpour(nil, stage)
end
--- Helper function to check if the current stage will spawn a spiked door to Mausoleum/Gehenna after
-- defeating the boss.
function ____exports.onStageWithSecretExitToMausoleum(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    local stageType = level:GetStageType()
    return ____exports.isStageWithSecretExitToMausoleum(nil, stage, stageType)
end
--- Helper function to check if the current stage will spawn a wooden door to Mines/Ashpit after
-- defeating the boss.
function ____exports.onStageWithSecretExitToMines(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    local stageType = level:GetStageType()
    return ____exports.isStageWithSecretExitToMines(nil, stage, stageType)
end
--- Helper function to check if the current stage is one that would create a trapdoor if We Need to
-- Go Deeper was used.
function ____exports.onStageWithShovelTrapdoors(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    local stageType = level:GetStageType()
    return ____exports.isStageWithShovelTrapdoors(nil, stage, stageType)
end
--- Helper function to check if the current stage is one with a story boss. Specifically, this is
-- Depths 2 (Mom), Womb 2 (Mom's Heart / It Lives), Blue Womb (Hush), Sheol (Satan), Cathedral
-- (Isaac), Dark Room (Lamb), The Chest (Blue Baby), The Void (Delirium), and Home (Dogma / The
-- Beast).
function ____exports.onStageWithStoryBoss(self)
    local level = game:GetLevel()
    local stage = level:GetStage()
    return ____exports.isStageWithStoryBoss(nil, stage)
end
--- Helper function to directly warp to a specific stage using the "stage" console command.
-- 
-- This is different from the vanilla `Level.SetStage` method, which will change the stage and/or
-- stage type of the current floor without moving the player to a new floor.
-- 
-- Note that if you use this function on game frame 0, it will confuse the
-- `POST_GAME_STARTED_REORDERED`, `POST_NEW_LEVEL_REORDERED`, and `POST_NEW_ROOM_REORDERED` custom
-- callbacks. If you are using the function in this situation, remember to call the
-- `reorderedCallbacksSetStage` function.
-- 
-- @param stage The stage number to warp to.
-- @param stageType The stage type to warp to.
-- @param reseed Optional. Whether to reseed the floor upon arrival. Default is false. Set this to
-- true if you are warping to the same stage but a different stage type (or else the
-- floor layout will be identical to the old floor).
function ____exports.setStage(self, stage, stageType, reseed)
    if reseed == nil then
        reseed = false
    end
    local stageTypeSuffix = ____exports.getStageTypeSuffix(nil, stageType)
    local command = ("stage " .. tostring(stage)) .. stageTypeSuffix
    log("Warping to a stage with a console command of: " .. command)
    Isaac.ExecuteCommand(command)
    if reseed then
        log("Reseeding the floor with a console command of: reseed")
        Isaac.ExecuteCommand("reseed")
    end
end
return ____exports
