#include "game/gameplay/room.hpp"

#include <algorithm>  // Para std::ranges::any_of
#include <exception>  // Para exception
#include <fstream>    // Para basic_ostream, operator<<, basic_istream
#include <iostream>   // Para cout, cerr
#include <sstream>    // Para basic_stringstream
#include <utility>

#include "core/rendering/screen.hpp"          // Para Screen
#include "core/rendering/surface.hpp"         // Para Surface
#include "core/rendering/surface_sprite.hpp"  // Para SSprite
#include "core/resources/resource.hpp"        // Para Resource
#include "core/system/debug.hpp"              // Para Debug
#include "external/jail_audio.h"              // Para JA_PlaySound
#include "game/gameplay/item_tracker.hpp"     // Para ItemTracker
#include "game/gameplay/scoreboard.hpp"       // Para ScoreboardData
#include "game/options.hpp"                   // Para Options, OptionsStats, options
#include "utils/defines.hpp"                  // Para BLOCK, PLAY_AREA_HEIGHT, PLAY_AREA_WIDTH
#include "utils/utils.hpp"                    // Para LineHorizontal, LineDiagonal, LineVertical

// Carga las variables y texturas desde un fichero de mapa de tiles
auto loadRoomTileFile(const std::string& file_path, bool verbose) -> std::vector<int> {
    std::vector<int> tile_map_file;
    const std::string FILENAME = file_path.substr(file_path.find_last_of("\\/") + 1);
    std::ifstream file(file_path);

    // El fichero se puede abrir
    if (file.good()) {
        std::string line;
        // Procesa el fichero linea a linea
        while (std::getline(file, line)) {  // Lee el fichero linea a linea
            if (line.find("data encoding") != std::string::npos) {
                // Lee la primera linea
                std::getline(file, line);
                while (line != "</data>") {  // Procesa lineas mientras haya
                    std::stringstream ss(line);
                    std::string tmp;
                    while (getline(ss, tmp, ',')) {
                        tile_map_file.push_back(std::stoi(tmp) - 1);
                    }

                    // Lee la siguiente linea
                    std::getline(file, line);
                }
            }
        }

        // Cierra el fichero
        if (verbose) {
            std::cout << "TileMap loaded: " << FILENAME.c_str() << '\n';
        }
        file.close();
    }

    else {  // El fichero no se puede abrir
        if (verbose) {
            std::cout << "Warning: Unable to open " << FILENAME.c_str() << " file" << '\n';
        }
    }

    return tile_map_file;
}

// Parsea una línea en key y value separados por '='
auto parseKeyValue(const std::string& line) -> std::pair<std::string, std::string> {
    int pos = line.find('=');
    std::string key = line.substr(0, pos);
    std::string value = line.substr(pos + 1, line.length());
    return {key, value};
}

// Muestra un warning de parámetro desconocido
void logUnknownParameter(const std::string& file_name, const std::string& key, bool verbose) {
    if (verbose) {
        std::cout << "Warning: file " << file_name.c_str() << "\n, unknown parameter \"" << key.c_str() << "\"" << '\n';
    }
}

// Carga un bloque [enemy]...[/enemy] desde un archivo
auto loadEnemyFromFile(std::ifstream& file, const std::string& file_name, bool verbose) -> EnemyData {
    EnemyData enemy;
    enemy.flip = false;
    enemy.mirror = false;
    enemy.frame = -1;

    std::string line;
    do {
        std::getline(file, line);
        auto [key, value] = parseKeyValue(line);

        if (!setEnemy(&enemy, key, value)) {
            logUnknownParameter(file_name, key, verbose);
        }
    } while (line != "[/enemy]");

    return enemy;
}

// Carga un bloque [item]...[/item] desde un archivo
auto loadItemFromFile(std::ifstream& file, const std::string& file_name, bool verbose) -> ItemData {
    ItemData item;
    item.counter = 0;
    item.color1 = stringToColor("yellow");
    item.color2 = stringToColor("magenta");

    std::string line;
    do {
        std::getline(file, line);
        auto [key, value] = parseKeyValue(line);

        if (!setItem(&item, key, value)) {
            logUnknownParameter(file_name, key, verbose);
        }
    } while (line != "[/item]");

    return item;
}

