"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.Pause = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const constants_1 = require("../../../core/constants");
const decorators_1 = require("../../../decorators");
const entitiesSpecific_1 = require("../../../functions/entitiesSpecific");
const isaacAPIClass_1 = require("../../../functions/isaacAPIClass");
const log_1 = require("../../../functions/log");
const playerCollectibles_1 = require("../../../functions/playerCollectibles");
const playerIndex_1 = require("../../../functions/playerIndex");
const tstlClass_1 = require("../../../functions/tstlClass");
const utils_1 = require("../../../functions/utils");
const ReadonlySet_1 = require("../../../types/ReadonlySet");
const Feature_1 = require("../../private/Feature");
const v = {
    run: {
        isPseudoPaused: false,
        shouldUnpause: false,
        initialDescriptions: new Map(),
    },
};
class Pause extends Feature_1.Feature {
    /** @internal */
    v = v;
    disableInputs;
    /** @internal */
    constructor(disableInputs) {
        super();
        this.callbacksUsed = [
            // 1
            [isaac_typescript_definitions_1.ModCallback.POST_UPDATE, this.postUpdate],
            // 13
            [
                isaac_typescript_definitions_1.ModCallback.INPUT_ACTION,
                this.inputActionGetActionValue,
                [isaac_typescript_definitions_1.InputHook.GET_ACTION_VALUE],
            ],
        ];
        this.disableInputs = disableInputs;
    }
    // ModCallback.POST_UPDATE (1)
    postUpdate = () => {
        if (!v.run.isPseudoPaused) {
            return;
        }
        const firstPlayer = Isaac.GetPlayer();
        (0, playerCollectibles_1.useActiveItemTemp)(firstPlayer, isaac_typescript_definitions_1.CollectibleType.PAUSE);
        this.stopTearsAndProjectilesFromMoving();
    };
    stopTearsAndProjectilesFromMoving() {
        const tearsAndProjectiles = [...(0, entitiesSpecific_1.getTears)(), ...(0, entitiesSpecific_1.getProjectiles)()];
        for (const tearOrProjectile of tearsAndProjectiles) {
            const ptrHash = GetPtrHash(tearOrProjectile);
            const initialDescription = v.run.initialDescriptions.get(ptrHash);
            if (initialDescription === undefined) {
                continue;
            }
            tearOrProjectile.Position = initialDescription.position;
            tearOrProjectile.PositionOffset = initialDescription.positionOffset;
            tearOrProjectile.Velocity = constants_1.VectorZero;
            tearOrProjectile.Height = initialDescription.height;
            tearOrProjectile.FallingSpeed = 0;
            if ((0, isaacAPIClass_1.isTear)(tearOrProjectile)) {
                tearOrProjectile.FallingAcceleration =
                    initialDescription.fallingAcceleration;
            }
            else {
                tearOrProjectile.FallingAccel = initialDescription.fallingAcceleration;
            }
        }
    }
    // ModCallback.INPUT_ACTION (13)
    // InputHook.GET_ACTION_VALUE (2)
    inputActionGetActionValue = (_entity, _inputHook, buttonAction) => {
        if (buttonAction !== isaac_typescript_definitions_1.ButtonAction.SHOOT_RIGHT) {
            return undefined;
        }
        if (!v.run.shouldUnpause) {
            return undefined;
        }
        v.run.shouldUnpause = false;
        // Returning a value of 1 for a single sub-frame will be enough for the game to register an
        // unpause but not enough for a tear to actually be fired.
        return 1;
    };
    /**
     * Helper function to check if the pause feature from `isaacscript-common` is currently
     * pseudo-pausing the game.
     *
     * @public
     */
    isPaused() {
        return v.run.isPseudoPaused;
    }
    /**
     * Helper function to emulate what happens when the player pauses the game. Use the `unpause`
     * function to return things back to normal.
     *
     * Under the hood, this function:
     * - uses the Pause collectible on every game frame
     * - disables any player inputs (except for `ButtonAction.MENU_CONFIRM` and
     *   `ButtonAction.CONSOLE`)
     *
     * In order to use this function, you must upgrade your mod with `ISCFeature.PAUSE`.
     *
     * @public
     */
    pause() {
        if (v.run.isPseudoPaused) {
            (0, log_1.logError)("Failed to pseudo-pause the game, since it was already pseudo-paused.");
            return;
        }
        v.run.isPseudoPaused = true;
        // Tears/projectiles in the room will move slightly on every frame, even when the Pause
        // collectible is active. Thus, we manually reset the initial positions and heights on every
        // frame.
        v.run.initialDescriptions.clear();
        const tearsAndProjectiles = [...(0, entitiesSpecific_1.getTears)(), ...(0, entitiesSpecific_1.getProjectiles)()];
        for (const tearOrProjectile of tearsAndProjectiles) {
            const ptrHash = GetPtrHash(tearOrProjectile);
            const initialDescription = {
                position: tearOrProjectile.Position,
                positionOffset: tearOrProjectile.PositionOffset,
                velocity: tearOrProjectile.Velocity,
                height: tearOrProjectile.Height,
                fallingSpeed: tearOrProjectile.FallingSpeed,
                fallingAcceleration: (0, isaacAPIClass_1.isTear)(tearOrProjectile)
                    ? tearOrProjectile.FallingAcceleration
                    : tearOrProjectile.FallingAccel,
            };
            v.run.initialDescriptions.set(ptrHash, initialDescription);
        }
        const firstPlayer = Isaac.GetPlayer();
        (0, playerCollectibles_1.useActiveItemTemp)(firstPlayer, isaac_typescript_definitions_1.CollectibleType.PAUSE);
        const tstlClassName = (0, tstlClass_1.getTSTLClassName)(this);
        (0, utils_1.assertDefined)(tstlClassName, "Failed to get the class name for the pause feature.");
        const whitelist = new ReadonlySet_1.ReadonlySet([
            isaac_typescript_definitions_1.ButtonAction.MENU_CONFIRM,
            isaac_typescript_definitions_1.ButtonAction.CONSOLE,
        ]);
        this.disableInputs.disableAllInputsExceptFor(tstlClassName, whitelist);
        for (const player of (0, playerIndex_1.getAllPlayers)()) {
            // Disable the controls to prevent the players from moving, shooting, and so on. (We also
            // disable the inputs in the `INPUT_ACTION` callback, but that does not prevent mouse inputs.)
            player.ControlsEnabled = false;
            // Prevent the players from leaving the room. (If we don't reset the velocity, they can
            // continue to move towards a door.)
            player.Velocity = constants_1.VectorZero;
        }
        this.stopTearsAndProjectilesFromMoving();
    }
    /**
     * Helper function to put things back to normal after the `pause` function was used.
     *
     * In order to use this function, you must upgrade your mod with `ISCFeature.PAUSE`.
     *
     * @public
     */
    unpause() {
        if (!v.run.isPseudoPaused) {
            (0, log_1.logError)("Failed to pseudo-unpause the game, since it was not already pseudo-paused.");
            return;
        }
        v.run.isPseudoPaused = false;
        v.run.shouldUnpause = true;
        const tstlClassName = (0, tstlClass_1.getTSTLClassName)(this);
        (0, utils_1.assertDefined)(tstlClassName, "Failed to find get the class name for the pause feature.");
        this.disableInputs.enableAllInputs(tstlClassName);
        for (const player of (0, playerIndex_1.getAllPlayers)()) {
            player.ControlsEnabled = true;
        }
        // After a vanilla pause, the tears will not resume their normal velocity and will "stick" to
        // the air. Even if we try to help the tears along by explicitly resetting all of the
        // velocity-related variables to their initial values, this will not make a difference. Thus,
        // revert to removing all of the tears and projectiles in the room. (If a Ludovico tear is
        // removed, it will automatically be respawned on the next frame.)
        (0, entitiesSpecific_1.removeAllTears)();
        (0, entitiesSpecific_1.removeAllProjectiles)();
    }
}
exports.Pause = Pause;
__decorate([
    decorators_1.Exported
], Pause.prototype, "isPaused", null);
__decorate([
    decorators_1.Exported
], Pause.prototype, "pause", null);
__decorate([
    decorators_1.Exported
], Pause.prototype, "unpause", null);
