#include "game/scenes/ending2.hpp"

#include <SDL3/SDL.h>

#include <algorithm>  // Para max, replace

#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/locale.hpp"                     // Para Locale
#include "core/rendering/screen.hpp"                  // Para Screen
#include "core/rendering/sprite/dissolve_sprite.hpp"  // Para SurfaceDissolveSprite
#include "core/rendering/sprite/moving_sprite.hpp"    // Para SMovingSprite
#include "core/rendering/surface.hpp"                 // Para Surface
#include "core/rendering/text.hpp"                    // Para Text
#include "core/resources/resource_cache.hpp"          // Para Resource
#include "core/system/global_events.hpp"              // Para check
#include "game/options.hpp"                           // Para Options, options, OptionsGame, Sectio...
#include "game/scene_manager.hpp"                     // Para SceneManager
#include "utils/defines.hpp"                          // Para GameCanvas::CENTER_X, GameCanvas::CENTER_Y
#include "utils/delta_timer.hpp"                      // Para DeltaTimer
#include "utils/utils.hpp"                            // Para PaletteColor, stringToColor

// Constructor
Ending2::Ending2()
    : delta_timer_(std::make_unique<DeltaTimer>()),
      state_{.state = EndingState::PRE_CREDITS, .duration = STATE_PRE_CREDITS_DURATION} {
    // Establece la escena
    SceneManager::current = SceneManager::Scene::ENDING2;
    SceneManager::options = SceneManager::Options::NONE;

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

    Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));  // Cambia el color del borde
    iniSpriteList();                                                         // Inicializa la lista de sprites
    loadSprites();                                                           // Carga todos los sprites desde una lista
    placeSprites();                                                          // Coloca los sprites en su sito
    createSpriteTexts();                                                     // Crea los sprites con las texturas con los textos
    createTexts();                                                           // Crea los sprites con las texturas con los textos del final

    Audio::get()->playMusic("ending2.ogg");
}

// Destructor
Ending2::~Ending2() {
    Audio::get()->stopMusic();
}

// Actualiza el objeto
void Ending2::update() {
    const float DELTA_TIME = delta_timer_->tick();

    handleInput();  // Comprueba las entradas

    updateState(DELTA_TIME);  // Actualiza el estado

    switch (state_.state) {
        case EndingState::CREDITS:
            // Actualiza los sprites, los textos y los textos del final
            updateSprites(DELTA_TIME);
            updateTextSprites(DELTA_TIME);
            updateTexts(DELTA_TIME);
            break;

        case EndingState::FADING:
            // Actualiza el fade final
            updateFinalFade();
            break;

        default:
            // No hacer nada si el estado no corresponde a un caso manejado
            break;
    }

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

// Dibuja el final en pantalla
void Ending2::render() {
    // Prepara para empezar a dibujar en la surface de juego
    Screen::get()->start();

    // Limpia la pantalla
    Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));

    // Dibuja los sprites
    renderSprites();

    // Dibuja los sprites con el texto
    renderSpriteTexts();

    // Dibuja los sprites con el texto del final
    renderTexts();

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

// Despatx d'un event (SDL3 Callback API)
void Ending2::handleEvent(const SDL_Event& event) {
    GlobalEvents::handle(event);
}

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

// Un frame de l'escena (SDL3 Callback API)
void Ending2::iterate() {
    update();
    render();
}

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

    switch (state_.state) {
        case EndingState::PRE_CREDITS:
            if (state_time_ >= STATE_PRE_CREDITS_DURATION) {
                transitionToState(EndingState::CREDITS);
            }
            break;

        case EndingState::CREDITS:
            if (texts_.back()->getPosY() <= GameCanvas::CENTER_Y) {
                transitionToState(EndingState::POST_CREDITS);
            }
            break;

        case EndingState::POST_CREDITS:
            if (state_time_ >= STATE_POST_CREDITS_DURATION) {
                transitionToState(EndingState::FADING);
            }
            break;

        case EndingState::FADING:
            if (state_time_ >= STATE_FADE_DURATION) {
                SceneManager::current = SceneManager::Scene::LOGO;
                SceneManager::options = SceneManager::Options::LOGO_TO_TITLE;
            }
            break;

        default:
            break;
    }
}

// Transición entre estados
void Ending2::transitionToState(EndingState new_state) {
    state_.state = new_state;
    state_time_ = 0.0F;

    // Actualizar duración según el nuevo estado
    switch (new_state) {
        case EndingState::PRE_CREDITS:
            state_.duration = STATE_PRE_CREDITS_DURATION;
            break;
        case EndingState::POST_CREDITS:
            state_.duration = STATE_POST_CREDITS_DURATION;
            break;
        case EndingState::FADING:
            state_.duration = STATE_FADE_DURATION;
            // Al entrar en FADING, iniciar fade de audio
            Audio::get()->fadeOutMusic(MUSIC_FADE_DURATION);
            break;
        case EndingState::CREDITS:
            state_.duration = 0.0F;  // CREDITS no tiene duración fija, termina cuando el último texto llega al centro
            break;
        default:
            break;
    }
}

