"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomRevive = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const cachedClasses_1 = require("../../../core/cachedClasses");
const ISCFeature_1 = require("../../../enums/ISCFeature");
const ModCallbackCustom_1 = require("../../../enums/ModCallbackCustom");
const external_1 = require("../../../functions/external");
const log_1 = require("../../../functions/log");
const playerIndex_1 = require("../../../functions/playerIndex");
const players_1 = require("../../../functions/players");
const Feature_1 = require("../../private/Feature");
const DEBUG = false;
var CustomReviveState;
(function (CustomReviveState) {
    CustomReviveState[CustomReviveState["DISABLED"] = 0] = "DISABLED";
    /**
     * We can't immediately jump to waiting for an item animation because it is possible for a player
     * to be holding an item above their head as they are dying (e.g. with Razor Blade).
     */
    CustomReviveState[CustomReviveState["WAITING_FOR_ROOM_TRANSITION"] = 1] = "WAITING_FOR_ROOM_TRANSITION";
    CustomReviveState[CustomReviveState["WAITING_FOR_ITEM_ANIMATION"] = 2] = "WAITING_FOR_ITEM_ANIMATION";
})(CustomReviveState || (CustomReviveState = {}));
const v = {
    run: {
        state: CustomReviveState.DISABLED,
        revivalType: null,
        dyingPlayerIndex: null,
    },
};
class CustomRevive extends Feature_1.Feature {
    v = v;
    preCustomRevive;
    postCustomRevive;
    runInNFrames;
    constructor(preCustomRevive, postCustomRevive, runInNFrames) {
        super();
        this.featuresUsed = [ISCFeature_1.ISCFeature.RUN_IN_N_FRAMES];
        this.callbacksUsed = [
            // 2
            [isaac_typescript_definitions_1.ModCallback.POST_RENDER, this.postRender],
            // 7
            [
                isaac_typescript_definitions_1.ModCallback.POST_FAMILIAR_INIT,
                this.postFamiliarInitOneUp,
                [isaac_typescript_definitions_1.FamiliarVariant.ONE_UP],
            ],
        ];
        this.customCallbacksUsed = [
            [ModCallbackCustom_1.ModCallbackCustom.POST_NEW_ROOM_REORDERED, this.postNewRoomReordered],
            [
                ModCallbackCustom_1.ModCallbackCustom.POST_PEFFECT_UPDATE_REORDERED,
                this.postPEffectUpdateReordered,
            ],
            [ModCallbackCustom_1.ModCallbackCustom.POST_PLAYER_FATAL_DAMAGE, this.postPlayerFatalDamage],
            [ModCallbackCustom_1.ModCallbackCustom.PRE_BERSERK_DEATH, this.preBerserkDeath],
        ];
        this.preCustomRevive = preCustomRevive;
        this.postCustomRevive = postCustomRevive;
        this.runInNFrames = runInNFrames;
    }
    // ModCallback.POST_RENDER (2)
    postRender = () => {
        if (v.run.state !== CustomReviveState.WAITING_FOR_ITEM_ANIMATION) {
            return;
        }
        // The 1-up sound will fire before the item holding animation begins, so we mute it on every
        // render frame.
        cachedClasses_1.sfxManager.Stop(isaac_typescript_definitions_1.SoundEffect.ONE_UP);
    };
    // ModCallback.POST_FAMILIAR_INIT (7)
    // FamiliarVariant.ONE_UP (41)
    postFamiliarInitOneUp = (familiar) => {
        if (v.run.state !== CustomReviveState.WAITING_FOR_ROOM_TRANSITION) {
            return;
        }
        familiar.Remove();
    };
    // ModCallbackCustom.POST_NEW_ROOM_REORDERED
    postNewRoomReordered = () => {
        if (v.run.state !== CustomReviveState.WAITING_FOR_ROOM_TRANSITION) {
            return;
        }
        v.run.state = CustomReviveState.WAITING_FOR_ITEM_ANIMATION;
        this.logStateChanged();
    };
    // ModCallbackCustom.POST_PEFFECT_UPDATE_REORDERED
    postPEffectUpdateReordered = (player) => {
        this.checkWaitingForItemAnimation(player);
    };
    checkWaitingForItemAnimation(player) {
        if (v.run.state !== CustomReviveState.WAITING_FOR_ITEM_ANIMATION) {
            return;
        }
        if (v.run.dyingPlayerIndex === null) {
            return;
        }
        const playerIndex = (0, playerIndex_1.getPlayerIndex)(player);
        if (playerIndex !== v.run.dyingPlayerIndex) {
            return;
        }
        let playerToCheckHoldingItem = player;
        if ((0, players_1.isCharacter)(player, isaac_typescript_definitions_1.PlayerType.SOUL_B)) {
            const forgottenBody = player.GetOtherTwin();
            if (forgottenBody !== undefined) {
                playerToCheckHoldingItem = forgottenBody;
            }
        }
        if (!playerToCheckHoldingItem.IsHoldingItem()) {
            return;
        }
        // The player is now playing the animation where they hold the 1-Up item overhead. The
        // `EntityPlayer.StopExtraAnimation` method will not work to stop this animation. End-users are
        // expected to play a new animation in the PostCustomRevive callback, which will overwrite the
        // 1-Up animation.
        if (v.run.revivalType !== null) {
            this.postCustomRevive.fire(playerToCheckHoldingItem, v.run.revivalType);
        }
        v.run.state = CustomReviveState.DISABLED;
        v.run.revivalType = null;
        v.run.dyingPlayerIndex = null;
        this.logStateChanged();
    }
    // ModCallbackCustom.POST_PLAYER_FATAL_DAMAGE
    postPlayerFatalDamage = (player) => {
        this.playerIsAboutToDie(player);
        return undefined;
    };
    // ModCallbackCustom.PRE_BERSERK_DEATH
    preBerserkDeath = (player) => {
        this.playerIsAboutToDie(player);
    };
    /**
     * The player is about to die, which will immediately delete the save data for the run. To prevent
     * this from happening, we grant the 1-Up item.
     */
    playerIsAboutToDie(player) {
        const revivalType = this.preCustomRevive.fire(player);
        if (revivalType === undefined) {
            return;
        }
        v.run.state = CustomReviveState.WAITING_FOR_ROOM_TRANSITION;
        v.run.revivalType = revivalType;
        v.run.dyingPlayerIndex = (0, playerIndex_1.getPlayerIndex)(player);
        this.logStateChanged();
        player.AddCollectible(isaac_typescript_definitions_1.CollectibleType.ONE_UP, 0, false);
        (0, external_1.rebirthItemTrackerRemoveCollectible)(isaac_typescript_definitions_1.CollectibleType.ONE_UP);
        // The player should always be dead one frame from now. If they are not, then something has gone
        // wrong, probably with the `isDamageToPlayerFatal` function. Since end-user code is already
        // assuming that a custom revive is occurring, explicitly kill the player.
        const playerIndex = (0, playerIndex_1.getPlayerIndex)(player);
        this.runInNFrames.runNextGameFrame(() => {
            const futurePlayer = (0, playerIndex_1.getPlayerFromIndex)(playerIndex);
            if (futurePlayer === undefined) {
                return;
            }
            if (futurePlayer.IsDead()) {
                return;
            }
            (0, log_1.logError)("The player is still alive after initializing a custom revive. Explicitly killing the player.");
            futurePlayer.Kill();
        });
    }
    logStateChanged() {
        if (DEBUG) {
            (0, log_1.log)(`Custom revive state changed: ${CustomReviveState[v.run.state]} (${v.run.state})`);
        }
    }
}
exports.CustomRevive = CustomRevive;
