#ifdef _DEBUG

#include "game/editor/mini_map.hpp"

#include <algorithm>  // Para std::max, std::min
#include <array>      // Para std::array
#include <cmath>      // Para std::floor
#include <iostream>   // Para cout
#include <map>        // Para std::map
#include <queue>      // Para queue (BFS)
#include <set>        // Para set

#include "core/input/key_config.hpp"          // Para KeyConfig
#include "core/rendering/screen.hpp"          // Para Screen
#include "core/rendering/screenshot.hpp"      // Para Screenshot::save
#include "core/rendering/surface.hpp"         // Para Surface
#include "core/rendering/text.hpp"            // Para Text (números de room)
#include "core/resources/resource_cache.hpp"  // Para Resource::Cache
#include "game/gameplay/room.hpp"             // Para Room::Data
#include "utils/defines.hpp"                  // Para Tile::SIZE, PlayArea
#include "utils/utils.hpp"

// Constructor: construye todo el minimapa
MiniMap::MiniMap(Uint8 bg_color, Uint8 conn_color)
    : bg_color_(bg_color),
      conn_color_(conn_color) {
    layoutRooms();
    buildRoomSurfaces();
    composeFinalSurface();
}

// Regenera la surface final con nuevo color de fondo
void MiniMap::rebuild(Uint8 bg_color, Uint8 conn_color) {
    bg_color_ = bg_color;
    conn_color_ = conn_color;
    composeFinalSurface();
}

// Devuelve (construyendo si hace falta) la tabla de color predominante para un tileset
auto MiniMap::getOrBuildTileColorTable(const std::string& tileset_name) -> const TileColorTable& {
    auto it = tileset_tables_.find(tileset_name);
    if (it != tileset_tables_.end()) { return it->second; }

    // Inserta entrada vacía. Si el tileset no se puede cargar, esto evita reintentos.
    auto& table = tileset_tables_[tileset_name];

    auto tileset = Resource::Cache::get()->getSurface(tileset_name);
    if (!tileset) { return table; }

    table.tileset_width = static_cast<int>(tileset->getWidth()) / Tile::SIZE;
    table.transparent = tileset->getTransparentColor();
    int tileset_height = static_cast<int>(tileset->getHeight()) / Tile::SIZE;
    int total_tiles = table.tileset_width * tileset_height;

    table.colors.resize(total_tiles, 0);

    for (int tile = 0; tile < total_tiles; ++tile) {
        int tile_x = (tile % table.tileset_width) * Tile::SIZE;
        int tile_y = (tile / table.tileset_width) * Tile::SIZE;

        // Contar frecuencia de cada color en el tile (ignorar el color transparente del tileset)
        std::array<int, 256> freq{};
        for (int y = 0; y < Tile::SIZE; ++y) {
            for (int x = 0; x < Tile::SIZE; ++x) {
                Uint8 pixel = tileset->getPixel(tile_x + x, tile_y + y);
                if (pixel != table.transparent) {
                    freq[pixel]++;
                }
            }
        }

        // Encontrar el color más frecuente (transparent = tile vacío, no se pinta)
        Uint8 best_color = table.transparent;
        int best_count = 0;
        for (int c = 0; c < 256; ++c) {
            if (c == table.transparent) { continue; }
            if (freq[c] > best_count) {
                best_count = freq[c];
                best_color = static_cast<Uint8>(c);
            }
        }

        table.colors[tile] = best_color;
    }

    return table;
}

