#include "game/gameplay/scoreboard.hpp"

#include <SDL3/SDL.h>  // Para SDL_DestroyTexture, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_CreateTexture, SDL_GetRenderTarget, SDL_GetTicks, SDL_RenderClear, SDL_RenderLine, SDL_RenderTexture, SDL_SetTextureBlendMode, SDL_FRect, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_Texture, SDL_TextureAccess

#include <algorithm>  // Para max
#include <cmath>      // Para roundf
#include <iomanip>    // Para operator<<, setfill, setw
#include <iostream>
#include <sstream>  // Para basic_ostream, basic_ostringstream, basic_ostream::operator<<, ostringstream

#include "core/locale/lang.hpp"              // Para getText
#include "core/rendering/screen.hpp"         // Para Screen
#include "core/rendering/sprite/sprite.hpp"  // Para Sprite
#include "core/rendering/text.hpp"           // Para Text, Text::CENTER, Text::COLOR
#include "core/rendering/texture.hpp"        // Para Texture
#include "core/resources/resource.hpp"       // Para Resource
#include "game/gameplay/enter_name.hpp"      // Para NAME_SIZE
#include "utils/color.hpp"
#include "utils/param.hpp"  // Para Param, ParamScoreboard, param
#include "utils/utils.hpp"  // Para easeOutCubic

// .at(SINGLETON) Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
Scoreboard* Scoreboard::instance = nullptr;

// .at(SINGLETON) Crearemos el objeto score_board con esta función estática
void Scoreboard::init() {
    Scoreboard::instance = new Scoreboard();
}

// .at(SINGLETON) Destruiremos el objeto score_board con esta función estática
void Scoreboard::destroy() {
    delete Scoreboard::instance;
}

// .at(SINGLETON) Con este método obtenemos el objeto score_board y podemos trabajar con él
auto Scoreboard::get() -> Scoreboard* {
    return Scoreboard::instance;
}

// Constructor
Scoreboard::Scoreboard()
    : renderer_(Screen::get()->getRenderer()),
      game_power_meter_texture_(Resource::get()->getTexture("game_power_meter.png")),
      power_meter_sprite_(std::make_unique<Sprite>(game_power_meter_texture_)),
      text_(Resource::get()->getText("8bithud")) {
    // Inicializa variables
    for (size_t i = 0; i < static_cast<size_t>(Id::SIZE); ++i) {
        name_.at(i).clear();
        enter_name_.at(i).clear();
        selector_pos_.at(i) = 0;
        score_.at(i) = 0;
        mult_.at(i) = 0;
        continue_counter_.at(i) = 0;
        carousel_prev_index_.at(i) = -1;  // Inicializar a -1 para detectar primera inicialización
        enter_name_ref_.at(i) = nullptr;
        text_slide_offset_.at(i) = 0.0F;
    }

    panel_.at(static_cast<size_t>(Id::LEFT)).mode = Mode::SCORE;
    panel_.at(static_cast<size_t>(Id::RIGHT)).mode = Mode::SCORE;
    panel_.at(static_cast<size_t>(Id::CENTER)).mode = Mode::STAGE_INFO;

    // Recalcula las anclas de los elementos
    recalculateAnchors();
    power_meter_sprite_->setPosition(SDL_FRect{
        .x = static_cast<float>(slot4_2_.x - 20),
        .y = slot4_2_.y,
        .w = 40,
        .h = 7});

    // Crea la textura de fondo
    background_ = nullptr;
    createBackgroundTexture();

    // Crea las texturas de los paneles
    createPanelTextures();

    // Rellena la textura de fondo
    fillBackgroundTexture();

    // Inicializa el ciclo de colores para el nombre
    name_color_cycle_ = Colors::generateMirroredCycle(color_.INVERSE(), ColorCycleStyle::VIBRANT);
    animated_color_ = name_color_cycle_.at(0);
}

Scoreboard::~Scoreboard() {
    if (background_ != nullptr) {
        SDL_DestroyTexture(background_);
    }

    for (auto* texture : panel_texture_) {
        if (texture != nullptr) {
            SDL_DestroyTexture(texture);
        }
    }
}

