#include "game/gameplay/room.hpp"

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

#include "core/rendering/screen.hpp"           // Para Screen
#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/enemy_manager.hpp"     // Para EnemyManager
#include "game/gameplay/item_manager.hpp"      // Para ItemManager
#include "game/gameplay/item_tracker.hpp"      // Para ItemTracker
#include "game/gameplay/room_loader.hpp"       // Para RoomLoader
#include "game/gameplay/scoreboard.hpp"        // Para Scoreboard::Data
#include "game/gameplay/tilemap_renderer.hpp"  // Para TilemapRenderer
#include "utils/defines.hpp"                   // Para TILE_SIZE
#include "utils/utils.hpp"                     // Para stringToColor

// 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);

    // Crea los managers de enemigos e items
    enemy_manager_ = std::make_unique<EnemyManager>();
    item_manager_ = std::make_unique<ItemManager>(room->name, data_);

    initializeRoom(*room);
    openTheJail();  // Abre la Jail si se da el caso

    // Crea el mapa de colisiones (necesita tile_map_, tile_set_width_, conveyor_belt_direction_)
    collision_map_ = std::make_unique<CollisionMap>(tile_map_, tile_set_width_, conveyor_belt_direction_);

    // Crea el renderizador del tilemap (necesita tile_map_, tile_set_width_, surface_, bg_color_, conveyor_belt_direction_)
    tilemap_renderer_ = std::make_unique<TilemapRenderer>(tile_map_, tile_set_width_, surface_, bg_color_, conveyor_belt_direction_);
    tilemap_renderer_->initialize(collision_map_.get());  // Inicializa (crea map_surface, pinta tiles, busca animados)

    Screen::get()->setBorderColor(stringToColor(border_color_));  // Establece el color del borde
}

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

void Room::initializeRoom(const Data& room) {
    // Asignar valores a las variables miembro
    number_ = room.number;
    name_ = room.name;
    bg_color_ = room.bg_color;
    border_color_ = room.border_color;
    item_color1_ = room.item_color1.empty() ? "yellow" : room.item_color1;
    item_color2_ = room.item_color2.empty() ? "magenta" : room.item_color2;
    upper_room_ = room.upper_room;
    lower_room_ = room.lower_room;
    left_room_ = room.left_room;
    right_room_ = room.right_room;
    tile_set_file_ = room.tile_set_file;
    conveyor_belt_direction_ = room.conveyor_belt_direction;
    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(std::make_shared<Enemy>(enemy_data));
    }

    // Crear los items usando el manager
    for (const auto& item : room.items) {
        const SDL_FPoint ITEM_POS = {item.x, item.y};

        if (!ItemTracker::get()->hasBeenPicked(room.name, ITEM_POS)) {
            // Crear una copia local de los datos del item
            Item::Data item_copy = item;
            item_copy.color1 = stringToColor(item_color1_);
            item_copy.color2 = stringToColor(item_color2_);

            // Crear el objeto Item usando la copia modificada
            item_manager_->addItem(std::make_shared<Item>(item_copy));
        }
    }
}

// Abre la jail para poder entrar
void Room::openTheJail() {
    if (data_->jail_is_open && number_ == Defaults::Game::Room::END_ROOM) {
        // Elimina el último enemigo (Bry debe ser el último enemigo definido en el fichero)
        if (!enemy_manager_->isEmpty()) {
            enemy_manager_->removeLastEnemy();
        }

        // Abre las puertas
        constexpr int TILE_A = 16 + (13 * 32);
        constexpr int TILE_B = 16 + (14 * 32);
        if (TILE_A < tile_map_.size()) {
            tile_map_[TILE_A] = -1;
        }
        if (TILE_B < tile_map_.size()) {
            tile_map_[TILE_B] = -1;
        }
    }
}

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

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

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

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

// 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);
    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 color de fondo y redibuja el mapa (para editor)
void Room::setBgColor(const std::string& color) {
    bg_color_ = color;
    tilemap_renderer_->setBgColor(color);
    tilemap_renderer_->redrawMap(collision_map_.get());
}

