#include "game/scenes/hiscore_table.hpp"

#include <SDL3/SDL.h>  // Para SDL_GetTicks, SDL_SetRenderTarget

#include <algorithm>   // Para max
#include <cstdlib>     // Para rand, size_t
#include <functional>  // Para function
#include <utility>     // Para std::cmp_less
#include <vector>      // Para vector

#include "core/audio/audio.hpp"                    // Para Audio
#include "core/input/global_inputs.hpp"            // Para check
#include "core/input/input.hpp"                    // Para Input
#include "core/locale/lang.hpp"                    // Para getText
#include "core/rendering/background.hpp"           // Para Background
#include "core/rendering/fade.hpp"                 // Para Fade, FadeMode, FadeType
#include "core/rendering/screen.hpp"               // Para Screen
#include "core/rendering/sprite/path_sprite.hpp"   // Para PathSprite, Path, PathType
#include "core/rendering/sprite/sprite.hpp"        // Para Sprite
#include "core/rendering/text.hpp"                 // Para Text, Text::SHADOW, Text::COLOR
#include "core/rendering/texture.hpp"              // Para Texture
#include "core/resources/resource.hpp"             // Para Resource
#include "core/system/global_events.hpp"           // Para check
#include "core/system/section.hpp"                 // Para Name, name, Options, options
#include "game/gameplay/manage_hiscore_table.hpp"  // Para HiScoreEntry
#include "game/options.hpp"                        // Para SettingsOptions, settings
#include "utils/color.hpp"                         // Para Color, easeOutQuint, Colors::NO_COLOR_MOD
#include "utils/param.hpp"                         // Para Param, param, ParamGame, ParamFade
#include "utils/utils.hpp"

// Constructor
HiScoreTable::HiScoreTable()
    : renderer_(Screen::get()->getRenderer()),
      backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
      fade_(std::make_unique<Fade>()),
      background_(std::make_unique<Background>()),

      view_area_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}),
      fade_mode_(Fade::Mode::IN),
      background_fade_color_(Color(0, 0, 0)) {
    // Inicializa el resto
    Section::name = Section::Name::HI_SCORE_TABLE;
    SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
    initFade();
    initBackground();
    iniEntryColors();
    createSprites();

    // Inicializa el timer de delta time y arranca la música
    last_time_ = SDL_GetTicks();
    Audio::get()->playMusic("title.ogg");
}

// Destructor
HiScoreTable::~HiScoreTable() {
    SDL_DestroyTexture(backbuffer_);
    Options::settings.clearLastHiScoreEntries();
}

// Actualiza las variables
void HiScoreTable::update(float delta_time) {
    elapsed_time_ += delta_time;  // Incrementa el tiempo transcurrido

    static auto* const SCREEN = Screen::get();
    SCREEN->update(delta_time);  // Actualiza el objeto screen
    Audio::update();             // Actualiza el objeto audio

    updateSprites(delta_time);        // Actualiza las posiciones de los sprites de texto
    background_->update(delta_time);  // Actualiza el fondo
    updateFade(delta_time);           // Gestiona el fade
    updateCounter();                  // Gestiona el contador y sus eventos
    fillTexture();                    // Dibuja los sprites en la textura
}

// Pinta en pantalla
void HiScoreTable::render() {
    static auto* const SCREEN = Screen::get();

    SCREEN->start();  // Prepara para empezar a dibujar en la textura de juego
    SCREEN->clean();  // Limpia la pantalla

    background_->render();                                                                    // Pinta el fondo
    float scroll_offset = elapsed_time_ * SCROLL_SPEED_PPS;                                   // Calcula el desplazamiento del scroll usando velocidad en pixels/segundo
    view_area_.y = std::round(std::max(0.0F, (param.game.height + 100.0F) - scroll_offset));  // Establece la ventana del backbuffer (redondeado para evitar deformaciones)
    SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_area_);                          // Copia el backbuffer al renderizador
    fade_->render();                                                                          // Renderiza el fade

    SCREEN->render();  // Vuelca el contenido del renderizador en pantalla
}

// Dibuja los sprites en la textura
void HiScoreTable::fillTexture() {
    // Pinta en el backbuffer el texto y los sprites
    auto* temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, backbuffer_);
    SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
    SDL_RenderClear(renderer_);

    // Escribe el texto: Mejores puntuaciones
    header_->render();

    // Escribe los nombres de la tabla de puntuaciones
    for (auto const& entry : entry_names_) {
        entry->render();
    }

    // Cambia el destino de renderizado
    SDL_SetRenderTarget(renderer_, temp);
}

// Comprueba los eventos
void HiScoreTable::checkEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        GlobalEvents::handle(event);
    }
}

// Comprueba las entradas
void HiScoreTable::checkInput() {
    Input::get()->update();
    GlobalInputs::check();
}

// Calcula el tiempo transcurrido desde el último frame
auto HiScoreTable::calculateDeltaTime() -> float {
    const Uint64 CURRENT_TIME = SDL_GetTicks();
    const float DELTA_TIME = static_cast<float>(CURRENT_TIME - last_time_) / 1000.0F;  // Convertir ms a segundos
    last_time_ = CURRENT_TIME;
    return DELTA_TIME;
}

