#include "room_loader.hpp"

#include <exception>  // Para exception
#include <iostream>   // Para cout, cerr

#include "core/resources/resource_helper.hpp"  // Para Resource::Helper
#include "external/fkyaml_node.hpp"            // Para fkyaml::node
#include "game/gameplay/zone.hpp"              // Para Zone::Data
#include "game/gameplay/zone_manager.hpp"      // Para ZoneManager
#include "utils/defines.hpp"                   // Para Tile::SIZE
#include "utils/utils.hpp"

// Convierte room connection de YAML a formato interno
auto RoomLoader::convertRoomConnection(const std::string& value) -> std::string {  // NOLINT(readability-convert-member-functions-to-static)
    if (value == "null" || value.empty()) {
        return "0";
    }
    // Si ya tiene .yaml, devolverlo tal cual; si no, añadirlo
    if (value.size() > 5 && value.substr(value.size() - 5) == ".yaml") {
        return value;
    }
    return value + ".yaml";
}

// Lee un nodo de color como Uint8 (acepta entero directo o string numérico)
static auto readColorNode(const fkyaml::node& node) -> Uint8 {
    if (node.is_integer()) { return static_cast<Uint8>(node.get_value<int>()); }
    if (node.is_string()) { return static_cast<Uint8>(safeStoi(node.get_value<std::string>(), 0)); }
    return 0;
}

// Convierte string de autoSurface a int
auto RoomLoader::convertAutoSurface(const fkyaml::node& node) -> int {  // NOLINT(readability-convert-member-functions-to-static)
    if (node.is_integer()) {
        return node.get_value<int>();
    }
    if (node.is_string()) {
        const auto VALUE = node.get_value<std::string>();
        if (VALUE == "left") {
            return -1;
        }
        if (VALUE == "right") {
            return 1;
        }
    }
    return 0;  // "none" o default
}

// Convierte un tilemap 2D a vector 1D flat
auto RoomLoader::flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int> {  // NOLINT(readability-convert-member-functions-to-static, readability-named-parameter)
    std::vector<int> tilemap_flat;
    tilemap_flat.reserve(Map::WIDTH * Map::HEIGHT);

    for (const auto& row : tilemap_2d) {
        for (int tile : row) {
            tilemap_flat.push_back(tile);
        }
    }

    return tilemap_flat;
}

// Parsea la configuración general de la habitación
void RoomLoader::parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name) {  // NOLINT(readability-convert-member-functions-to-static)
    if (!yaml.contains("room")) {
        return;
    }

    const auto& room_node = yaml["room"];

    // Extract room number from filename (e.g., "01.yaml" → "01")
    room.number = file_name.substr(0, file_name.find_last_of('.'));

    // Basic properties
    // bgColor ya no se lee del YAML; bg_color queda siempre a 0

    // --- Resolución de zona + overrides (tileSetFile, music) ---
    // Obtener zona declarada (o caer al default si no existe)
    std::string zone_name;
    if (room_node.contains("zone")) {
        zone_name = room_node["zone"].get_value<std::string>();
    } else {
        std::cerr << "Warning: room " << file_name << " has no 'zone' field, using default\n";
        const Zone::Data* default_zone = ZoneManager::get()->getDefaultZone();
        if (default_zone != nullptr) { zone_name = default_zone->name; }
    }
    room.zone = zone_name;

    // Localizar la zona en el catálogo (fallback al default si no existe)
    const Zone::Data* zone = ZoneManager::get()->getZone(zone_name);
    if (zone == nullptr) {
        std::cerr << "Warning: unknown zone '" << zone_name << "' in " << file_name << ", using default\n";
        zone = ZoneManager::get()->getDefaultZone();
    }

    // tileSetFile: zona, override si está en el yaml
    if (room_node.contains("tileSetFile")) {
        room.tile_set_file = room_node["tileSetFile"].get_value<std::string>();
        room.tile_set_overridden = true;
    } else if (zone != nullptr) {
        room.tile_set_file = zone->tile_set_file;
    }

    // music: zona, override si está en el yaml
    if (room_node.contains("music")) {
        room.music = room_node["music"].get_value<std::string>();
        room.music_overridden = true;
    } else if (zone != nullptr) {
        room.music = zone->music;
    }

    // Room connections
    if (room_node.contains("connections")) {
        parseRoomConnections(room_node["connections"], room);
    }

    // Item colors
    if (room_node.contains("itemColor1")) {
        room.item_color1 = readColorNode(room_node["itemColor1"]);
    }

    if (room_node.contains("itemColor2")) {
        room.item_color2 = readColorNode(room_node["itemColor2"]);
    }

    // Dirección de la cinta transportadora (left/none/right)
    room.conveyor_belt_direction = room_node.contains("conveyorBelt")
        ? convertAutoSurface(room_node["conveyorBelt"])
        : 0;
}

