#include "game/scenes/logo.hpp"

#include <SDL3/SDL.h>

#include <algorithm>  // Para std::clamp
#include <array>      // Para std::array
#include <random>     // Para generador aleatorio

#include "core/audio/audio.hpp"               // Para Audio
#include "core/input/global_inputs.hpp"       // Para check
#include "core/input/input.hpp"               // Para Input
#include "core/rendering/screen.hpp"          // Para Screen
#include "core/rendering/sprite/sprite.hpp"   // Para SSprite
#include "core/rendering/surface.hpp"         // Para Surface
#include "core/resources/resource_cache.hpp"  // Para Resource
#include "core/system/event_buffer.hpp"       // Para EventBuffer
#include "core/system/global_events.hpp"      // Para check
#include "game/options.hpp"                   // Para Options, SectionState, options, Section
#include "game/scene_manager.hpp"             // Para SceneManager
#include "utils/delta_timer.hpp"              // Para DeltaTimer
#include "utils/easing_functions.hpp"         // Para funciones de suavizado
#include "utils/utils.hpp"                    // Para PaletteColor

// Constructor
Logo::Logo()
    : jailgames_surface_(Resource::Cache::get()->getSurface("jailgames.gif")),
      since_1998_surface_(Resource::Cache::get()->getSurface("since_1998.gif")),
      since_1998_sprite_(std::make_shared<Sprite>(since_1998_surface_, (256 - since_1998_surface_->getWidth()) / 2, 83 + jailgames_surface_->getHeight() + 5, since_1998_surface_->getWidth(), since_1998_surface_->getHeight())),
      delta_timer_(std::make_unique<DeltaTimer>()) {
    // Configura variables
    since_1998_sprite_->setClip(0, 0, since_1998_surface_->getWidth(), since_1998_surface_->getHeight());
    since_1998_color_ = 0;
    jailgames_color_ = 15;

    // Inicializa variables
    SceneManager::current = SceneManager::Scene::LOGO;

    initSprites();  // Crea los sprites de cada linea
    initColors();   // Inicializa el vector de colores

    // Seleccionar función de easing aleatoria para la animación del logo
    // Usamos lambdas para funciones con parámetros opcionales
    static const std::array<EasingFunction, 4> EASING_OPTIONS = {
        [](float t) -> float { return Easing::backOut(t); },     // Overshoot retro
        [](float t) -> float { return Easing::elasticOut(t); },  // Rebote múltiple con oscilación
        Easing::bounceOut,                                       // Rebote físico decreciente
        Easing::cubicOut                                         // Suavizado sin overshoot (para variedad)
    };
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<size_t> dist(0, EASING_OPTIONS.size() - 1);
    easing_function_ = EASING_OPTIONS[dist(gen)];

    // Cambia el color del borde
    Screen::get()->setBorderColor(0);
}

// Comprueba el manejador de eventos
void Logo::handleEvents() {
    for (const auto& event : EventBuffer::events) {
        GlobalEvents::handle(event);
    }
}

// Comprueba las entradas
void Logo::handleInput() {
    Input::get()->update();
    GlobalInputs::handle();
}

// Gestiona el logo de JAILGAME
void Logo::updateJAILGAMES(float delta_time) {
    // Solo actualizar durante el estado JAILGAMES_SLIDE_IN
    if (state_ != State::JAILGAMES_SLIDE_IN) {
        return;
    }

    // Calcular el progreso de la animación (0.0 a 1.0)
    const float PROGRESS = std::clamp(state_time_ / JAILGAMES_SLIDE_DURATION, 0.0F, 1.0F);

    // Aplicar función de suavizado seleccionada aleatoriamente (permite overshoot para efecto de rebote)
    // La posición final exacta se garantiza en updateState() antes de transicionar
    const float EASED_PROGRESS = easing_function_(PROGRESS);

    // Actualizar cada línea del sprite JAILGAMES interpolando con easing
    for (size_t i = 0; i < jailgames_sprite_.size(); ++i) {
        // Interpolar entre posición inicial y destino usando el progreso suavizado
        const auto INITIAL_X = static_cast<float>(jailgames_initial_x_[i]);
        const auto DEST_X = static_cast<float>(JAILGAMES_DEST_X);
        const float NEW_X = INITIAL_X + ((DEST_X - INITIAL_X) * EASED_PROGRESS);

        jailgames_sprite_[i]->setX(NEW_X);
    }
}

// Calcula el índice de color según el progreso (0.0-1.0)
auto Logo::getColorIndex(float progress) const -> int {
    // Asegurar que progress esté en el rango [0.0, 1.0]
    progress = std::clamp(progress, 0.0F, 1.0F);

    // Mapear el progreso al índice de color (0-7)
    const int MAX_INDEX = static_cast<int>(color_.size()) - 1;
    const int INDEX = static_cast<int>(progress * MAX_INDEX);

    return INDEX;
}