// Carga las variables desde un fichero de mapa
auto loadRoomFile(const std::string& file_path, bool verbose) -> RoomData {
    RoomData room;
    room.item_color1 = "yellow";
    room.item_color2 = "magenta";
    room.conveyor_belt_direction = 1;

    const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
    room.number = FILE_NAME.substr(0, FILE_NAME.find_last_of('.'));

    std::ifstream file(file_path);

    // El fichero se puede abrir
    if (file.good()) {
        std::string line;
        // Procesa el fichero linea a linea
        while (std::getline(file, line)) {
            // Si la linea contiene el texto [enemy] se realiza el proceso de carga de un enemigo
            if (line == "[enemy]") {
                room.enemies.push_back(loadEnemyFromFile(file, FILE_NAME, verbose));
            }
            // Si la linea contiene el texto [item] se realiza el proceso de carga de un item
            else if (line == "[item]") {
                room.items.push_back(loadItemFromFile(file, FILE_NAME, verbose));
            }
            // En caso contrario se parsea el fichero para buscar las variables y los valores
            else {
                auto [key, value] = parseKeyValue(line);
                if (!setRoom(&room, key, value)) {
                    logUnknownParameter(FILE_NAME, key, verbose);
                }
            }
        }

        // Cierra el fichero
        if (verbose) {
            std::cout << "Room loaded: " << FILE_NAME.c_str() << '\n';
        }
        file.close();
    }
    // El fichero no se puede abrir
    else {
        std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << '\n';
    }

    return room;
}

// Asigna variables a una estructura RoomData
auto setRoom(RoomData* room, const std::string& key, const std::string& value) -> bool {
    // Indicador de éxito en la asignación
    bool success = true;

    try {
        if (key == "tileMapFile") {
            room->tile_map_file = value;
        } else if (key == "name") {
            room->name = value;
        } else if (key == "bgColor") {
            room->bg_color = value;
        } else if (key == "border") {
            room->border_color = value;
        } else if (key == "itemColor1") {
            room->item_color1 = value;
        } else if (key == "itemColor2") {
            room->item_color2 = value;
        } else if (key == "tileSetFile") {
            room->tile_set_file = value;
        } else if (key == "roomUp") {
            room->upper_room = value;
        } else if (key == "roomDown") {
            room->lower_room = value;
        } else if (key == "roomLeft") {
            room->left_room = value;
        } else if (key == "roomRight") {
            room->right_room = value;
        } else if (key == "autoSurface") {
            room->conveyor_belt_direction = (value == "right") ? 1 : -1;
        } else if (key.empty() || key.substr(0, 1) == "#") {
            // No se realiza ninguna acción para estas claves
        } else {
            success = false;
        }
    } catch (const std::exception& e) {
        std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << '\n';
        success = false;
    }

    return success;
}

// Asigna variables a una estructura EnemyData
auto setEnemy(EnemyData* enemy, const std::string& key, const std::string& value) -> bool {
    // Indicador de éxito en la asignación
    bool success = true;

    try {
        if (key == "tileSetFile") {
            enemy->surface_path = value;
        } else if (key == "animation") {
            enemy->animation_path = value;
        } else if (key == "width") {
            enemy->w = std::stoi(value);
        } else if (key == "height") {
            enemy->h = std::stoi(value);
        } else if (key == "x") {
            enemy->x = std::stof(value) * BLOCK;
        } else if (key == "y") {
            enemy->y = std::stof(value) * BLOCK;
        } else if (key == "vx") {
            enemy->vx = std::stof(value);
        } else if (key == "vy") {
            enemy->vy = std::stof(value);
        } else if (key == "x1") {
            enemy->x1 = std::stoi(value) * BLOCK;
        } else if (key == "x2") {
            enemy->x2 = std::stoi(value) * BLOCK;
        } else if (key == "y1") {
            enemy->y1 = std::stoi(value) * BLOCK;
        } else if (key == "y2") {
            enemy->y2 = std::stoi(value) * BLOCK;
        } else if (key == "flip") {
            enemy->flip = stringToBool(value);
        } else if (key == "mirror") {
            enemy->mirror = stringToBool(value);
        } else if (key == "color") {
            enemy->color = value;
        } else if (key == "frame") {
            enemy->frame = std::stoi(value);
        } else if (key == "[/enemy]" || key == "tileSetFile" || key.substr(0, 1) == "#") {
            // No se realiza ninguna acción para estas claves
        } else {
            success = false;
        }
    } catch (const std::exception& e) {
        std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << '\n';
        success = false;
    }

    return success;
}