// Inicializa la lista de sprites
void Ending2::iniSpriteList() {
    // Reinicia el vector
    sprite_list_.clear();

    // Añade los valores
    sprite_list_.emplace_back("bin");
    sprite_list_.emplace_back("floppy");
    sprite_list_.emplace_back("bird");
    sprite_list_.emplace_back("chip");
    sprite_list_.emplace_back("jeannine");
    sprite_list_.emplace_back("spark");
    sprite_list_.emplace_back("code");
    sprite_list_.emplace_back("paco");
    sprite_list_.emplace_back("elsa");
    sprite_list_.emplace_back("z80");

    sprite_list_.emplace_back("bell");
    sprite_list_.emplace_back("dong");

    sprite_list_.emplace_back("amstrad_cs");
    sprite_list_.emplace_back("breakout");

    sprite_list_.emplace_back("flying_arounder");
    sprite_list_.emplace_back("stopped_arounder");
    sprite_list_.emplace_back("walking_arounder");
    sprite_list_.emplace_back("arounders_door");
    sprite_list_.emplace_back("arounders_machine");

    sprite_list_.emplace_back("abad");
    sprite_list_.emplace_back("abad_bell");
    sprite_list_.emplace_back("lord_abad");

    sprite_list_.emplace_back("bat");
    sprite_list_.emplace_back("batman_bell");
    sprite_list_.emplace_back("batman_fire");
    sprite_list_.emplace_back("batman");

    sprite_list_.emplace_back("demon");
    sprite_list_.emplace_back("heavy");
    sprite_list_.emplace_back("dimallas");
    sprite_list_.emplace_back("guitar");

    sprite_list_.emplace_back("jailbattle_alien");
    sprite_list_.emplace_back("jailbattle_human");

    sprite_list_.emplace_back("jailer1");
    sprite_list_.emplace_back("jailer2");
    sprite_list_.emplace_back("jailer3");
    sprite_list_.emplace_back("bry");
    sprite_list_.emplace_back("upv_student");

    sprite_list_.emplace_back("lamp");
    sprite_list_.emplace_back("robot");
    sprite_list_.emplace_back("congo");
    sprite_list_.emplace_back("crosshair");
    sprite_list_.emplace_back("tree_thing");

    sprite_list_.emplace_back("matatunos");
    sprite_list_.emplace_back("tuno");

    sprite_list_.emplace_back("mummy");
    sprite_list_.emplace_back("sam");

    sprite_list_.emplace_back("qvoid");
    sprite_list_.emplace_back("sigmasua");

    sprite_list_.emplace_back("tv_panel");
    sprite_list_.emplace_back("tv");

    sprite_list_.emplace_back("spider");
    sprite_list_.emplace_back("shock");
    sprite_list_.emplace_back("wave");

    sprite_list_.emplace_back("player");
}

// Carga todos los sprites desde una lista
void Ending2::loadSprites() {
    // Inicializa variables
    sprite_max_width_ = 0;
    sprite_max_height_ = 0;

    // Carga los sprites
    for (const auto& file : sprite_list_) {
        const auto& animation_data = Resource::Cache::get()->getAnimationData(file + ".yaml");
        sprites_.emplace_back(std::make_shared<DissolveSprite>(animation_data));
        sprites_.back()->setColorReplace(1, static_cast<Uint8>(PaletteColor::RED));
        sprites_.back()->setProgress(1.0F);  // comença invisible
        sprite_max_width_ = std::max(sprites_.back()->getWidth(), sprite_max_width_);
        sprite_max_height_ = std::max(sprites_.back()->getHeight(), sprite_max_height_);
    }

    // El último sprite (player) va en blanco, no en rojo
    sprites_.back()->setColorReplace(1, static_cast<Uint8>(PaletteColor::WHITE));
}

// Actualiza los sprites
void Ending2::updateSprites(float delta) {
    for (const auto& sprite : sprites_) {
        sprite->update(delta);

        const float Y = sprite->getPosY();
        const float H = sprite->getHeight();
        const auto CANVAS_H = Options::game.height;

        // Checkpoint inferior: sprite entra per baix → generar de dalt a baix
        if (Y > static_cast<float>(ENTRY_EXIT_PADDING) && Y <= CANVAS_H - H - ENTRY_EXIT_PADDING && sprite->getProgress() >= 1.0F && sprite->isTransitionDone()) {
            sprite->startGenerate(TRANSITION_DURATION_MS, DissolveDirection::UP);
        }

        // Checkpoint superior: sprite surt per dalt → dissoldre de dalt a baix
        if (Y <= static_cast<float>(ENTRY_EXIT_PADDING) && sprite->getProgress() <= 0.0F && sprite->isTransitionDone()) {
            sprite->startDissolve(TRANSITION_DURATION_MS, DissolveDirection::DOWN);
        }
    }
}

