#include "game/gameplay/room.hpp"

#include <utility>  // Para std::move

#include "core/rendering/surface.hpp"             // Para Surface
#include "core/resources/resource_cache.hpp"      // Para Resource
#include "game/defaults.hpp"                      // Para Defaults::Game
#include "game/gameplay/collision_map.hpp"        // Para CollisionMap
#include "game/gameplay/door_manager.hpp"         // Para DoorManager
#include "game/gameplay/door_tracker.hpp"         // Para DoorTracker
#include "game/gameplay/enemy_manager.hpp"        // Para EnemyManager
#include "game/gameplay/item_manager.hpp"         // Para ItemManager
#include "game/gameplay/item_tracker.hpp"         // Para ItemTracker
#include "game/gameplay/key_manager.hpp"          // Para KeyManager
#include "game/gameplay/key_tracker.hpp"          // Para KeyTracker
#include "game/gameplay/platform_manager.hpp"     // Para PlatformManager
#include "game/gameplay/room_format.hpp"          // Para RoomFormat
#include "game/gameplay/scoreboard.hpp"           // Para Scoreboard::Data
#include "game/gameplay/solid_actor_manager.hpp"  // Para SolidActorManager
#include "game/gameplay/tilemap_renderer.hpp"     // Para TilemapRenderer
#include "utils/defines.hpp"                      // Para TILE_SIZE
#include "utils/utils.hpp"

// Constructor
Room::Room(const std::string& room_path, std::shared_ptr<Scoreboard::Data> data)
    : data_(std::move(data)) {
    auto room = Resource::Cache::get()->getRoom(room_path);

    // Gestor de actores sólidos dinámicos (AABBs): puertas, plataformas móviles,
    // y otros actores que participan en la resolución de colisión del Player
    // sin escribir en el tilemap. Debe ir antes que DoorManager/PlatformManager
    // para poder pasarles su puntero.
    solid_actor_manager_ = std::make_unique<SolidActorManager>();

    // Crea los managers de enemigos, items, plataformas y llaves
    enemy_manager_ = std::make_unique<EnemyManager>();
    item_manager_ = std::make_unique<ItemManager>(room->number, data_);
    platform_manager_ = std::make_unique<PlatformManager>(solid_actor_manager_.get());
    key_manager_ = std::make_unique<KeyManager>(room->number);

    // Crea el mapa de colisiones desde el collision_tile_map
    collision_map_ = std::make_unique<CollisionMap>(room->collision_tile_map);

    // Crea el manager de puertas (registra los Door como SolidActor en el
    // SolidActorManager; ya no escribe tiles en el CollisionMap).
    door_manager_ = std::make_unique<DoorManager>(room->number, solid_actor_manager_.get());

    initializeRoom(*room);

    // Crea el renderizador del tilemap. Se inicializa con el collision tile map ya
    // modificado por las puertas (que han marcado sus celdas como WALL en el
    // collision_map_, pero el renderer solo lo usa para detectar superficies de
    // dibujo, no para colisión).
    tilemap_renderer_ = std::make_unique<TilemapRenderer>(tile_map_, tile_set_width_, surface_, bg_color_);
    tilemap_renderer_->initialize(collision_map_->getCollisionTileMap());
}

// Destructor
Room::~Room() = default;

