#include "game/gameplay/scoreboard.hpp"

#include <SDL3/SDL.h>

#include <utility>

#include "core/locale/locale.hpp"                     // Para Locale
#include "core/rendering/screen.hpp"                  // Para Screen
#include "core/rendering/sprite/animated_sprite.hpp"  // Para SAnimatedSprite
#include "core/rendering/surface.hpp"                 // Para Surface
#include "core/rendering/text.hpp"                    // Para Text
#include "core/resources/resource_cache.hpp"          // Para Resource
#include "game/entities/player.hpp"                   // Para Player::skinToAnimationPath
#include "game/options.hpp"                           // Para Options, options, Cheat, OptionsGame
#include "utils/defines.hpp"                          // Para BLOCK
#include "utils/utils.hpp"                            // Para stringToColor

// Constructor
Scoreboard::Scoreboard(std::shared_ptr<Data> data)
    : item_surface_(Resource::Cache::get()->getSurface("items.gif")),
      data_(std::move(std::move(data))) {
    const float SURFACE_WIDTH = Options::game.width;
    constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE;

    // Reserva memoria para los objetos
    const std::string PLAYER_ANIM_PATH = Player::skinToAnimationPath(Options::game.player_skin);
    const auto& player_animation_data = Resource::Cache::get()->getAnimationData(PLAYER_ANIM_PATH);
    player_sprite_ = std::make_shared<AnimatedSprite>(player_animation_data);
    player_sprite_->setCurrentAnimation("default");

    surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
    surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};

    // Inicializa el color de items
    items_color_ = stringToColor("white");

    // Inicializa el vector de colores
    const std::vector<std::string> COLORS = {"blue", "magenta", "green", "cyan", "yellow", "white", "bright_blue", "bright_magenta", "bright_green", "bright_cyan", "bright_yellow", "bright_white"};
    color_.reserve(COLORS.size());
    std::ranges::transform(COLORS, std::back_inserter(color_), [](const auto& color) { return stringToColor(color); });
}

// Pinta el objeto en pantalla
void Scoreboard::render() {
    surface_->render(nullptr, &surface_dest_);
}

// Actualiza las variables del objeto
void Scoreboard::update(float delta_time) {
    // Acumular tiempo para animaciones
    time_accumulator_ += delta_time;

    // Actualiza el color de la cantidad de items recogidos
    updateItemsColor(delta_time);

    // Dibuja la textura
    fillTexture();

    if (!is_paused_) {
        // Si está en pausa no se actualiza el reloj
        clock_ = getTime();
    }
}

// Obtiene el tiempo transcurrido de partida
auto Scoreboard::getTime() -> Scoreboard::ClockData {
    const Uint32 TIME_ELAPSED = SDL_GetTicks() - data_->ini_clock - paused_time_elapsed_;

    ClockData time;
    time.hours = TIME_ELAPSED / 3600000;
    time.minutes = TIME_ELAPSED / 60000;
    time.seconds = TIME_ELAPSED / 1000;
    time.separator = (TIME_ELAPSED % 1000 <= 500) ? ":" : " ";

    return time;
}

// Actualiza el sprite del jugador con la skin actual
void Scoreboard::refreshPlayerSkin() {
    const std::string PLAYER_ANIM_PATH = Player::skinToAnimationPath(Options::game.player_skin);
    const auto& player_animation_data = Resource::Cache::get()->getAnimationData(PLAYER_ANIM_PATH);
    player_sprite_ = std::make_shared<AnimatedSprite>(player_animation_data);
    player_sprite_->setCurrentAnimation("default");
}

// Pone el marcador en modo pausa
void Scoreboard::setPaused(bool value) {
    if (is_paused_ == value) {
        // Evita ejecutar lógica si el estado no cambia
        return;
    }

    is_paused_ = value;

    if (is_paused_) {
        // Guarda el tiempo actual al pausar
        paused_time_ = SDL_GetTicks();
    } else {
        // Calcula el tiempo pausado acumulado al reanudar
        paused_time_elapsed_ += SDL_GetTicks() - paused_time_;
    }
}