// Configura la animación del carrusel
void Scoreboard::setCarouselAnimation(Id id, int selected_index, EnterName* enter_name_ptr) {
    auto idx = static_cast<size_t>(id);

    // Guardar referencia
    enter_name_ref_.at(idx) = enter_name_ptr;
    if ((enter_name_ptr == nullptr) || selected_index < 0) {
        return;
    }

    // ===== Inicialización (primera vez) =====
    if (carousel_prev_index_.at(idx) == -1) {
        carousel_position_.at(idx) = static_cast<float>(selected_index);
        carousel_target_.at(idx) = static_cast<float>(selected_index);
        carousel_prev_index_.at(idx) = selected_index;
        return;
    }

    int prev_index = carousel_prev_index_.at(idx);
    if (selected_index == prev_index) {
        return;  // nada que hacer
    }

    // ===== Bloquear si aún animando =====
    if (std::abs(carousel_position_.at(idx) - carousel_target_.at(idx)) > 0.01F) {
        return;
    }

    // ===== Calcular salto circular =====
    int delta = selected_index - prev_index;
    const int LIST_SIZE = static_cast<int>(enter_name_ptr->getCharacterList().size());
    if (delta > LIST_SIZE / 2) {
        delta -= LIST_SIZE;
    } else if (delta < -LIST_SIZE / 2) {
        delta += LIST_SIZE;
    }

    // ===== Alinear posición actual antes de moverse =====
    carousel_position_.at(idx) = std::round(carousel_position_.at(idx));

    // ===== Control del salto =====
    const int ABS_DELTA = std::abs(delta);

    if (ABS_DELTA <= 2) {
        // Movimiento corto → animación normal
        carousel_target_.at(idx) = carousel_position_.at(idx) + static_cast<float>(delta);
    } else {
        // Movimiento largo → animado pero limitado en tiempo
        // Normalizamos el salto para que visualmente tarde como mucho el doble
        const float MAX_DURATION_FACTOR = 2.0F;  // máximo 2x la duración de una letra
        const float SPEED_SCALE = std::min(1.0F, MAX_DURATION_FACTOR / static_cast<float>(ABS_DELTA));

        // Guardamos el destino real
        float target = std::round(carousel_position_.at(idx)) + static_cast<float>(delta);

        // Interpolaremos más rápido en updateCarouselAnimation usando un factor auxiliar
        // guardado en un nuevo vector (si no existe aún, puedes declararlo en la clase):
        carousel_speed_scale_.at(idx) = SPEED_SCALE;

        // Asignamos el target real
        carousel_target_.at(idx) = target;
    }

    carousel_prev_index_.at(idx) = selected_index;
}

// Establece el modo del panel y gestiona transiciones
void Scoreboard::setMode(Id id, Mode mode) {
    auto idx = static_cast<size_t>(id);

    // Cambiar el modo
    panel_.at(idx).mode = mode;

    // Gestionar inicialización/transiciones según el nuevo modo
    switch (mode) {
        case Mode::SCORE_TO_ENTER_NAME:
            // Iniciar animación de transición SCORE → ENTER_NAME
            text_slide_offset_.at(idx) = 0.0F;
            // Resetear carrusel para que se inicialice correctamente en ENTER_NAME
            if (carousel_prev_index_.at(idx) != -1) {
                carousel_prev_index_.at(idx) = -1;
            }
            break;

        case Mode::ENTER_NAME:
            // Resetear carrusel al entrar en modo de entrada de nombre
            // Esto fuerza una reinicialización en la próxima llamada a setCarouselAnimation()
            if (carousel_prev_index_.at(idx) != -1) {
                carousel_prev_index_.at(idx) = -1;
            }
            text_slide_offset_.at(idx) = 0.0F;
            break;

        case Mode::ENTER_TO_SHOW_NAME:
            // Iniciar animación de transición ENTER_NAME → SHOW_NAME
            text_slide_offset_.at(idx) = 0.0F;
            break;

        case Mode::SHOW_NAME:
            // Asegurar que la animación está completa
            text_slide_offset_.at(idx) = 1.0F;
            break;

        // Otros modos no requieren inicialización especial
        default:
            break;
    }
}

// Transforma un valor numérico en una cadena de 7 cifras
auto Scoreboard::updateScoreText(int num) -> std::string {
    std::ostringstream oss;
    oss << std::setw(7) << std::setfill('0') << num;
    return oss.str();
}

// Actualiza el contador
void Scoreboard::updateTimeCounter() {
    constexpr int TICKS_SPEED = 100;

    if (SDL_GetTicks() - ticks_ > TICKS_SPEED) {
        ticks_ = SDL_GetTicks();
        ++time_counter_;
    }
}