void Room::initializeRoom(const Data& room) {
    // Asignar valores a las variables miembro
    number_ = room.number;
    bg_color_ = room.bg_color;
    upper_room_ = room.upper_room;
    lower_room_ = room.lower_room;
    left_room_ = room.left_room;
    right_room_ = room.right_room;
    zone_ = room.zone;
    tile_set_file_ = room.tile_set_file;
    music_ = room.music;
    tile_map_ = room.tile_map;  // Tilemap viene embebido en el YAML
    surface_ = Resource::Cache::get()->getSurface(room.tile_set_file);
    tile_set_width_ = surface_->getWidth() / TILE_SIZE;
    is_paused_ = false;

    // Crear los enemigos usando el manager
    for (const auto& enemy_data : room.enemies) {
        enemy_manager_->addEnemy(Enemy::create(enemy_data));
    }

    // Crear las plataformas usando el manager
    for (const auto& plat_data : room.platforms) {
        platform_manager_->addPlatform(std::make_shared<MovingPlatform>(plat_data));
    }

    // Crear los items usando el manager. Cada item lleva sus propios colores
    // (con defaults si el yaml no los especificó); ya no hay color por habitación.
    for (const auto& item : room.items) {
        const SDL_FPoint ITEM_POS = {item.x, item.y};
        if (!ItemTracker::get()->hasBeenPicked(room.number, ITEM_POS)) {
            item_manager_->addItem(std::make_shared<Item>(item));
        }
    }

    // Crear las llaves usando el manager (saltar las ya recogidas)
    for (const auto& key_data : room.keys) {
        const SDL_FPoint KEY_POS{.x = key_data.x, .y = key_data.y};
        if (!KeyTracker::get()->hasBeenPicked(room.number, KEY_POS)) {
            key_manager_->addKey(std::make_shared<Key>(key_data));
        }
    }

    // Crear las puertas usando el manager. Las que ya hayan sido abiertas
    // antes (DoorTracker) se construyen directamente en estado OPENED y no
    // pintan WALLs en el collision_map_.
    for (const auto& door_data : room.doors) {
        const SDL_FPoint DOOR_POS{.x = door_data.x, .y = door_data.y};
        const bool ALREADY_OPENED = DoorTracker::get()->hasBeenOpened(room.number, DOOR_POS);
        door_manager_->addDoor(std::make_shared<Door>(door_data, ALREADY_OPENED));
    }
}

// Dibuja el mapa en pantalla
void Room::renderMap() {
    tilemap_renderer_->render();
}

// Dibuja las plataformas en pantalla
void Room::renderPlatforms() {
    platform_manager_->render();
}

// Dibuja los enemigos en pantalla
void Room::renderEnemies() {
    enemy_manager_->render();
}

// Dibuja los objetos en pantalla
void Room::renderItems() {
    item_manager_->render();
}

// Dibuja las llaves en pantalla
void Room::renderKeys() {
    key_manager_->render();
}

// Dibuja las puertas en pantalla
void Room::renderDoors() {
    door_manager_->render();
}

#ifdef _DEBUG
// Redibuja el mapa (para actualizar modo debug)
void Room::redrawMap() {
    tilemap_renderer_->redrawMap(collision_map_->getCollisionTileMap());
}

// Actualiza animaciones sin mover enemigos (para editor de mapas)
void Room::updateEditorMode(float delta_time) {
    tilemap_renderer_->update(delta_time);
    enemy_manager_->updateAnimations(delta_time);
    platform_manager_->updateAnimations(delta_time);
    item_manager_->update(delta_time);
}

// Resetea enemigos a posiciones iniciales (para editor de mapas)
void Room::resetEnemyPositions(const std::vector<Enemy::Data>& enemy_data) {
    enemy_manager_->resetPositions(enemy_data);
}

// Cambia un tile y repinta la celda (para editor)
void Room::setTile(int index, int tile_value) {
    if (index >= 0 && index < static_cast<int>(tile_map_.size())) {
        tile_map_[index] = tile_value;
        tilemap_renderer_->setTile(index, tile_value);
    }
}

// Cambia un collision tile en vivo (para editor)
void Room::setCollisionTile(int index, int value) {
    collision_map_->setCollisionTile(index, value);
}

// Cambia una conexión de habitación en vivo (para editor)
void Room::setConnection(Border border, const std::string& room_name) {
    switch (border) {
        case Border::TOP:
            upper_room_ = room_name;
            break;
        case Border::BOTTOM:
            lower_room_ = room_name;
            break;
        case Border::LEFT:
            left_room_ = room_name;
            break;
        case Border::RIGHT:
            right_room_ = room_name;
            break;
        default:
            break;
    }
}

