#include "game/scenes/instructions.hpp"

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

#include <algorithm>  // Para max
#include <array>      // Para array
#include <string>     // Para basic_string, string
#include <utility>    // Para move
#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/fade.hpp"           // Para Fade, FadeMode, FadeType
#include "core/rendering/screen.hpp"         // Para Screen
#include "core/rendering/sprite/sprite.hpp"  // Para Sprite
#include "core/rendering/text.hpp"           // Para Text, Text::CENTER, Text::COLOR, Text::SHADOW
#include "core/rendering/tiled_bg.hpp"       // Para TiledBG, TiledBGMode
#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/entities/item.hpp"            // Para Item
#include "utils/color.hpp"                   // Para Color, Colors::SHADOW_TEXT, Zone, NO_TEXT_C...
#include "utils/param.hpp"                   // Para Param, param, ParamGame, ParamFade, Param...
#include "utils/utils.hpp"

// Constructor
Instructions::Instructions()
    : renderer_(Screen::get()->getRenderer()),
      texture_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
      backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
      text_(Resource::get()->getText("smb2")),
      tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::STATIC)),
      fade_(std::make_unique<Fade>()) {
    // Configura las texturas
    SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
    SDL_SetTextureBlendMode(texture_, SDL_BLENDMODE_BLEND);

    // Inicializa variables
    Section::name = Section::Name::INSTRUCTIONS;
    view_ = param.game.game_area.rect;

    // Inicializa objetos
    tiled_bg_->setColor(param.title.bg_color);
    fade_->setColor(param.fade.color);
    fade_->setType(Fade::Type::FULLSCREEN);
    fade_->setPostDuration(param.fade.post_duration_ms);
    fade_->setMode(Fade::Mode::IN);
    fade_->activate();

    // Inicializa las líneas con un retraso progresivo
    lines_ = initializeLines(256, LINE_START_DELAY_S);

    // Rellena la textura de texto
    fillTexture();

    // Inicializa los sprites de los items
    iniSprites();

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

// Destructor
Instructions::~Instructions() {
    item_textures_.clear();
    sprites_.clear();

    SDL_DestroyTexture(backbuffer_);
    SDL_DestroyTexture(texture_);
}

// Inicializa los sprites de los items
void Instructions::iniSprites() {
    // Inicializa las texturas
    item_textures_.emplace_back(Resource::get()->getTexture("item_points1_disk.png"));
    item_textures_.emplace_back(Resource::get()->getTexture("item_points2_gavina.png"));
    item_textures_.emplace_back(Resource::get()->getTexture("item_points3_pacmar.png"));
    item_textures_.emplace_back(Resource::get()->getTexture("item_clock.png"));
    item_textures_.emplace_back(Resource::get()->getTexture("item_coffee.png"));

    // Inicializa los sprites
    for (int i = 0; std::cmp_less(i, item_textures_.size()); ++i) {
        auto sprite = std::make_unique<Sprite>(item_textures_[i], 0, 0, Item::WIDTH, Item::HEIGHT);
        sprite->setPosition((SDL_FPoint){.x = sprite_pos_.x, .y = sprite_pos_.y + ((Item::HEIGHT + item_space_) * i)});
        sprites_.push_back(std::move(sprite));
    }
}

// Actualiza los sprites
void Instructions::updateSprites() {
    SDL_FRect src_rect = {.x = 0, .y = 0, .w = Item::WIDTH, .h = Item::HEIGHT};

    // Disquito (desplazamiento 12/60 = 0.2s)
    src_rect.y = Item::HEIGHT * (static_cast<int>((elapsed_time_ + 0.2F) / SPRITE_ANIMATION_CYCLE_S) % 2);
    sprites_[0]->setSpriteClip(src_rect);

    // Gavina (desplazamiento 9/60 = 0.15s)
    src_rect.y = Item::HEIGHT * (static_cast<int>((elapsed_time_ + 0.15F) / SPRITE_ANIMATION_CYCLE_S) % 2);
    sprites_[1]->setSpriteClip(src_rect);

    // Pacmar (desplazamiento 6/60 = 0.1s)
    src_rect.y = Item::HEIGHT * (static_cast<int>((elapsed_time_ + 0.1F) / SPRITE_ANIMATION_CYCLE_S) % 2);
    sprites_[2]->setSpriteClip(src_rect);

    // Time Stopper (desplazamiento 3/60 = 0.05s)
    src_rect.y = Item::HEIGHT * (static_cast<int>((elapsed_time_ + 0.05F) / SPRITE_ANIMATION_CYCLE_S) % 2);
    sprites_[3]->setSpriteClip(src_rect);

    // Coffee (sin desplazamiento)
    src_rect.y = Item::HEIGHT * (static_cast<int>(elapsed_time_ / SPRITE_ANIMATION_CYCLE_S) % 2);
    sprites_[4]->setSpriteClip(src_rect);
}

// Rellena la textura de texto
void Instructions::fillTexture() {
    const int X_OFFSET = Item::WIDTH + 8;

    // Modifica el renderizador para pintar en la textura
    auto* temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, texture_);

    // Limpia la textura
    SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
    SDL_RenderClear(renderer_);

    // Constantes
    constexpr int NUM_LINES = 4;
    constexpr int NUM_ITEM_LINES = 4;
    constexpr int NUM_POST_HEADERS = 2;
    constexpr int NUM_PRE_HEADERS = 1;

    constexpr int SPACE_POST_HEADER = 20;
    constexpr int SPACE_PRE_HEADER = 28;
    const int SPACE_BETWEEN_LINES = text_->getCharacterSize() * 1.5F;
    const int SPACE_BETWEEN_ITEM_LINES = Item::HEIGHT + item_space_;
    const int SPACE_NEW_PARAGRAPH = SPACE_BETWEEN_LINES * 0.5F;

    const int SIZE = (NUM_LINES * SPACE_BETWEEN_LINES) + (NUM_ITEM_LINES * SPACE_BETWEEN_ITEM_LINES) + (NUM_POST_HEADERS * SPACE_POST_HEADER) + (NUM_PRE_HEADERS * SPACE_PRE_HEADER) + SPACE_NEW_PARAGRAPH;
    const int FIRST_LINE = (param.game.height - SIZE) / 2;

    // Calcula cual es el texto más largo de las descripciones de los items
    int length = 0;
    const std::array<std::string, 5> ITEM_DESCRIPTIONS = {
        Lang::getText("[INSTRUCTIONS] 07"),
        Lang::getText("[INSTRUCTIONS] 08"),
        Lang::getText("[INSTRUCTIONS] 09"),
        Lang::getText("[INSTRUCTIONS] 10"),
        Lang::getText("[INSTRUCTIONS] 11")};
    for (const auto& desc : ITEM_DESCRIPTIONS) {
        const int L = text_->length(desc);
        length = L > length ? L : length;
    }
    const int ANCHOR_ITEM = (param.game.width - (length + X_OFFSET)) / 2;

    auto caption_style = Text::Style(Text::CENTER | Text::COLOR | Text::SHADOW, Colors::ORANGE_TEXT, Colors::SHADOW_TEXT);
    auto text_style = Text::Style(Text::CENTER | Text::COLOR | Text::SHADOW, Colors::NO_COLOR_MOD, Colors::SHADOW_TEXT);

    // Escribe el texto de las instrucciones
    text_->writeStyle(param.game.game_area.center_x, FIRST_LINE, Lang::getText("[INSTRUCTIONS] 01"), caption_style);

    const int ANCHOR1 = FIRST_LINE + SPACE_POST_HEADER;
    text_->writeStyle(param.game.game_area.center_x, ANCHOR1 + (SPACE_BETWEEN_LINES * 0), Lang::getText("[INSTRUCTIONS] 02"), text_style);
    text_->writeStyle(param.game.game_area.center_x, ANCHOR1 + (SPACE_BETWEEN_LINES * 1), Lang::getText("[INSTRUCTIONS] 03"), text_style);
    text_->writeStyle(param.game.game_area.center_x, ANCHOR1 + SPACE_NEW_PARAGRAPH + (SPACE_BETWEEN_LINES * 2), Lang::getText("[INSTRUCTIONS] 04"), text_style);
    text_->writeStyle(param.game.game_area.center_x, ANCHOR1 + SPACE_NEW_PARAGRAPH + (SPACE_BETWEEN_LINES * 3), Lang::getText("[INSTRUCTIONS] 05"), text_style);

    // Escribe el texto de los objetos y sus puntos
    const int ANCHOR2 = ANCHOR1 + SPACE_PRE_HEADER + SPACE_NEW_PARAGRAPH + (SPACE_BETWEEN_LINES * 3);
    text_->writeStyle(param.game.game_area.center_x, ANCHOR2, Lang::getText("[INSTRUCTIONS] 06"), caption_style);

    const int ANCHOR3 = ANCHOR2 + SPACE_POST_HEADER;
    text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 0), Lang::getText("[INSTRUCTIONS] 07"), Colors::SHADOW_TEXT);
    text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 1), Lang::getText("[INSTRUCTIONS] 08"), Colors::SHADOW_TEXT);
    text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 2), Lang::getText("[INSTRUCTIONS] 09"), Colors::SHADOW_TEXT);
    text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 3), Lang::getText("[INSTRUCTIONS] 10"), Colors::SHADOW_TEXT);
    text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 4), Lang::getText("[INSTRUCTIONS] 11"), Colors::SHADOW_TEXT);

    // Deja el renderizador como estaba
    SDL_SetRenderTarget(renderer_, temp);

    // Da valor a la variable
    sprite_pos_.x = ANCHOR_ITEM;
    sprite_pos_.y = ANCHOR3 - ((Item::HEIGHT - text_->getCharacterSize()) / 2);
}