// Asigna variables a una estructura ItemData
auto setItem(ItemData* item, const std::string& key, const std::string& value) -> bool {
    // Indicador de éxito en la asignación
    bool success = true;

    try {
        if (key == "tileSetFile") {
            item->tile_set_file = value;
        } else if (key == "counter") {
            item->counter = std::stoi(value);
        } else if (key == "x") {
            item->x = std::stof(value) * BLOCK;
        } else if (key == "y") {
            item->y = std::stof(value) * BLOCK;
        } else if (key == "tile") {
            item->tile = std::stof(value);
        } else if (key == "[/item]") {
            // No se realiza ninguna acción para esta clave
        } else {
            success = false;
        }
    } catch (const std::exception& e) {
        std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << '\n';
        success = false;
    }

    return success;
}

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

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

    // Inicializa las superficies de colision
    initRoomSurfaces();

    // Busca los tiles animados
    setAnimatedTiles();

    // Crea la textura para el mapa de tiles de la habitación
    map_surface_ = std::make_shared<Surface>(PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT);

    // Pinta el mapa de la habitación en la textura
    fillMapTexture();

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

void Room::initializeRoom(const RoomData& 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;
    tile_map_file_ = room.tile_map_file;
    conveyor_belt_direction_ = room.conveyor_belt_direction;
    tile_map_ = Resource::get()->getTileMap(room.tile_map_file);
    surface_ = Resource::get()->getSurface(room.tile_set_file);
    tile_set_width_ = surface_->getWidth() / TILE_SIZE;
    is_paused_ = false;
    counter_ = 0;

    // Crear los enemigos
    for (const auto& enemy_data : room.enemies) {
        enemies_.emplace_back(std::make_shared<Enemy>(enemy_data));
    }

    // Crear los items
    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
            ItemData item_copy = item;
            item_copy.color1 = stringToColor(item_color1_);
            item_copy.color2 = stringToColor(item_color2_);

            // Crear el objeto Item usando la copia modificada
            items_.emplace_back(std::make_shared<Item>(item_copy));
        }
    }
}

// Crea la textura con el mapeado de la habitación
void Room::fillMapTexture() {
    const Uint8 COLOR = stringToColor(bg_color_);
    auto previuos_renderer = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(map_surface_);
    map_surface_->clear(COLOR);

    // Los tileSetFiles son de 20x20 tiles. El primer tile es el 0. Cuentan hacia la derecha y hacia abajo

    SDL_FRect clip = {0, 0, TILE_SIZE, TILE_SIZE};
    for (int y = 0; y < MAP_HEIGHT; ++y) {
        for (int x = 0; x < MAP_WIDTH; ++x) {
            // Tiled pone los tiles vacios del mapa como cero y empieza a contar de 1 a n.
            // Al cargar el mapa en memoria, se resta uno, por tanto los tiles vacios son -1
            // Tampoco hay que dibujar los tiles animados que estan en la fila 19 (indices)
            const int INDEX = (y * MAP_WIDTH) + x;
            const bool A = (tile_map_[INDEX] >= 18 * tile_set_width_) && (tile_map_[INDEX] < 19 * tile_set_width_);
            const bool B = tile_map_[INDEX] > -1;

            if (B && !A) {
                clip.x = (tile_map_[INDEX] % tile_set_width_) * TILE_SIZE;
                clip.y = (tile_map_[INDEX] / tile_set_width_) * TILE_SIZE;
                surface_->render(x * TILE_SIZE, y * TILE_SIZE, &clip);
            }
        }
    }

#ifdef _DEBUG
    if (Debug::get()->getEnabled()) {
        auto surface = Screen::get()->getRendererSurface();

        // BottomSurfaces
        {
            for (auto l : bottom_floors_) {
                surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast<Uint8>(PaletteColor::BLUE));
            }
        }

        // TopSurfaces
        {
            for (auto l : top_floors_) {
                surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast<Uint8>(PaletteColor::RED));
            }
        }

        // LeftSurfaces
        {
            for (auto l : left_walls_) {
                surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast<Uint8>(PaletteColor::GREEN));
            }
        }

        // RightSurfaces
        {
            for (auto l : right_walls_) {
                surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast<Uint8>(PaletteColor::MAGENTA));
            }
        }

        // LeftSlopes
        {
            for (auto l : left_slopes_) {
                surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast<Uint8>(PaletteColor::CYAN));
            }
        }

        // RightSlopes
        {
            for (auto l : right_slopes_) {
                surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast<Uint8>(PaletteColor::YELLOW));
            }
        }

        // AutoSurfaces
        {
            for (auto l : conveyor_belt_floors_) {
                surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast<Uint8>(PaletteColor::WHITE));
            }
        }
    }