// Posiciona las rooms en un grid usando BFS desde las conexiones
void MiniMap::layoutRooms() {
    const auto& rooms = Resource::Cache::get()->getRooms();
    if (rooms.empty()) { return; }

    // Mapa de nombre → Room::Data
    std::unordered_map<std::string, std::shared_ptr<Room::Data>> room_map;
    for (const auto& r : rooms) {
        room_map[r.name] = r.room;
    }

    // BFS para posicionar rooms
    std::set<std::string> visited;
    std::queue<std::pair<std::string, GridPos>> bfs;

    // Empezar por la primera room
    const std::string& start = rooms[0].name;
    bfs.push({start, {.x = 0, .y = 0}});
    visited.insert(start);

    // Grid ocupado: posición → nombre de room
    std::map<std::pair<int, int>, std::string> grid_occupied;

    while (!bfs.empty()) {
        auto [name, pos] = bfs.front();
        bfs.pop();

        auto key = std::make_pair(pos.x, pos.y);
        if (grid_occupied.contains(key)) { continue; }

        grid_occupied[key] = name;
        room_positions_[name] = RoomMini{.surface = nullptr, .pos = pos};

        auto it = room_map.find(name);
        if (it == room_map.end()) { continue; }
        const auto& data = it->second;

        // Vecinos: up, down, left, right
        struct Neighbor {
            std::string room;
            int dx, dy;
        };
        std::array<Neighbor, 4> neighbors = {{
            {.room = data->upper_room, .dx = 0, .dy = -1},
            {.room = data->lower_room, .dx = 0, .dy = 1},
            {.room = data->left_room, .dx = -1, .dy = 0},
            {.room = data->right_room, .dx = 1, .dy = 0},
        }};

        for (const auto& [neighbor_name, dx, dy] : neighbors) {
            if (neighbor_name == "0" || neighbor_name.empty()) { continue; }
            if (visited.contains(neighbor_name)) { continue; }
            if (!room_map.contains(neighbor_name)) { continue; }  // Room no existe en cache

            GridPos neighbor_pos = {.x = pos.x + dx, .y = pos.y + dy};
            auto nkey = std::make_pair(neighbor_pos.x, neighbor_pos.y);
            if (!grid_occupied.contains(nkey)) {
                visited.insert(neighbor_name);
                bfs.emplace(neighbor_name, neighbor_pos);
            }
        }
    }

    // Calcular bounds del grid
    min_grid_x_ = 0;
    min_grid_y_ = 0;
    int max_grid_x = 0;
    int max_grid_y = 0;
    for (const auto& [name, mini] : room_positions_) {
        min_grid_x_ = std::min(min_grid_x_, mini.pos.x);
        min_grid_y_ = std::min(min_grid_y_, mini.pos.y);
        max_grid_x = std::max(max_grid_x, mini.pos.x);
        max_grid_y = std::max(max_grid_y, mini.pos.y);
    }

    int cols = max_grid_x - min_grid_x_ + 1;
    int rows = max_grid_y - min_grid_y_ + 1;
    map_width_ = cols * (CELL_W + GAP) - GAP + PADDING * 2;
    map_height_ = rows * (CELL_H + GAP) - GAP + PADDING * 2;

    std::cout << "MiniMap: " << room_positions_.size() << " rooms, grid " << cols << "x" << rows
              << " → " << map_width_ << "x" << map_height_ << " px\n";
}

// Genera una mini-surface de 32x16 por room
void MiniMap::buildRoomSurfaces() {
    for (auto& [name, mini] : room_positions_) {
        mini.surface = getRoomMiniSurface(name);
    }
}

// Genera la mini-surface de una room: 1 pixel por tile, color predominante
auto MiniMap::getRoomMiniSurface(const std::string& room_name) -> std::shared_ptr<Surface> {
    std::shared_ptr<Room::Data> room_data;
    try {
        room_data = Resource::Cache::get()->getRoom(room_name);
    } catch (...) {
        return nullptr;  // Room no existe en cache
    }
    if (!room_data) { return nullptr; }

    // Tabla de colores específica del tileset que usa esta room (cacheada).
    const auto& table = getOrBuildTileColorTable(room_data->tile_set_file);

    auto surface = std::make_shared<Surface>(ROOM_W, ROOM_H);

    auto prev = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(surface);
    surface->clear(room_data->bg_color);

    const auto& tile_map = room_data->tile_map;
    for (int y = 0; y < ROOM_H; ++y) {
        for (int x = 0; x < ROOM_W; ++x) {
            int index = (y * ROOM_W) + x;
            if (index >= static_cast<int>(tile_map.size())) { continue; }

            int tile = tile_map[index];
            if (tile < 0 || tile >= static_cast<int>(table.colors.size())) { continue; }

            Uint8 color = table.colors[tile];
            if (color != table.transparent) {
                surface->putPixel(x, y, color);
            }
        }
    }

    Screen::get()->setRendererSurface(prev);
    return surface;
}