// Actualiza el índice del color animado del nombre
void Scoreboard::updateNameColorIndex() {
    constexpr Uint64 COLOR_UPDATE_INTERVAL = 100;  // 100ms entre cambios de color

    if (SDL_GetTicks() - name_color_last_update_ >= COLOR_UPDATE_INTERVAL) {
        ++name_color_index_;
        name_color_last_update_ = SDL_GetTicks();
    }

    // Precalcular el color actual del ciclo
    animated_color_ = name_color_cycle_.at(name_color_index_ % name_color_cycle_.size());
}

// Actualiza la animación del carrusel
void Scoreboard::updateCarouselAnimation(float delta_time) {
    const float BASE_SPEED = 8.0F;  // Posiciones por segundo

    for (size_t i = 0; i < carousel_position_.size(); ++i) {
        // Solo animar si no hemos llegado al target
        if (std::abs(carousel_position_.at(i) - carousel_target_.at(i)) > 0.01F) {
            // Determinar dirección
            float direction = (carousel_target_.at(i) > carousel_position_.at(i)) ? 1.0F : -1.0F;

            // Calcular movimiento
            float speed = BASE_SPEED / carousel_speed_scale_.at(i);  // ajusta según salto
            float movement = speed * delta_time * direction;

            // Mover, pero no sobrepasar el target
            float new_position = carousel_position_.at(i) + movement;

            // Clamp para no sobrepasar
            if (direction > 0) {
                carousel_position_.at(i) = std::min(new_position, carousel_target_.at(i));
            } else {
                carousel_position_.at(i) = std::max(new_position, carousel_target_.at(i));
            }
        } else {
            // Forzar al target exacto cuando estamos muy cerca
            carousel_position_.at(i) = carousel_target_.at(i);
            carousel_speed_scale_.at(i) = 1.0F;  // restaurar velocidad normal
        }
    }
}

// Actualiza las animaciones de deslizamiento de texto
void Scoreboard::updateTextSlideAnimation(float delta_time) {
    for (size_t i = 0; i < static_cast<size_t>(Id::SIZE); ++i) {
        Mode current_mode = panel_.at(i).mode;

        if (current_mode == Mode::SCORE_TO_ENTER_NAME) {
            // Incrementar progreso de animación SCORE → ENTER_NAME (0.0 a 1.0)
            text_slide_offset_.at(i) += delta_time / TEXT_SLIDE_DURATION;

            // Terminar animación y cambiar a ENTER_NAME cuando se complete
            if (text_slide_offset_.at(i) >= 1.0F) {
                setMode(static_cast<Id>(i), Mode::ENTER_NAME);
            }
        } else if (current_mode == Mode::ENTER_TO_SHOW_NAME) {
            // Incrementar progreso de animación ENTER_NAME → SHOW_NAME (0.0 a 1.0)
            text_slide_offset_.at(i) += delta_time / TEXT_SLIDE_DURATION;

            // Terminar animación y cambiar a SHOW_NAME cuando se complete
            if (text_slide_offset_.at(i) >= 1.0F) {
                setMode(static_cast<Id>(i), Mode::SHOW_NAME);
            }
        }
    }
}

// Actualiza las animaciones de pulso de los paneles
void Scoreboard::updatePanelPulses(float delta_time) {
    for (size_t i = 0; i < static_cast<size_t>(Id::SIZE); ++i) {
        auto& pulse = panel_pulse_.at(i);

        if (!pulse.active) {
            continue;
        }

        // Avanzar el tiempo transcurrido
        pulse.elapsed_s += delta_time;

        // Desactivar el pulso si ha terminado
        if (pulse.elapsed_s >= pulse.duration_s) {
            pulse.active = false;
            pulse.elapsed_s = 0.0F;
        }
    }
}

// Activa un pulso en el panel especificado
void Scoreboard::triggerPanelPulse(Id id, float duration_s) {
    auto idx = static_cast<size_t>(id);
    panel_pulse_.at(idx).active = true;
    panel_pulse_.at(idx).elapsed_s = 0.0F;
    panel_pulse_.at(idx).duration_s = duration_s;
}

