#pragma once

#include <SDL3/SDL.h>

#include <vector>

class SolidActor;

/**
 * @brief Gestor de actores sólidos dinámicos (AABB) de una habitación.
 *
 * Paralelo al TileCollider: proporciona sweeps axis-aligned del Player
 * contra una lista de AABBs de entidades (puertas, plataformas móviles,
 * bloques empujables, ascensores, etc). El manager NO es propietario de
 * los actores — sus entidades concretas viven en sus respectivos managers
 * (DoorManager, PlatformManager, ...). Este manager solo mantiene una
 * lista de raw pointers para consultas de colisión.
 *
 * Cross-room: igual que CollisionMap::updateBorders, este manager acepta
 * punteros a los managers de las rooms adyacentes (setAdjacent). Cuando
 * el Player está cerca del borde, los sweeps iteran también sobre los
 * actores vecinos, trasladando sus AABBs por ±PlayArea::WIDTH/HEIGHT
 * para ponerlos en el sistema de coordenadas de la room actual.
 */
class SolidActorManager {
   public:
    struct FloorHit {
        float y{-1};
        SolidActor* carrier{nullptr};
    };

    struct AdjacentActors {
        SolidActorManager* upper{nullptr};
        SolidActorManager* lower{nullptr};
        SolidActorManager* left{nullptr};
        SolidActorManager* right{nullptr};
    };

    SolidActorManager() = default;
    ~SolidActorManager() = default;

    SolidActorManager(const SolidActorManager&) = delete;
    auto operator=(const SolidActorManager&) -> SolidActorManager& = delete;
    SolidActorManager(SolidActorManager&&) = delete;
    auto operator=(SolidActorManager&&) -> SolidActorManager& = delete;

    // Registro de actores (el manager no es propietario)
    void registerActor(SolidActor* actor);
    void unregisterActor(SolidActor* actor);
    void clear();

    // Cross-room: punteros a los managers de las rooms adyacentes
    void setAdjacent(const AdjacentActors& adjacent);
    void clearAdjacent();

    // Sweeps del Player (paralelos a TileCollider). Todas las coordenadas
    // están en el sistema de la room actual (room-local pixel space).

    /// Devuelve el x del borde derecho del primer actor bloqueante cuyo AABB
    /// esté "a la izquierda" del Player y solape su rect (px, py, pw, ph).
    /// Collision::NONE si no hay colisión.
    [[nodiscard]] auto checkWallLeft(float px, float py, float pw, float ph) const -> float;

    /// Devuelve el x del borde izquierdo del primer actor bloqueante cuyo AABB
    /// esté "a la derecha" del Player y solape su rect. Collision::NONE si no.
    [[nodiscard]] auto checkWallRight(float px, float py, float pw, float ph) const -> float;

    /// Devuelve el y del borde inferior del primer techo bloqueante que solape
    /// la parte superior del Player a (px, py, pw). Collision::NONE si no.
    [[nodiscard]] auto checkCeiling(float px, float py, float pw) const -> float;

    /// Devuelve el suelo más cercano debajo de los pies del Player dentro del
    /// rango vertical [foot_y_cur, foot_y_new]. Los actores ONEWAY_TOP solo
    /// colisionan si foot_y_cur <= r.y (el Player venía desde arriba).
    /// Si el actor tiene CARRY_ON_TOP, FloorHit.carrier apunta a él.
    [[nodiscard]] auto checkFloor(float px, float foot_y_cur, float pw, float foot_y_new) const -> FloorHit;

    /// Devuelve true si hay algún actor con top justo debajo de los pies
    /// (dentro de 1 px). Usado por checkFalling para no desengancharse de
    /// plataformas móviles.
    [[nodiscard]] auto hasGroundBelow(float px, float foot_y, float pw) const -> bool;

   private:
    // Itera sobre todos los actores relevantes (locales + vecinos) aplicando
    // el offset correcto a los AABBs vecinos. La lambda recibe (actor, aabb_world).
    template <typename Fn>
    void forEachActor(Fn&& fn) const;

    std::vector<SolidActor*> actors_;
    AdjacentActors adjacent_{};
};