// Rellena el backbuffer
void Instructions::fillBackbuffer() {
    // Modifica el renderizador para pintar en la textura
    auto* temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, backbuffer_);

    // Limpia la textura
    SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
    SDL_RenderClear(renderer_);

    // Coloca el texto de fondo
    SDL_RenderTexture(renderer_, texture_, nullptr, nullptr);

    // Dibuja los sprites
    for (auto& sprite : sprites_) {
        sprite->render();
    }

    // Deja el renderizador como estaba
    SDL_SetRenderTarget(renderer_, temp);
}

// Actualiza las variables
void Instructions::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();                // Actualiza los sprites
    updateBackbuffer(delta_time);   // Gestiona la textura con los graficos
    tiled_bg_->update(delta_time);  // Actualiza el mosaico de fondo
    fade_->update(delta_time);      // Actualiza el objeto "fade"
    fillBackbuffer();               // Rellena el backbuffer
}

// Pinta en pantalla
void Instructions::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 mosacico de fondo

    // Copia la textura y el backbuffer al renderizador
    if (view_.y == 0) {
        renderLines(renderer_, backbuffer_, lines_);
    } else {
        SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_);
    }

    fade_->render();  // Renderiza el fundido

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

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

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

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

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

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

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

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

// Método para inicializar las líneas
auto Instructions::initializeLines(int height, float line_delay) -> std::vector<Line> {
    std::vector<Line> lines;
    for (int y = 0; y < height; y++) {
        int direction = (y % 2 == 0) ? -1 : 1;  // Pares a la izquierda, impares a la derecha
        float delay = y * line_delay;           // Retraso progresivo basado en la línea
        lines.emplace_back(y, 0.0F, direction, delay);
    }
    return lines;
}