// Avanza un frame (llamado desde Director::iterate)
void HiScoreTable::iterate() {
    const float DELTA_TIME = calculateDeltaTime();
    checkInput();
    update(DELTA_TIME);
    render();
}

// Procesa un evento (llamado desde Director::handleEvent)
void HiScoreTable::handleEvent(const SDL_Event& /*event*/) {
    // Eventos globales ya gestionados por Director::handleEvent
}

// Bucle para la pantalla de puntuaciones (fallback legacy)
void HiScoreTable::run() {
    last_time_ = SDL_GetTicks();
    Audio::get()->playMusic("title.ogg");

    while (Section::name == Section::Name::HI_SCORE_TABLE) {
        const float DELTA_TIME = calculateDeltaTime();

        checkInput();
        update(DELTA_TIME);
        checkEvents();  // Tiene que ir antes del render
        render();
    }
}

// Gestiona el fade
void HiScoreTable::updateFade(float delta_time) {
    fade_->update(delta_time);

    if (fade_->hasEnded() && fade_mode_ == Fade::Mode::IN) {
        (*fade_).reset();
        fade_mode_ = Fade::Mode::OUT;
        fade_->setMode(fade_mode_);
    }

    if (fade_->hasEnded() && fade_mode_ == Fade::Mode::OUT) {
        Section::name = (Section::options == Section::Options::HI_SCORE_AFTER_PLAYING)
            ? Section::Name::TITLE
            : Section::Name::INSTRUCTIONS;
        Section::options = Section::Options::NONE;
    }
}

// Convierte un entero a un string con separadores de miles
auto HiScoreTable::format(int number) -> std::string {
    const std::string SEPARATOR = ".";
    const std::string SCORE = std::to_string(number);

    auto index = static_cast<int>(SCORE.size()) - 1;
    std::string result;
    auto i = 0;
    while (index >= 0) {
        result = SCORE.at(index) + result;
        index--;
        i++;
        if (i == 3) {
            i = 0;
            result = SEPARATOR + result;
        }
    }

    return result;
}

// Crea los sprites con los textos
void HiScoreTable::createSprites() {
    auto header_text = Resource::get()->getText("04b_25_grey");
    auto entry_text = Resource::get()->getText("smb2");

    // Obtiene el tamaño de la textura
    float backbuffer_width;
    float backbuffer_height;
    SDL_GetTextureSize(backbuffer_, &backbuffer_width, &backbuffer_height);

    constexpr int ENTRY_LENGTH = 22;
    constexpr int MAX_NAMES = 10;
    const int SPACE_BETWEEN_HEADER = entry_text->getCharacterSize() * 4;
    const int SPACE_BETWEEN_LINES = entry_text->getCharacterSize() * 2;
    const int SIZE = SPACE_BETWEEN_HEADER + (SPACE_BETWEEN_LINES * (MAX_NAMES - 1)) + entry_text->getCharacterSize();
    const int FIRST_LINE = (param.game.height - SIZE) / 2;

    // Crea el sprite para el texto de cabecera
    header_ = std::make_unique<Sprite>(header_text->writeDXToTexture(Text::COLOR, Lang::getText("[HIGHSCORE_TABLE] CAPTION"), -2, background_fade_color_.INVERSE().LIGHTEN(25)));
    header_->setPosition(param.game.game_area.center_x - (header_->getWidth() / 2), FIRST_LINE);

    // Crea los sprites para las entradas en la tabla de puntuaciones
    const int ANIMATION = rand() % 4;
    const std::string SAMPLE_LINE(ENTRY_LENGTH + 3, ' ');
    auto sample_entry = std::make_unique<Sprite>(entry_text->writeDXToTexture(Text::SHADOW, SAMPLE_LINE, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT));
    const auto ENTRY_WIDTH = sample_entry->getWidth();
    for (int i = 0; i < MAX_NAMES; ++i) {
        const auto TABLE_POSITION = format(i + 1) + ". ";
        const auto SCORE = format(Options::settings.hi_score_table.at(i).score);
        const auto NUM_DOTS = ENTRY_LENGTH - Options::settings.hi_score_table.at(i).name.size() - SCORE.size();
        const auto* const ONE_CC = Options::settings.hi_score_table.at(i).one_credit_complete ? " }" : "";
        std::string dots;
        for (int j = 0; std::cmp_less(j, NUM_DOTS); ++j) {
            dots = dots + ".";
        }
        const auto LINE = TABLE_POSITION + Options::settings.hi_score_table.at(i).name + dots + SCORE + ONE_CC;

        entry_names_.emplace_back(std::make_shared<PathSprite>(entry_text->writeDXToTexture(Text::SHADOW, LINE, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT)));
        const int DEFAULT_POS_X = (backbuffer_width - ENTRY_WIDTH) / 2;
        const int POS_X = (i < 9) ? DEFAULT_POS_X : DEFAULT_POS_X - entry_text->getCharacterSize();
        const int POS_Y = (i * SPACE_BETWEEN_LINES) + FIRST_LINE + SPACE_BETWEEN_HEADER;
        switch (ANIMATION) {
            case 0:  // Ambos lados alternativamente
            {
                if (i % 2 == 0) {
                    entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint);
                    entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
                } else {
                    entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint);
                    entry_names_.back()->setPosition(backbuffer_width, 0);
                }
                break;
            }

            case 1:  // Entran por la izquierda
            {
                entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint);
                entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
                break;
            }

            case 2:  // Entran por la derecha
            {
                entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint);
                entry_names_.back()->setPosition(backbuffer_width, 0);
                break;
            }

            case 3:  // Entran desde la parte inferior
            {
                entry_names_.back()->addPath(backbuffer_height, POS_Y, PathType::VERTICAL, POS_X, ANIM_DURATION_S, easeOutQuint);
                entry_names_.back()->setPosition(0, backbuffer_height);
            }

            default:
                break;
        }
    }
}