// Actualiza la lógica del marcador
void Scoreboard::update(float delta_time) {
    updateTimeCounter();
    updateNameColorIndex();
    updateCarouselAnimation(delta_time);
    updateTextSlideAnimation(delta_time);
    updatePanelPulses(delta_time);
    fillBackgroundTexture();  // Renderizar DESPUÉS de actualizar
}

// Pinta el marcador
void Scoreboard::render() {
    SDL_RenderTexture(renderer_, background_, nullptr, &rect_);
}

// Establece el valor de la variable
void Scoreboard::setColor(Color color) {
    // Actualiza las variables de colores
    color_ = color;
    text_color1_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(100) : param.scoreboard.text_color1;
    text_color2_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(150) : param.scoreboard.text_color2;

    // Aplica los colores
    power_meter_sprite_->getTexture()->setColor(text_color2_);
    fillBackgroundTexture();
    name_color_cycle_ = Colors::generateMirroredCycle(color_.INVERSE(), ColorCycleStyle::VIBRANT);
}

// Establece el valor de la variable
void Scoreboard::setPos(SDL_FRect rect) {
    rect_ = rect;

    recalculateAnchors();       // Recalcula las anclas de los elementos
    createBackgroundTexture();  // Crea la textura de fondo
    createPanelTextures();      // Crea las texturas de los paneles
    fillBackgroundTexture();    // Rellena la textura de fondo
}

// Rellena los diferentes paneles del marcador
void Scoreboard::fillPanelTextures() {
    // Guarda a donde apunta actualmente el renderizador
    auto* temp = SDL_GetRenderTarget(renderer_);

    // Genera el contenido de cada panel_
    for (size_t i = 0; i < static_cast<int>(Id::SIZE); ++i) {
        // Cambia el destino del renderizador
        SDL_SetRenderTarget(renderer_, panel_texture_.at(i));

        // Calcula el color de fondo del panel (puede tener pulso activo)
        Color background_color = Color(0, 0, 0, 0);  // Transparente por defecto

        const auto& pulse = panel_pulse_.at(i);
        if (pulse.active) {
            // Calcular el progreso del pulso (0.0 a 1.0 y de vuelta a 0.0)
            float progress = pulse.elapsed_s / pulse.duration_s;

            // Crear curva de ida y vuelta (0 → 1 → 0)
            float pulse_intensity;
            if (progress < 0.5F) {
                pulse_intensity = progress * 2.0F;  // 0.0 a 1.0
            } else {
                pulse_intensity = (1.0F - progress) * 2.0F;  // 1.0 a 0.0
            }

            // Interpolar entre color base y color aclarado
            Color target_color = color_.LIGHTEN(PANEL_PULSE_LIGHTEN_AMOUNT);
            // Color target_color = color_.INVERSE();
            background_color = color_.LERP(target_color, pulse_intensity);
            background_color.a = 255;  // Opaco durante el pulso
        }

        // Dibuja el fondo de la textura
        SDL_SetRenderDrawColor(renderer_, background_color.r, background_color.g, background_color.b, background_color.a);
        SDL_RenderClear(renderer_);

        renderPanelContent(i);
    }

    // Deja el renderizador apuntando donde estaba
    SDL_SetRenderTarget(renderer_, temp);
}

void Scoreboard::renderPanelContent(size_t panel_index) {
    switch (panel_.at(panel_index).mode) {
        case Mode::SCORE:
            renderScoreMode(panel_index);
            break;
        case Mode::DEMO:
            renderDemoMode();
            break;
        case Mode::WAITING:
            renderWaitingMode();
            break;
        case Mode::GAME_OVER:
            renderGameOverMode();
            break;
        case Mode::STAGE_INFO:
            renderStageInfoMode();
            break;
        case Mode::CONTINUE:
            renderContinueMode(panel_index);
            break;
        case Mode::SCORE_TO_ENTER_NAME:
            renderScoreToEnterNameMode(panel_index);
            break;
        case Mode::ENTER_NAME:
            renderEnterNameMode(panel_index);
            break;
        case Mode::ENTER_TO_SHOW_NAME:
            renderEnterToShowNameMode(panel_index);
            break;
        case Mode::SHOW_NAME:
            renderShowNameMode(panel_index);
            break;
        case Mode::GAME_COMPLETED:
            renderGameCompletedMode(panel_index);
            break;
        default:
            break;
    }
}