// Parsea las conexiones de la habitación (arriba/abajo/izq/der)
void RoomLoader::parseRoomConnections(const fkyaml::node& conn_node, Room::Data& room) {
    room.upper_room = conn_node.contains("up")
        ? convertRoomConnection(conn_node["up"].get_value_or<std::string>("null"))
        : "0";

    room.lower_room = conn_node.contains("down")
        ? convertRoomConnection(conn_node["down"].get_value_or<std::string>("null"))
        : "0";

    room.left_room = conn_node.contains("left")
        ? convertRoomConnection(conn_node["left"].get_value_or<std::string>("null"))
        : "0";

    room.right_room = conn_node.contains("right")
        ? convertRoomConnection(conn_node["right"].get_value_or<std::string>("null"))
        : "0";
}

// Lee un array 2D de enteros desde un nodo YAML
static auto readTilemap2D(const fkyaml::node& node) -> std::vector<std::vector<int>> {
    std::vector<std::vector<int>> tilemap_2d;
    tilemap_2d.reserve(Map::HEIGHT);

    for (const auto& row_node : node) {
        std::vector<int> row;
        row.reserve(Map::WIDTH);

        for (const auto& tile_node : row_node) {
            row.push_back(tile_node.get_value<int>());
        }

        tilemap_2d.push_back(row);
    }

    return tilemap_2d;
}

// Parsea el tilemap de la habitación
void RoomLoader::parseTilemap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose) {  // NOLINT(readability-convert-member-functions-to-static)
    if (!yaml.contains("tilemap")) {
        std::cerr << "Warning: No tilemap found in " << file_name << '\n';
        return;
    }

    const auto& tilemap_node = yaml["tilemap"];

    // Nuevo formato: tilemap.draw + tilemap.collision
    if (tilemap_node.contains("draw")) {
        room.tile_map = flattenTilemap(readTilemap2D(tilemap_node["draw"]));

        if (tilemap_node.contains("collision")) {
            room.collision_tile_map = flattenTilemap(readTilemap2D(tilemap_node["collision"]));
        }
    } else {
        // Formato antiguo: tilemap es directamente el array 2D de dibujo
        room.tile_map = flattenTilemap(readTilemap2D(tilemap_node));
    }

    if (verbose) {
        std::cout << "Loaded tilemap: " << room.tile_map.size() << " tiles";
        if (!room.collision_tile_map.empty()) {
            std::cout << " + collision: " << room.collision_tile_map.size() << " tiles";
        }
        std::cout << '\n';
    }
}

