#include "game/scenes/intro.hpp"

#include <SDL3/SDL.h>  // Para SDL_GetTicks, SDL_SetRenderDrawColor, SDL_FRect, SDL_RenderFillRect, SDL_GetRenderTarget, SDL_RenderClear, SDL_RenderRect, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_PollEvent, SDL_RenderTexture, SDL_TextureAccess, SDL_Event, Uint64

#include <array>    // Para array
#include <string>   // Para basic_string, string
#include <utility>  // Para move

#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/screen.hpp"              // Para Screen
#include "core/rendering/sprite/card_sprite.hpp"  // Para CardSprite
#include "core/rendering/text.hpp"                // Para Text
#include "core/rendering/texture.hpp"             // Para Texture
#include "core/rendering/tiled_bg.hpp"            // Para TiledBG, TiledBGMode
#include "core/rendering/writer.hpp"              // Para Writer
#include "core/resources/resource.hpp"            // Para Resource
#include "core/system/global_events.hpp"          // Para handle
#include "core/system/section.hpp"                // Para Name, name, Options, options
#include "utils/color.hpp"                        // Para Color
#include "utils/param.hpp"                        // Para Param, param, ParamGame, ParamIntro, ParamTitle
#include "utils/utils.hpp"                        // Para easeOutBounce