// Compone la surface final con todas las rooms posicionadas
void MiniMap::composeFinalSurface() {
    if (map_width_ <= 0 || map_height_ <= 0) { return; }

    // Surface un poco más grande para la sombra del borde inferior/derecho
    map_surface_ = std::make_shared<Surface>(map_width_ + SHADOW_OFFSET, map_height_ + SHADOW_OFFSET);

    auto prev = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(map_surface_);

    // 1. Fondo general
    map_surface_->clear(bg_color_);

    // 2. Líneas de conexión entre rooms (debajo de todo)
    drawConnections();

    // 3. Sombras de las rooms (desplazadas 1px abajo-derecha)
    for (const auto& [name, mini] : room_positions_) {
        if (!mini.surface) { continue; }
        int px = cellPixelX(mini.pos.x) + SHADOW_OFFSET;
        int py = cellPixelY(mini.pos.y) + SHADOW_OFFSET;
        SDL_FRect shadow = {.x = static_cast<float>(px), .y = static_cast<float>(py), .w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
        map_surface_->fillRect(&shadow, COLOR_SHADOW);
    }

    // 4. Borde negro de cada room + contenido de la room
    for (const auto& [name, mini] : room_positions_) {
        if (!mini.surface) { continue; }
        int px = cellPixelX(mini.pos.x);
        int py = cellPixelY(mini.pos.y);

        // Borde negro (la celda entera)
        SDL_FRect cell = {.x = static_cast<float>(px), .y = static_cast<float>(py), .w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
        map_surface_->fillRect(&cell, COLOR_ROOM_BORDER);

        // Miniroom dentro del borde
        SDL_FRect dst = {.x = static_cast<float>(px + BORDER), .y = static_cast<float>(py + BORDER), .w = static_cast<float>(ROOM_W), .h = static_cast<float>(ROOM_H)};
        mini.surface->render(nullptr, &dst);
    }

    Screen::get()->setRendererSurface(prev);
}

// Dibuja las líneas de conexión entre rooms vecinas
void MiniMap::drawConnections() {
    for (const auto& [name, mini] : room_positions_) {
        auto room_data = Resource::Cache::get()->getRoom(name);
        if (!room_data) { continue; }

        int px = cellPixelX(mini.pos.x);
        int py = cellPixelY(mini.pos.y);

        // Conexión derecha
        if (room_data->right_room != "0" && !room_data->right_room.empty() && room_positions_.contains(room_data->right_room)) {
            int x1 = px + CELL_W;
            int y_mid = py + (CELL_H / 2) - 1;
            SDL_FRect line = {.x = static_cast<float>(x1), .y = static_cast<float>(y_mid), .w = static_cast<float>(GAP), .h = 3.0F};
            map_surface_->fillRect(&line, conn_color_);
        }

        // Conexión abajo
        if (room_data->lower_room != "0" && !room_data->lower_room.empty() && room_positions_.contains(room_data->lower_room)) {
            int x_mid = px + (CELL_W / 2) - 1;
            int y1 = py + CELL_H;
            SDL_FRect line = {.x = static_cast<float>(x_mid), .y = static_cast<float>(y1), .w = 3.0F, .h = static_cast<float>(GAP)};
            map_surface_->fillRect(&line, conn_color_);
        }
    }
}

// Dibuja los números de room centrados en cada celda
void MiniMap::renderRoomNumbers(float offset_x, float offset_y) {
    auto text = Resource::Cache::get()->getText("8bithud");
    if (!text) { return; }

    int font_h = text->getCharacterSize();

    for (const auto& [name, mini] : room_positions_) {
        // "001.yaml" → "001"
        std::string number = name.substr(0, name.find_last_of('.'));

        int text_w = text->length(number);
        int text_x = static_cast<int>(offset_x) + cellPixelX(mini.pos.x) + (CELL_W - text_w) / 2;
        int text_y = static_cast<int>(offset_y) + cellPixelY(mini.pos.y) + (CELL_H - font_h) / 2;

        text->writeDX(Text::COLOR_FLAG | Text::SHADOW_FLAG, text_x, text_y, number, 1, COLOR_NUMBER_TEXT, 1, COLOR_NUMBER_SHADOW);
    }
}

// Centra el viewport en una room
void MiniMap::centerOnRoom(const std::string& room_name) {
    auto it = room_positions_.find(room_name);
    if (it == room_positions_.end()) { return; }
    const auto& pos = it->second.pos;

    auto room_cx = static_cast<float>(cellPixelX(pos.x) + (CELL_W / 2));
    auto room_cy = static_cast<float>(cellPixelY(pos.y) + (CELL_H / 2));
    view_x_ = static_cast<float>(PlayArea::WIDTH) / 2.0F - room_cx;
    view_y_ = static_cast<float>(PlayArea::HEIGHT) / 2.0F - room_cy;
}

// Devuelve el nombre de la room en una posición de pantalla, o vacío si no hay ninguna
auto MiniMap::roomAtScreen(float screen_x, float screen_y) -> std::string {
    // Convertir coordenada de pantalla a coordenada dentro del minimapa
    float map_x = screen_x - view_x_;
    float map_y = screen_y - view_y_;

    for (const auto& [name, mini] : room_positions_) {
        auto rx = static_cast<float>(cellPixelX(mini.pos.x));
        auto ry = static_cast<float>(cellPixelY(mini.pos.y));
        if (map_x >= rx && map_x < rx + CELL_W && map_y >= ry && map_y < ry + CELL_H) {
            return name;
        }
    }
    return "";
}

// Renderiza el minimapa
void MiniMap::render(const std::string& current_room) {
    if (!map_surface_) { return; }

    auto game_surface = Screen::get()->getRendererSurface();
    if (!game_surface) { return; }

    // Renderizar la surface del minimapa con el viewport actual (alineado a pixel)
    float vx = std::floor(view_x_);
    float vy = std::floor(view_y_);
    SDL_FRect dst = {.x = vx, .y = vy, .w = static_cast<float>(map_width_ + SHADOW_OFFSET), .h = static_cast<float>(map_height_ + SHADOW_OFFSET)};
    map_surface_->render(nullptr, &dst);

    // Números de room (sobre la surface del mapa, antes del highlight)
    if (show_numbers_) { renderRoomNumbers(vx, vy); }

    // Highlight de la room actual (solo si está completamente visible en el play area)
    auto it = room_positions_.find(current_room);
    if (it != room_positions_.end()) {
        float cur_x = vx + static_cast<float>(cellPixelX(it->second.pos.x)) - 1;
        float cur_y = vy + static_cast<float>(cellPixelY(it->second.pos.y)) - 1;
        auto cur_w = static_cast<float>(CELL_W + 2);
        auto cur_h = static_cast<float>(CELL_H + 2);
        if (cur_x >= 0 && cur_y >= 0 && cur_x + cur_w <= PlayArea::WIDTH && cur_y + cur_h <= PlayArea::HEIGHT) {
            SDL_FRect highlight = {.x = cur_x, .y = cur_y, .w = cur_w, .h = cur_h};
            game_surface->drawRectBorder(&highlight, 15);
        }
    }
}

// Maneja eventos del minimapa (drag para explorar, click para navegar)
void MiniMap::handleEvent(const SDL_Event& event, const std::string& current_room) {
    // Toggle de números de room
    const auto* kc = KeyConfig::get();

    if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("MINIMAP", "numbers") && static_cast<int>(event.key.repeat) == 0) {
        show_numbers_ = !show_numbers_;
        return;
    }

    // Captura del minimapa
    if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("MINIMAP", "capture") && static_cast<int>(event.key.repeat) == 0) {
        if (map_surface_) {
            // Renderizar números sobre map_surface_ si están activos
            if (show_numbers_) {
                auto prev = Screen::get()->getRendererSurface();
                Screen::get()->setRendererSurface(map_surface_);
                renderRoomNumbers(0.0F, 0.0F);
                Screen::get()->setRendererSurface(prev);
            }
            auto game_surface = Screen::get()->getRendererSurface();
            if (game_surface) {
                Palette src_palette{};
                for (int i = 0; i < 256; ++i) { src_palette[i] = game_surface->getPaletteColor(static_cast<Uint8>(i)); }
                map_surface_->setPalette(src_palette);
            }
            std::string file = Screenshot::save(*map_surface_);
            if (!file.empty()) { std::cout << "MiniMap screenshot: " << file << "\n"; }
            // Recomponer para limpiar los números de la surface
            if (show_numbers_) { composeFinalSurface(); }
        }
        return;
    }

    if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
        // Guardar posición inicial para detectar si es click o drag
        float mouse_x = 0.0F;
        float mouse_y = 0.0F;
        SDL_GetMouseState(&mouse_x, &mouse_y);
        float render_x = 0.0F;
        float render_y = 0.0F;
        SDL_RenderCoordinatesFromWindow(Screen::get()->getRenderer(), mouse_x, mouse_y, &render_x, &render_y);
        SDL_FRect dst_rect = Screen::get()->getGameSurfaceDstRect();

        dragging_ = true;
        drag_start_x_ = render_x - dst_rect.x;
        drag_start_y_ = render_y - dst_rect.y;
        view_start_x_ = view_x_;
        view_start_y_ = view_y_;
    }

    if (event.type == SDL_EVENT_MOUSE_MOTION && dragging_) {
        float mouse_x = 0.0F;
        float mouse_y = 0.0F;
        SDL_GetMouseState(&mouse_x, &mouse_y);
        float render_x = 0.0F;
        float render_y = 0.0F;
        SDL_RenderCoordinatesFromWindow(Screen::get()->getRenderer(), mouse_x, mouse_y, &render_x, &render_y);
        SDL_FRect dst_rect = Screen::get()->getGameSurfaceDstRect();

        float game_x = render_x - dst_rect.x;
        float game_y = render_y - dst_rect.y;
        view_x_ = view_start_x_ + (game_x - drag_start_x_);
        view_y_ = view_start_y_ + (game_y - drag_start_y_);
    }

    if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) {
        if (dragging_) {
            // Comprobar si fue click (sin mover) o drag
            float mouse_x = 0.0F;
            float mouse_y = 0.0F;
            SDL_GetMouseState(&mouse_x, &mouse_y);
            float render_x = 0.0F;
            float render_y = 0.0F;
            SDL_RenderCoordinatesFromWindow(Screen::get()->getRenderer(), mouse_x, mouse_y, &render_x, &render_y);
            SDL_FRect dst_rect = Screen::get()->getGameSurfaceDstRect();

            float game_x = render_x - dst_rect.x;
            float game_y = render_y - dst_rect.y;

            float dx = game_x - drag_start_x_;
            float dy = game_y - drag_start_y_;
            bool was_click = (dx * dx + dy * dy) < 4.0F;  // Menos de 2px de movimiento = click

            if (was_click) {
                // Click: navegar a la room bajo el cursor
                std::string room = roomAtScreen(game_x, game_y);
                if (!room.empty() && room != current_room && on_navigate) {
                    on_navigate(room);
                }
            }

            dragging_ = false;
        }
    }
}

#endif  // _DEBUG