// Parsea los límites de movimiento de un enemigo
void RoomLoader::parseEnemyBoundaries(const fkyaml::node& bounds_node, Enemy::Data& enemy) {  // NOLINT(readability-convert-member-functions-to-static)
    // Nuevo formato: position1 y position2
    if (bounds_node.contains("position1")) {
        const auto& pos1 = bounds_node["position1"];
        if (pos1.contains("x")) {
            enemy.x1 = pos1["x"].get_value<int>() * Tile::SIZE;
        }
        if (pos1.contains("y")) {
            enemy.y1 = pos1["y"].get_value<int>() * Tile::SIZE;
        }
    }
    if (bounds_node.contains("position2")) {
        const auto& pos2 = bounds_node["position2"];
        if (pos2.contains("x")) {
            enemy.x2 = pos2["x"].get_value<int>() * Tile::SIZE;
        }
        if (pos2.contains("y")) {
            enemy.y2 = pos2["y"].get_value<int>() * Tile::SIZE;
        }
    }

    // Formato antiguo: x1/y1/x2/y2 (compatibilidad)
    if (bounds_node.contains("x1")) {
        enemy.x1 = bounds_node["x1"].get_value<int>() * Tile::SIZE;
    }
    if (bounds_node.contains("y1")) {
        enemy.y1 = bounds_node["y1"].get_value<int>() * Tile::SIZE;
    }
    if (bounds_node.contains("x2")) {
        enemy.x2 = bounds_node["x2"].get_value<int>() * Tile::SIZE;
    }
    if (bounds_node.contains("y2")) {
        enemy.y2 = bounds_node["y2"].get_value<int>() * Tile::SIZE;
    }
}

// Parsea los datos de un enemigo individual
auto RoomLoader::parseEnemyData(const fkyaml::node& enemy_node) -> Enemy::Data {  // NOLINT(readability-convert-member-functions-to-static)
    Enemy::Data enemy;

    // Enemy type (default: "path")
    if (enemy_node.contains("type")) {
        enemy.type = enemy_node["type"].get_value<std::string>();
    }

    // Animation path
    if (enemy_node.contains("animation")) {
        enemy.animation_path = enemy_node["animation"].get_value<std::string>();
    }

    // Position (in tiles, convert to pixels)
    if (enemy_node.contains("position")) {
        const auto& pos = enemy_node["position"];
        if (pos.contains("x")) {
            enemy.x = pos["x"].get_value<float>() * Tile::SIZE;
        }
        if (pos.contains("y")) {
            enemy.y = pos["y"].get_value<float>() * Tile::SIZE;
        }
    }

    // Velocity (already in pixels/second)
    if (enemy_node.contains("velocity")) {
        const auto& vel = enemy_node["velocity"];
        if (vel.contains("x")) {
            enemy.vx = vel["x"].get_value<float>();
        }
        if (vel.contains("y")) {
            enemy.vy = vel["y"].get_value<float>();
        }
    }

    // Boundaries (in tiles, convert to pixels)
    if (enemy_node.contains("boundaries")) {
        parseEnemyBoundaries(enemy_node["boundaries"], enemy);
    }

    // Optional fields
    enemy.flip = enemy_node.contains("flip")
        ? enemy_node["flip"].get_value_or<bool>(false)
        : false;

    enemy.mirror = enemy_node.contains("mirror")
        ? enemy_node["mirror"].get_value_or<bool>(false)
        : false;

    enemy.frame = enemy_node.contains("frame")
        ? enemy_node["frame"].get_value_or<int>(-1)
        : -1;

    return enemy;
}

// Parsea la lista de enemigos de la habitación
void RoomLoader::parseEnemies(const fkyaml::node& yaml, Room::Data& room, bool verbose) {  // NOLINT(readability-convert-member-functions-to-static)
    if (!yaml.contains("enemies") || yaml["enemies"].is_null()) {
        return;
    }

    const auto& enemies_node = yaml["enemies"];

    for (const auto& enemy_node : enemies_node) {
        room.enemies.push_back(parseEnemyData(enemy_node));
    }

    if (verbose) {
        std::cout << "Loaded " << room.enemies.size() << " enemies\n";
    }
}