#endif  // _DEBUG
    Screen::get()->setRendererSurface(previuos_renderer);
}

// Dibuja el mapa en pantalla
void Room::renderMap() {
    // Dibuja la textura con el mapa en pantalla
    SDL_FRect dest = {0, 0, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT};
    map_surface_->render(nullptr, &dest);

// Dibuja los tiles animados
#ifdef _DEBUG
    if (!Debug::get()->getEnabled()) {
        renderAnimatedTiles();
    }
#else
    renderAnimatedTiles();
#endif
}

// Dibuja los enemigos en pantalla
void Room::renderEnemies() {
    for (const auto& enemy : enemies_) {
        enemy->render();
    }
}

// Dibuja los objetos en pantalla
void Room::renderItems() {
    for (const auto& item : items_) {
        item->render();
    }
}

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

    // Actualiza el contador
    counter_++;

    // Actualiza los tiles animados
    updateAnimatedTiles();

    for (const auto& enemy : enemies_) {
        // Actualiza los enemigos
        enemy->update();
    }

    for (const auto& item : items_) {
        // Actualiza los items
        item->update();
    }
}

// Devuelve la cadena del fichero de la habitación contigua segun el borde
auto Room::getRoom(RoomBorder border) -> std::string {
    switch (border) {
        case RoomBorder::TOP:
            return upper_room_;
            break;

        case RoomBorder::BOTTOM:
            return lower_room_;
            break;

        case RoomBorder::RIGHT:
            return right_room_;
            break;

        case RoomBorder::LEFT:
            return left_room_;
            break;

        default:
            break;
    }
    return "";
}

// Devuelve el tipo de tile que hay en ese pixel
auto Room::getTile(SDL_FPoint point) -> TileType {
    const int POS = ((point.y / TILE_SIZE) * MAP_WIDTH) + (point.x / TILE_SIZE);
    return getTile(POS);
}

// Devuelve el tipo de tile que hay en ese indice
auto Room::getTile(int index) -> TileType {
    // const bool onRange = (index > -1) && (index < mapWidth * mapHeight);
    const bool ON_RANGE = (index > -1) && (index < (int)tile_map_.size());

    if (ON_RANGE) {
        // Las filas 0-8 son de tiles t_wall
        if ((tile_map_[index] >= 0) && (tile_map_[index] < 9 * tile_set_width_)) {
            return TileType::WALL;
        }

        // Las filas 9-17 son de tiles t_passable
        if ((tile_map_[index] >= 9 * tile_set_width_) && (tile_map_[index] < 18 * tile_set_width_)) {
            return TileType::PASSABLE;
        }

        // Las filas 18-20 es de tiles t_animated
        if ((tile_map_[index] >= 18 * tile_set_width_) && (tile_map_[index] < 21 * tile_set_width_)) {
            return TileType::ANIMATED;
        }

        // La fila 21 es de tiles t_slope_r
        if ((tile_map_[index] >= 21 * tile_set_width_) && (tile_map_[index] < 22 * tile_set_width_)) {
            return TileType::SLOPE_R;
        }

        // La fila 22 es de tiles t_slope_l
        if ((tile_map_[index] >= 22 * tile_set_width_) && (tile_map_[index] < 23 * tile_set_width_)) {
            return TileType::SLOPE_L;
        }

        // La fila 23 es de tiles t_kill
        if ((tile_map_[index] >= 23 * tile_set_width_) && (tile_map_[index] < 24 * tile_set_width_)) {
            return TileType::KILL;
        }
    }

    return TileType::EMPTY;
}