void Scoreboard::renderScoreMode(size_t panel_index) {
    // SCORE
    text_->writeDX(Text::COLOR | Text::CENTER, slot4_1_.x, slot4_1_.y, name_.at(panel_index), 1, text_color1_);
    text_->writeDX(Text::COLOR | Text::CENTER, slot4_2_.x, slot4_2_.y, updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // MULT
    text_->writeDX(Text::COLOR | Text::CENTER, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 3"), 1, text_color1_);
    text_->writeDX(Text::COLOR | Text::CENTER, slot4_4_.x, slot4_4_.y, "x" + std::to_string(mult_.at(panel_index)).substr(0, 3), 1, text_color2_);
}

void Scoreboard::renderDemoMode() {
    // DEMO MODE
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y + 4, Lang::getText("[SCOREBOARD] 6"), 1, text_color1_);

    // PRESS START TO PLAY
    if (time_counter_ % 10 < 8) {
        text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y - 2, Lang::getText("[SCOREBOARD] 8"), 1, text_color1_);
        text_->writeDX(Text::CENTER | Text::COLOR, slot4_4_.x, slot4_4_.y - 2, Lang::getText("[SCOREBOARD] 9"), 1, text_color1_);
    }
}

void Scoreboard::renderWaitingMode() {
    // GAME OVER
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y + 4, Lang::getText("[SCOREBOARD] 7"), 1, text_color1_);

    // PRESS START TO PLAY
    if (time_counter_ % 10 < 8) {
        text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y - 2, Lang::getText("[SCOREBOARD] 8"), 1, text_color1_);
        text_->writeDX(Text::CENTER | Text::COLOR, slot4_4_.x, slot4_4_.y - 2, Lang::getText("[SCOREBOARD] 9"), 1, text_color1_);
    }
}

void Scoreboard::renderGameOverMode() {
    // GAME OVER
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y + 4, Lang::getText("[SCOREBOARD] 7"), 1, text_color1_);

    // PLEASE WAIT
    if (time_counter_ % 10 < 8) {
        text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y - 2, Lang::getText("[SCOREBOARD] 12"), 1, text_color1_);
        text_->writeDX(Text::CENTER | Text::COLOR, slot4_4_.x, slot4_4_.y - 2, Lang::getText("[SCOREBOARD] 13"), 1, text_color1_);
    }
}

void Scoreboard::renderStageInfoMode() {
    // STAGE
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y, Lang::getText("[SCOREBOARD] 5") + " " + std::to_string(stage_), 1, text_color1_);

    // POWERMETER
    power_meter_sprite_->setSpriteClip(0, 0, 40, 7);
    power_meter_sprite_->render();
    power_meter_sprite_->setSpriteClip(40, 0, static_cast<int>(power_ * 40.0F), 7);
    power_meter_sprite_->render();

    // HI-SCORE
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 4"), 1, text_color1_);
    const std::string NAME = hi_score_name_.empty() ? "" : hi_score_name_ + " - ";
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_4_.x, slot4_4_.y, NAME + updateScoreText(hi_score_), 1, text_color2_);
}

void Scoreboard::renderContinueMode(size_t panel_index) {
    // SCORE
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y, name_.at(panel_index), 1, text_color1_);
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_2_.x, slot4_2_.y, updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // CONTINUE
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 10"), 1, text_color1_);
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_4_.x, slot4_4_.y, std::to_string(continue_counter_.at(panel_index)), 1, text_color2_);
}

void Scoreboard::renderScoreToEnterNameMode(size_t panel_index) {
    // Calcular progreso suavizado de la animación (0.0 a 1.0)
    const auto T = static_cast<float>(easeInOutSine(text_slide_offset_.at(panel_index)));

    // Calcular desplazamientos reales entre slots (no son exactamente ROW_SIZE)
    const float DELTA_1_TO_2 = slot4_2_.y - slot4_1_.y;  // Diferencia real entre ROW1 y ROW2
    const float DELTA_2_TO_3 = slot4_3_.y - slot4_2_.y;  // Diferencia real entre ROW2 y ROW3
    const float DELTA_3_TO_4 = slot4_4_.y - slot4_3_.y;  // Diferencia real entre ROW3 y ROW4

    // ========== Texto que SALE hacia arriba ==========
    // name_ (sale desde ROW1 hacia arriba)
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y - (T * DELTA_1_TO_2), name_.at(panel_index), 1, text_color1_);

    // ========== Textos que SE MUEVEN hacia arriba ==========
    // score_ (se mueve de ROW2 a ROW1)
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_2_.x, slot4_2_.y - (T * DELTA_1_TO_2), updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // "ENTER NAME" (se mueve de ROW3 a ROW2)
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y - (T * DELTA_2_TO_3), Lang::getText("[SCOREBOARD] 11"), 1, text_color1_);

    // enter_name_ (se mueve de ROW4 a ROW3)
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_4_.x, slot4_4_.y - (T * DELTA_3_TO_4), enter_name_.at(panel_index), 1, text_color2_);

    // ========== Elemento que ENTRA desde abajo ==========
    // CARRUSEL (entra desde debajo de ROW4 hacia ROW4)
    renderCarousel(panel_index, slot4_4_.x, static_cast<int>(slot4_4_.y + DELTA_3_TO_4 - (T * DELTA_3_TO_4)));
}