// Parsea los datos de un item individual
auto RoomLoader::parseItemData(const fkyaml::node& item_node, const Room::Data& room) -> Item::Data {  // NOLINT(readability-convert-member-functions-to-static)
    Item::Data item;

    // Tileset file
    if (item_node.contains("tileSetFile")) {
        item.tile_set_file = item_node["tileSetFile"].get_value<std::string>();
    }

    // Tile index
    if (item_node.contains("tile")) {
        item.tile = item_node["tile"].get_value<int>();
    }

    // Position (in tiles, convert to pixels)
    if (item_node.contains("position")) {
        const auto& pos = item_node["position"];
        if (pos.contains("x")) {
            item.x = pos["x"].get_value<float>() * Tile::SIZE;
        }
        if (pos.contains("y")) {
            item.y = pos["y"].get_value<float>() * Tile::SIZE;
        }
    }

    // Counter
    item.counter = item_node.contains("counter")
        ? item_node["counter"].get_value_or<int>(0)
        : 0;

    // Colors (assigned from room defaults)
    item.color1 = room.item_color1;
    item.color2 = room.item_color2;

    return item;
}

// Parsea la lista de items de la habitación
void RoomLoader::parseItems(const fkyaml::node& yaml, Room::Data& room, bool verbose) {  // NOLINT(readability-convert-member-functions-to-static)
    if (!yaml.contains("items") || yaml["items"].is_null()) {
        return;
    }

    const auto& items_node = yaml["items"];

    for (const auto& item_node : items_node) {
        room.items.push_back(parseItemData(item_node, room));
    }

    if (verbose) {
        std::cout << "Loaded " << room.items.size() << " items\n";
    }
}

// Parsea los datos de una plataforma individual
auto RoomLoader::parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data {
    MovingPlatform::Data platform;

    if (platform_node.contains("animation")) {
        platform.animation_path = platform_node["animation"].get_value<std::string>();
    }
    if (platform_node.contains("speed")) {
        platform.speed = platform_node["speed"].get_value<float>();
    }
    if (platform_node.contains("loop")) {
        auto loop_str = platform_node["loop"].get_value<std::string>();
        platform.loop = (loop_str == "circular") ? LoopMode::CIRCULAR : LoopMode::PINGPONG;
    }
    if (platform_node.contains("easing")) {
        platform.easing = platform_node["easing"].get_value<std::string>();
    }
    platform.frame = platform_node.contains("frame")
        ? platform_node["frame"].get_value_or<int>(-1)
        : -1;

    // Path: lista de waypoints en tiles → pixels
    if (platform_node.contains("path")) {
        for (const auto& wp_node : platform_node["path"]) {
            Waypoint wp;
            wp.x = wp_node["x"].get_value<float>() * Tile::SIZE;
            wp.y = wp_node["y"].get_value<float>() * Tile::SIZE;
            if (wp_node.contains("wait")) {
                wp.wait = wp_node["wait"].get_value<float>();
            }
            platform.path.push_back(wp);
        }
    }

    return platform;
}

// Parsea la lista de plataformas de la habitación
void RoomLoader::parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool verbose) {
    if (!yaml.contains("platforms") || yaml["platforms"].is_null()) {
        return;
    }

    const auto& platforms_node = yaml["platforms"];

    for (const auto& platform_node : platforms_node) {
        room.platforms.push_back(parsePlatformData(platform_node));
    }

    if (verbose) {
        std::cout << "Loaded " << room.platforms.size() << " platforms\n";
    }
}

// Parsea los datos de una llave individual
auto RoomLoader::parseKeyData(const fkyaml::node& key_node) -> Key::Data {  // NOLINT(readability-convert-member-functions-to-static)
    Key::Data key;

    if (key_node.contains("animation")) {
        key.animation_path = key_node["animation"].get_value<std::string>();
    }
    if (key_node.contains("id")) {
        key.id = key_node["id"].get_value<std::string>();
    }
    if (key_node.contains("position")) {
        const auto& pos = key_node["position"];
        if (pos.contains("x")) {
            key.x = pos["x"].get_value<float>() * Tile::SIZE;
        }
        if (pos.contains("y")) {
            key.y = pos["y"].get_value<float>() * Tile::SIZE;
        }
    }

    return key;
}

// Parsea la lista de llaves de la habitación
void RoomLoader::parseKeys(const fkyaml::node& yaml, Room::Data& room, bool verbose) {  // NOLINT(readability-convert-member-functions-to-static)
    if (!yaml.contains("keys") || yaml["keys"].is_null()) {
        return;
    }

    const auto& keys_node = yaml["keys"];

    for (const auto& key_node : keys_node) {
        room.keys.push_back(parseKeyData(key_node));
    }

    if (verbose) {
        std::cout << "Loaded " << room.keys.size() << " keys\n";
    }
}