// Indica si hay colision con un enemigo a partir de un rectangulo
auto Room::enemyCollision(SDL_FRect& rect) -> bool {
    return std::ranges::any_of(enemies_, [&rect](const auto& enemy) {
        return checkCollision(rect, enemy->getCollider());
    });
}

// Indica si hay colision con un objeto a partir de un rectangulo
auto Room::itemCollision(SDL_FRect& rect) -> bool {
    for (int i = 0; i < static_cast<int>(items_.size()); ++i) {
        if (checkCollision(rect, items_.at(i)->getCollider())) {
            ItemTracker::get()->addItem(name_, items_.at(i)->getPos());
            items_.erase(items_.begin() + i);
            JA_PlaySound(Resource::get()->getSound("item.wav"));
            data_->items++;
            Options::stats.items = data_->items;
            return true;
        }
    }

    return false;
}

// Obten la coordenada de la cuesta a partir de un punto perteneciente a ese tile
auto Room::getSlopeHeight(SDL_FPoint p, TileType slope) -> int {
    // Calcula la base del tile
    int base = ((p.y / TILE_SIZE) * TILE_SIZE) + TILE_SIZE;
#ifdef _DEBUG
    Debug::get()->add("BASE = " + std::to_string(base));
#endif

    // Calcula cuanto se ha entrado en el tile horizontalmente
    const int POS = (static_cast<int>(p.x) % TILE_SIZE);  // Esto da un valor entre 0 y 7
#ifdef _DEBUG
    Debug::get()->add("POS = " + std::to_string(POS));
#endif

    // Se resta a la base la cantidad de pixeles pos en funcion de la rampa
    if (slope == TileType::SLOPE_R) {
        base -= POS + 1;
#ifdef _DEBUG
        Debug::get()->add("BASE_R = " + std::to_string(base));
#endif
    } else {
        base -= (TILE_SIZE - POS);
#ifdef _DEBUG
        Debug::get()->add("BASE_L = " + std::to_string(base));
#endif
    }

    return base;
}

// Helper: recopila tiles inferiores (muros sin muro debajo)
auto Room::collectBottomTiles() -> std::vector<int> {
    std::vector<int> tile;

    // Busca todos los tiles de tipo muro que no tengan debajo otro muro
    // Hay que recorrer la habitación por filas (excepto los de la última fila)
    for (int i = 0; i < (int)tile_map_.size() - MAP_WIDTH; ++i) {
        if (getTile(i) == TileType::WALL && getTile(i + MAP_WIDTH) != TileType::WALL) {
            tile.push_back(i);

            // Si llega al final de la fila, introduce un separador
            if (i % MAP_WIDTH == MAP_WIDTH - 1) {
                tile.push_back(-1);
            }
        }
    }

    // Añade un terminador
    tile.push_back(-1);
    return tile;
}

// Helper: recopila tiles superiores (muros o pasables sin muro encima)
auto Room::collectTopTiles() -> std::vector<int> {
    std::vector<int> tile;

    // Busca todos los tiles de tipo muro o pasable que no tengan encima un muro
    // Hay que recorrer la habitación por filas (excepto los de la primera fila)
    for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) {
        if ((getTile(i) == TileType::WALL || getTile(i) == TileType::PASSABLE) && getTile(i - MAP_WIDTH) != TileType::WALL) {
            tile.push_back(i);

            // Si llega al final de la fila, introduce un separador
            if (i % MAP_WIDTH == MAP_WIDTH - 1) {
                tile.push_back(-1);
            }
        }
    }

    // Añade un terminador
    tile.push_back(-1);
    return tile;
}