// Cambia colores de items en vivo (para editor)
void Room::setItemColors(const std::string& color1, const std::string& color2) {
    item_color1_ = color1;
    item_color2_ = color2;
    Uint8 c1 = stringToColor(color1);
    Uint8 c2 = stringToColor(color2);
    auto* item_mgr = item_manager_.get();
    for (int i = 0; i < item_mgr->getCount(); ++i) {
        item_mgr->getItem(i)->setColors(c1, c2);
    }
}
#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 los items usando el manager
    item_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);
}

// 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 "";
    }
}

// Devuelve el tipo de tile que hay en ese pixel
auto Room::getTile(SDL_FPoint point) -> Tile {
    // Delega a CollisionMap y convierte el resultado
    const auto COLLISION_TILE = collision_map_->getTile(point);
    return static_cast<Tile>(COLLISION_TILE);
}

// Devuelve el tipo de tile en un índice del tilemap
auto Room::getTile(int index) -> Tile {
    // Delega a CollisionMap y convierte el resultado
    const auto COLLISION_TILE = collision_map_->getTile(index);
    return static_cast<Tile>(COLLISION_TILE);
}

// Indica si hay colision con un enemigo a partir de un rectangulo
auto Room::enemyCollision(SDL_FRect& rect) -> bool {
    return enemy_manager_->checkCollision(rect);
}

// Indica si hay colision con un objeto a partir de un rectangulo
auto Room::itemCollision(SDL_FRect& rect) -> bool {
    return item_manager_->checkCollision(rect);
}

// Obten la coordenada de la cuesta a partir de un punto perteneciente a ese tile
auto Room::getSlopeHeight(SDL_FPoint p, Tile slope) -> int {
    // Delega a CollisionMap (método estático)
    const auto COLLISION_TILE = static_cast<CollisionMap::Tile>(slope);
    return CollisionMap::getSlopeHeight(p, COLLISION_TILE);
}

// === Métodos de colisión (delegados a CollisionMap) ===

// Comprueba las colisiones con paredes derechas
auto Room::checkRightSurfaces(const SDL_FRect& rect) -> int {
    return collision_map_->checkRightSurfaces(rect);
}

// Comprueba las colisiones con paredes izquierdas
auto Room::checkLeftSurfaces(const SDL_FRect& rect) -> int {
    return collision_map_->checkLeftSurfaces(rect);
}

// Comprueba las colisiones con techos
auto Room::checkTopSurfaces(const SDL_FRect& rect) -> int {
    return collision_map_->checkTopSurfaces(rect);
}

// Comprueba las colisiones punto con techos
auto Room::checkTopSurfaces(const SDL_FPoint& p) -> bool {
    return collision_map_->checkTopSurfaces(p);
}

// Comprueba las colisiones con suelos
auto Room::checkBottomSurfaces(const SDL_FRect& rect) -> int {
    return collision_map_->checkBottomSurfaces(rect);
}

// Comprueba las colisiones con conveyor belts
auto Room::checkAutoSurfaces(const SDL_FRect& rect) -> int {
    return collision_map_->checkAutoSurfaces(rect);
}

// Comprueba las colisiones punto con conveyor belts
auto Room::checkConveyorBelts(const SDL_FPoint& p) -> bool {
    return collision_map_->checkConveyorBelts(p);
}

// Comprueba las colisiones línea con rampas izquierdas
auto Room::checkLeftSlopes(const LineVertical& line) -> int {
    return collision_map_->checkLeftSlopes(line);
}

// Comprueba las colisiones punto con rampas izquierdas
auto Room::checkLeftSlopes(const SDL_FPoint& p) -> bool {
    return collision_map_->checkLeftSlopes(p);
}

// Comprueba las colisiones línea con rampas derechas
auto Room::checkRightSlopes(const LineVertical& line) -> int {
    return collision_map_->checkRightSlopes(line);
}

// Comprueba las colisiones punto con rampas derechas
auto Room::checkRightSlopes(const SDL_FPoint& p) -> bool {
    return collision_map_->checkRightSlopes(p);
}

// Obtiene puntero a slope en un punto
auto Room::getSlopeAtPoint(const SDL_FPoint& p) const -> const LineDiagonal* {
    return collision_map_->getSlopeAtPoint(p);
}

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