// Parsea los datos de una puerta individual
auto RoomLoader::parseDoorData(const fkyaml::node& door_node) -> Door::Data {  // NOLINT(readability-convert-member-functions-to-static)
    Door::Data door;

    if (door_node.contains("animation")) {
        door.animation_path = door_node["animation"].get_value<std::string>();
    }
    if (door_node.contains("id")) {
        door.id = door_node["id"].get_value<std::string>();
    }
    if (door_node.contains("position")) {
        const auto& pos = door_node["position"];
        if (pos.contains("x")) {
            door.x = pos["x"].get_value<float>() * Tile::SIZE;
        }
        if (pos.contains("y")) {
            door.y = pos["y"].get_value<float>() * Tile::SIZE;
        }
    }

    return door;
}

// Parsea la lista de puertas de la habitación
void RoomLoader::parseDoors(const fkyaml::node& yaml, Room::Data& room, bool verbose) {  // NOLINT(readability-convert-member-functions-to-static)
    if (!yaml.contains("doors") || yaml["doors"].is_null()) {
        return;
    }

    const auto& doors_node = yaml["doors"];

    for (const auto& door_node : doors_node) {
        room.doors.push_back(parseDoorData(door_node));
    }

    if (verbose) {
        std::cout << "Loaded " << room.doors.size() << " doors\n";
    }
}

#ifdef _DEBUG
// Carga una habitación desde un string YAML (para el editor de mapas, evita el resource pack)
auto RoomLoader::loadFromString(const std::string& yaml_content, const std::string& file_name) -> Room::Data {
    Room::Data room;
    try {
        auto yaml = fkyaml::node::deserialize(yaml_content);
        parseRoomConfig(yaml, room, file_name);
        parseTilemap(yaml, room, file_name, false);
        parseEnemies(yaml, room, false);
        parseItems(yaml, room, false);
        parsePlatforms(yaml, room, false);
        parseKeys(yaml, room, false);
        parseDoors(yaml, room, false);
    } catch (const fkyaml::exception& e) {
        std::cerr << "YAML parsing error in " << file_name << ": " << e.what() << '\n';
    } catch (const std::exception& e) {
        std::cerr << "Error loading room " << file_name << ": " << e.what() << '\n';
    }
    return room;
}
#endif

// Carga un archivo de room en formato YAML
auto RoomLoader::loadYAML(const std::string& file_path, bool verbose) -> Room::Data {  // NOLINT(readability-convert-member-functions-to-static)
    Room::Data room;

    // Extract filename for logging
    const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);

    try {
        // Load YAML file using ResourceHelper (supports both filesystem and pack)
        auto file_data = Resource::Helper::loadFile(file_path);

        if (file_data.empty()) {
            std::cerr << "Error: Unable to load file " << FILE_NAME << '\n';
            return room;
        }

        // Parse YAML from string
        std::string yaml_content(file_data.begin(), file_data.end());
        auto yaml = fkyaml::node::deserialize(yaml_content);

        // Delegación a funciones especializadas
        parseRoomConfig(yaml, room, FILE_NAME);
        parseTilemap(yaml, room, FILE_NAME, verbose);
        parseEnemies(yaml, room, verbose);
        parseItems(yaml, room, verbose);
        parsePlatforms(yaml, room, verbose);
        parseKeys(yaml, room, verbose);
        parseDoors(yaml, room, verbose);

        if (verbose) {
            std::cout << "Room loaded successfully: " << FILE_NAME << '\n';
        }

    } catch (const fkyaml::exception& e) {
        std::cerr << "YAML parsing error in " << FILE_NAME << ": " << e.what() << '\n';
    } catch (const std::exception& e) {
        std::cerr << "Error loading room " << FILE_NAME << ": " << e.what() << '\n';
    }

    return room;
}