// Actualiza los sprites de texto
void Ending2::updateTextSprites(float delta) {
    for (const auto& sprite : sprite_texts_) {
        sprite->update(delta);

        const float Y = sprite->getPosY();
        const float H = sprite->getHeight();
        const auto CANVAS_H = Options::game.height;

        if (Y > static_cast<float>(ENTRY_EXIT_PADDING) && Y <= CANVAS_H - H - ENTRY_EXIT_PADDING && sprite->getProgress() >= 1.0F && sprite->isTransitionDone()) {
            sprite->startGenerate(TRANSITION_DURATION_MS, DissolveDirection::UP);
        }

        if (Y <= static_cast<float>(ENTRY_EXIT_PADDING) && sprite->getProgress() <= 0.0F && sprite->isTransitionDone()) {
            sprite->startDissolve(TRANSITION_DURATION_MS, DissolveDirection::DOWN);
        }
    }
}

// Actualiza los sprites de texto del final
void Ending2::updateTexts(float delta) {
    for (const auto& sprite : texts_) {
        sprite->update(delta);

        const float Y = sprite->getPosY();
        const float H = sprite->getHeight();
        const auto CANVAS_H = Options::game.height;

        // Checkpoint inferior: text entra per baix → generar de dalt a baix
        if (Y > static_cast<float>(ENTRY_EXIT_PADDING) && Y <= CANVAS_H - H - ENTRY_EXIT_PADDING && sprite->getProgress() >= 1.0F && sprite->isTransitionDone()) {
            sprite->startGenerate(TRANSITION_DURATION_MS, DissolveDirection::UP);
        }

        // Checkpoint superior: text surt per dalt → dissoldre de dalt a baix
        if (Y <= static_cast<float>(ENTRY_EXIT_PADDING) && sprite->getProgress() <= 0.0F && sprite->isTransitionDone()) {
            sprite->startDissolve(TRANSITION_DURATION_MS, DissolveDirection::DOWN);
        }
    }
}

// Dibuja los sprites
void Ending2::renderSprites() {
    for (const auto& sprite : sprites_) {
        const bool A = sprite->getRect().y + sprite->getRect().h > 0;
        const bool B = sprite->getRect().y < Options::game.height;
        if (A && B) {
            sprite->render();
        }
    }
}

// Dibuja los sprites con el texto
void Ending2::renderSpriteTexts() {
    for (const auto& sprite : sprite_texts_) {
        const bool A = sprite->getRect().y + sprite->getRect().h > 0;
        const bool B = sprite->getRect().y < Options::game.height;
        if (A && B) {
            sprite->render();
        }
    }
}

// Dibuja los sprites con el texto del final
void Ending2::renderTexts() {
    for (const auto& sprite : texts_) {
        const bool A = sprite->getRect().y + sprite->getRect().h > 0;
        const bool B = sprite->getRect().y < Options::game.height;
        if (A && B) {
            sprite->render();
        }
    }
}

// Coloca los sprites en su sito
void Ending2::placeSprites() const {
    for (int i = 0; i < static_cast<int>(sprites_.size()); ++i) {
        const float X = i % 2 == 0 ? FIRST_COL : SECOND_COL;
        const float Y = ((i / 1) * (sprite_max_height_ + DIST_SPRITE_TEXT + Resource::Cache::get()->getText("smb2")->getCharacterSize() + DIST_SPRITE_SPRITE)) + Options::game.height + INITIAL_Y_OFFSET;
        const float W = sprites_.at(i)->getWidth();
        const float H = sprites_.at(i)->getHeight();
        const float DX = -(W / 2);
        const float DY = sprite_max_height_ - H;

        sprites_.at(i)->setPos({.x = X + DX, .y = Y + DY, .w = W, .h = H});
        sprites_.at(i)->setVelY(SPRITE_DESP_SPEED);
    }

    // Recoloca el sprite del jugador, que es el último de la lista
    const float X = (Options::game.width - sprites_.back()->getWidth()) / 2;
    const float Y = sprites_.back()->getPosY() + (sprite_max_height_ * 2);
    sprites_.back()->setPos(X, Y);
    sprites_.back()->setCurrentAnimation("default");
}