void Scoreboard::renderEnterNameMode(size_t panel_index) {
    /*
    // SCORE
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y, name_.at(panel_index), 1, text_color1_);
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_2_.x, slot4_2_.y, updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // ENTER NAME
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 11"), 1, text_color1_);

    renderNameInputField(panel_index);
    */

    // SCORE
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y, updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // ENTER NAME
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_2_.x, slot4_2_.y, Lang::getText("[SCOREBOARD] 11"), 1, text_color1_);

    // NAME
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y, enter_name_.at(panel_index), 1, text_color2_);

    // CARRUSEL
    renderCarousel(panel_index, slot4_4_.x, slot4_4_.y);
}

void Scoreboard::renderEnterToShowNameMode(size_t panel_index) {
    // Calcular progreso suavizado de la animación (0.0 a 1.0)
    const auto T = static_cast<float>(easeInOutSine(text_slide_offset_.at(panel_index)));

    // Calcular desplazamientos reales entre slots (no son exactamente ROW_SIZE)
    const float DELTA_1_TO_2 = slot4_2_.y - slot4_1_.y;  // Diferencia real entre ROW1 y ROW2
    const float DELTA_2_TO_3 = slot4_3_.y - slot4_2_.y;  // Diferencia real entre ROW2 y ROW3
    const float DELTA_3_TO_4 = slot4_4_.y - slot4_3_.y;  // Diferencia real entre ROW3 y ROW4

    // ========== Texto que ENTRA desde arriba ==========
    // name_ (entra desde arriba hacia ROW1)
    // Debe venir desde donde estaría ROW0, que está a delta_1_to_2 píxeles arriba de ROW1
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y + (T * DELTA_1_TO_2) - DELTA_1_TO_2, name_.at(panel_index), 1, text_color1_);

    // ========== Textos que SE MUEVEN (renderizar UNA sola vez) ==========
    // SCORE (se mueve de ROW1 a ROW2)
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y + (T * DELTA_1_TO_2), updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // "ENTER NAME" (se mueve de ROW2 a ROW3)
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_2_.x, slot4_2_.y + (T * DELTA_2_TO_3), Lang::getText("[SCOREBOARD] 11"), 1, text_color1_);

    // enter_name_ (se mueve de ROW3 a ROW4)
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y + (T * DELTA_3_TO_4), enter_name_.at(panel_index), 1, text_color2_);

    // ========== Elemento que SALE hacia abajo ==========
    // CARRUSEL (sale desde ROW4 hacia abajo, fuera de pantalla)
    renderCarousel(panel_index, slot4_4_.x, static_cast<int>(slot4_4_.y + (T * DELTA_3_TO_4)));
}

void Scoreboard::renderShowNameMode(size_t panel_index) {
    // NOMBRE DEL JUGADOR
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y, name_.at(panel_index), 1, text_color1_);

    // SCORE
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_2_.x, slot4_2_.y, updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // "ENTER NAME"
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 11"), 1, text_color1_);

    // NOMBRE INTRODUCIDO (con color animado)
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_4_.x, slot4_4_.y, enter_name_.at(panel_index), 1, animated_color_);
}

void Scoreboard::renderGameCompletedMode(size_t panel_index) {
    // GAME OVER
    text_->writeDX(Text::CENTER | Text::COLOR, slot4_1_.x, slot4_1_.y + 4, Lang::getText("[SCOREBOARD] 7"), 1, text_color1_);

    // SCORE
    if (time_counter_ % 10 < 8) {
        text_->writeDX(Text::CENTER | Text::COLOR, slot4_3_.x, slot4_3_.y - 2, Lang::getText("[SCOREBOARD] 14"), 1, text_color1_);
        text_->writeDX(Text::CENTER | Text::COLOR, slot4_4_.x, slot4_4_.y - 2, updateScoreText(score_.at(panel_index)), 1, text_color2_);
    }
}