// Actualiza el color de la cantidad de items recogidos
void Scoreboard::updateItemsColor(float delta_time) {
    if (!data_->jail_is_open) {
        return;
    }

    items_color_timer_ += delta_time;

    // Resetear timer cada 2 ciclos (0.666s total)
    if (items_color_timer_ >= ITEMS_COLOR_BLINK_DURATION * 2.0F) {
        items_color_timer_ = 0.0F;
    }

    // Alternar color cada ITEMS_COLOR_BLINK_DURATION
    if (items_color_timer_ < ITEMS_COLOR_BLINK_DURATION) {
        items_color_ = stringToColor("white");
    } else {
        items_color_ = stringToColor("magenta");
    }
}

// Devuelve la cantidad de minutos de juego transcurridos
auto Scoreboard::getMinutes() -> int {
    return getTime().minutes;
}

// Dibuja los elementos del marcador en la textura
void Scoreboard::fillTexture() {
    // Empieza a dibujar en la textura
    auto previuos_renderer = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(surface_);

    // Limpia la textura
    surface_->clear(stringToColor("black"));

    // Dibuja las vidas
    const int WALK_FRAMES = player_sprite_->getCurrentAnimationSize();
    const int DESP = static_cast<int>(time_accumulator_ / SPRITE_WALK_CYCLE_DURATION) % (WALK_FRAMES * 2);
    const int FRAME = DESP % WALK_FRAMES;
    player_sprite_->setCurrentAnimationFrame(FRAME);
    player_sprite_->setPosY(LINE2_Y);
    for (int i = 0; i < data_->lives; ++i) {
        player_sprite_->setPosX(LIVES_START_X + (LIVES_SPACING * i) + DESP);
        const int INDEX = i % color_.size();
        player_sprite_->render(1, color_.at(INDEX));
    }

    // Muestra si suena la música
    if (data_->music) {
        const Uint8 C = data_->color;
        SDL_FRect clip = {.x = 0, .y = 8, .w = 8, .h = 8};
        item_surface_->renderWithColorReplace(MUSIC_ICON_X, LINE2_Y, 1, C, &clip);
    }

    // Escribe los textos
    auto text = Resource::Cache::get()->getText("smb2");
    const std::string TIME_TEXT = std::to_string((clock_.minutes % 100) / 10) + std::to_string(clock_.minutes % 10) + clock_.separator + std::to_string((clock_.seconds % 60) / 10) + std::to_string(clock_.seconds % 10);
    const std::string ITEMS_TEXT = std::to_string(data_->items / 100) + std::to_string((data_->items % 100) / 10) + std::to_string(data_->items % 10);
    text->writeColored(ITEMS_LABEL_X, LINE1_Y, Locale::get()->get("scoreboard.items"), data_->color);
    text->writeColored(ITEMS_VALUE_X, LINE1_Y, ITEMS_TEXT, items_color_);
    text->writeColored(TIME_LABEL_X, LINE1_Y, Locale::get()->get("scoreboard.time"), data_->color);
    text->writeColored(TIME_VALUE_X, LINE1_Y, TIME_TEXT, stringToColor("white"));

    const std::string ROOMS_TEXT = std::to_string(data_->rooms / 100) + std::to_string((data_->rooms % 100) / 10) + std::to_string(data_->rooms % 10);
    text->writeColored(ROOMS_LABEL_X, LINE2_Y, Locale::get()->get("scoreboard.rooms"), stringToColor("white"));
    text->writeColored(ROOMS_VALUE_X, LINE2_Y, ROOMS_TEXT, stringToColor("white"));

    // Indicadores de trucos activos (fuente 8bithud)
    auto cheat_text = Resource::Cache::get()->getText("8bithud");
    if (Options::cheats.infinite_lives == Options::Cheat::State::ENABLED) {
        cheat_text->writeColored(CHEAT_INF_LIVES_X, CHEAT_INF_LIVES_Y, Locale::get()->get("scoreboard.cheat_infinite_lives"), data_->color);
    }
    if (Options::cheats.invincible == Options::Cheat::State::ENABLED) {
        cheat_text->writeColored(CHEAT_INVINCIBLE_X, CHEAT_INVINCIBLE_Y, Locale::get()->get("scoreboard.cheat_invincibility"), data_->color);
    }

    // Deja el renderizador como estaba
    Screen::get()->setRendererSurface(previuos_renderer);
}