"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.PersistentEntities = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const decorators_1 = require("../../../decorators");
const ISCFeature_1 = require("../../../enums/ISCFeature");
const ModCallbackCustom_1 = require("../../../enums/ModCallbackCustom");
const entities_1 = require("../../../functions/entities");
const roomData_1 = require("../../../functions/roomData");
const Feature_1 = require("../../private/Feature");
const v = {
    run: {
        /** Iterates upward as new persistent entities are created. */
        persistentEntityIndexCounter: 0,
    },
    level: {
        /**
         * Indexed by persistent entity index.
         *
         * When the entity is spawned in the currently room, its corresponding entry in this map will be
         * temporarily deleted (until the entity itself is despawned).
         */
        persistentEntities: new Map(),
    },
    room: {
        spawnedPersistentEntities: new Map(),
    },
};
class PersistentEntities extends Feature_1.Feature {
    /** @internal */
    v = v;
    roomHistory;
    /** @internal */
    constructor(roomHistory) {
        super();
        this.featuresUsed = [ISCFeature_1.ISCFeature.ROOM_HISTORY];
        this.callbacksUsed = [
            // 67
            [isaac_typescript_definitions_1.ModCallback.POST_ENTITY_REMOVE, this.postEntityRemove],
        ];
        this.customCallbacksUsed = [
            [ModCallbackCustom_1.ModCallbackCustom.POST_NEW_ROOM_REORDERED, this.postNewRoomReordered],
        ];
        this.roomHistory = roomHistory;
    }
    // ModCallback.POST_ENTITY_REMOVE (67)
    postEntityRemove = (entity) => {
        const ptrHash = GetPtrHash(entity);
        const tuple = v.room.spawnedPersistentEntities.get(ptrHash);
        if (tuple === undefined) {
            return;
        }
        // A persistent entity is being removed, either because it was killed / manually despawned, or
        // the player left the room.
        const index = tuple[0];
        if (this.roomHistory.isLeavingRoom()) {
            this.trackDespawningPickupPosition(entity, index);
        }
        else {
            this.removePersistentEntity(index, false);
        }
    };
    /**
     * The persistent entity is despawning because the player is in the process of leaving the room.
     * Keep track of the position for later.
     */
    trackDespawningPickupPosition(entity, index) {
        // (The "latest" room description is really the previous room, because the `POST_NEW_ROOM`
        // callback was not fired yet.)
        const previousRoomDescription = this.roomHistory.getLatestRoomDescription();
        if (previousRoomDescription === undefined) {
            return;
        }
        const persistentEntityDescription = {
            entityType: entity.Type,
            variant: entity.Variant,
            subType: entity.SubType,
            dimension: previousRoomDescription.dimension,
            roomListIndex: previousRoomDescription.roomListIndex,
            position: entity.Position,
        };
        v.level.persistentEntities.set(index, persistentEntityDescription);
    }
    // ModCallbackCustom.POST_NEW_ROOM_REORDERED
    postNewRoomReordered = () => {
        const roomListIndex = (0, roomData_1.getRoomListIndex)();
        const persistentEntities = [...v.level.persistentEntities.entries()];
        const persistentEntitiesInThisRoom = persistentEntities.filter(([_index, description]) => roomListIndex === description.roomListIndex);
        for (const [index, description] of persistentEntitiesInThisRoom) {
            v.level.persistentEntities.delete(index);
            this.spawnAndTrack(description.entityType, description.variant, description.subType, description.position, index, true);
        }
    };
    spawnAndTrack(entityType, variant, subType, position, index, respawning = false) {
        const entity = (0, entities_1.spawn)(entityType, variant, subType, position);
        if (respawning) {
            entity.ClearEntityFlags(isaac_typescript_definitions_1.EntityFlag.APPEAR);
        }
        const ptrHash = GetPtrHash(entity);
        // Keep track that we spawned it so that we can respawn it if the player re-enters the room.
        const tuple = [index, EntityPtr(entity)];
        v.room.spawnedPersistentEntities.set(ptrHash, tuple);
        return entity;
    }
    /**
     * Helper function to stop an entity spawned with the `spawnPersistentEntity` helper function from
     * respawning.
     *
     * In order to use this function, you must upgrade your mod with `ISCFeature.PERSISTENT_ENTITIES`.
     *
     * @param persistentEntityIndex The index that was returned by the `spawnPersistentEntity`
     *                              function.
     * @param removeEntity Optional. True by default. Set to false if you want to stop an entity from
     *                     being persistent but you don't want to actually remove the
     *                     currently-spawned entity from the room.
     * @public
     */
    removePersistentEntity(persistentEntityIndex, removeEntity = true) {
        v.level.persistentEntities.delete(persistentEntityIndex);
        for (const [ptrHash, tuple] of v.room.spawnedPersistentEntities) {
            const [index, entityPtr] = tuple;
            if (index !== persistentEntityIndex) {
                continue;
            }
            v.room.spawnedPersistentEntities.delete(ptrHash);
            if (removeEntity && entityPtr.Ref !== undefined) {
                entityPtr.Ref.Remove();
            }
        }
    }
    /**
     * Helper function to spawn an entity that will have persistence similar to a pickup.
     *
     * By default, as soon as you leave a room, any spawned entities will be despawned and will not
     * return if the player revisits the room. This means that if you want to have an entity like a
     * pickup, you have to manually respawn it when the player re-enters the room. Use this helper
     * function to avoid having to do any tracking on your own.
     *
     * Conventionally, the word "persistent" refers to `EntityFlag.FLAG_PERSISTENT`, which is used on
     * e.g. familiars to make them appear in every room. On the other hand, pickups are also
     * persistent, but they are not present in every room, only one specific room. This function
     * spawns entities like pickups, not familiars.
     *
     * In order to use this function, you must upgrade your mod with `ISCFeature.PERSISTENT_ENTITIES`.
     *
     * @returns An object containing the entity and the persistent entity index. You can use the index
     *          with the `removePersistentEntity` function.
     * @public
     */
    spawnPersistentEntity(entityType, variant, subType, position) {
        v.run.persistentEntityIndexCounter++;
        const entity = this.spawnAndTrack(entityType, variant, subType, position, v.run.persistentEntityIndexCounter);
        return { entity, persistentIndex: v.run.persistentEntityIndexCounter };
    }
}
exports.PersistentEntities = PersistentEntities;
__decorate([
    decorators_1.Exported
], PersistentEntities.prototype, "removePersistentEntity", null);
__decorate([
    decorators_1.Exported
], PersistentEntities.prototype, "spawnPersistentEntity", null);
