#include "tilemap_renderer.hpp"

#include "core/rendering/screen.hpp"
#include "core/rendering/sprite/sprite.hpp"
#include "core/rendering/surface.hpp"
#ifdef _DEBUG
#include "core/system/debug.hpp"
#endif
#include "game/gameplay/collision_map.hpp"
#include "utils/utils.hpp"

// Constructor
TilemapRenderer::TilemapRenderer(std::vector<int> tile_map, int tile_set_width, std::shared_ptr<Surface> tileset_surface, std::string bg_color, int conveyor_belt_direction)
    : tile_map_(std::move(tile_map)),
      tile_set_width_(tile_set_width),
      tileset_surface_(std::move(tileset_surface)),
      bg_color_(std::move(bg_color)),
      conveyor_belt_direction_(conveyor_belt_direction),
      map_surface_(std::make_shared<Surface>(PlayArea::WIDTH, PlayArea::HEIGHT)) {
}

// Inicializa el renderizador
void TilemapRenderer::initialize(const CollisionMap* collision_map) {
    setAnimatedTiles(collision_map);
    fillMapTexture(collision_map);
}

// Actualiza las animaciones de tiles
void TilemapRenderer::update(float delta_time) {
    if (is_paused_) {
        return;
    }

    // Actualiza el acumulador de tiempo
    time_accumulator_ += delta_time;

    // Actualiza los tiles animados
    updateAnimatedTiles();
}

// Renderiza el mapa completo en pantalla
void TilemapRenderer::render() {
    // Dibuja la textura con el mapa en pantalla
    SDL_FRect dest = {.x = 0, .y = 0, .w = PlayArea::WIDTH, .h = PlayArea::HEIGHT};
    map_surface_->render(nullptr, &dest);

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

#ifdef _DEBUG
// Renderiza las superficies de colisión en modo debug (función helper estática)
static void renderDebugCollisionSurfaces(const CollisionMap* collision_map) {
    auto surface = Screen::get()->getRendererSurface();

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

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

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

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

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

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

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

// Redibuja el tilemap (para actualizar modo debug)
void TilemapRenderer::redrawMap(const CollisionMap* collision_map) {
    fillMapTexture(collision_map);
}

// Cambia un tile y repinta solo esa celda en la map_surface
void TilemapRenderer::setTile(int index, int tile_value) {
    if (index < 0 || index >= static_cast<int>(tile_map_.size())) { return; }

    tile_map_[index] = tile_value;

    int col = index % MAP_WIDTH;
    int row = index / MAP_WIDTH;

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

    // Borrar la celda con el color de fondo
    SDL_FRect cell = {.x = static_cast<float>(col * TILE_SIZE), .y = static_cast<float>(row * TILE_SIZE), .w = static_cast<float>(TILE_SIZE), .h = static_cast<float>(TILE_SIZE)};
    map_surface_->fillRect(&cell, stringToColor(bg_color_));

    // Dibujar el nuevo tile (si no es vacío ni animado)
    if (tile_value > -1) {
        const bool IS_ANIMATED = (tile_value >= 18 * tile_set_width_) && (tile_value < 19 * tile_set_width_);
        if (!IS_ANIMATED) {
            SDL_FRect clip = {.x = static_cast<float>((tile_value % tile_set_width_) * TILE_SIZE),
                .y = static_cast<float>((tile_value / tile_set_width_) * TILE_SIZE),
                .w = static_cast<float>(TILE_SIZE),
                .h = static_cast<float>(TILE_SIZE)};
            tileset_surface_->render(col * TILE_SIZE, row * TILE_SIZE, &clip);
        }
    }

    Screen::get()->setRendererSurface(previous_renderer);
}
#endif

// Pinta el mapa estático y debug lines
void TilemapRenderer::fillMapTexture(const CollisionMap* collision_map) {  // NOLINT(readability-convert-member-functions-to-static)
    const Uint8 COLOR = stringToColor(bg_color_);
    auto previous_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 = {.x = 0, .y = 0, .w = TILE_SIZE, .h = 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;
#ifdef _DEBUG
                if (!Debug::get()->isEnabled()) {
                    tileset_surface_->render(x * TILE_SIZE, y * TILE_SIZE, &clip);
                }
#else
                tileset_surface_->render(x * TILE_SIZE, y * TILE_SIZE, &clip);
#endif
            }
        }
    }

#ifdef _DEBUG
    // Pinta las superficies en el modo debug
    if (Debug::get()->isEnabled()) {
        renderDebugCollisionSurfaces(collision_map);
    }
#endif  // _DEBUG
    Screen::get()->setRendererSurface(previous_renderer);
}

// Localiza todos los tiles animados
void TilemapRenderer::setAnimatedTiles(const CollisionMap* collision_map) {  // NOLINT(readability-convert-member-functions-to-static)
    // Recorre la habitación entera por filas buscando tiles de tipo t_animated
    for (int i = 0; i < (int)tile_map_.size(); ++i) {
        const auto TILE_TYPE = collision_map->getTile(i);
        if (TILE_TYPE == CollisionMap::Tile::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<Sprite>(tileset_surface_, X, Y, 8, 8);
            at.sprite->setClip(XC, YC, 8, 8);
            at.x_orig = XC;
            animated_tiles_.push_back(at);
        }
    }
}

// Actualiza tiles animados
void TilemapRenderer::updateAnimatedTiles() {  // NOLINT(readability-make-member-function-const)
    const int NUM_FRAMES = 4;

    // Calcular frame actual basado en tiempo
    const int CURRENT_FRAME = static_cast<int>(time_accumulator_ / CONVEYOR_FRAME_DURATION) % NUM_FRAMES;

    // Calcular offset basado en dirección
    int offset = 0;
    if (conveyor_belt_direction_ == -1) {
        offset = CURRENT_FRAME * TILE_SIZE;
    } else {
        offset = (NUM_FRAMES - 1 - CURRENT_FRAME) * TILE_SIZE;
    }

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

// Renderiza tiles animados
void TilemapRenderer::renderAnimatedTiles() {
    for (const auto& a : animated_tiles_) {
        a.sprite->render();
    }
}