// Helper: construye lineas horizontales a partir de tiles consecutivos
void Room::buildHorizontalLines(const std::vector<int>& tiles, std::vector<LineHorizontal>& lines, bool is_bottom_surface) {
    if (tiles.size() <= 1) {
        return;
    }

    int i = 0;
    while (i < (int)tiles.size() - 1) {
        LineHorizontal line;
        line.x1 = (tiles[i] % MAP_WIDTH) * TILE_SIZE;

        // Calcula Y segun si es superficie inferior o superior
        if (is_bottom_surface) {
            line.y = ((tiles[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
        } else {
            line.y = (tiles[i] / MAP_WIDTH) * TILE_SIZE;
        }

        int last_one = i;
        i++;

        // Encuentra tiles consecutivos
        if (i < (int)tiles.size()) {
            while (tiles[i] == tiles[i - 1] + 1) {
                last_one = i;
                i++;
                if (i >= (int)tiles.size()) {
                    break;
                }
            }
        }

        line.x2 = ((tiles[last_one] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
        lines.push_back(line);

        // Salta separadores
        if (i < (int)tiles.size() && tiles[i] == -1) {
            i++;
        }
    }
}

// Calcula las superficies inferiores
void Room::setBottomSurfaces() {
    std::vector<int> tile = collectBottomTiles();
    buildHorizontalLines(tile, bottom_floors_, true);
}

// Calcula las superficies superiores
void Room::setTopSurfaces() {
    std::vector<int> tile = collectTopTiles();
    buildHorizontalLines(tile, top_floors_, false);
}

// Calcula las superficies laterales izquierdas
void Room::setLeftSurfaces() {
    std::vector<int> tile;

    // Busca todos los tiles de tipo muro que no tienen a su izquierda un tile de tipo muro
    // Hay que recorrer la habitación por columnas (excepto los de la primera columna)
    for (int i = 1; i < MAP_WIDTH; ++i) {
        for (int j = 0; j < MAP_HEIGHT; ++j) {
            const int POS = ((j * MAP_WIDTH) + i);
            if (getTile(POS) == TileType::WALL && getTile(POS - 1) != TileType::WALL) {
                tile.push_back(POS);
            }
        }
    }

    // Añade un terminador
    tile.push_back(-1);

    // Recorre el vector de tiles buscando tiles consecutivos
    // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth)
    // para localizar las superficies
    if ((int)tile.size() > 1) {
        int i = 0;
        do {
            LineVertical line;
            line.x = (tile[i] % MAP_WIDTH) * TILE_SIZE;
            line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE);
            while (tile[i] + MAP_WIDTH == tile[i + 1]) {
                if (i == (int)tile.size() - 1) {
                    break;
                }
                i++;
            }
            line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
            left_walls_.push_back(line);
            i++;
        } while (i < (int)tile.size() - 1);
    }
}

// Calcula las superficies laterales derechas
void Room::setRightSurfaces() {
    std::vector<int> tile;

    // Busca todos los tiles de tipo muro que no tienen a su derecha un tile de tipo muro
    // Hay que recorrer la habitación por columnas (excepto los de la última columna)
    for (int i = 0; i < MAP_WIDTH - 1; ++i) {
        for (int j = 0; j < MAP_HEIGHT; ++j) {
            const int POS = ((j * MAP_WIDTH) + i);
            if (getTile(POS) == TileType::WALL && getTile(POS + 1) != TileType::WALL) {
                tile.push_back(POS);
            }
        }
    }

    // Añade un terminador
    tile.push_back(-1);

    // Recorre el vector de tiles buscando tiles consecutivos
    // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth)
    // para localizar las superficies
    if ((int)tile.size() > 1) {
        int i = 0;
        do {
            LineVertical line;
            line.x = ((tile[i] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
            line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE);
            while (tile[i] + MAP_WIDTH == tile[i + 1]) {
                if (i == (int)tile.size() - 1) {
                    break;
                }
                i++;
            }
            line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
            right_walls_.push_back(line);
            i++;
        } while (i < (int)tile.size() - 1);
    }
}

// Encuentra todas las rampas que suben hacia la izquierda
void Room::setLeftSlopes() {
    // Recorre la habitación entera por filas buscando tiles de tipo t_slope_l
    std::vector<int> found;
    for (int i = 0; i < (int)tile_map_.size(); ++i) {
        if (getTile(i) == TileType::SLOPE_L) {
            found.push_back(i);
        }
    }

    // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes,
    // que seran i + mapWidth + 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de
    // tiles encontrados hasta que esté vacío

    while (!found.empty()) {
        LineDiagonal line;
        line.x1 = (found[0] % MAP_WIDTH) * TILE_SIZE;
        line.y1 = (found[0] / MAP_WIDTH) * TILE_SIZE;
        int looking_for = found[0] + MAP_WIDTH + 1;
        int last_one_found = found[0];
        found.erase(found.begin());
        for (int i = 0; i < (int)found.size(); ++i) {
            if (found[i] == looking_for) {
                last_one_found = looking_for;
                looking_for += MAP_WIDTH + 1;
                found.erase(found.begin() + i);
                i--;
            }
        }
        line.x2 = ((last_one_found % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
        line.y2 = ((last_one_found / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
        left_slopes_.push_back(line);
    }
}

// Encuentra todas las rampas que suben hacia la derecha
void Room::setRightSlopes() {
    // Recorre la habitación entera por filas buscando tiles de tipo t_slope_r
    std::vector<int> found;
    for (int i = 0; i < (int)tile_map_.size(); ++i) {
        if (getTile(i) == TileType::SLOPE_R) {
            found.push_back(i);
        }
    }

    // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes,
    // que seran i + mapWidth - 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de
    // tiles encontrados hasta que esté vacío

    while (!found.empty()) {
        LineDiagonal line;
        line.x1 = ((found[0] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
        line.y1 = (found[0] / MAP_WIDTH) * TILE_SIZE;
        int looking_for = found[0] + MAP_WIDTH - 1;
        int last_one_found = found[0];
        found.erase(found.begin());
        for (int i = 0; i < (int)found.size(); ++i) {
            if (found[i] == looking_for) {
                last_one_found = looking_for;
                looking_for += MAP_WIDTH - 1;
                found.erase(found.begin() + i);
                i--;
            }
        }
        line.x2 = (last_one_found % MAP_WIDTH) * TILE_SIZE;
        line.y2 = ((last_one_found / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
        right_slopes_.push_back(line);
    }
}

// Calcula las superficies automaticas
// Helper: recopila tiles animados (para superficies automaticas/conveyor belts)
auto Room::collectAnimatedTiles() -> std::vector<int> {
    std::vector<int> tile;

    // Busca todos los tiles de tipo animado
    // Hay que recorrer la habitación por filas (excepto los de la primera fila)
    for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) {
        if (getTile(i) == TileType::ANIMATED) {
            tile.push_back(i);

            // Si llega al final de la fila, introduce un separador
            if (i % MAP_WIDTH == MAP_WIDTH - 1) {
                tile.push_back(-1);
            }
        }
    }

    // Añade un terminador si hay tiles
    if (!tile.empty()) {
        tile.push_back(-1);
    }

    return tile;
}

void Room::setAutoSurfaces() {
    std::vector<int> tile = collectAnimatedTiles();
    buildHorizontalLines(tile, conveyor_belt_floors_, false);
}

// Localiza todos los tiles animados de la habitación
void Room::setAnimatedTiles() {
    // Recorre la habitación entera por filas buscando tiles de tipo t_animated
    for (int i = 0; i < (int)tile_map_.size(); ++i) {
        if (getTile(i) == TileType::ANIMATED) {
            // La i es la ubicación
            const int X = (i % MAP_WIDTH) * TILE_SIZE;
            const int Y = (i / MAP_WIDTH) * TILE_SIZE;

            // TileMap[i] es el tile a poner
            const int XC = (tile_map_[i] % tile_set_width_) * TILE_SIZE;
            const int YC = (tile_map_[i] / tile_set_width_) * TILE_SIZE;

            AnimatedTile at;
            at.sprite = std::make_shared<SurfaceSprite>(surface_, X, Y, 8, 8);
            at.sprite->setClip(XC, YC, 8, 8);
            at.x_orig = XC;
            animated_tiles_.push_back(at);
        }
    }
}

// Actualiza los tiles animados
void Room::updateAnimatedTiles() {
    const int NUM_FRAMES = 4;
    int offset = 0;
    if (conveyor_belt_direction_ == -1) {
        offset = ((counter_ / 3) % NUM_FRAMES * TILE_SIZE);
    } else {
        offset = ((NUM_FRAMES - 1 - ((counter_ / 3) % NUM_FRAMES)) * TILE_SIZE);
    }

    for (auto& a : animated_tiles_) {
        SDL_FRect rect = a.sprite->getClip();
        rect.x = a.x_orig + offset;
        a.sprite->setClip(rect);
    }
}

// Pinta los tiles animados en pantalla
void Room::renderAnimatedTiles() {
    for (const auto& a : animated_tiles_) {
        a.sprite->render();
    }
}

// Comprueba las colisiones
auto Room::checkRightSurfaces(SDL_FRect* rect) -> int {
    for (const auto& s : right_walls_) {
        if (checkCollision(s, *rect)) {
            return s.x;
        }
    }

    return -1;
}

// Comprueba las colisiones
auto Room::checkLeftSurfaces(SDL_FRect* rect) -> int {
    for (const auto& s : left_walls_) {
        if (checkCollision(s, *rect)) {
            return s.x;
        }
    }

    return -1;
}

// Comprueba las colisiones
auto Room::checkTopSurfaces(SDL_FRect* rect) -> int {
    for (const auto& s : top_floors_) {
        if (checkCollision(s, *rect)) {
            return s.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
auto Room::checkBottomSurfaces(SDL_FRect* rect) -> int {
    for (const auto& s : bottom_floors_) {
        if (checkCollision(s, *rect)) {
            return s.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
auto Room::checkAutoSurfaces(SDL_FRect* rect) -> int {
    for (const auto& s : conveyor_belt_floors_) {
        if (checkCollision(s, *rect)) {
            return s.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
auto Room::checkTopSurfaces(SDL_FPoint* p) -> bool {
    return std::ranges::any_of(top_floors_, [&](const auto& s) {
        return checkCollision(s, *p);
    });
}

// Comprueba las colisiones
auto Room::checkAutoSurfaces(SDL_FPoint* p) -> bool {
    return std::ranges::any_of(conveyor_belt_floors_, [&](const auto& s) {
        return checkCollision(s, *p);
    });
}

// Comprueba las colisiones
auto Room::checkLeftSlopes(const LineVertical* line) -> int {
    for (const auto& slope : left_slopes_) {
        const auto P = checkCollision(slope, *line);
        if (P.x != -1) {
            return P.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
auto Room::checkLeftSlopes(SDL_FPoint* p) -> bool {
    return std::ranges::any_of(left_slopes_, [&](const auto& slope) {
        return checkCollision(*p, slope);
    });
}

// Comprueba las colisiones
auto Room::checkRightSlopes(const LineVertical* line) -> int {
    for (const auto& slope : right_slopes_) {
        const auto P = checkCollision(slope, *line);
        if (P.x != -1) {
            return P.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
auto Room::checkRightSlopes(SDL_FPoint* p) -> bool {
    return std::ranges::any_of(right_slopes_, [&](const auto& slope) {
        return checkCollision(*p, slope);
    });
}

// Abre la Jail si se da el caso
void Room::openTheJail() {
    if (data_->jail_is_open && name_ == "THE JAIL") {
        // Elimina el último enemigo (Bry debe ser el último enemigo definido en el fichero)
        if (!enemies_.empty()) {
            enemies_.pop_back();
        }

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

// Inicializa las superficies de colision
void Room::initRoomSurfaces() {
    setBottomSurfaces();
    setTopSurfaces();
    setLeftSurfaces();
    setRightSurfaces();
    setLeftSlopes();
    setRightSlopes();
    setAutoSurfaces();
}