#include "tilemap_renderer.hpp"

#include "core/rendering/screen.hpp"
#include "core/rendering/sprite/sprite.hpp"
#include "core/rendering/surface.hpp"
#ifdef _DEBUG
#include "core/resources/resource_cache.hpp"  // Para Resource::Cache (collision.gif)
#include "core/system/debug.hpp"
#endif

TilemapRenderer::TilemapRenderer(std::vector<int> tile_map, int tile_set_width, std::shared_ptr<Surface> tileset_surface, Uint8 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_(bg_color),
      conveyor_belt_direction_(conveyor_belt_direction) {
    map_surface_ = std::make_shared<Surface>(PlayArea::WIDTH, PlayArea::HEIGHT);
}

void TilemapRenderer::initialize(const std::vector<int>& collision_tile_map) {
    setAnimatedTiles(collision_tile_map);
    fillMapTexture(collision_tile_map);
}

void TilemapRenderer::update(float delta_time) {
    if (is_paused_) { return; }
    time_accumulator_ += delta_time;
    updateAnimatedTiles();
}

void TilemapRenderer::render() {
    SDL_FRect dest = {.x = 0, .y = 0, .w = PlayArea::WIDTH, .h = PlayArea::HEIGHT};
    map_surface_->render(nullptr, &dest);

#ifdef _DEBUG
    if (!Debug::get()->isEnabled()) {
        renderAnimatedTiles();
    }
#else
    renderAnimatedTiles();
#endif
}

#ifdef _DEBUG
// Redibuja el tilemap (para actualizar modo debug: pinta collision tilemap o tiles normales)
void TilemapRenderer::redrawMap(const std::vector<int>& collision_tile_map) {
    fillMapTexture(collision_tile_map);
}

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_);

    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, bg_color_);

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

// Renderiza el collision tilemap superpuesto (modo debug)
static void renderDebugCollisionTilemap(const std::vector<int>& collision_tile_map) {
    auto collision_surface = Resource::Cache::get()->getSurface("collision.gif");
    if (!collision_surface) { return; }

    for (int y = 0; y < Map::HEIGHT; ++y) {
        for (int x = 0; x < Map::WIDTH; ++x) {
            int index = (y * Map::WIDTH) + x;
            if (index >= static_cast<int>(collision_tile_map.size())) { continue; }
            int tile = collision_tile_map[index];
            if (tile <= 0) { continue; }
            SDL_FRect clip = {
                .x = static_cast<float>(tile * Tile::SIZE),
                .y = 0,
                .w = static_cast<float>(Tile::SIZE),
                .h = static_cast<float>(Tile::SIZE)};
            collision_surface->render(x * Tile::SIZE, y * Tile::SIZE, &clip);
        }
    }
}
#endif

// Pinta el mapa estático
void TilemapRenderer::fillMapTexture(const std::vector<int>& collision_tile_map) {
    auto previous_renderer = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(map_surface_);
    map_surface_->clear(bg_color_);

    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) {
            const int INDEX = (y * MAP_WIDTH) + x;
            // Los tiles animados (fila 18 del tileset) no se pintan en la textura estática
            const bool IS_ANIMATED = (tile_map_[INDEX] >= 18 * tile_set_width_) && (tile_map_[INDEX] < 19 * tile_set_width_);
            const bool HAS_TILE = tile_map_[INDEX] > -1;

            if (HAS_TILE && !IS_ANIMATED) {
                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
    // En modo debug, pintar el collision tilemap en vez de las líneas de colisión antiguas
    if (Debug::get()->isEnabled()) {
        renderDebugCollisionTilemap(collision_tile_map);
    }
#endif
    Screen::get()->setRendererSurface(previous_renderer);
}

// Localiza tiles animados (conveyor belts) usando el collision_tile_map
void TilemapRenderer::setAnimatedTiles(const std::vector<int>& collision_tile_map) {
    for (int i = 0; i < static_cast<int>(tile_map_.size()); ++i) {
        // Un tile es animado si su valor en el collision tilemap es COLLISION_ANIMATED (6)
        if (i < static_cast<int>(collision_tile_map.size()) && collision_tile_map[i] == COLLISION_ANIMATED) {
            const int X = (i % MAP_WIDTH) * TILE_SIZE;
            const int Y = (i / MAP_WIDTH) * TILE_SIZE;
            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);
        }
    }
}

void TilemapRenderer::updateAnimatedTiles() {
    const int NUM_FRAMES = 4;
    const int CURRENT_FRAME = static_cast<int>(time_accumulator_ / CONVEYOR_FRAME_DURATION) % NUM_FRAMES;

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

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