// Rellena la textura de fondo
void Scoreboard::fillBackgroundTexture() {
    // Rellena los diferentes paneles del marcador
    fillPanelTextures();

    // Cambia el destino del renderizador
    SDL_Texture* temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, background_);

    // Dibuja el fondo del marcador
    SDL_SetRenderDrawColor(renderer_, color_.r, color_.g, color_.b, 255);
    SDL_RenderClear(renderer_);

    // Copia las texturas de los paneles
    for (int i = 0; i < static_cast<int>(Id::SIZE); ++i) {
        SDL_RenderTexture(renderer_, panel_texture_.at(i), nullptr, &panel_.at(i).pos);
    }

    // Dibuja la linea que separa la zona de juego del marcador
    renderSeparator();

    // Deja el renderizador apuntando donde estaba
    SDL_SetRenderTarget(renderer_, temp);
}

// Recalcula las anclas de los elementos
void Scoreboard::recalculateAnchors() {
    // Recalcula la posición y el tamaño de los paneles
    const float PANEL_WIDTH = rect_.w / static_cast<float>(static_cast<int>(Id::SIZE));
    for (int i = 0; i < static_cast<int>(Id::SIZE); ++i) {
        panel_.at(i).pos.x = roundf(PANEL_WIDTH * i);
        panel_.at(i).pos.y = 0;
        panel_.at(i).pos.w = roundf(PANEL_WIDTH * (i + 1)) - panel_.at(i).pos.x;
        panel_.at(i).pos.h = rect_.h;
    }

    // Constantes para definir las zonas del panel_: 4 filas y 1 columna
    const int ROW_SIZE = rect_.h / 4;
    const int TEXT_HEIGHT = 7;

    // Filas
    const float ROW1 = 1 + (ROW_SIZE * 0) + (TEXT_HEIGHT / 2);
    const float ROW2 = 1 + (ROW_SIZE * 1) + (TEXT_HEIGHT / 2) - 1;
    const float ROW3 = 1 + (ROW_SIZE * 2) + (TEXT_HEIGHT / 2) - 2;
    const float ROW4 = 1 + (ROW_SIZE * 3) + (TEXT_HEIGHT / 2) - 3;

    // Columna
    const float COL = PANEL_WIDTH / 2;

    // Slots de 4
    slot4_1_ = {.x = COL, .y = ROW1};
    slot4_2_ = {.x = COL, .y = ROW2};
    slot4_3_ = {.x = COL, .y = ROW3};
    slot4_4_ = {.x = COL, .y = ROW4};

    // Primer cuadrado para poner el nombre de record
    const int ENTER_NAME_LENGTH = text_->length(std::string(EnterName::MAX_NAME_SIZE, 'A'));
    enter_name_pos_.x = COL - (ENTER_NAME_LENGTH / 2);
    enter_name_pos_.y = ROW4;

    // Recoloca los sprites
    if (power_meter_sprite_) {
        power_meter_sprite_->setX(slot4_2_.x - 20);
        power_meter_sprite_->setY(slot4_2_.y);
    }
}

// Crea la textura de fondo
void Scoreboard::createBackgroundTexture() {
    // Elimina la textura en caso de existir
    if (background_ != nullptr) {
        SDL_DestroyTexture(background_);
    }

    // Recrea la textura de fondo
    background_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect_.w, rect_.h);
    SDL_SetTextureBlendMode(background_, SDL_BLENDMODE_BLEND);
}

// Crea las texturas de los paneles
void Scoreboard::createPanelTextures() {
    // Elimina las texturas en caso de existir
    for (auto* texture : panel_texture_) {
        if (texture != nullptr) {
            SDL_DestroyTexture(texture);
        }
    }
    panel_texture_.clear();

    // Crea las texturas para cada panel_
    for (auto& i : panel_) {
        SDL_Texture* tex = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, i.pos.w, i.pos.h);
        SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
        panel_texture_.push_back(tex);
    }
}