// Cambia el tileset en vivo (para editor)
void Room::setTileSet(const std::string& tile_set_file) {
    tile_set_file_ = tile_set_file;
    surface_ = Resource::Cache::get()->getSurface(tile_set_file);
    tile_set_width_ = surface_->getWidth() / TILE_SIZE;
    tilemap_renderer_ = std::make_unique<TilemapRenderer>(
        tile_map_,
        tile_set_width_,
        surface_,
        bg_color_);
    tilemap_renderer_->initialize(collision_map_->getCollisionTileMap());
}

// Cambia el color de fondo en vivo (para editor)
void Room::setBgColor(Uint8 bg_color) {
    bg_color_ = bg_color;
    tilemap_renderer_ = std::make_unique<TilemapRenderer>(
        tile_map_,
        tile_set_width_,
        surface_,
        bg_color_);
    tilemap_renderer_->initialize(collision_map_->getCollisionTileMap());
}
#endif

// Actualiza las variables y objetos de la habitación
void Room::update(float delta_time) {
    if (is_paused_) {
        // Si está en modo pausa no se actualiza nada
        return;
    }

    // Actualiza los tiles animados usando el renderer
    tilemap_renderer_->update(delta_time);

    // Actualiza los enemigos usando el manager
    enemy_manager_->update(delta_time);

    // Actualiza las plataformas usando el manager
    platform_manager_->update(delta_time);

    // Actualiza los items usando el manager
    item_manager_->update(delta_time);

    // Actualiza las llaves usando el manager
    key_manager_->update(delta_time);

    // Actualiza las puertas usando el manager (procesa transiciones a OPENED)
    door_manager_->update(delta_time);
}

// Pone el mapa en modo pausa
void Room::setPaused(bool value) {
    is_paused_ = value;
    tilemap_renderer_->setPaused(value);
    item_manager_->setPaused(value);
    key_manager_->setPaused(value);
    door_manager_->setPaused(value);
}

auto Room::getTileCollider() const -> const TileCollider& {
    return collision_map_->getTileCollider();
}

auto Room::getSolidActors() const -> const SolidActorManager& {
    return *solid_actor_manager_;
}

auto Room::getSolidActors() -> SolidActorManager& {
    return *solid_actor_manager_;
}

auto Room::getCollisionTileMap() const -> const std::vector<int>& {
    return collision_map_->getCollisionTileMap();
}

void Room::updateCollisionBorders(const CollisionMap::AdjacentData& adjacent) {
    collision_map_->updateBorders(adjacent);
}

void Room::updateSolidActorBorders(const SolidActorManager::AdjacentActors& adjacent) {
    solid_actor_manager_->setAdjacent(adjacent);
}

// Devuelve la cadena del fichero de la habitación contigua segun el borde
auto Room::getRoom(Border border) -> std::string {
    switch (border) {
        case Border::TOP:
            return upper_room_;
        case Border::BOTTOM:
            return lower_room_;
        case Border::RIGHT:
            return right_room_;
        case Border::LEFT:
            return left_room_;
        default:
            return "";
    }
}

auto Room::enemyCollision(SDL_FRect& rect) -> bool {
    return enemy_manager_->checkCollision(rect);
}

auto Room::itemCollision(SDL_FRect& rect) -> bool {
    return item_manager_->checkCollision(rect);
}

auto Room::keyCollision(SDL_FRect& rect) -> bool {
    return key_manager_->checkCollision(rect);
}

void Room::tryUnlockDoors(const SDL_FRect& player_rect) {
    door_manager_->tryUnlock(player_rect);
}

// Carga una habitación desde un archivo YAML (delegado a RoomFormat)
auto Room::loadYAML(const std::string& file_path, bool verbose) -> Data {
    return RoomFormat::loadYAML(file_path, verbose);
}