// Constructor
Intro::Intro()
    : tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::DIAGONAL)) {
    // Inicializa variables
    Section::name = Section::Name::INTRO;
    Section::options = Section::Options::NONE;

    // Inicializa las tarjetas
    initSprites();

    // Inicializa los textos
    initTexts();

    // Configura el fondo
    tiled_bg_->setSpeed(TILED_BG_SPEED);
    tiled_bg_->setColor(bg_color_);

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

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

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

// Actualiza las escenas de la intro
void Intro::updateScenes() {
    // Sonido al lanzar la tarjeta (enable() devuelve true solo la primera vez)
    if (card_sprites_.at(scene_)->enable()) {
        Audio::get()->playSound(SFX_CARD_THROW);
    }

    // Cuando la tarjeta actual toca la mesa por primera vez: shake + sonido + la anterior sale despedida
    if (!shake_done_ && card_sprites_.at(scene_)->hasFirstTouch()) {
        Screen::get()->shake();
        Audio::get()->playSound(SFX_CARD_IMPACT);
        shake_done_ = true;

        if (scene_ > 0) {
            card_sprites_.at(scene_ - 1)->startExit();
        }
    }

    switch (scene_) {
        case 0:
            updateScene0();
            break;
        case 1:
            updateScene1();
            break;
        case 2:
            updateScene2();
            break;
        case 3:
            updateScene3();
            break;
        case 4:
            updateScene4();
            break;
        case 5:
            updateScene5();
            break;
        default:
            break;
    }
}

void Intro::updateScene0() {
    // Primer texto cuando aterriza
    if (card_sprites_.at(0)->hasLanded() && !texts_.at(0)->hasFinished()) {
        texts_.at(0)->setEnabled(true);
    }

    // Segundo texto
    if (texts_.at(0)->hasFinished() && !texts_.at(1)->hasFinished()) {
        switchText(0, 1);
    }

    // Tercer texto
    if (texts_.at(1)->hasFinished() && !texts_.at(2)->hasFinished()) {
        switchText(1, 2);
    }

    // Fin de la primera escena: la tarjeta sale despedida
    if (texts_.at(2)->hasFinished()) {
        texts_.at(2)->setEnabled(false);
        scene_++;
        shake_done_ = false;
    }
}

void Intro::updateScene1() {
    // Texto cuando aterriza
    if (card_sprites_.at(1)->hasLanded() && !texts_.at(3)->hasFinished()) {
        texts_.at(3)->setEnabled(true);
    }

    // Fin de la segunda escena
    if (texts_.at(3)->hasFinished()) {
        texts_.at(3)->setEnabled(false);
        scene_++;
        shake_done_ = false;
    }
}

void Intro::updateScene2() {
    // Tercera imagen - GRITO: tarjeta y texto a la vez
    if (!texts_.at(4)->hasFinished()) {
        texts_.at(4)->setEnabled(true);
    }

    // Fin de la tercera escena
    if (card_sprites_.at(2)->hasLanded() && texts_.at(4)->hasFinished()) {
        texts_.at(4)->setEnabled(false);
        scene_++;
        shake_done_ = false;
    }
}

void Intro::updateScene3() {
    // Cuarta imagen - Reflexión
    if (!texts_.at(5)->hasFinished()) {
        texts_.at(5)->setEnabled(true);
    }

    // Segundo texto
    if (texts_.at(5)->hasFinished() && !texts_.at(6)->hasFinished()) {
        switchText(5, 6);
    }

    // Fin de la cuarta escena
    if (card_sprites_.at(3)->hasLanded() && texts_.at(6)->hasFinished()) {
        texts_.at(6)->setEnabled(false);
        scene_++;
        shake_done_ = false;
    }
}

void Intro::updateScene4() {
    // Quinta imagen - Patada
    if (!texts_.at(7)->hasFinished()) {
        texts_.at(7)->setEnabled(true);
    }

    // Fin de la quinta escena
    if (card_sprites_.at(4)->hasLanded() && texts_.at(7)->hasFinished()) {
        texts_.at(7)->setEnabled(false);
        scene_++;
        shake_done_ = false;
    }
}

void Intro::updateScene5() {
    // Sexta imagen - Globos de café
    if (!texts_.at(8)->hasFinished()) {
        texts_.at(8)->setEnabled(true);
    }

    // Acaba el último texto
    if (texts_.at(8)->hasFinished()) {
        texts_.at(8)->setEnabled(false);
    }

    // Última tarjeta: sale "como si se la llevara el viento" y transición a POST
    if (card_sprites_.at(5)->hasLanded() && texts_.at(8)->hasFinished()) {
        card_sprites_.at(5)->startExit();
        state_ = State::POST;
        state_start_time_ = SDL_GetTicks() / 1000.0F;
    }
}

void Intro::switchText(int from_index, int to_index) {
    texts_.at(from_index)->setEnabled(false);
    texts_.at(to_index)->setEnabled(true);
}

// Actualiza las variables del objeto
void Intro::update(float delta_time) {
    static auto* const SCREEN = Screen::get();
    SCREEN->update(delta_time);  // Actualiza el objeto screen
    Audio::update();             // Actualiza el objeto Audio

    tiled_bg_->update(delta_time);  // Actualiza el fondo

    switch (state_) {
        case State::SCENES:
            // Pausa inicial antes de empezar
            if (initial_elapsed_ < INITIAL_DELAY_S) {
                initial_elapsed_ += delta_time;
                break;
            }
            updateSprites(delta_time);
            updateTexts(delta_time);
            updateScenes();
            break;

        case State::POST:
            updateSprites(delta_time);  // La última tarjeta puede estar saliendo durante POST
            updatePostState();
            break;
    }
}

// Dibuja el objeto en pantalla
void Intro::render() {
    static auto* const SCREEN = Screen::get();

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

    tiled_bg_->render();  // Dibuja el fondo

    switch (state_) {
        case State::SCENES: {
            renderTextRect();
            renderSprites();
            renderTexts();
            break;
        }
        case State::POST:
            renderSprites();  // La última tarjeta puede estar saliendo
            break;
    }

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

// Calcula el tiempo transcurrido desde el último frame
auto Intro::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 Intro::iterate() {
    const float DELTA_TIME = calculateDeltaTime();
    checkInput();
    update(DELTA_TIME);
    render();
}

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

// Bucle principal legacy (fallback)
void Intro::run() {
    last_time_ = SDL_GetTicks();
    Audio::get()->playMusic("intro.ogg", 0);

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

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

// Inicializa las tarjetas
void Intro::initSprites() {
    // Listado de imagenes a usar
    const std::array<std::string, 6> TEXTURE_LIST = {
        "intro1.png",
        "intro2.png",
        "intro3.png",
        "intro4.png",
        "intro5.png",
        "intro6.png"};

    // Constantes
    constexpr int TOTAL_SPRITES = TEXTURE_LIST.size();
    const float BORDER = CARD_BORDER_SIZE;

    auto texture = Resource::get()->getTexture(TEXTURE_LIST.front());
    const float CARD_WIDTH = texture->getWidth() + (BORDER * 2);
    const float CARD_HEIGHT = texture->getHeight() + (BORDER * 2);

    // Crea las texturas para las tarjetas (imagen con marco)
    std::vector<std::shared_ptr<Texture>> card_textures;

    for (int i = 0; i < TOTAL_SPRITES; ++i) {
        auto card_texture = std::make_unique<Texture>(Screen::get()->getRenderer());
        card_texture->createBlank(CARD_WIDTH, CARD_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
        card_texture->setBlendMode(SDL_BLENDMODE_BLEND);

        auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
        card_texture->setAsRenderTarget(Screen::get()->getRenderer());

        SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
        SDL_RenderClear(Screen::get()->getRenderer());

        // Marco de la tarjeta
        auto color = param.intro.card_color;
        SDL_SetRenderDrawColor(Screen::get()->getRenderer(), color.r, color.g, color.b, color.a);
        SDL_FRect rect1 = {.x = 1, .y = 0, .w = CARD_WIDTH - 2, .h = CARD_HEIGHT};
        SDL_FRect rect2 = {.x = 0, .y = 1, .w = CARD_WIDTH, .h = CARD_HEIGHT - 2};
        SDL_RenderRect(Screen::get()->getRenderer(), &rect1);
        SDL_RenderRect(Screen::get()->getRenderer(), &rect2);

        // Imagen dentro del marco
        SDL_FRect dest = {.x = BORDER, .y = BORDER, .w = CARD_WIDTH - (BORDER * 2), .h = CARD_HEIGHT - (BORDER * 2)};
        SDL_RenderTexture(Screen::get()->getRenderer(), Resource::get()->getTexture(TEXTURE_LIST.at(i))->getSDLTexture(), nullptr, &dest);

        SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
        card_textures.push_back(std::move(card_texture));
    }

    // Crea la textura de sombra (compartida entre todas las tarjetas)
    auto shadow_texture = std::make_shared<Texture>(Screen::get()->getRenderer());
    shadow_texture->createBlank(CARD_WIDTH, CARD_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
    shadow_texture->setBlendMode(SDL_BLENDMODE_BLEND);

    auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
    shadow_texture->setAsRenderTarget(Screen::get()->getRenderer());

    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
    SDL_RenderClear(Screen::get()->getRenderer());

    auto shadow_color = param.intro.shadow_color;
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), shadow_color.r, shadow_color.g, shadow_color.b, Color::MAX_ALPHA_VALUE);
    SDL_FRect shadow_rect1 = {.x = 1, .y = 0, .w = CARD_WIDTH - 2, .h = CARD_HEIGHT};
    SDL_FRect shadow_rect2 = {.x = 0, .y = 1, .w = CARD_WIDTH, .h = CARD_HEIGHT - 2};
    SDL_RenderFillRect(Screen::get()->getRenderer(), &shadow_rect1);
    SDL_RenderFillRect(Screen::get()->getRenderer(), &shadow_rect2);

    SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
    shadow_texture->setAlpha(shadow_color.a);

    // Posición de aterrizaje (centro de la zona de juego)
    const float X_DEST = param.game.game_area.center_x - (CARD_WIDTH / 2);
    const float Y_DEST = param.game.game_area.first_quarter_y - (CARD_HEIGHT / 4);

    // Configuración por tarjeta: posición de entrada, ángulo, salida
    // Cada tarjeta viene de un borde diferente (gente alrededor de una mesa lanzando cartas al centro)
    struct CardConfig {
        float entry_x;         // Posición inicial X
        float entry_y;         // Posición inicial Y
        double entry_angle;    // Ángulo de entrada
        float exit_vx;         // Velocidad de salida X
        float exit_vy;         // Velocidad de salida Y
        float exit_ax;         // Aceleración de salida X
        float exit_ay;         // Aceleración de salida Y
        double exit_rotation;  // Velocidad de rotación de salida
    };

    const float W = param.game.width;
    const float H = param.game.height;
    const float S = CARD_EXIT_SPEED;
    const float A = CARD_EXIT_ACCEL;
    const double R = CARD_EXIT_ROTATION;

    const CardConfig CARD_CONFIGS[] = {
        // 0: Entra desde la izquierda.          La 1 entra desde la derecha → sale empujada hacia la izquierda
        {-CARD_WIDTH, Y_DEST - 20.0F, CARD_ANGLE_0, -S, S * 0.1F, -A, 0.0F, -R},
        // 1: Entra desde la derecha.            La 2 entra desde arriba → sale empujada hacia abajo
        {W + CARD_WIDTH, Y_DEST + 15.0F, CARD_ANGLE_1, S * 0.15F, S, 0.0F, A, R * 1.1},
        // 2: Entra desde arriba.                La 3 entra desde abajo → sale empujada hacia arriba
        {X_DEST + 30.0F, -CARD_HEIGHT, CARD_ANGLE_2, -S * 0.15F, -S, 0.0F, -A, -R * 0.9},
        // 3: Entra desde abajo.                 La 4 entra desde arriba-izquierda → sale empujada hacia abajo-derecha
        {X_DEST - 25.0F, H + CARD_HEIGHT, CARD_ANGLE_3, S * 0.8F, S * 0.6F, A * 0.5F, A * 0.4F, R},
        // 4: Entra desde arriba-izquierda.      La 5 entra desde derecha-abajo → sale empujada hacia arriba-izquierda
        {-CARD_WIDTH * 0.5F, -CARD_HEIGHT, CARD_ANGLE_4, -S * 0.7F, -S * 0.5F, -A * 0.5F, -A * 0.3F, -R * 1.2},
        // 5: Entra desde la derecha-abajo.      Última: sale hacia la izquierda suave (viento)
        {W + CARD_WIDTH, H * 0.6F, CARD_ANGLE_5, -S * 0.6F, -S * 0.1F, -A * 0.5F, 0.0F, -R * 0.7},
    };

    // Inicializa los CardSprites
    for (int i = 0; i < TOTAL_SPRITES; ++i) {
        auto card = std::make_unique<CardSprite>(card_textures.at(i));
        card->setWidth(CARD_WIDTH);
        card->setHeight(CARD_HEIGHT);
        card->setSpriteClip(0, 0, CARD_WIDTH, CARD_HEIGHT);

        const auto& cfg = CARD_CONFIGS[i];

        // Posición de aterrizaje (centro)
        card->setLandingPosition(X_DEST, Y_DEST);

        // Posición de entrada (borde de pantalla)
        card->setEntryPosition(cfg.entry_x, cfg.entry_y);

        // Parámetros de entrada: zoom, ángulo, duración, easing
        card->setEntryParams(CARD_START_ZOOM, cfg.entry_angle, CARD_ENTRY_DURATION_S, easeOutBounce);

        // Parámetros de salida
        card->setExitParams(cfg.exit_vx, cfg.exit_vy, cfg.exit_ax, cfg.exit_ay, cfg.exit_rotation);

        // Sombra
        card->setShadowTexture(shadow_texture);
        card->setShadowOffset(SHADOW_OFFSET, SHADOW_OFFSET);

        // Límites de pantalla
        card->setScreenBounds(param.game.width, param.game.height);

        // Última tarjeta: gana algo de altura al salir (se la lleva el viento)
        if (i == TOTAL_SPRITES - 1) {
            card->setExitLift(1.2F, 0.15F);  // Hasta zoom 1.2, a 0.15/s
        }

        card_sprites_.push_back(std::move(card));
    }
}

// Inicializa los textos
void Intro::initTexts() {
    constexpr int TOTAL_TEXTS = 9;
    for (int i = 0; i < TOTAL_TEXTS; ++i) {
        auto writer = std::make_unique<Writer>(Resource::get()->getText("04b_25_metal"));
        writer->setPosX(0);
        writer->setPosY(param.game.height - param.intro.text_distance_from_bottom);
        writer->setKerning(TEXT_KERNING);
        writer->setEnabled(false);
        writer->setFinishedTimerS(TEXT_DISPLAY_DURATION_S);
        texts_.push_back(std::move(writer));
    }

    // Un dia qualsevol de l'any 2000
    texts_.at(0)->setCaption(Lang::getText("[INTRO] 1"));
    texts_.at(0)->setSpeedS(TEXT_SPEED_NORMAL);

    // Tot esta tranquil a la UPV
    texts_.at(1)->setCaption(Lang::getText("[INTRO] 2"));
    texts_.at(1)->setSpeedS(TEXT_SPEED_NORMAL);

    // Fins que un desaprensiu...
    texts_.at(2)->setCaption(Lang::getText("[INTRO] 3"));
    texts_.at(2)->setSpeedS(TEXT_SPEED_SLOW);

    // HEY! ME ANE A FERME UN CORTAET...
    texts_.at(3)->setCaption(Lang::getText("[INTRO] 4"));
    texts_.at(3)->setSpeedS(TEXT_SPEED_NORMAL);

    // UAAAAAAAAAAAAA!!!
    texts_.at(4)->setCaption(Lang::getText("[INTRO] 5"));
    texts_.at(4)->setSpeedS(TEXT_SPEED_ULTRA_FAST);

    // Espera un moment...
    texts_.at(5)->setCaption(Lang::getText("[INTRO] 6"));
    texts_.at(5)->setSpeedS(TEXT_SPEED_VERY_SLOW);

    // Si resulta que no tinc solt!
    texts_.at(6)->setCaption(Lang::getText("[INTRO] 7"));
    texts_.at(6)->setSpeedS(TEXT_SPEED_VERY_FAST);

    // MERDA DE MAQUINA!
    texts_.at(7)->setCaption(Lang::getText("[INTRO] 8"));
    texts_.at(7)->setSpeedS(TEXT_SPEED_FAST);

    // Blop... blop... blop...
    texts_.at(8)->setCaption(Lang::getText("[INTRO] 9"));
    texts_.at(8)->setSpeedS(TEXT_SPEED_ULTRA_SLOW);

    for (auto& text : texts_) {
        text->center(param.game.game_area.center_x);
    }
}

// Actualiza los sprites
void Intro::updateSprites(float delta_time) {
    for (auto& sprite : card_sprites_) {
        sprite->update(delta_time);
    }
}

// Actualiza los textos
void Intro::updateTexts(float delta_time) {
    for (auto& text : texts_) {
        text->updateS(delta_time);  // Usar updateS para delta_time en segundos
    }
}

// Dibuja los sprites (todas las tarjetas activas, para que convivan la saliente y la entrante)
void Intro::renderSprites() {
    for (auto& card : card_sprites_) {
        card->render();
    }
}

// Dibuja los textos
void Intro::renderTexts() {
    for (const auto& text : texts_) {
        text->render();
    }
}

// Actualiza el estado POST
void Intro::updatePostState() {
    const float ELAPSED_TIME = (SDL_GetTicks() / 1000.0F) - state_start_time_;

    switch (post_state_) {
        case PostState::STOP_BG:
            // EVENTO: Detiene el fondo después del tiempo especificado
            if (ELAPSED_TIME >= POST_BG_STOP_DELAY_S) {
                tiled_bg_->stopGracefully();

                if (!bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
                    bg_color_ = bg_color_.APPROACH_TO(param.title.bg_color, 1);
                }

                tiled_bg_->setColor(bg_color_);
            }

            // Cambia de estado si el fondo se ha detenido y recuperado el color
            if (tiled_bg_->isStopped() && bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
                post_state_ = PostState::END;
                state_start_time_ = SDL_GetTicks() / 1000.0F;
            }
            break;

        case PostState::END:
            // Finaliza la intro después del tiempo especificado
            if (ELAPSED_TIME >= POST_END_DELAY_S) {
                Audio::get()->stopMusic();
                Section::name = Section::Name::TITLE;
                Section::options = Section::Options::TITLE_1;
            }
            break;

        default:
            break;
    }
}

void Intro::renderTextRect() {
    static const float HEIGHT = Resource::get()->getText("04b_25_metal")->getCharacterSize();
    static SDL_FRect rect_ = {.x = 0.0F, .y = param.game.height - param.intro.text_distance_from_bottom - HEIGHT, .w = param.game.width, .h = HEIGHT * 3};
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), param.intro.shadow_color.r, param.intro.shadow_color.g, param.intro.shadow_color.b, param.intro.shadow_color.a);
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_);
}