// Método para mover las líneas con suavizado (usando delta_time puro)
auto Instructions::moveLines(std::vector<Line>& lines, int width, float duration, float delta_time) -> bool {
    bool all_lines_off_screen = true;

    for (auto& line : lines) {
        // Verificar si la línea ha superado su tiempo de retraso
        if (!line.started) {
            line.delay_time -= delta_time;
            if (line.delay_time <= 0.0F) {
                line.started = true;
                line.accumulated_time = 0.0F;
            } else {
                all_lines_off_screen = false;  // Aún hay líneas esperando para empezar
                continue;
            }
        }

        // Si la línea ya ha completado su movimiento, saltarla
        if (line.accumulated_time >= duration) {
            continue;  // Esta línea ya terminó
        }

        // Acumular tiempo y calcular posición
        line.accumulated_time += delta_time;

        if (line.accumulated_time >= duration) {
            // La línea ha completado su movimiento
            line.accumulated_time = duration;
        } else {
            // La línea aún se está moviendo
            all_lines_off_screen = false;
        }

        // Calcular posición con suavizado
        float t = line.accumulated_time / duration;
        float smooth_factor = easeInOutQuint(t);
        line.x = line.direction * smooth_factor * width;
    }

    return all_lines_off_screen;
}

// Método para renderizar las líneas
void Instructions::renderLines(SDL_Renderer* renderer, SDL_Texture* texture, const std::vector<Line>& lines) {
    for (const auto& line : lines) {
        SDL_FRect src_rect = {.x = 0, .y = static_cast<float>(line.y), .w = 320, .h = 1};
        SDL_FRect dst_rect = {.x = static_cast<float>(line.x), .y = static_cast<float>(line.y), .w = 320, .h = 1};
        SDL_RenderTexture(renderer, texture, &src_rect, &dst_rect);
    }
}

// Gestiona la textura con los graficos
void Instructions::updateBackbuffer(float delta_time) {
    // Establece la ventana del backbuffer usando velocidad en pixels por segundo
    // El scroll comienza desde (param.game.height + 100) y desciende a 0
    // IMPORTANTE: Se redondea a entero para evitar deformaciones de textura causadas por sub-pixel rendering
    float scroll_offset = elapsed_time_ * SCROLL_SPEED_PPS;
    view_.y = std::round(std::max(0.0F, (param.game.height + 100.0F) - scroll_offset));

    // Verifica si view_.y == 0 y gestiona el temporizador
    if (view_.y == 0.0F) {
        if (!start_delay_triggered_) {
            // Activa el temporizador si no ha sido activado
            start_delay_triggered_ = true;
            start_delay_timer_ = 0.0F;
        } else {
            start_delay_timer_ += delta_time;
            if (start_delay_timer_ >= START_DELAY_S) {
                // Han pasado los segundos de retraso, mover líneas
                all_lines_off_screen_ = moveLines(lines_, 320, LINE_MOVE_DURATION_S, delta_time);
            }
        }
    }

    // Comprueba si todas las líneas han terminado
    if (all_lines_off_screen_) {
        Section::name = Section::Name::TITLE;
        Section::options = Section::Options::TITLE_1;
    }
}