#include "game/scenes/credits.hpp"

#include <SDL3/SDL.h>

#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/pixel_reveal.hpp"            // Para PixelReveal
#include "core/rendering/screen.hpp"                  // Para Screen
#include "core/rendering/sprite/animated_sprite.hpp"  // Para SAnimatedSprite
#include "core/rendering/surface.hpp"                 // Para Surface
#include "core/rendering/text.hpp"                    // Para Text, Text::CENTER_FLAG, Text::COLOR_FLAG
#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 GAME_SPEED, PlayArea::CENTER_X, PLAY_...
#include "utils/delta_timer.hpp"                      // Para DeltaTimer
#include "utils/utils.hpp"                            // Para PaletteColor

// Destructor
Credits::~Credits() = default;

// Constructor
Credits::Credits()
    : text_surface_(std::make_shared<Surface>(Options::game.width, Options::game.height)),
      shining_sprite_(std::make_shared<AnimatedSprite>(Resource::Cache::get()->getAnimationData("shine.yaml"))),
      delta_timer_(std::make_unique<DeltaTimer>()) {
    // Configura la escena
    SceneManager::current = SceneManager::Scene::CREDITS;
    SceneManager::options = SceneManager::Options::NONE;
    shining_sprite_->setPos({.x = 194, .y = 174, .w = 8, .h = 8});

    Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));  // Cambia el color del borde
    fillTexture();                                                           // Escribe el texto en la textura
    Audio::get()->playMusic("title.ogg");                                    // Inicia la musica
}

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

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

// Inicializa los textos
void Credits::iniTexts() {  // NOLINT(readability-convert-member-functions-to-static)
    const auto* loc = Locale::get();

    texts_.clear();
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.instructions"), .color = static_cast<Uint8>(PaletteColor::YELLOW)});
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.l0"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.l1"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.l2"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});

    texts_.push_back({.label = loc->get("credits.keys"), .color = static_cast<Uint8>(PaletteColor::YELLOW)});
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.keys_move"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.f8"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.f11"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.f1f2"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.f3"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = loc->get("credits.f9"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});

    texts_.push_back({.label = loc->get("credits.author"), .color = static_cast<Uint8>(PaletteColor::YELLOW)});
    texts_.push_back({.label = loc->get("credits.date"), .color = static_cast<Uint8>(PaletteColor::YELLOW)});
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});

    texts_.push_back({.label = loc->get("credits.love"), .color = static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({.label = "", .color = static_cast<Uint8>(PaletteColor::WHITE)});
}

// Escribe el texto en la textura
void Credits::fillTexture() {
    // Inicializa los textos
    iniTexts();

    // Rellena la textura de texto
    auto previuos_renderer = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(text_surface_);
    text_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK));

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

    // Escribe el texto en la textura
    const int SIZE = text->getCharacterSize();
    int pos_y = 0;

    for (const auto& t : texts_) {
        text->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, pos_y * SIZE, t.label, 1, t.color);
        pos_y++;
    }

    // Escribe el corazón
    const int TEXT_LENGHT = text->length(texts_[22].label, 1) - text->length(" ", 1);  // Se resta el ultimo caracter que es un espacio
    const int POS_X = ((PlayArea::WIDTH - TEXT_LENGHT) / 2) + TEXT_LENGHT;
    text->writeColored(POS_X, 176, "ä", static_cast<Uint8>(PaletteColor::BRIGHT_RED));
    Screen::get()->setRendererSurface(previuos_renderer);

    // Recoloca el sprite del brillo
    shining_sprite_->setPosX(POS_X + 2);

    // Crea el efecto de revelado pixel a pixel
    pixel_reveal_ = std::make_unique<PixelReveal>(Options::game.width, Options::game.height, PIXELS_PER_SECOND, STEP_DURATION, REVEAL_STEPS);
}

// Actualiza las variables
void Credits::update() {
    const float DELTA_TIME = delta_timer_->tick();
    total_time_ += DELTA_TIME;  // Actualiza el tiempo total

    handleInput();  // Comprueba las entradas

    updateState(DELTA_TIME);  // Actualiza la máquina de estados

    pixel_reveal_->update(reveal_time_);  // Actualiza el efecto de revelado

    // Actualiza el sprite con el brillo si está después del tiempo de inicio
    if (reveal_time_ > SHINE_START_TIME) {
        shining_sprite_->update(DELTA_TIME);
    }

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

// Transición entre estados
void Credits::transitionToState(State new_state) {
    state_ = new_state;
    state_time_ = 0.0F;
}

// Actualiza la máquina de estados
void Credits::updateState(float delta_time) {
    state_time_ += delta_time;

    switch (state_) {
        case State::REVEALING_TEXT:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante revelación
            if (state_time_ >= REVEAL_PHASE_1_DURATION) {
                transitionToState(State::PAUSE_1);
            }
            break;

        case State::PAUSE_1:
            // reveal_time_ NO incrementa durante pausa (se congela)
            if (state_time_ >= PAUSE_DURATION) {
                transitionToState(State::REVEALING_TEXT_2);
            }
            break;

        case State::REVEALING_TEXT_2:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante revelación
            if (state_time_ >= REVEAL_PHASE_2_DURATION) {
                transitionToState(State::PAUSE_2);
            }
            break;

        case State::PAUSE_2:
            // reveal_time_ NO incrementa durante pausa (se congela)
            if (state_time_ >= PAUSE_DURATION) {
                transitionToState(State::REVEALING_TEXT_3);
            }
            break;

        case State::REVEALING_TEXT_3:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante revelación
            if (state_time_ >= REVEAL_PHASE_3_DURATION) {
                transitionToState(State::PAUSE_3);
            }
            break;

        case State::PAUSE_3:
            // reveal_time_ NO incrementa durante pausa (se congela)
            if (state_time_ >= PAUSE_DURATION) {
                transitionToState(State::DISPLAYING_WITH_SHINE);
            }
            break;

        case State::DISPLAYING_WITH_SHINE:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante revelación
            if (state_time_ >= DISPLAY_WITH_SHINE_DURATION) {
                transitionToState(State::FADING_OUT);
            }
            break;

        case State::FADING_OUT:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante fade
            if (state_time_ >= FADE_OUT_DURATION) {
                transitionToState(State::EXITING);
            }
            break;

        case State::EXITING:
            SceneManager::current = SceneManager::Scene::DEMO;
            break;
    }
}

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

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

    if (state_ != State::EXITING) {
        // Dibuja la textura con el texto en pantalla
        text_surface_->render(0, 0);

        // Dibuja la máscara de revelado pixel a pixel
        pixel_reveal_->render(0, 0);

        // Dibuja el sprite con el brillo
        if (reveal_time_ > SHINE_START_TIME) {
            shining_sprite_->render(1, static_cast<Uint8>(PaletteColor::BRIGHT_WHITE));
        }
    }

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

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