#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/options.hpp"                    // Para Options::language
#include "utils/defines.hpp"                   // Para Tile::SIZE
#include "utils/utils.hpp"                     // Para stringToColor

// Convierte room connection de YAML a formato interno
auto RoomLoader::convertRoomConnection(const std::string& value) -> std::string {
    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";
}

// Convierte string de autoSurface a int
auto RoomLoader::convertAutoSurface(const fkyaml::node& node) -> int {
    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> {
    std::vector<int> tilemap_flat;
    tilemap_flat.reserve(512);  // 16 rows × 32 cols

    for (const auto& row : tilemap_2d) {
        std::ranges::copy(row, std::back_inserter(tilemap_flat));
    }

    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) {
    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
    const std::string LANG_KEY = "name_" + Options::language;
    if (room_node.contains(LANG_KEY)) {
        room.name = room_node[LANG_KEY].get_value<std::string>();
    } else if (room_node.contains("name")) {
        room.name = room_node["name"].get_value<std::string>();
    }
    if (room_node.contains("bgColor")) {
        room.bg_color = room_node["bgColor"].get_value<std::string>();
    }
    if (room_node.contains("border")) {
        room.border_color = room_node["border"].get_value<std::string>();
    }
    if (room_node.contains("tileSetFile")) {
        room.tile_set_file = room_node["tileSetFile"].get_value<std::string>();
    }

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

    // Item colors
    room.item_color1 = room_node.contains("itemColor1")
        ? room_node["itemColor1"].get_value_or<std::string>("yellow")
        : "yellow";

    room.item_color2 = room_node.contains("itemColor2")
        ? room_node["itemColor2"].get_value_or<std::string>("magenta")
        : "magenta";

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

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

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

    // Read 2D array
    std::vector<std::vector<int>> tilemap_2d;
    tilemap_2d.reserve(16);

    for (const auto& row_node : tilemap_node) {
        std::vector<int> row;
        row.reserve(32);
        std::ranges::transform(row_node, std::back_inserter(row), [](const auto& tile_node) { return tile_node.template get_value<int>(); });
        tilemap_2d.push_back(std::move(row));
    }

    // Convert to 1D flat array
    room.tile_map = flattenTilemap(tilemap_2d);

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

// Parsea los límites de movimiento de un enemigo
void RoomLoader::parseEnemyBoundaries(const fkyaml::node& bounds_node, Enemy::Data& enemy) {
    // 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 {
    Enemy::Data enemy;

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

    // Color
    enemy.color = enemy_node.contains("color")
        ? enemy_node["color"].get_value_or<std::string>("white")
        : "white";

    // 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) {
    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 {
    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 = stringToColor(room.item_color1);
    item.color2 = stringToColor(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) {
    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";
    }
}

#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);
    } 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 {
    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);

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