// Dibuja la linea que separa la zona de juego del marcador
void Scoreboard::renderSeparator() {
    // Dibuja la linea que separa el marcador de la zona de juego
    auto color = param.scoreboard.separator_autocolor ? color_.DARKEN() : param.scoreboard.separator_color;
    SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 255);
    SDL_RenderLine(renderer_, 0, 0, rect_.w, 0);
}

// Pinta el carrusel de caracteres con efecto de color LERP y animación suave
void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) {
    // Obtener referencia a EnterName
    EnterName* enter_name = enter_name_ref_.at(panel_index);
    if (enter_name == nullptr) {
        return;
    }

    // Obtener la lista completa de caracteres
    const std::string& char_list = enter_name->getCharacterList();
    if (char_list.empty()) {
        return;
    }

    // --- Parámetros del carrusel ---
    constexpr int EXTRA_SPACING = 2;
    constexpr int HALF_VISIBLE = CAROUSEL_VISIBLE_LETTERS / 2;  // 4 letras a cada lado

    // Posición flotante actual del carrusel (índice en la lista de caracteres)
    float carousel_pos = carousel_position_.at(panel_index);
    const int CHAR_LIST_SIZE = static_cast<int>(char_list.size());

    // Calcular ancho promedio de una letra (asumimos ancho uniforme)
    const int AVG_CHAR_WIDTH = text_->getCharacterSize();
    const int CHAR_STEP = AVG_CHAR_WIDTH + EXTRA_SPACING;

    // --- Corrección visual de residuales flotantes (evita “baile”) ---
    float frac = carousel_pos - std::floor(carousel_pos);
    if (frac > 0.999F || frac < 0.001F) {
        carousel_pos = std::round(carousel_pos);
        frac = 0.0F;
    }

    const float FRACTIONAL_OFFSET = frac;
    const int PIXEL_OFFSET = static_cast<int>((FRACTIONAL_OFFSET * CHAR_STEP) + 0.5F);

    // Índice base en la lista de caracteres (posición central)
    const int BASE_INDEX = static_cast<int>(std::floor(carousel_pos));

    // Calcular posición X inicial (centrar las 9 letras visibles)
    int start_x = center_x - (HALF_VISIBLE * CHAR_STEP) - (AVG_CHAR_WIDTH / 2) - PIXEL_OFFSET;

    // === Renderizar las letras visibles del carrusel ===
    for (int i = -HALF_VISIBLE; i <= HALF_VISIBLE; ++i) {
        // Índice real en character_list_ (con wrap-around circular)
        int char_index = BASE_INDEX + i;
        char_index = char_index % CHAR_LIST_SIZE;
        if (char_index < 0) {
            char_index += CHAR_LIST_SIZE;
        }

        // --- Calcular distancia circular correcta (corregido el bug de wrap) ---
        float normalized_pos = std::fmod(carousel_pos, static_cast<float>(CHAR_LIST_SIZE));
        if (normalized_pos < 0.0F) {
            normalized_pos += static_cast<float>(CHAR_LIST_SIZE);
        }

        float diff = std::abs(static_cast<float>(char_index) - normalized_pos);
        if (diff > static_cast<float>(CHAR_LIST_SIZE) / 2.0F) {
            diff = static_cast<float>(CHAR_LIST_SIZE) - diff;
        }

        const float DISTANCE_FROM_CENTER = diff;

        // --- Seleccionar color con LERP según la distancia ---
        Color letter_color;
        if (DISTANCE_FROM_CENTER < 0.5F) {
            // Letra central → transiciona hacia animated_color_
            float lerp_to_animated = DISTANCE_FROM_CENTER / 0.5F;  // 0.0 a 1.0
            letter_color = animated_color_.LERP(text_color1_, lerp_to_animated);
        } else {
            // Letras alejadas → degradan hacia color_ base
            float base_lerp = (DISTANCE_FROM_CENTER - 0.5F) / (HALF_VISIBLE - 0.5F);
            base_lerp = std::min(base_lerp, 1.0F);
            const float LERP_FACTOR = base_lerp * 0.85F;
            letter_color = text_color1_.LERP(color_, LERP_FACTOR);
        }

        // Calcular posición X de la letra
        const int LETTER_X = start_x + ((i + HALF_VISIBLE) * CHAR_STEP);

        // Renderizar la letra
        std::string single_char(1, char_list[char_index]);
        text_->writeDX(Text::COLOR, LETTER_X, y, single_char, 1, letter_color);
    }
}