// Actualiza las posiciones de los sprites de texto
void HiScoreTable::updateSprites(float delta_time) {
    if (elapsed_time_ >= INIT_DELAY_S) {
        const float ELAPSED_SINCE_INIT = elapsed_time_ - INIT_DELAY_S;
        int index = static_cast<int>(ELAPSED_SINCE_INIT / ENTRY_DELAY_S);
        if (std::cmp_less(index, entry_names_.size()) && index >= 0) {
            // Verificar si este índice debe activarse ahora
            float expected_time = index * ENTRY_DELAY_S;
            if (ELAPSED_SINCE_INIT >= expected_time && ELAPSED_SINCE_INIT < expected_time + delta_time) {
                entry_names_.at(index)->enable();
            }
        }
    }
    for (auto const& entry : entry_names_) {
        entry->update(delta_time);
    }

    glowEntryNames();
}

// Inicializa el fade
void HiScoreTable::initFade() {
    fade_->setColor(param.fade.color);
    fade_->setType(Fade::Type::RANDOM_SQUARE2);
    fade_->setPostDuration(param.fade.post_duration_ms);
    fade_->setMode(fade_mode_);
    fade_->activate();
}

// Inicializa el fondo
void HiScoreTable::initBackground() {
    background_->setManualMode(true);
    background_->setPos(param.game.game_area.rect);
    background_->setCloudsSpeed(CLOUDS_SPEED);

    const int LUCKY = rand() % 3;
    switch (LUCKY) {
        case 0:  // Fondo verde
        {
            background_->setGradientNumber(2);
            background_->setTransition(0.0F);
            background_->setSunProgression(1.0F);
            background_->setMoonProgression(0.0F);
            background_fade_color_ = Colors::GREEN_SKY;
            break;
        }

        case 1:  // Fondo naranja
        {
            background_->setGradientNumber(1);
            background_->setTransition(0.0F);
            background_->setSunProgression(0.65F);
            background_->setMoonProgression(0.0F);
            background_fade_color_ = Colors::PINK_SKY;
            break;
        }

        case 2:  // Fondo azul
        {
            background_->setGradientNumber(0);
            background_->setTransition(0.0F);
            background_->setSunProgression(0.0F);
            background_->setMoonProgression(0.0F);
            background_fade_color_ = Colors::BLUE_SKY;
            break;
        }

        default:
            break;
    }
}

// Obtiene un color del vector de colores de entradas
auto HiScoreTable::getEntryColor(int counter) -> Color {
    int cycle_length = (entry_colors_.size() * 2) - 2;
    size_t n = counter % cycle_length;

    size_t index;
    if (n < entry_colors_.size()) {
        index = n;  // Avanza: 0,1,2,3
    } else {
        index = (2 * (entry_colors_.size() - 1)) - n;  // Retrocede: 2,1
    }

    return entry_colors_[index];
}

// Inicializa los colores de las entradas
void HiScoreTable::iniEntryColors() {
    entry_colors_.clear();
    entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(75));
    entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(50));
    entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(25));
    entry_colors_.emplace_back(background_fade_color_.INVERSE());
}

// Hace brillar los nombres de la tabla de records
void HiScoreTable::glowEntryNames() {
    int color_counter = static_cast<int>(elapsed_time_ * 60.0F / 5.0F);  // Convertir tiempo a equivalente frame
    const Color ENTRY_COLOR = getEntryColor(color_counter);
    for (const auto& entry_index : Options::settings.glowing_entries) {
        if (entry_index != -1) {
            entry_names_.at(entry_index)->getTexture()->setColor(ENTRY_COLOR);
        }
    }
}

// Gestiona el contador
void HiScoreTable::updateCounter() {
    if (elapsed_time_ >= BACKGROUND_CHANGE_S && !hiscore_flags_.background_changed) {
        background_->setColor(background_fade_color_.DARKEN());
        background_->setAlpha(96);
        hiscore_flags_.background_changed = true;
    }

    if (elapsed_time_ >= COUNTER_END_S && !hiscore_flags_.fade_activated) {
        fade_->activate();
        hiscore_flags_.fade_activated = true;
    }
}