// Crea los sprites con las texturas con los textos
void Ending2::createSpriteTexts() {  // NOLINT(readability-convert-member-functions-to-static)
    // Crea los sprites de texto a partir de la lista
    for (size_t i = 0; i < sprite_list_.size(); ++i) {
        auto text = Resource::Cache::get()->getText("smb2");

        // Procesa y ajusta el texto del sprite actual
        std::string txt = sprite_list_[i];
        std::ranges::replace(txt, '_', ' ');  // Reemplaza '_' por ' '
        if (txt == "player") {
            txt = Locale::get()->get("ending2.jaildoctor");  // NOLINT(readability-static-accessed-through-instance) Reemplaza "player" por nombre localizado
        }

        // Calcula las dimensiones del texto
        const float W = text->length(txt, 1);
        const float H = text->getCharacterSize();

        // Determina la columna y la posición X del texto
        const float X = (i == sprite_list_.size() - 1)
            ? (GameCanvas::CENTER_X - (W / 2))
            : ((i % 2 == 0 ? FIRST_COL : SECOND_COL) - (W / 2));

        // Calcula la posición Y del texto en base a la posición y altura del sprite
        const float Y = sprites_.at(i)->getPosY() + sprites_.at(i)->getHeight() + DIST_SPRITE_TEXT;

        // Crea la surface
        auto surface = std::make_shared<Surface>(W, H);
        auto previuos_renderer = Screen::get()->getRendererSurface();
        Screen::get()->setRendererSurface(surface);
        text->write(0, 0, txt);

        // Crea el sprite
        SDL_FRect pos = {.x = X, .y = Y, .w = W, .h = H};
        sprite_texts_.emplace_back(std::make_shared<DissolveSprite>(surface, pos));
        sprite_texts_.back()->setColorReplace(1, static_cast<Uint8>(PaletteColor::WHITE));
        sprite_texts_.back()->setProgress(1.0F);  // comença invisible
        sprite_texts_.back()->setVelY(SPRITE_DESP_SPEED);
        Screen::get()->setRendererSurface(previuos_renderer);
    }
}

// Crea los sprites con las texturas con los textos del final
void Ending2::createTexts() {  // NOLINT(readability-convert-member-functions-to-static)
    // Crea los primeros textos
    std::vector<std::string> list;
    list.emplace_back(Locale::get()->get("ending2.starring"));

    auto text = Resource::Cache::get()->getText("smb2");

    // Crea los sprites de texto a partir de la lista
    for (size_t i = 0; i < list.size(); ++i) {
        // Calcula constantes
        const float W = text->length(list[i], 1);
        const float H = text->getCharacterSize();
        const float X = GameCanvas::CENTER_X;
        const float DX = -(W / 2);
        const float Y = Options::game.height + (text->getCharacterSize() * (i * TEXT_SPACING_MULTIPLIER));

        // Crea la surface
        auto surface = std::make_shared<Surface>(W, H);
        auto previuos_renderer = Screen::get()->getRendererSurface();
        Screen::get()->setRendererSurface(surface);
        text->write(0, 0, list[i]);

        // Crea el sprite
        SDL_FRect pos = {.x = X + DX, .y = Y, .w = W, .h = H};
        texts_.emplace_back(std::make_shared<DissolveSprite>(surface, pos));
        texts_.back()->setProgress(1.0F);  // comença invisible
        texts_.back()->setVelY(SPRITE_DESP_SPEED);
        Screen::get()->setRendererSurface(previuos_renderer);
    }

    // Crea los últimos textos
    // El primer texto va a continuación del ultimo spriteText
    const int START = sprite_texts_.back()->getPosY() + (text->getCharacterSize() * 15);
    list.clear();
    list.emplace_back(Locale::get()->get("ending2.thank_you"));
    list.emplace_back(Locale::get()->get("ending2.for_playing"));

    // Crea los sprites de texto a partir de la lista
    for (size_t i = 0; i < list.size(); ++i) {
        // Calcula constantes
        const float W = text->length(list[i], 1);
        const float H = text->getCharacterSize();
        const float X = GameCanvas::CENTER_X;
        const float DX = -(W / 2);
        const float Y = START + (text->getCharacterSize() * (i * TEXT_SPACING_MULTIPLIER));

        // Crea la surface
        auto surface = std::make_shared<Surface>(W, H);
        auto previuos_renderer = Screen::get()->getRendererSurface();
        Screen::get()->setRendererSurface(surface);
        text->write(0, 0, list[i]);

        // Crea el sprite
        SDL_FRect pos = {.x = X + DX, .y = Y, .w = W, .h = H};
        texts_.emplace_back(std::make_shared<DissolveSprite>(surface, pos));
        texts_.back()->setProgress(1.0F);  // comença invisible
        texts_.back()->setVelY(SPRITE_DESP_SPEED);
        Screen::get()->setRendererSurface(previuos_renderer);
    }
}

// Actualiza el fade final
void Ending2::updateFinalFade() {
    for (const auto& sprite : texts_) {
        sprite->getSurface()->fadeSubPalette(0);
    }
}
