"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GameReorderedCallbacks = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const cachedClasses_1 = require("../../../core/cachedClasses");
const decorators_1 = require("../../../decorators");
const frames_1 = require("../../../functions/frames");
const Feature_1 = require("../../private/Feature");
/**
 * By default, callbacks fire in the following order:
 * - `POST_NEW_ROOM` --> `POST_NEW_LEVEL` --> `POST_GAME_STARTED`
 *
 * It is easier to write mod code if the callbacks run in a more logical order:
 * - `POST_GAME_STARTED` --> `POST_NEW_LEVEL` --> `POST_NEW_ROOM`
 *
 * `isaacscript-common` provides three new callbacks that change the order to this:
 * - `POST_GAME_STARTED_REORDERED`
 * - `POST_NEW_LEVEL_REORDERED`
 * - `POST_NEW_ROOM_REORDERED`
 *
 * Additionally, there are some helper functions listed below that can deal with some edge cases
 * that you may run into with these callbacks.
 */
class GameReorderedCallbacks extends Feature_1.Feature {
    /** Used to detect a player resuming a saved run. */
    renderFrameRunStarted = null;
    currentStage = null;
    currentStageType = null;
    usedGlowingHourGlass = false;
    forceNewLevel = false;
    forceNewRoom = false;
    postGameStartedReordered;
    postNewLevelReordered;
    postNewRoomReordered;
    postGameStartedReorderedLast;
    /** @internal */
    constructor(postGameStartedReordered, postNewLevelReordered, postNewRoomReordered, postGameStartedReorderedLast) {
        super();
        this.callbacksUsed = [
            // 3
            [
                isaac_typescript_definitions_1.ModCallback.POST_USE_ITEM,
                this.postUseItemGlowingHourGlass,
                [isaac_typescript_definitions_1.CollectibleType.GLOWING_HOUR_GLASS],
            ],
            // 9
            [isaac_typescript_definitions_1.ModCallback.POST_PLAYER_INIT, this.postPlayerInit],
            // 15
            // eslint-disable-next-line deprecation/deprecation
            [isaac_typescript_definitions_1.ModCallback.POST_GAME_STARTED, this.postGameStarted],
            // 17
            [isaac_typescript_definitions_1.ModCallback.PRE_GAME_EXIT, this.preGameExit],
            // 18
            // eslint-disable-next-line deprecation/deprecation
            [isaac_typescript_definitions_1.ModCallback.POST_NEW_LEVEL, this.postNewLevel],
            // 19
            // eslint-disable-next-line deprecation/deprecation
            [isaac_typescript_definitions_1.ModCallback.POST_NEW_ROOM, this.postNewRoom],
        ];
        this.postGameStartedReordered = postGameStartedReordered;
        this.postNewLevelReordered = postNewLevelReordered;
        this.postNewRoomReordered = postNewRoomReordered;
        this.postGameStartedReorderedLast = postGameStartedReorderedLast;
    }
    // ModCallback.POST_USE_ITEM (3)
    // CollectibleType.GLOWING_HOUR_GLASS (422)
    postUseItemGlowingHourGlass = () => {
        // If Glowing Hourglass is used on the first room of a floor, it will send the player to the
        // previous floor without triggering the `POST_NEW_LEVEL` callback. Manually check for this.
        this.usedGlowingHourGlass = true;
        return undefined;
    };
    // ModCallback.POST_PLAYER_INIT (9)
    postPlayerInit = (_player) => {
        if (this.renderFrameRunStarted === null) {
            this.renderFrameRunStarted = Isaac.GetFrameCount();
        }
    };
    // ModCallback.POST_GAME_STARTED (15)
    postGameStarted = (isContinued) => {
        const level = cachedClasses_1.game.GetLevel();
        const stage = level.GetStage();
        const stageType = level.GetStageType();
        const room = cachedClasses_1.game.GetRoom();
        const roomType = room.GetType();
        this.recordCurrentStage();
        this.postGameStartedReordered.fire(isContinued);
        this.postGameStartedReorderedLast.fire(isContinued);
        if (!isContinued) {
            // The vanilla `POST_NEW_LEVEL` callback only fires on non-continued runs, which makes sense,
            // because we do not want to blow away level variables in this case.
            this.postNewLevelReordered.fire(stage, stageType);
        }
        this.postNewRoomReordered.fire(roomType);
    };
    // ModCallback.PRE_GAME_EXIT (17)
    preGameExit = () => {
        this.renderFrameRunStarted = null;
    };
    // ModCallback.POST_NEW_LEVEL (18)
    postNewLevel = () => {
        const level = cachedClasses_1.game.GetLevel();
        const stage = level.GetStage();
        const stageType = level.GetStageType();
        const room = cachedClasses_1.game.GetRoom();
        const roomType = room.GetType();
        if ((0, frames_1.onGameFrame)(0) && !this.forceNewLevel) {
            // Wait for the `POST_GAME_STARTED` callback to fire.
            return;
        }
        this.forceNewLevel = false;
        this.recordCurrentStage();
        this.postNewLevelReordered.fire(stage, stageType);
        this.postNewRoomReordered.fire(roomType);
    };
    // ModCallback.POST_NEW_ROOM (19)
    postNewRoom = () => {
        const level = cachedClasses_1.game.GetLevel();
        const stage = level.GetStage();
        const stageType = level.GetStageType();
        const room = cachedClasses_1.game.GetRoom();
        const roomType = room.GetType();
        if (this.usedGlowingHourGlass) {
            this.usedGlowingHourGlass = false;
            if (this.currentStage !== stage || this.currentStageType !== stageType) {
                // The player has used the Glowing Hourglass to take them to the previous floor (which does
                // not trigger the `POST_NEW_LEVEL` callback). Emulate what happens in the `POST_NEW_LEVEL`
                // callback.
                this.recordCurrentStage();
                this.postNewLevelReordered.fire(stage, stageType);
                this.postNewRoomReordered.fire(roomType);
                return;
            }
        }
        if (((0, frames_1.onGameFrame)(0) ||
            (0, frames_1.onRenderFrame)(this.renderFrameRunStarted) ||
            this.currentStage !== stage ||
            this.currentStageType !== stageType) &&
            !this.forceNewRoom) {
            return;
        }
        this.forceNewRoom = false;
        this.postNewRoomReordered.fire(roomType);
    };
    recordCurrentStage() {
        const level = cachedClasses_1.game.GetLevel();
        const stage = level.GetStage();
        const stageType = level.GetStageType();
        this.currentStage = stage;
        this.currentStageType = stageType;
    }
    /**
     * Helper function to tell the `POST_NEW_LEVEL_REORDERED` callback that it should always fire on
     * the next `POST_NEW_LEVEL`.
     *
     * If some specific cases, mods can change the current level during run initialization on the 0th
     * frame. (For example, if you had a mod that made the player start the run in Caves instead of
     * Basement.) However, due to how the callback reordering works, the `POST_NEW_LEVEL_REORDERED`
     * callback will never fire on the 0th frame. To get around this, call this function before
     * changing levels to temporarily force the callback to fire.
     *
     * In order to use this function, you must upgrade your mod with
     * `ISCFeature.GAME_REORDERED_CALLBACKS`.
     *
     * @public
     */
    forceNewLevelCallback() {
        this.forceNewLevel = true;
    }
    /**
     * Helper function to tell the `POST_NEW_ROOM_REORDERED` callback that it should always fire on
     * the next `POST_NEW_ROOM`.
     *
     * If some specific cases, mods can change the current room during run initialization on the 0th
     * frame. (For example, if you had a mod that made the player start the Treasure Room of Basement
     * 1 instead of the normal starting room.) However, due to how the callback reordering works, the
     * `POST_NEW_ROOM_REORDERED` callback will never fire on the 0th frame. To get around this, call
     * this function before changing rooms to temporarily force the callback to fire.
     *
     * In order to use this function, you must upgrade your mod with
     * `ISCFeature.GAME_REORDERED_CALLBACKS`.
     *
     * @public
     */
    forceNewRoomCallback() {
        this.forceNewRoom = true;
    }
    /**
     * Helper function to manually set the variables that the reordered callback logic uses to track
     * the current stage and stage type.
     *
     * This is useful because if the stage is changed with the `Game.SetStage` method (or the
     * `setStage` helper function), the reordered callbacks will stop working.
     *
     * In order to use this function, you must upgrade your mod with
     * `ISCFeature.GAME_REORDERED_CALLBACKS`.
     *
     * @public
     */
    reorderedCallbacksSetStage(stage, stageType) {
        this.currentStage = stage;
        this.currentStageType = stageType;
    }
}
exports.GameReorderedCallbacks = GameReorderedCallbacks;
__decorate([
    decorators_1.Exported
], GameReorderedCallbacks.prototype, "forceNewLevelCallback", null);
__decorate([
    decorators_1.Exported
], GameReorderedCallbacks.prototype, "forceNewRoomCallback", null);
__decorate([
    decorators_1.Exported
], GameReorderedCallbacks.prototype, "reorderedCallbacksSetStage", null);