// Gestiona el color de las texturas
void Logo::updateTextureColors() {
    switch (state_) {
        case State::SINCE_1998_FADE_IN: {
            // Fade-in de "Since 1998" de negro a blanco
            const float PROGRESS = state_time_ / SINCE_1998_FADE_DURATION;
            since_1998_color_ = color_[getColorIndex(PROGRESS)];
            break;
        }

        case State::DISPLAY: {
            // Asegurar que ambos logos estén en blanco durante el display
            jailgames_color_ = color_.back();   // BRIGHT_WHITE
            since_1998_color_ = color_.back();  // BRIGHT_WHITE
            break;
        }

        case State::FADE_OUT: {
            // Fade-out de ambos logos de blanco a negro
            const float PROGRESS = 1.0F - (state_time_ / FADE_OUT_DURATION);
            const int COLOR_INDEX = getColorIndex(PROGRESS);
            jailgames_color_ = color_[COLOR_INDEX];
            since_1998_color_ = color_[COLOR_INDEX];
            break;
        }

        default:
            // En otros estados, mantener los colores actuales
            break;
    }
}

// Transiciona a un nuevo estado
void Logo::transitionToState(State new_state) {
    state_ = new_state;
    state_time_ = 0.0F;
}

// Actualiza el estado actual
void Logo::updateState(float delta_time) {
    state_time_ += delta_time;

    // Gestionar transiciones entre estados basándose en el tiempo
    switch (state_) {
        case State::INITIAL:
            if (state_time_ >= INITIAL_DELAY) {
                transitionToState(State::JAILGAMES_SLIDE_IN);
            }
            break;

        case State::JAILGAMES_SLIDE_IN:
            if (state_time_ >= JAILGAMES_SLIDE_DURATION) {
                // Garantizar que todas las líneas estén exactamente en la posición final
                // antes de transicionar (previene race condition con updateJAILGAMES)
                for (auto& sprite : jailgames_sprite_) {
                    sprite->setX(JAILGAMES_DEST_X);
                }
                transitionToState(State::SINCE_1998_FADE_IN);
            }
            break;

        case State::SINCE_1998_FADE_IN:
            if (state_time_ >= SINCE_1998_FADE_DURATION) {
                transitionToState(State::DISPLAY);
            }
            break;

        case State::DISPLAY:
            if (state_time_ >= DISPLAY_DURATION) {
                transitionToState(State::FADE_OUT);
            }
            break;

        case State::FADE_OUT:
            if (state_time_ >= FADE_OUT_DURATION) {
                transitionToState(State::BLACK_SCREEN);
            }
            break;

        case State::BLACK_SCREEN:
            if (state_time_ >= BLACK_SCREEN_DURATION) {
                transitionToState(State::END);
                endSection();
            }
            break;

        case State::END:
            // Estado final, no hacer nada
            break;
    }
}

// Actualiza las variables
void Logo::update() {
    const float DELTA_TIME = delta_timer_->tick();

    handleEvents();  // Comprueba los eventos
    handleInput();   // Comprueba las entradas

    updateState(DELTA_TIME);      // Actualiza el estado y gestiona transiciones
    updateJAILGAMES(DELTA_TIME);  // Gestiona el logo de JAILGAME
    updateTextureColors();        // Gestiona el color de las texturas

    Audio::update();                    // Actualiza el objeto Audio
    Screen::get()->update(DELTA_TIME);  // Actualiza el objeto Screen
}

// Dibuja en pantalla
void Logo::render() {
    // Prepara para empezar a dibujar en la textura de juego
    Screen::get()->start();
    Screen::get()->clearSurface(0);

    // Dibuja los objetos
    for (const auto& sprite : jailgames_sprite_) {
        sprite->render(1, jailgames_color_);
    }
    since_1998_sprite_->render(1, since_1998_color_);

    // Vuelca el contenido del renderizador en pantalla
    Screen::get()->render();
}

// Termina la sección
void Logo::endSection() {
    SceneManager::current = SceneManager::Scene::TITLE;
}

// Inicializa el vector de colores
void Logo::initColors() {
    // Inicializa el vector de colores
    const std::vector<Uint8> COLORS = {
        0,
        2,
        4,
        6,
        8,
        10,
        12,
        15};
    for (const auto& color : COLORS) {
        color_.push_back(color);
    }
}

// Crea los sprites de cada linea
void Logo::initSprites() {
    const float WIDTH = jailgames_surface_->getWidth();
    jailgames_initial_x_.reserve(jailgames_surface_->getHeight());

    for (int i = 0; i < jailgames_surface_->getHeight(); ++i) {
        jailgames_sprite_.push_back(std::make_shared<Sprite>(jailgames_surface_, 0, i, jailgames_surface_->getWidth(), 1));
        jailgames_sprite_.back()->setClip(0, i, jailgames_surface_->getWidth(), 1);

        // Calcular posición inicial (alternando entre derecha e izquierda)
        constexpr int LINE_OFFSET = 6;
        const int INITIAL_X = (i % 2 == 0) ? (256 + (i * LINE_OFFSET)) : (static_cast<int>(-WIDTH) - (i * LINE_OFFSET));
        jailgames_initial_x_.push_back(INITIAL_X);

        jailgames_sprite_.at(i)->setX(INITIAL_X);
        jailgames_sprite_.at(i)->setY(83 + i);
    }
}