#include "game/scenes/game.hpp"

#include <SDL3/SDL.h>  // Para SDL_GetTicks, SDL_SetRenderTarget, SDL_EventType, SDL_CreateTexture, SDL_Delay, SDL_DestroyTexture, SDL_Event, SDL_GetRenderTarget, SDL_PollEvent, SDL_RenderTexture, SDL_SetTextureBlendMode, SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, SDLK_7, SDLK_8, SDLK_9, SDLK_KP_MINUS, SDLK_KP_PLUS, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_Point, SDL_TextureAccess, Uint64

#include <algorithm>   // Para find, clamp, find_if, min, shuffle
#include <array>       // Para array
#include <cstdlib>     // Para rand, size_t
#include <functional>  // Para function
#include <iterator>    // Para size
#include <memory>      // Para shared_ptr, unique_ptr, __shared_ptr_access, make_unique, allocator, operator==
#include <numeric>     // Para iota
#include <optional>    // Para optional
#include <random>      // Para random_device, default_random_engine
#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/input/input_types.hpp"              // Para InputAction
#include "core/input/pause_manager.hpp"            // Para PauseManager
#include "core/locale/lang.hpp"                    // Para getText
#include "core/rendering/background.hpp"           // Para Background
#include "core/rendering/fade.hpp"                 // Para Fade
#include "core/rendering/screen.hpp"               // Para Screen
#include "core/rendering/sprite/path_sprite.hpp"   // Para Path, PathSprite, PathType
#include "core/rendering/sprite/smart_sprite.hpp"  // Para SmartSprite
#include "core/rendering/text.hpp"                 // Para Text
#include "core/rendering/texture.hpp"              // Para Texture
#include "core/resources/asset.hpp"                // Para Asset
#include "core/resources/resource.hpp"             // Para Resource
#include "core/system/global_events.hpp"           // Para handle
#include "core/system/section.hpp"                 // Para Name, name, AttractMode, Options, attract_mode, options
#include "game/entities/balloon.hpp"               // Para Balloon
#include "game/entities/bullet.hpp"                // Para Bullet
#include "game/entities/item.hpp"                  // Para Item, ItemType
#include "game/entities/player.hpp"                // Para Player
#include "game/entities/tabe.hpp"                  // Para Tabe
#include "game/gameplay/balloon_manager.hpp"       // Para BalloonManager
#include "game/gameplay/bullet_manager.hpp"        // Para BulletManager
#include "game/gameplay/difficulty.hpp"            // Para Code
#include "game/gameplay/manage_hiscore_table.hpp"  // Para HiScoreEntry, ManageHiScoreTable
#include "game/gameplay/scoreboard.hpp"            // Para Scoreboard
#include "game/gameplay/stage.hpp"                 // Para StageManager, StageData
#include "game/ui/service_menu.hpp"                // Para ServiceMenu
#include "utils/color.hpp"                         // Para Color, FLASH, NO_COLOR_MOD
#include "utils/param.hpp"                         // Para Param, param, ParamGame, ParamScoreboard, ParamFade, ParamBalloon
#include "utils/utils.hpp"                         // Para Zone, checkCollision, easeInQuint, easeOutQuint, boolToString
#ifdef _DEBUG
#include <iostream>  // Para basic_ostream, basic_ostream::operator<<, operator<<, cout

#include "core/system/director.hpp"  // Para Director::debug_config
#include "game/ui/notifier.hpp"      // Para Notifier
#endif

// Constructor
Game::Game(Player::Id player_id, int current_stage, bool demo_enabled)
    : renderer_(Screen::get()->getRenderer()),
      screen_(Screen::get()),
      input_(Input::get()),
      canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h)),
      pause_manager_(std::make_unique<PauseManager>([this](bool is_paused) -> void { onPauseStateChanged(is_paused); })),
      stage_manager_(std::make_unique<StageManager>()),
      balloon_manager_(std::make_unique<BalloonManager>(stage_manager_.get())),
      bullet_manager_(std::make_unique<BulletManager>()),
      background_(std::make_unique<Background>(stage_manager_->getPowerNeededToReachStage(stage_manager_->getTotalStages() - 1))),
      fade_in_(std::make_unique<Fade>()),
      fade_out_(std::make_unique<Fade>()),
      tabe_(std::make_unique<Tabe>()) {
    // Pasa variables
    demo_.enabled = demo_enabled;

    // Otras variables
    Section::name = Section::Name::GAME;
    Section::options = Section::Options::NONE;
    stage_manager_->initialize(Asset::get()->getPath("stages.txt"));
    stage_manager_->setPowerChangeCallback([this](int amount) -> void { background_->incrementProgress(amount); });
    stage_manager_->jumpToStage(current_stage);

    // Asigna texturas y animaciones
    setResources();

    // Crea y configura los objetos
    Scoreboard::init();
    scoreboard_ = Scoreboard::get();

    fade_in_->setColor(param.fade.color);
    fade_in_->setPreDuration(demo_.enabled ? static_cast<int>(DEMO_FADE_PRE_DURATION_S * 1000) : 0);
    fade_in_->setPostDuration(0);
    fade_in_->setType(Fade::Type::RANDOM_SQUARE2);
    fade_in_->setMode(Fade::Mode::IN);
#ifndef RECORDING
    fade_in_->activate();
#endif

    fade_out_->setColor(param.fade.color);
    fade_out_->setPostDuration(param.fade.post_duration_ms);
    fade_out_->setType(Fade::Type::VENETIAN);

    background_->setPos(param.game.play_area.rect);

    balloon_manager_->setBouncingSounds(param.balloon.bouncing_sound);

    SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND);

    // Inicializa el resto de variables
    initPlayers(player_id);
    initScoreboard();
    initDifficultyVars();
    initDemo(player_id);
    initPaths();

    // Registra callbacks
    ServiceMenu::get()->setStateChangeCallback([this](bool is_active) -> void {
        // Solo aplicar pausa si NO estamos en modo demo
        if (!demo_.enabled) {
            pause_manager_->setServiceMenuPause(is_active);
        }
    });

    // Configura callbacks del BulletManager
    bullet_manager_->setTabeCollisionCallback([this](const std::shared_ptr<Bullet>& bullet) -> bool {
        return checkBulletTabeCollision(bullet);
    });

    bullet_manager_->setBalloonCollisionCallback([this](const std::shared_ptr<Bullet>& bullet) -> bool {
        return checkBulletBalloonCollision(bullet);
    });

    bullet_manager_->setOutOfBoundsCallback([this](const std::shared_ptr<Bullet>& bullet) -> void {
        getPlayer(static_cast<Player::Id>(bullet->getOwner()))->decScoreMultiplier();
    });
#ifdef RECORDING
    setState(State::PLAYING);
#endif

    // Inicializa el timer de delta time para el primer frame del callback
    last_time_ = SDL_GetTicks();
}

Game::~Game() {
    // [Modo JUEGO] Guarda puntuaciones y transita a modo título
    if (!demo_.enabled) {
        auto manager = std::make_unique<ManageHiScoreTable>(Options::settings.hi_score_table);
        manager->saveToFile(Asset::get()->getPath("score.bin"));
        Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;
        if (Options::audio.enabled) {
            // Musica
            Audio::get()->stopMusic();
            Audio::get()->setMusicVolume(Options::audio.music.volume);
            // Sonidos
            Audio::get()->stopAllSounds();
            Audio::get()->setSoundVolume(Options::audio.sound.volume, Audio::Group::GAME);
        }
    }
    ServiceMenu::get()->setStateChangeCallback(nullptr);

#ifdef RECORDING
    saveDemoFile(Asset::get()->getPath("demo1.bin"), demo_.data.at(0));
#endif

    Scoreboard::destroy();
    SDL_DestroyTexture(canvas_);

    // Desregistra los jugadores de Options
    Options::keyboard.clearPlayers();
    Options::gamepad_manager.clearPlayers();
}

// Asigna texturas y animaciones
void Game::setResources() {
    // Texturas - Game_text
    game_text_textures_.clear();
    game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_1000_points"));
    game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_2500_points"));
    game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_5000_points"));
    game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_powerup"));
    game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_one_hit"));
    game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_stop"));
    game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_100000_points"));

    // Texturas - Items
    item_textures_.clear();
    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"));
    item_textures_.emplace_back(Resource::get()->getTexture("item_debian.png"));
    item_textures_.emplace_back(Resource::get()->getTexture("item_coffee_machine.png"));

    player_textures_.clear();
    // Texturas - Player1
    std::vector<std::shared_ptr<Texture>> player1_textures;
    player1_textures.emplace_back(Resource::get()->getTexture("player1_pal0"));
    player1_textures.emplace_back(Resource::get()->getTexture("player1_pal1"));
    player1_textures.emplace_back(Resource::get()->getTexture("player1_pal2"));
    player1_textures.emplace_back(Resource::get()->getTexture("player1_pal3"));
    player1_textures.emplace_back(Resource::get()->getTexture("player1_power.png"));
    player_textures_.push_back(player1_textures);

    // Texturas - Player2
    std::vector<std::shared_ptr<Texture>> player2_textures;
    player2_textures.emplace_back(Resource::get()->getTexture("player2_pal0"));
    player2_textures.emplace_back(Resource::get()->getTexture("player2_pal1"));
    player2_textures.emplace_back(Resource::get()->getTexture("player2_pal2"));
    player2_textures.emplace_back(Resource::get()->getTexture("player2_pal3"));
    player2_textures.emplace_back(Resource::get()->getTexture("player2_power.png"));
    player_textures_.push_back(player2_textures);

    // Animaciones -- Jugador
    player1_animations_.clear();
    player1_animations_.emplace_back(Resource::get()->getAnimation("player1.ani"));
    player1_animations_.emplace_back(Resource::get()->getAnimation("player_power.ani"));
    player2_animations_.clear();
    player2_animations_.emplace_back(Resource::get()->getAnimation("player2.ani"));
    player2_animations_.emplace_back(Resource::get()->getAnimation("player_power.ani"));

    // Animaciones -- Items
    item_animations_.clear();
    item_animations_.emplace_back(Resource::get()->getAnimation("item_points1_disk.ani"));
    item_animations_.emplace_back(Resource::get()->getAnimation("item_points2_gavina.ani"));
    item_animations_.emplace_back(Resource::get()->getAnimation("item_points3_pacmar.ani"));
    item_animations_.emplace_back(Resource::get()->getAnimation("item_clock.ani"));
    item_animations_.emplace_back(Resource::get()->getAnimation("item_coffee.ani"));
    item_animations_.emplace_back(Resource::get()->getAnimation("item_debian.ani"));
    item_animations_.emplace_back(Resource::get()->getAnimation("item_coffee_machine.ani"));
}

// Actualiza el valor de hiScore en caso necesario
void Game::updateHiScore() {
    hi_score_.name = Options::settings.hi_score_table.front().name;

    // Si la puntuación actual es mayor que la máxima puntuación
    for (const auto& player : players_) {
        if (player->getScore() > hi_score_.score) {
            // Actualiza la máxima puntuación
            hi_score_.score = player->getScore();
            hi_score_.name.clear();

            // Si se supera la máxima puntuación
            if (!hi_score_achieved_) {
                hi_score_achieved_ = true;
                playSound("hi_score_achieved.wav");                                                                // Emite un sonido
                createMessage({paths_.at(8), paths_.at(9)}, Resource::get()->getTexture("game_text_new_record"));  // CRea un mensaje
            }
        }
    }
}

// Actualiza las variables del jugador
void Game::updatePlayers(float delta_time) {
    for (auto& player : players_) {
        player->update(delta_time);

        if (player->isPlaying()) {
            // Comprueba la colisión entre el jugador y los globos
            auto balloon = checkPlayerBalloonCollision(player);

            // Si hay colisión
            if (balloon) {
                // Si el globo está parado y el temporizador activo, lo explota
                if (balloon->isStopped() && time_stopped_timer_ > 0) {
                    balloon_manager_->popBalloon(balloon);
                }
                // En caso contrario, el jugador ha sido golpeado por un globo activo
                else {
                    handlePlayerCollision(player, balloon);

                    if (demo_.enabled && allPlayersAreNotPlaying()) {
                        fade_out_->setType(Fade::Type::RANDOM_SQUARE2);
                        fade_out_->setPostDuration(500);
                        fade_out_->activate();
                    }
                }
            }

            // Comprueba las colisiones entre el jugador y los items
            checkPlayerItemCollision(player);
        }
    }

    // Organiza la lista de jugadores
    // sortPlayersByZOrder();
}

// Dibuja a los jugadores
void Game::renderPlayers() {
    renderPlayerDrawList(players_draw_list_);
}

// Comprueba si hay cambio de fase y actualiza las variables
void Game::updateStage() {
    if (!stage_manager_->isCurrentStageCompleted()) {
        return;  // No hay cambio de fase
    }

    // Calcular poder sobrante antes de avanzar
    int power_overflow = 0;
    auto current_stage = stage_manager_->getCurrentStage();
    if (current_stage.has_value()) {
        int current_power = stage_manager_->getCurrentPower();
        int power_needed = current_stage->getPowerToComplete();
        power_overflow = current_power - power_needed;  // Poder que sobra
    }

    // Intentar avanzar a la siguiente fase
    if (!stage_manager_->advanceToNextStage()) {
        // No se pudo avanzar (probablemente juego completado)
        return;
    }

    // Aplicar el poder sobrante a la nueva fase
    if (power_overflow > 0) {
        stage_manager_->addPower(power_overflow);
    }

    // Efectos de cambio de fase
    playSound("stage_change.wav");
    balloon_manager_->resetBalloonSpeed();
    screen_->flash(Colors::FLASH, 0.05F);
    screen_->shake();

    // Obtener datos de la nueva fase
    size_t current_stage_index = stage_manager_->getCurrentStageIndex();
    size_t total_stages = stage_manager_->getTotalStages();

    // Escribir texto por pantalla
    if (current_stage_index < total_stages) {  // No es la última fase
        std::vector<Path> paths = {paths_.at(2), paths_.at(3)};

        if (current_stage_index == total_stages - 1) {  // Penúltima fase (será la última)
            createMessage(paths, Resource::get()->getTexture("game_text_last_stage"));
        } else {
            auto text = Resource::get()->getText("04b_25_2x_enhanced");
            const std::string CAPTION = Lang::getText("[GAME_TEXT] 2") +
                std::to_string(total_stages - current_stage_index) +
                Lang::getText("[GAME_TEXT] 2A");
            createMessage(paths, text->writeDXToTexture(Text::STROKE, CAPTION, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
        }
    }

    // Modificar color de fondo en la última fase
    if (current_stage_index == total_stages - 1) {  // Última fase
        background_->setColor(Color(0xdd, 0x19, 0x1d).DARKEN());
        background_->setAlpha(96);
    }
}

// Actualiza las variables y sistemas durante el estado de fin de partida
void Game::updateGameStateGameOver(float delta_time) {
    fade_out_->update();
    updatePlayers(delta_time);
    updateScoreboard(delta_time);
    updateBackground(delta_time);
    balloon_manager_->update(delta_time);
    tabe_->update(delta_time);
    bullet_manager_->update(delta_time);
    updateItems(delta_time);
    updateSmartSprites(delta_time);
    updatePathSprites(delta_time);
    updateTimeStopped(delta_time);
    bullet_manager_->checkCollisions();
    cleanLists();

    if (game_over_timer_ < GAME_OVER_DURATION_S) {
        game_over_timer_ += delta_time;  // Incremento time-based primero
        handleGameOverEvents();          // Maneja eventos después del incremento
    }

    if (Options::audio.enabled) {
        const float PROGRESS = std::min(game_over_timer_ / GAME_OVER_DURATION_S, 1.0F);
        const float VOL = 64.0F * (1.0F - PROGRESS);
        Audio::get()->setSoundVolume(static_cast<int>(VOL), Audio::Group::GAME);
    }

    if (fade_out_->hasEnded()) {
        Section::name = Section::Name::HI_SCORE_TABLE;
        Section::options = Section::Options::HI_SCORE_AFTER_PLAYING;
    }
}

// Gestiona eventos para el estado del final del juego
void Game::updateGameStateCompleted(float delta_time) {
    fade_out_->update();
    updatePlayers(delta_time);
    updateScoreboard(delta_time);
    updateBackground(delta_time);
    balloon_manager_->update(delta_time);
    tabe_->update(delta_time);
    bullet_manager_->update(delta_time);
    updateItems(delta_time);
    updateSmartSprites(delta_time);
    updatePathSprites(delta_time);
    cleanLists();

    // Maneja eventos del juego completado
    handleGameCompletedEvents();

    // Si los jugadores ya no estan y no quedan mensajes en pantalla
    if (allPlayersAreGameOver() && path_sprites_.empty()) {
        fade_out_->activate();
    }

    if (fade_out_->hasEnded()) {
        Section::name = Section::Name::CREDITS;
        Section::options = Section::Options::HI_SCORE_AFTER_PLAYING;
    }

    // Incrementa el acumulador al final
    game_completed_timer_ += delta_time;
}

// Comprueba el estado del juego
void Game::checkState() {
    if (state_ != State::COMPLETED && stage_manager_->isGameCompleted()) {
        setState(State::COMPLETED);
        background_->setState(Background::State::COMPLETED);
    }

    if (state_ != State::GAME_OVER && allPlayersAreGameOver()) {
        setState(State::GAME_OVER);
    }
}

// Destruye todos los items
void Game::destroyAllItems() {
    for (auto& item : items_) {
        item->disable();
    }
}

// Comprueba la colisión entre el jugador y los globos activos
auto Game::checkPlayerBalloonCollision(std::shared_ptr<Player>& player) -> std::shared_ptr<Balloon> {
#ifndef RECORDING
    for (auto& balloon : balloon_manager_->getBalloons()) {
        if (!balloon->isInvulnerable() && !balloon->isPowerBall()) {
            if (checkCollision(player->getCollider(), balloon->getCollider())) {
                return balloon;  // Devuelve el globo con el que se ha producido la colisión
            }
        }
    }
#endif

    return nullptr;  // No se ha producido ninguna colisión
}

// Comprueba la colisión entre el jugador y los items
void Game::checkPlayerItemCollision(std::shared_ptr<Player>& player) {
    if (!player->isPlaying()) {
        return;
    }

    for (auto& item : items_) {
        if (item->isEnabled()) {
            if (checkCollision(player->getCollider(), item->getCollider())) {
                switch (item->getType()) {
                    case ItemType::DISK: {
                        player->addScore(1000, Options::settings.hi_score_table.back().score);
                        const auto X = item->getPosX() + ((item->getWidth() - game_text_textures_.at(0)->getWidth()) / 2);
                        createItemText(X, game_text_textures_.at(0));
                        playSound("item_pickup.wav");
                        break;
                    }
                    case ItemType::GAVINA: {
                        player->addScore(2500, Options::settings.hi_score_table.back().score);
                        const auto X = item->getPosX() + ((item->getWidth() - game_text_textures_.at(1)->getWidth()) / 2);
                        createItemText(X, game_text_textures_.at(1));
                        playSound("item_pickup.wav");
                        break;
                    }
                    case ItemType::PACMAR: {
                        player->addScore(5000, Options::settings.hi_score_table.back().score);
                        const auto X = item->getPosX() + ((item->getWidth() - game_text_textures_.at(2)->getWidth()) / 2);
                        createItemText(X, game_text_textures_.at(2));
                        playSound("item_pickup.wav");
                        break;
                    }
                    case ItemType::DEBIAN: {
                        player->addScore(100000, Options::settings.hi_score_table.back().score);
                        const auto X = item->getPosX() + ((item->getWidth() - game_text_textures_.at(6)->getWidth()) / 2);
                        createItemText(X, game_text_textures_.at(6));
                        playSound("debian_pickup.wav");
                        break;
                    }
                    case ItemType::CLOCK: {
                        enableTimeStopItem();
                        const auto X = item->getPosX() + ((item->getWidth() - game_text_textures_.at(5)->getWidth()) / 2);
                        createItemText(X, game_text_textures_.at(5));
                        playSound("item_pickup.wav");
                        break;
                    }
                    case ItemType::COFFEE: {
                        if (player->getCoffees() == 2) {
                            player->addScore(5000, Options::settings.hi_score_table.back().score);
                            const auto X = item->getPosX() + ((item->getWidth() - game_text_textures_.at(2)->getWidth()) / 2);
                            createItemText(X, game_text_textures_.at(2));
                        } else {
                            player->giveExtraHit();
                            const auto X = item->getPosX() + ((item->getWidth() - game_text_textures_.at(4)->getWidth()) / 2);
                            createItemText(X, game_text_textures_.at(4));
                        }
                        playSound("voice_coffee.wav");
                        break;
                    }
                    case ItemType::COFFEE_MACHINE: {
                        player->setPowerUp();
                        coffee_machine_enabled_ = false;
                        const auto X = item->getPosX() + ((item->getWidth() - game_text_textures_.at(3)->getWidth()) / 2);
                        createItemText(X, game_text_textures_.at(3));
                        playSound("voice_power_up.wav");
                        break;
                    }
                    default:
                        break;
                }

                updateHiScore();
                item->disable();
            }
        }
    }
}

// Maneja la colisión entre bala y Tabe
auto Game::checkBulletTabeCollision(const std::shared_ptr<Bullet>& bullet) -> bool {
    if (!tabe_->isEnabled()) {
        return false;
    }

    if (!checkCollision(bullet->getCollider(), tabe_->getCollider())) {
        return false;
    }

    tabe_->setState(Tabe::State::HIT);
    bullet->disable();

    handleTabeHitEffects();
    return true;
}

// Maneja los efectos de golpear al Tabe
void Game::handleTabeHitEffects() {
    auto pos = tabe_->getCollider();

    if (tabe_->tryToGetBonus()) {
        createItem(ItemType::DEBIAN, pos.x, pos.y);
        playSound("debian_drop.wav");
    } else {
        if (rand() % 3 == 0) {
            createItem(ItemType::COFFEE, pos.x, pos.y);
        }
        playSound("tabe_hit.wav");
    }
}

// Maneja la colisión entre bala y globos
auto Game::checkBulletBalloonCollision(const std::shared_ptr<Bullet>& bullet) -> bool {
    return std::ranges::any_of(balloon_manager_->getBalloons(), [this, &bullet](auto& balloon) -> auto {
        if (!balloon->isEnabled() || balloon->isInvulnerable()) {
            return false;
        }

        if (!checkCollision(balloon->getCollider(), bullet->getCollider())) {
            return false;
        }

        processBalloonHit(bullet, balloon);
        return true;
    });
}

// Procesa el impacto en un globo
void Game::processBalloonHit(const std::shared_ptr<Bullet>& bullet, const std::shared_ptr<Balloon>& balloon) {
    auto player = getPlayer(static_cast<Player::Id>(bullet->getOwner()));

    handleItemDrop(balloon, player);
    handleBalloonDestruction(balloon, player);

    bullet->disable();
}

// Maneja la caída de items cuando se destruye un globo
void Game::handleItemDrop(const std::shared_ptr<Balloon>& balloon, const std::shared_ptr<Player>& player) {
    const auto DROPPED_ITEM = dropItem();
    if (DROPPED_ITEM == ItemType::NONE || demo_.recording) {
        return;
    }

    if (DROPPED_ITEM != ItemType::COFFEE_MACHINE) {
        createItem(DROPPED_ITEM, balloon->getPosX(), balloon->getPosY());
        playSound("item_drop.wav");
    } else {
        createItem(DROPPED_ITEM, player->getPosX(), param.game.game_area.rect.y - Item::COFFEE_MACHINE_HEIGHT);
        coffee_machine_enabled_ = true;
    }
}

// Maneja la destrucción del globo y puntuación
void Game::handleBalloonDestruction(const std::shared_ptr<Balloon>& balloon, const std::shared_ptr<Player>& player) {
    if (player->isPlaying()) {
        auto const SCORE = balloon_manager_->popBalloon(balloon) * player->getScoreMultiplier() * difficulty_score_multiplier_;
        player->addScore(SCORE, Options::settings.hi_score_table.back().score);
        player->incScoreMultiplier();
    }
    setMenace();
    updateHiScore();
}

// Actualiza los items
void Game::updateItems(float delta_time) {
    for (auto& item : items_) {
        if (item->isEnabled()) {
            item->update(delta_time);
            if (item->isOnFloor()) {
                playSound("title.wav");
                screen_->shake(1, 0.033F, 0.067F);  // desp=1, delay=0.033s, length=0.067s
            }
        }
    }
}

// Pinta los items activos
void Game::renderItems() {
    for (auto& item : items_) {
        item->render();
    }
}

// Devuelve un item al azar y luego segun sus probabilidades
auto Game::dropItem() -> ItemType {
    const auto LUCKY_NUMBER = rand() % 100;
    const auto ITEM = rand() % 6;

    switch (ITEM) {
        case 0:
            if (LUCKY_NUMBER < helper_.item_disk_odds) {
                return ItemType::DISK;
            }
            break;
        case 1:
            if (LUCKY_NUMBER < helper_.item_gavina_odds) {
                return ItemType::GAVINA;
            }
            break;
        case 2:
            if (LUCKY_NUMBER < helper_.item_pacmar_odds) {
                return ItemType::PACMAR;
            }
            break;
        case 3:
            if (LUCKY_NUMBER < helper_.item_clock_odds) {
                return ItemType::CLOCK;
            }
            break;
        case 4:
            if (LUCKY_NUMBER < helper_.item_coffee_odds) {
                helper_.item_coffee_odds = ITEM_COFFEE_ODDS;
                return ItemType::COFFEE;
            } else {
                if (helper_.need_coffee) {
                    helper_.item_coffee_odds++;
                }
            }
            break;
        case 5:
            if (LUCKY_NUMBER < helper_.item_coffee_machine_odds) {
                helper_.item_coffee_machine_odds = ITEM_COFFEE_MACHINE_ODDS;
                if (!coffee_machine_enabled_ && helper_.need_coffee_machine) {
                    return ItemType::COFFEE_MACHINE;
                }
            } else {
                if (helper_.need_coffee_machine) {
                    helper_.item_coffee_machine_odds++;
                }
            }
            break;
        default:
            break;
    }

    return ItemType::NONE;
}

// Crea un objeto item
void Game::createItem(ItemType type, float x, float y) {
    items_.emplace_back(std::make_unique<Item>(type, x, y, param.game.play_area.rect, item_textures_[static_cast<int>(type) - 1], item_animations_[static_cast<int>(type) - 1]));
}

// Vacia el vector de items
void Game::freeItems() {
    std::erase_if(items_, [&](const auto& item) -> auto {
        if (!item->isEnabled()) {
            // Comprobamos si hay que realizar una acción extra
            if (item->getType() == ItemType::COFFEE_MACHINE) {
                coffee_machine_enabled_ = false;
            }
            // Devolvemos 'true' para indicar que este elemento debe ser borrado.
            return true;
        }
        // Devolvemos 'false' para conservarlo.
        return false;
    });
}

// Crea un objeto PathSprite
void Game::createItemText(int x, const std::shared_ptr<Texture>& texture) {
    path_sprites_.emplace_back(std::make_unique<PathSprite>(texture));

    const auto W = texture->getWidth();
    const auto H = texture->getHeight();

    const int Y0 = param.game.play_area.rect.h - H;
    const int Y1 = 160 - (H / 2);
    const int Y2 = -H;

    // Ajusta para que no se dibuje fuera de pantalla
    x = std::clamp(x, 2, static_cast<int>(param.game.play_area.rect.w) - W - 2);

    // Inicializa
    path_sprites_.back()->setWidth(W);
    path_sprites_.back()->setHeight(H);
    path_sprites_.back()->setSpriteClip({.x = 0, .y = 0, .w = static_cast<float>(W), .h = static_cast<float>(H)});
    path_sprites_.back()->addPath(Y0, Y1, PathType::VERTICAL, x, 1.667F, easeOutQuint, 0);  // 100 frames → 1.667s
    path_sprites_.back()->addPath(Y1, Y2, PathType::VERTICAL, x, 1.333F, easeInQuint, 0);   // 80 frames → 1.333s
    path_sprites_.back()->enable();
}

// Crea un objeto PathSprite
void Game::createMessage(const std::vector<Path>& paths, const std::shared_ptr<Texture>& texture) {
    path_sprites_.emplace_back(std::make_unique<PathSprite>(texture));

    // Inicializa
    for (const auto& path : paths) {
        path_sprites_.back()->addPath(path, true);
    }
    path_sprites_.back()->enable();
}

// Vacia la lista de smartsprites
void Game::freeSmartSprites() {
    std::erase_if(smart_sprites_, [](const auto& sprite) -> auto {
        return sprite->hasFinished();
    });
}

// Vacia la lista de pathsprites
void Game::freePathSprites() {
    std::erase_if(path_sprites_, [](const auto& sprite) -> auto {
        return sprite->hasFinished();
    });
}

// Crea un SpriteSmart para arrojar el item café al recibir un impacto
void Game::throwCoffee(int x, int y) {
    smart_sprites_.emplace_back(std::make_unique<SmartSprite>(item_textures_[4]));

    smart_sprites_.back()->setPosX(x - 8);
    smart_sprites_.back()->setPosY(y - 8);
    smart_sprites_.back()->setWidth(Item::WIDTH);
    smart_sprites_.back()->setHeight(Item::HEIGHT);
    smart_sprites_.back()->setVelX((-1.0F + ((rand() % 5) * 0.5F)) * 60.0F);  // Convertir a pixels/segundo
    smart_sprites_.back()->setVelY(-4.0F * 60.0F);                            // Convertir a pixels/segundo
    smart_sprites_.back()->setAccelX(0.0F);
    smart_sprites_.back()->setAccelY(0.2F * 60.0F * 60.0F);  // Convertir a pixels/segundo² (0.2 × 60²)
    smart_sprites_.back()->setDestX(x + (smart_sprites_.back()->getVelX() * 50));
    smart_sprites_.back()->setDestY(param.game.height + 1);
    smart_sprites_.back()->setEnabled(true);
    smart_sprites_.back()->setFinishedDelay(0.0F);
    smart_sprites_.back()->setSpriteClip(0, Item::HEIGHT, Item::WIDTH, Item::HEIGHT);
    smart_sprites_.back()->setRotatingCenter({.x = Item::WIDTH / 2, .y = Item::HEIGHT / 2});
    smart_sprites_.back()->setRotate(true);
    smart_sprites_.back()->setRotateAmount(90.0);
}

// Actualiza los SmartSprites
void Game::updateSmartSprites(float delta_time) {
    for (auto& sprite : smart_sprites_) {
        sprite->update(delta_time);
    }
}

// Pinta los SmartSprites activos
void Game::renderSmartSprites() {
    for (auto& sprite : smart_sprites_) {
        sprite->render();
    }
}

// Actualiza los PathSprites
void Game::updatePathSprites(float delta_time) {
    for (auto& sprite : path_sprites_) {
        sprite->update(delta_time);
    }
}

// Pinta los PathSprites activos
void Game::renderPathSprites() {
    for (auto& sprite : path_sprites_) {
        sprite->render();
    }
}

// Acciones a realizar cuando el jugador colisiona con un globo
void Game::handlePlayerCollision(std::shared_ptr<Player>& player, std::shared_ptr<Balloon>& balloon) {
    if (!player->isPlaying() || player->isInvulnerable()) {
        return;  // Si no está jugando o tiene inmunidad, no hace nada
    }

#ifdef _DEBUG
    if (Director::debug_config.invincibility) {
        return;
    }
#endif

    // Si tiene cafes
    if (player->hasExtraHit()) {
        // Lo pierde
        player->removeExtraHit();
        throwCoffee(player->getPosX() + (Player::WIDTH / 2), player->getPosY() + (Player::HEIGHT / 2));
        playSound("coffee_out.wav");
        screen_->shake();
    } else {
        // Si no tiene cafes, muere
        playSound("player_collision.wav");
        screen_->shake();
        playSound("voice_no.wav");
        player->setPlayingState(Player::State::ROLLING);
        sendPlayerToBack(players_, player, players_draw_list_);
        if (allPlayersAreNotPlaying()) {
            stage_manager_->disablePowerCollection();
        }
    }
}

// Actualiza el estado del tiempo detenido
void Game::updateTimeStopped(float delta_time) {
    static constexpr float WARNING_THRESHOLD_S = 2.0F;
    static constexpr float CLOCK_SOUND_INTERVAL_S = 0.5F;
    static constexpr float COLOR_FLASH_INTERVAL_S = 0.25F;

    if (time_stopped_timer_ > 0) {
        time_stopped_timer_ -= delta_time;

        // Fase de advertencia (últimos 2 segundos)
        if (time_stopped_timer_ <= WARNING_THRESHOLD_S) {
            // CLAC al entrar en fase de advertencia
            if (!time_stopped_flags_.warning_phase_started) {
                playSound("clock.wav");
                time_stopped_flags_.warning_phase_started = true;
                time_stopped_sound_timer_ = 0.0F;  // Reset para empezar el ciclo rápido
            }

            time_stopped_sound_timer_ += delta_time;

            if (time_stopped_sound_timer_ >= CLOCK_SOUND_INTERVAL_S) {
                balloon_manager_->normalColorsToAllBalloons();
                playSound("clock.wav");
                time_stopped_sound_timer_ = 0.0F;
                time_stopped_flags_.color_flash_sound_played = false;  // Reset flag para el próximo intervalo
            } else if (time_stopped_sound_timer_ >= COLOR_FLASH_INTERVAL_S && !time_stopped_flags_.color_flash_sound_played) {
                balloon_manager_->reverseColorsToAllBalloons();
                playSound("clock.wav");
                time_stopped_flags_.color_flash_sound_played = true;  // Evita que suene múltiples veces
            }

        } else {
            // Fase normal - solo sonido ocasional
            time_stopped_sound_timer_ += delta_time;
            if (time_stopped_sound_timer_ >= CLOCK_SOUND_INTERVAL_S) {
                playSound("clock.wav");
                time_stopped_sound_timer_ = 0.0F;
            }
        }

        // Si el timer llega a 0 o menos, desactivar
        if (time_stopped_timer_ <= 0) {
            playSound("clock.wav");  // CLAC final
            disableTimeStopItem();
        }
    }
}

// Actualiza toda la lógica del juego
void Game::update(float delta_time) {
    screen_->update(delta_time);  // Actualiza el objeto screen
    Audio::update();              // Actualiza el objeto audio

    updateDemo(delta_time);
#ifdef RECORDING
    updateRecording(deltaTime);
#endif
    updateGameStates(delta_time);
    fillCanvas();
}

// Dibuja el juego
void Game::render() {
    screen_->start();  // Prepara para empezar a dibujar en la textura de juego

    SDL_RenderTexture(renderer_, canvas_, nullptr, &param.game.play_area.rect);  // Copia la textura con la zona de juego a la pantalla
    scoreboard_->render();                                                       // Dibuja el marcador
    fade_in_->render();                                                          // Dibuja el fade de entrada
    fade_out_->render();                                                         // Dibuja el fade de salida

    // SDL_SetRenderDrawColor(renderer_, 255, 0, 0, 255);
    // SDL_RenderLine(renderer_, param.game.play_area.rect.x, param.game.play_area.center_y, param.game.play_area.rect.w, param.game.play_area.center_y);

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

// Actualiza los estados del juego
void Game::updateGameStates(float delta_time) {
    if (!pause_manager_->isPaused()) {
        switch (state_) {
            case State::FADE_IN:
                updateGameStateFadeIn(delta_time);
                break;
            case State::ENTERING_PLAYER:
                updateGameStateEnteringPlayer(delta_time);
                break;
            case State::SHOWING_GET_READY_MESSAGE:
                updateGameStateShowingGetReadyMessage(delta_time);
                break;
            case State::PLAYING:
                updateGameStatePlaying(delta_time);
                break;
            case State::COMPLETED:
                updateGameStateCompleted(delta_time);
                break;
            case State::GAME_OVER:
                updateGameStateGameOver(delta_time);
                break;
            default:
                break;
        }
    }
}

// Actualiza el fondo
void Game::updateBackground(float delta_time) {
    background_->update(delta_time);
}

// Dibuja los elementos de la zona de juego en su textura
void Game::fillCanvas() {
    // Dibuja el contenido de la zona de juego en su textura
    auto* temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, canvas_);

    // Dibuja los objetos
    background_->render();

    renderSmartSprites();  // El cafe que sale cuando te golpean
    renderItems();
    balloon_manager_->render();
    tabe_->render();
    bullet_manager_->render();
    renderPlayers();

    renderPathSprites();

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

// Habilita el efecto del item de detener el tiempo
void Game::enableTimeStopItem() {
    balloon_manager_->stopAllBalloons();
    balloon_manager_->reverseColorsToAllBalloons();
    time_stopped_timer_ = TIME_STOPPED_DURATION_S;
    time_stopped_sound_timer_ = 0.0F;
    time_stopped_flags_.reset();  // Resetea flags al activar el item
}

// Deshabilita el efecto del item de detener el tiempo
void Game::disableTimeStopItem() {
    time_stopped_timer_ = 0.0F;
    time_stopped_sound_timer_ = 0.0F;
    balloon_manager_->startAllBalloons();
    balloon_manager_->normalColorsToAllBalloons();
}

// Calcula el deltatime en segundos
auto Game::calculateDeltaTime() -> float {
    const Uint64 CURRENT_TIME = SDL_GetTicks();
    const auto DELTA_TIME_MS = static_cast<float>(CURRENT_TIME - last_time_);
    last_time_ = CURRENT_TIME;
    return DELTA_TIME_MS / 1000.0F;  // Convertir de milisegundos a segundos
}

// Avanza un frame del juego (llamado desde Director::iterate)
void Game::iterate() {
    const float DELTA_TIME = calculateDeltaTime();
    checkInput();
    update(DELTA_TIME);
    render();
}

// Procesa un evento del juego (llamado desde Director::handleEvent)
void Game::handleEvent(const SDL_Event& event) {
    switch (event.type) {
        case SDL_EVENT_WINDOW_FOCUS_LOST:
            pause_manager_->setFocusLossPause(!demo_.enabled);
            break;
        case SDL_EVENT_WINDOW_FOCUS_GAINED:
            pause_manager_->setFocusLossPause(false);
            break;
        default:
            break;
    }

#ifdef _DEBUG
    handleDebugEvents(event);
#endif
}

// Bucle para el juego (fallback legacy)
void Game::run() {
    last_time_ = SDL_GetTicks();

    while (Section::name == Section::Name::GAME) {
        const float DELTA_TIME = calculateDeltaTime();
        handleEvents();
        checkInput();
        update(DELTA_TIME);
        render();
    }
}

// Inicializa las variables que contienen puntos de ruta para mover objetos
void Game::initPaths() {
    // --- Duración estándar para 80 frames ---
    // (Basado en tu ejemplo: 80 frames → 1.333s)
    const float DURATION_80F_S = 1.333F;

    // Recorrido para el texto de "Get Ready!" (0,1)
    {
        const auto& texture = Resource::get()->getTexture("game_text_get_ready");
        const auto W = texture->getWidth();
        const int X0 = -W;
        const int X1 = param.game.play_area.center_x - (W / 2);
        const int X2 = param.game.play_area.rect.w;
        // Y_base es la LÍNEA CENTRAL. addPath(true) restará H/2
        const int Y_BASE = param.game.play_area.center_y;
        paths_.emplace_back(X0, X1, PathType::HORIZONTAL, Y_BASE, DURATION_80F_S, 0.5F, easeOutQuint);
        paths_.emplace_back(X1, X2, PathType::HORIZONTAL, Y_BASE, DURATION_80F_S, 0.0F, easeInQuint);
    }

    // Recorrido para el texto de "Last Stage!" o de "X stages left" (2,3)
    {
        const auto& texture = Resource::get()->getTexture("game_text_last_stage");
        const auto H = texture->getHeight();
        const int Y0 = param.game.play_area.rect.h - H;
        const int Y1 = param.game.play_area.center_y - (H / 2);
        const int Y2 = -H;
        // X_base es la LÍNEA CENTRAL. addPath(true) restará W/2
        const int X_BASE = param.game.play_area.center_x;
        paths_.emplace_back(Y0, Y1, PathType::VERTICAL, X_BASE, DURATION_80F_S, 0.5F, easeOutQuint);
        paths_.emplace_back(Y1, Y2, PathType::VERTICAL, X_BASE, DURATION_80F_S, 0.0F, easeInQuint);
    }

    // Recorrido para el texto de "Congratulations!!" (4,5)
    {
        const auto& texture = Resource::get()->getTexture("game_text_congratulations");
        const auto W = texture->getWidth();
        const auto H = texture->getHeight();
        const int X0 = -W;
        const int X1 = param.game.play_area.center_x - (W / 2);
        const int X2 = param.game.play_area.rect.w;
        // Y_base es la LÍNEA CENTRAL. addPath(true) restará H/2
        const int Y_BASE = param.game.play_area.center_y - (H / 2);
        paths_.emplace_back(X0, X1, PathType::HORIZONTAL, Y_BASE, DURATION_80F_S, 7.0F, easeOutQuint);
        paths_.emplace_back(X1, X2, PathType::HORIZONTAL, Y_BASE, DURATION_80F_S, 0.0F, easeInQuint);
    }

    // Recorrido para el texto de "1.000.000 points!" (6,7)
    {
        const auto& texture = Resource::get()->getTexture("game_text_1000000_points");
        const auto W = texture->getWidth();
        const auto H = texture->getHeight();
        const int X0 = param.game.play_area.rect.w;
        const int X1 = param.game.play_area.center_x - (W / 2);
        const int X2 = -W;
        // Y_base es la LÍNEA CENTRAL (desplazada PREV_H hacia abajo). addPath(true) restará H/2
        const int Y_BASE = param.game.play_area.center_y + (H / 2);
        paths_.emplace_back(X0, X1, PathType::HORIZONTAL, Y_BASE, DURATION_80F_S, 7.0F, easeOutQuint);
        paths_.emplace_back(X1, X2, PathType::HORIZONTAL, Y_BASE, DURATION_80F_S, 0.0F, easeInQuint);
    }

    // Recorrido para el texto de "New Record!" (8,9)
    {
        const auto& texture = Resource::get()->getTexture("game_text_new_record");
        const auto W = texture->getWidth();
        const auto H = texture->getHeight();
        const int X0 = -W;
        const int X1 = param.game.play_area.center_x - (W / 2);  // Destino (no-fijo), está bien
        const int X2 = param.game.play_area.rect.w;
        // Y_base es la LÍNEA CENTRAL (desplazada 2*H hacia arriba). addPath(true) restará H/2
        const int Y_BASE = param.game.play_area.center_y - (H * 2);
        paths_.emplace_back(X0, X1, PathType::HORIZONTAL, Y_BASE, DURATION_80F_S, 1.0F, easeOutQuint);
        paths_.emplace_back(X1, X2, PathType::HORIZONTAL, Y_BASE, DURATION_80F_S, 0.0F, easeInQuint);
    }

    // Recorrido para el texto de "Game Over" (10,11)
    {
        const auto& texture = Resource::get()->getTexture("game_text_game_over");
        const auto H = texture->getHeight();
        const int Y0 = param.game.play_area.rect.h - H;
        const int Y1 = param.game.play_area.center_y - (H / 2);  // Destino (no-fijo), está bien
        const int Y2 = -H;
        // X_base es la LÍNEA CENTRAL. addPath(true) restará W/2
        const int X_BASE = param.game.play_area.center_x;
        paths_.emplace_back(Y0, Y1, PathType::VERTICAL, X_BASE, DURATION_80F_S, 2.0F, easeOutQuint);
        paths_.emplace_back(Y1, Y2, PathType::VERTICAL, X_BASE, DURATION_80F_S, 0.0F, easeInQuint);
    }
}

// Actualiza las variables de ayuda
void Game::updateHelper() {
    // Solo ofrece ayuda cuando la amenaza es elevada
    if (menace_ > 15) {
        helper_.need_coffee = true;
        helper_.need_coffee_machine = true;
        for (const auto& player : players_) {
            if (player->isPlaying()) {
                helper_.need_coffee &= (player->getCoffees() == 0);
                helper_.need_coffee_machine &= (!player->isPowerUp());
            }
        }
    } else {
        helper_.need_coffee = helper_.need_coffee_machine = false;
    }
}

// Comprueba si todos los jugadores han terminado de jugar
auto Game::allPlayersAreWaitingOrGameOver() -> bool {
    auto success = true;
    for (const auto& player : players_) {
        success &= player->isWaiting() || player->isGameOver();
    }

    return success;
}

// Comprueba si todos los jugadores han terminado de jugar
auto Game::allPlayersAreGameOver() -> bool {
    auto success = true;
    for (const auto& player : players_) {
        success &= player->isGameOver();
    }

    return success;
}

// Comprueba si todos los jugadores han terminado de jugar
auto Game::allPlayersAreNotPlaying() -> bool {
    auto success = true;
    for (const auto& player : players_) {
        success &= !player->isPlaying();
    }

    return success;
}

// Comprueba los eventos que hay en cola
void Game::handleEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        switch (event.type) {
            case SDL_EVENT_WINDOW_FOCUS_LOST: {
                pause_manager_->setFocusLossPause(!demo_.enabled);
                break;
            }
            case SDL_EVENT_WINDOW_FOCUS_GAINED: {
                pause_manager_->setFocusLossPause(false);
                break;
            }
            default:
                break;
        }

#ifdef _DEBUG
        handleDebugEvents(event);
#endif
        GlobalEvents::handle(event);
    }
}

// Actualiza el marcador
void Game::updateScoreboard(float delta_time) {
    for (const auto& player : players_) {
        scoreboard_->setScore(player->getScoreBoardPanel(), player->getScore());
        scoreboard_->setMult(player->getScoreBoardPanel(), player->getScoreMultiplier());
    }

    // Resto de marcador
    scoreboard_->setStage(stage_manager_->getCurrentStageIndex() + 1);
    scoreboard_->setPower(stage_manager_->getCurrentStageProgressFraction());
    scoreboard_->setHiScore(hi_score_.score);
    scoreboard_->setHiScoreName(hi_score_.name);

    scoreboard_->update(delta_time);
}

// Pone en el marcador el nombre del primer jugador de la tabla
void Game::updateHiScoreName() {
    hi_score_.name = Options::settings.hi_score_table.front().name;
}

// Saca del estado de GAME OVER al jugador si el otro está activo
void Game::checkAndUpdatePlayerStatus(int active_player_index, int inactive_player_index) {
    if (players_[active_player_index]->isGameOver() && !players_[inactive_player_index]->isGameOver() && !players_[inactive_player_index]->isWaiting()) {
        players_[active_player_index]->setPlayingState(Player::State::WAITING);
    }
}

// Comprueba el estado de los jugadores
void Game::checkPlayersStatusPlaying() {
    if (demo_.enabled) {
        return;
    }

    // Comprueba si todos los jugadores estan esperando
    if (allPlayersAreWaitingOrGameOver()) {
        // Entonces los pone en estado de Game Over
        for (auto& player : players_) {
            player->setPlayingState(Player::State::GAME_OVER);
        }
    }

    // Comprobar estado de ambos jugadores
    checkAndUpdatePlayerStatus(0, 1);
    checkAndUpdatePlayerStatus(1, 0);
}

// Obtiene un jugador a partir de su "id"
auto Game::getPlayer(Player::Id id) -> std::shared_ptr<Player> {
    auto it = std::ranges::find_if(players_, [id](const auto& player) -> auto { return player->getId() == id; });

    if (it != players_.end()) {
        return *it;
    }
    return nullptr;
}

// Obtiene un controlador a partir del "id" del jugador
auto Game::getController(Player::Id player_id) -> int {
    switch (player_id) {
        case Player::Id::PLAYER1:
            return 0;
        case Player::Id::PLAYER2:
            return 1;
        default:
            return -1;
    }
}

// Gestiona la entrada durante el juego
void Game::checkInput() {
    Input::get()->update();

    // Comprueba las entradas si no está el menú de servicio activo
    if (!ServiceMenu::get()->isEnabled()) {
        checkPauseInput();
        if (demo_.enabled) {
            demoHandlePassInput();
        } else {
#ifdef _DEBUG
            if (!autoplay_) {
                handlePlayersInput();
            }
#else
            handlePlayersInput();
#endif
        }
    }

    // Mueve los jugadores en el modo demo
    if (demo_.enabled) {
        demoHandleInput();
    }

#ifdef _DEBUG
    if (autoplay_) {
        autoplayHandleInput();
    }
#endif

    // Verifica los inputs globales.
    GlobalInputs::check();
}

// Verifica si alguno de los controladores ha solicitado una pausa y actualiza el estado de pausa del juego.
void Game::checkPauseInput() {
    // Comprueba los mandos
    for (const auto& gamepad : input_->getGamepads()) {
        if (input_->checkAction(Input::Action::PAUSE, Input::DO_NOT_ALLOW_REPEAT, Input::DO_NOT_CHECK_KEYBOARD, gamepad)) {
            pause_manager_->togglePlayerPause();
            return;
        }
    }

    // Comprueba el teclado
    if (input_->checkAction(Input::Action::PAUSE, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
        pause_manager_->togglePlayerPause();
        return;
    }
}

// Gestiona las entradas de los jugadores en el modo demo para saltarse la demo.
void Game::demoHandlePassInput() {
    if (input_->checkAnyButton()) {
        Section::name = Section::Name::TITLE;                         // Salir del modo demo y regresar al menú principal.
        Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;  // El juego volverá a mostrar la demo
        return;
    }
}

// Gestiona las entradas de los jugadores en el modo demo, incluyendo movimientos y disparos automáticos.
void Game::demoHandleInput() {
    for (const auto& player : players_) {
        if (player->isPlaying()) {
            demoHandlePlayerInput(player, player->getDemoFile());  // Maneja el input específico del jugador en modo demo.
        }
    }
}

// Procesa las entradas para un jugador específico durante el modo demo.
void Game::demoHandlePlayerInput(const std::shared_ptr<Player>& player, int index) {
    const auto& demo_data = demo_.data.at(index).at(demo_.index);

    if (demo_data.left == 1) {
        player->setInput(Input::Action::LEFT);
    } else if (demo_data.right == 1) {
        player->setInput(Input::Action::RIGHT);
    } else if (demo_data.no_input == 1) {
        player->setInput(Input::Action::NONE);
    }

    if (demo_data.fire == 1) {
        handleFireInput(player, Bullet::Type::UP);
    } else if (demo_data.fire_left == 1) {
        handleFireInput(player, Bullet::Type::LEFT);
    } else if (demo_data.fire_right == 1) {
        handleFireInput(player, Bullet::Type::RIGHT);
    }
}

// Maneja el disparo de un jugador, incluyendo la creación de balas y la gestión del tiempo de espera entre disparos.
void Game::handleFireInput(const std::shared_ptr<Player>& player, Bullet::Type type) {
    if (player->canFire()) {
        SDL_Point bullet = {.x = 0, .y = 0};
        switch (type) {
            case Bullet::Type::UP:
                player->setInput(Input::Action::FIRE_CENTER);
                bullet.x = 2 + player->getPosX() + ((Player::WIDTH - Bullet::WIDTH) / 2);
                bullet.y = player->getPosY() - (Bullet::HEIGHT / 2);
                break;
            case Bullet::Type::LEFT:
                player->setInput(Input::Action::FIRE_LEFT);
                bullet.x = player->getPosX() - (Bullet::WIDTH / 2);
                bullet.y = player->getPosY();
                break;
            case Bullet::Type::RIGHT:
                player->setInput(Input::Action::FIRE_RIGHT);
                bullet.x = player->getPosX() + Player::WIDTH - (Bullet::WIDTH / 2);
                bullet.y = player->getPosY();
                break;
            default:
                break;
        }
        bullet_manager_->createBullet(bullet.x, bullet.y, type, player->getNextBulletColor(), static_cast<int>(player->getId()));
        playSound(player->getBulletSoundFile());

        // Establece un tiempo de espera para el próximo disparo.
        constexpr int POWERUP_COOLDOWN = 5;
        constexpr int AUTOFIRE_COOLDOWN = 10;
        constexpr int NORMAL_COOLDOWN = 7;

        int cant_fire_counter;
        if (player->isPowerUp()) {
            cant_fire_counter = POWERUP_COOLDOWN;
        } else if (Options::settings.autofire) {
            cant_fire_counter = AUTOFIRE_COOLDOWN;
        } else {
            cant_fire_counter = NORMAL_COOLDOWN;
        }

        player->startFiringSystem(cant_fire_counter);  // Sistema de disparo de dos líneas
    }
}

// Gestiona las entradas de todos los jugadores en el modo normal (fuera del modo demo)
void Game::handlePlayersInput() {
    for (const auto& player : players_) {
        if (player->isPlaying()) {
            handleNormalPlayerInput(player);  // Maneja el input de los jugadores en modo normal
        } else if (player->isContinue()) {
            handlePlayerContinueInput(player);  // Gestiona la continuación del jugador
        } else if (player->isWaiting()) {
            handlePlayerWaitingInput(player);  // Gestiona la (re)entrada del jugador
        } else if (player->isEnteringName() || player->isEnteringNameGameCompleted() || player->isShowingName()) {
            handleNameInput(player);  // Gestiona la introducción del nombre del jugador
        }
    }
}

// Maneja las entradas de movimiento y disparo para un jugador en modo normal.
void Game::handleNormalPlayerInput(const std::shared_ptr<Player>& player) {
    if (input_->checkAction(Input::Action::LEFT, Input::ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        player->setInput(Input::Action::LEFT);
#ifdef RECORDING
        demo_.keys.left = 1;
#endif
    } else if (input_->checkAction(Input::Action::RIGHT, Input::ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        player->setInput(Input::Action::RIGHT);
#ifdef RECORDING
        demo_.keys.right = 1;
#endif
    } else {
        player->setInput(Input::Action::NONE);
#ifdef RECORDING
        demo_.keys.no_input = 1;
#endif
    }

    const bool AUTOFIRE = player->isPowerUp() || Options::settings.autofire;
    handleFireInputs(player, AUTOFIRE);  // Verifica y maneja todas las posibles entradas de disparo.
}

// Procesa las entradas de disparo del jugador, permitiendo disparos automáticos si está habilitado.
void Game::handleFireInputs(const std::shared_ptr<Player>& player, bool autofire) {
    if (!player) {
        return;
    }

    if (input_->checkAction(Input::Action::FIRE_CENTER, autofire, player->getUsesKeyboard(), player->getGamepad())) {
        handleFireInput(player, Bullet::Type::UP);
#ifdef RECORDING
        demo_.keys.fire = 1;
#endif
        return;
    }

    if (input_->checkAction(Input::Action::FIRE_LEFT, autofire, player->getUsesKeyboard(), player->getGamepad())) {
        handleFireInput(player, Bullet::Type::LEFT);
#ifdef RECORDING
        demo_.keys.fire_left = 1;
#endif
        return;
    }

    if (input_->checkAction(Input::Action::FIRE_RIGHT, autofire, player->getUsesKeyboard(), player->getGamepad())) {
        handleFireInput(player, Bullet::Type::RIGHT);
#ifdef RECORDING
        demo_.keys.fire_right = 1;
#endif
    }
}

// Maneja la continuación del jugador cuando no está jugando, permitiendo que continúe si se pulsa el botón de inicio.
void Game::handlePlayerContinueInput(const std::shared_ptr<Player>& player) {
    if (input_->checkAction(Input::Action::START, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        player->setPlayingState(Player::State::RECOVER);
        player->addCredit();
        bringPlayerToFront(players_, player, players_draw_list_);
        return;
    }

    // Disminuye el contador de continuación si se presiona cualquier botón de disparo.
    if (input_->checkAction(Input::Action::FIRE_LEFT, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad()) ||
        input_->checkAction(Input::Action::FIRE_CENTER, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad()) ||
        input_->checkAction(Input::Action::FIRE_RIGHT, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        if (player->getContinueCounter() < param.scoreboard.skip_countdown_value) {
            player->decContinueCounter();
        }
    }
}

// Maneja la continuación del jugador cuando no está jugando, permitiendo que continúe si se pulsa el botón de inicio.
void Game::handlePlayerWaitingInput(const std::shared_ptr<Player>& player) {
    if (input_->checkAction(Input::Action::START, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        player->setPlayingState(Player::State::ENTERING_SCREEN);
        player->addCredit();
        bringPlayerToFront(players_, player, players_draw_list_);
    }
}

// Procesa las entradas para la introducción del nombre del jugador.
void Game::handleNameInput(const std::shared_ptr<Player>& player) {
    if (input_->checkAction(Input::Action::FIRE_LEFT, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        player->setInput(Input::Action::FIRE_LEFT);
        return;
    }

    if (input_->checkAction(Input::Action::FIRE_CENTER, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad()) ||
        input_->checkAction(Input::Action::FIRE_RIGHT, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        player->setInput(Input::Action::FIRE_CENTER);
        return;
    }

    if (input_->checkAction(Input::Action::LEFT, Input::ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        player->setInput(Input::Action::LEFT);
        return;
    }

    if (input_->checkAction(Input::Action::RIGHT, Input::ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        player->setInput(Input::Action::RIGHT);
        return;
    }

    if (input_->checkAction(Input::Action::START, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
        player->setInput(Input::Action::START);
        updateHiScoreName();
        return;
    }

    player->setInput(Input::Action::NONE);
}

// Inicializa las variables para el modo DEMO
void Game::initDemo(Player::Id player_id) {
#ifdef RECORDING
    // En modo grabación, inicializar vector vacío para almacenar teclas
    demo_.data.emplace_back();                  // Vector vacío para grabación
    demo_.data.at(0).reserve(TOTAL_DEMO_DATA);  // Reservar espacio para 2000 elementos
#endif

    if (demo_.enabled) {
        // Cambia el estado del juego
        setState(State::PLAYING);

#ifndef RECORDING
        // En modo juego: cargar todas las demos y asignar una diferente a cada jugador
        auto const NUM_DEMOS = Asset::get()->getListByType(Asset::Type::DEMODATA).size();
        for (size_t num_demo = 0; num_demo < NUM_DEMOS; ++num_demo) {
            demo_.data.emplace_back(Resource::get()->getDemoData(num_demo));
        }

        // Crear índices mezclados para asignación aleatoria
        std::vector<size_t> demo_indices(NUM_DEMOS);
        std::iota(demo_indices.begin(), demo_indices.end(), 0);
        std::random_device rd;
        std::default_random_engine rng(rd());
        std::shuffle(demo_indices.begin(), demo_indices.end(), rng);

        // Asignar demos a jugadores (round-robin si hay más jugadores que demos)
        for (size_t i = 0; i < players_.size(); ++i) {
            size_t demo_index = demo_indices[i % NUM_DEMOS];
            players_.at(i)->setDemoFile(demo_index);
        }
#endif

        // Selecciona una pantalla al azar
        constexpr auto NUM_STAGES = 3;
        const auto STAGE = rand() % NUM_STAGES;
        constexpr std::array<float, NUM_STAGES> STAGES = {0.005F, 0.32F, 0.53F};
        stage_manager_->setTotalPower(stage_manager_->getTotalPowerNeededToCompleteGame() * STAGES.at(STAGE));
        background_->setProgress(stage_manager_->getTotalPower());

        // Activa o no al otro jugador
        if (rand() % 3 != 0) {
            const auto OTHER_PLAYER_ID = player_id == Player::Id::PLAYER1 ? Player::Id::PLAYER2 : Player::Id::PLAYER1;
            auto other_player = getPlayer(OTHER_PLAYER_ID);
            other_player->setPlayingState(Player::State::PLAYING);
        }

        // Asigna cafes a los jugadores
        for (auto& player : players_) {
            if (player->isPlaying()) {
                for (int i = 0; i < rand() % 3; ++i) {
                    player->giveExtraHit();
                }
            }

            player->setInvulnerable(true);
        }

        // Configura los marcadores
        scoreboard_->setMode(Scoreboard::Id::LEFT, Scoreboard::Mode::DEMO);
        scoreboard_->setMode(Scoreboard::Id::RIGHT, Scoreboard::Mode::DEMO);

        // Silencia los globos
        balloon_manager_->setSounds(false);
    }

    // Modo grabar demo
#ifdef RECORDING
    demo_.recording = true;
#else
    demo_.recording = false;
#endif
    demo_.index = 0;

#ifdef _DEBUG
    autoplay_ = Director::debug_config.autoplay && !demo_.enabled;
    if (autoplay_) {
        auto const NUM_DEMOS = Asset::get()->getListByType(Asset::Type::DEMODATA).size();
        for (size_t num_demo = 0; num_demo < NUM_DEMOS; ++num_demo) {
            autoplay_data_.emplace_back(Resource::get()->getDemoData(num_demo));
        }

        std::vector<size_t> demo_indices(NUM_DEMOS);
        std::iota(demo_indices.begin(), demo_indices.end(), 0);
        std::random_device rd;
        std::default_random_engine rng(rd());
        std::shuffle(demo_indices.begin(), demo_indices.end(), rng);

        for (size_t i = 0; i < players_.size(); ++i) {
            size_t demo_index = demo_indices[i % NUM_DEMOS];
            players_.at(i)->setDemoFile(demo_index);
        }
        autoplay_elapsed_s_ = 0.0F;
        autoplay_index_ = 0;
    }
#endif
}

// Inicializa el marcador
void Game::initScoreboard() {
    scoreboard_->setPos(param.scoreboard.rect);
    scoreboard_->setMode(Scoreboard::Id::CENTER, Scoreboard::Mode::STAGE_INFO);
    for (const auto& player : players_) {
        scoreboard_->setName(player->getScoreBoardPanel(), player->getName());
        if (player->isWaiting()) {
            scoreboard_->setMode(player->getScoreBoardPanel(), Scoreboard::Mode::WAITING);
        }
    }
}

// Inicializa las opciones relacionadas con la dificultad
void Game::initDifficultyVars() {
    // Variables relacionadas con la dificultad
    switch (difficulty_) {
        case Difficulty::Code::EASY: {
            balloon_manager_->setDefaultBalloonSpeed(Balloon::GAME_TEMPO.at(0));
            difficulty_score_multiplier_ = 0.5F;
            scoreboard_->setColor(param.scoreboard.easy_color);
            break;
        }

        case Difficulty::Code::NORMAL: {
            balloon_manager_->setDefaultBalloonSpeed(Balloon::GAME_TEMPO.at(0));
            difficulty_score_multiplier_ = 1.0F;
            scoreboard_->setColor(param.scoreboard.normal_color);
            break;
        }

        case Difficulty::Code::HARD: {
            balloon_manager_->setDefaultBalloonSpeed(Balloon::GAME_TEMPO.at(4));
            difficulty_score_multiplier_ = 1.5F;
            scoreboard_->setColor(param.scoreboard.hard_color);
            break;
        }

        default:
            break;
    }

    balloon_manager_->resetBalloonSpeed();
}

// Inicializa los jugadores
void Game::initPlayers(Player::Id player_id) {
    const int Y = param.game.play_area.rect.h - Player::HEIGHT;  // Se hunde un pixel para esconder el outline de los pies
    const Player::State STATE = demo_.enabled ? Player::State::PLAYING : Player::State::ENTERING_SCREEN;

    // Crea al jugador uno y lo pone en modo espera
    Player::Config config_player1{
        .id = Player::Id::PLAYER1,
#ifdef RECORDING
        .x = param.game.play_area.center_x - (Player::WIDTH / 2),
#else
        .x = param.game.play_area.first_quarter_x - (Player::WIDTH / 2),
#endif
        .y = Y,
        .demo = demo_.enabled,
        .play_area = &param.game.play_area.rect,
        .texture = player_textures_.at(0),
        .animations = player1_animations_,
        .hi_score_table = &Options::settings.hi_score_table,
        .glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER1) - 1),
        .stage_info = stage_manager_.get()};

    auto player1 = std::make_unique<Player>(config_player1);
    player1->setBulletColors(Bullet::Color::YELLOW, Bullet::Color::GREEN);
    player1->setBulletSoundFile("bullet1p.wav");
    player1->setScoreBoardPanel(Scoreboard::Id::LEFT);
    player1->setName(Lang::getText("[SCOREBOARD] 1"));
    player1->setGamepad(Options::gamepad_manager.getGamepad(Player::Id::PLAYER1).instance);
    player1->setUsesKeyboard(Player::Id::PLAYER1 == Options::keyboard.player_id);
    player1->setZOrder(0);
#ifdef RECORDING
    player1->setPlayingState(Player::State::PLAYING);
#else
    player1->setPlayingState((player_id == Player::Id::BOTH_PLAYERS || player_id == Player::Id::PLAYER1) ? STATE : Player::State::WAITING);
#endif

    // Crea al jugador dos y lo pone en modo espera
    Player::Config config_player2{
        .id = Player::Id::PLAYER2,
        .x = param.game.play_area.third_quarter_x - (Player::WIDTH / 2),
        .y = Y,
        .demo = demo_.enabled,
        .play_area = &param.game.play_area.rect,
        .texture = player_textures_.at(1),
        .animations = player2_animations_,
        .hi_score_table = &Options::settings.hi_score_table,
        .glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER2) - 1),
        .stage_info = stage_manager_.get()};

    auto player2 = std::make_unique<Player>(config_player2);
    player2->setBulletColors(Bullet::Color::RED, Bullet::Color::PURPLE);
    player2->setBulletSoundFile("bullet2p.wav");
    player2->setScoreBoardPanel(Scoreboard::Id::RIGHT);
    player2->setName(Lang::getText("[SCOREBOARD] 2"));
    player2->setGamepad(Options::gamepad_manager.getGamepad(Player::Id::PLAYER2).instance);
    player2->setUsesKeyboard(Player::Id::PLAYER2 == Options::keyboard.player_id);
    player2->setPlayingState((player_id == Player::Id::BOTH_PLAYERS || player_id == Player::Id::PLAYER2) ? STATE : Player::State::WAITING);
    player2->setZOrder(1);

    // Añade los jugadores al vector de forma que el jugador 1 se pinte por delante del jugador 2
    players_.push_back(std::move(player2));
    players_.push_back(std::move(player1));

    // Registra los jugadores en Options
    for (const auto& player : players_) {
        Options::keyboard.addPlayer(player);
        Options::gamepad_manager.addPlayer(player);
    }

    // Construye la lista de dibujado de los jugadores
    buildPlayerDrawList(players_, players_draw_list_);
}

// Hace sonar la música
void Game::playMusic(const std::string& music_file, int loop) {
    Audio::get()->playMusic(music_file, loop);
}

// Pausa la música
void Game::pauseMusic() {
    Audio::get()->pauseMusic();
}

// Retoma la música que eestaba pausada
void Game::resumeMusic() {
    Audio::get()->resumeMusic();
}

// Detiene la música
void Game::stopMusic() const {
    if (!demo_.enabled) {
        Audio::get()->stopMusic();
    }
}

// Actualiza las variables durante el modo demo
void Game::updateDemo(float delta_time) {
    if (demo_.enabled) {
        balloon_manager_->setCreationTimeEnabled(balloon_manager_->getNumBalloons() != 0);

        // Actualiza ambos fades
        fade_in_->update();
        fade_out_->update();

        // Actualiza el contador de tiempo y el índice
        demo_.elapsed_s += delta_time;
        demo_.index = static_cast<int>(demo_.elapsed_s * 60.0F);

        // Activa el fundido antes de acabar con los datos de la demo
        if (demo_.index == TOTAL_DEMO_DATA - 200) {
            fade_out_->setType(Fade::Type::RANDOM_SQUARE2);
            fade_out_->setPostDuration(param.fade.post_duration_ms);
            fade_out_->activate();
        }

        // Si ha terminado el fundido, cambia de sección
        if (fade_out_->hasEnded()) {
            Section::name = Section::Name::HI_SCORE_TABLE;
            return;
        }
    }
}

#ifdef RECORDING
// Actualiza las variables durante el modo de grabación
void Game::updateRecording(float deltaTime) {
    // Actualiza el contador de tiempo y el índice
    demo_.elapsed_s += deltaTime;
    demo_.index = static_cast<int>(demo_.elapsed_s * 60.0F);

    if (demo_.index >= TOTAL_DEMO_DATA) {
        Section::name = Section::Name::QUIT;
        return;
    }

    // Almacenar las teclas del frame actual en el vector de grabación
    if (demo_.index < TOTAL_DEMO_DATA && demo_.data.size() > 0) {
        // Asegurar que el vector tenga el tamaño suficiente
        if (demo_.data.at(0).size() <= static_cast<size_t>(demo_.index)) {
            demo_.data.at(0).resize(demo_.index + 1);
        }

        // Almacenar las teclas del frame actual
        demo_.data.at(0).at(demo_.index) = demo_.keys;

        // Resetear las teclas para el siguiente frame
        demo_.keys = DemoKeys();
    }
}
#endif

// Actualiza las variables durante dicho estado
void Game::updateGameStateFadeIn(float delta_time) {
    fade_in_->update();
    updateScoreboard(delta_time);
    updateBackground(delta_time);
    if (fade_in_->hasEnded()) {
        setState(State::ENTERING_PLAYER);
        balloon_manager_->createTwoBigBalloons();
        setMenace();
    }
}

// Actualiza las variables durante dicho estado
void Game::updateGameStateEnteringPlayer(float delta_time) {
    balloon_manager_->update(delta_time);
    updatePlayers(delta_time);
    updateScoreboard(delta_time);
    updateBackground(delta_time);
    for (const auto& player : players_) {
        if (player->isPlaying()) {
            setState(State::SHOWING_GET_READY_MESSAGE);
            createMessage({paths_.at(0), paths_.at(1)}, Resource::get()->getTexture("game_text_get_ready"));
            playSound("voice_get_ready.wav");
        }
    }
}

// Actualiza las variables durante dicho estado
void Game::updateGameStateShowingGetReadyMessage(float delta_time) {
    updateGameStatePlaying(delta_time);

    constexpr float MUSIC_START_S = 1.67F;
    static float music_timer_ = 0.0F;
    music_timer_ += delta_time;
    if (music_timer_ >= MUSIC_START_S) {
        playMusic("playing.ogg");
        music_timer_ = 0.0F;
        setState(State::PLAYING);
    }
}

// Actualiza las variables durante el transcurso normal del juego
void Game::updateGameStatePlaying(float delta_time) {
#ifdef _DEBUG
    if (auto_pop_balloons_) {
        stage_manager_->addPower(2);
    }
    if (autoplay_) {
        autoplay_elapsed_s_ += delta_time;
        autoplay_index_ = static_cast<int>(autoplay_elapsed_s_ * 60.0F);
        if (autoplay_index_ >= TOTAL_DEMO_DATA) {
            autoplay_elapsed_s_ = 0.0F;
            autoplay_index_ = 0;
        }
    }
#endif
    updatePlayers(delta_time);
    checkPlayersStatusPlaying();
    updateScoreboard(delta_time);
    updateBackground(delta_time);
    balloon_manager_->update(delta_time);
    tabe_->update(delta_time);
    bullet_manager_->update(delta_time);
    updateItems(delta_time);
    updateStage();
    updateSmartSprites(delta_time);
    updatePathSprites(delta_time);
    updateTimeStopped(delta_time);
    updateHelper();
    bullet_manager_->checkCollisions();
    updateMenace();
    checkAndUpdateBalloonSpeed();
    checkState();
    cleanLists();
}

// Vacía los vectores de elementos deshabilitados
void Game::cleanLists() {
    bullet_manager_->freeBullets();
    balloon_manager_->freeBalloons();
    freeItems();
    freeSmartSprites();
    freePathSprites();
}

// Gestiona el nivel de amenaza
void Game::updateMenace() {
    if (state_ != State::PLAYING) {
        return;
    }

    auto current_stage = stage_manager_->getCurrentStage();
    if (!current_stage.has_value()) {
        return;
    }

    const auto& stage = current_stage.value();
    const double FRACTION = stage_manager_->getCurrentStageProgressFraction();
    const int DIFFERENCE = stage.getMaxMenace() - stage.getMinMenace();

    // Aumenta el nivel de amenaza en función del progreso de la fase
    menace_threshold_ = stage.getMinMenace() + (DIFFERENCE * FRACTION);

    if (menace_ < menace_threshold_) {
        balloon_manager_->deployRandomFormation(stage_manager_->getCurrentStageIndex());
        setMenace();
    }
}

// Calcula y establece el valor de amenaza en funcion de los globos activos
void Game::setMenace() {
    menace_ = balloon_manager_->getMenace();
}

// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
void Game::checkAndUpdateBalloonSpeed() {
    if (difficulty_ != Difficulty::Code::NORMAL) {
        return;
    }

    const float PERCENT = stage_manager_->getCurrentStageProgressFraction();
    constexpr std::array<float, 4> THRESHOLDS = {0.2F, 0.4F, 0.6F, 0.8F};

    for (size_t i = 0; i < std::size(THRESHOLDS); ++i) {
        // Si la velocidad actual del globo es la correspondiente al umbral "i" y el porcentaje de progreso ha superado ese umbral
        if (balloon_manager_->getBalloonSpeed() == Balloon::GAME_TEMPO.at(i) && PERCENT > THRESHOLDS.at(i)) {
            // Sube la velocidad al siguiente nivel (i + 1)
            balloon_manager_->setBalloonSpeed(Balloon::GAME_TEMPO.at(i + 1));
            return;
        }
    }
}

// Cambia el estado del juego
void Game::setState(State state) {
    state_ = state;
    counter_ = 0;

    switch (state) {
        case State::COMPLETED:                       // Para la música y elimina todos los globos e items
            stopMusic();                             // Detiene la música
            balloon_manager_->destroyAllBalloons();  // Destruye a todos los globos
            disableTimeStopItem();                   // Deshabilita el temporizador de tiempo
            playSound("power_ball_explosion.wav");   // Sonido de destruir todos los globos
            destroyAllItems();                       // Destruye todos los items
            background_->setAlpha(0);                // Elimina el tono rojo de las últimas pantallas
            tabe_->disableSpawning();                // Deshabilita la creacion de Tabes
            game_completed_flags_.reset();           // Resetea flags de juego completado
            break;
        case State::GAME_OVER:
            game_over_flags_.reset();  // Resetea flags de game over
            break;
        default:
            break;
    }
}

void Game::playSound(const std::string& name) const {
    if (demo_.enabled) {
        return;
    }

    static auto* audio_ = Audio::get();
    audio_->playSound(name);
}

void Game::onPauseStateChanged(bool is_paused) {
    screen_->attenuate(is_paused);
    tabe_->pauseTimer(is_paused);
}

// Maneja eventos del juego completado usando flags para triggers únicos
void Game::handleGameCompletedEvents() {
    constexpr float START_CELEBRATIONS_S = 6.0F;
    constexpr float END_CELEBRATIONS_S = 14.0F;

    // Inicio de celebraciones
    if (!game_completed_flags_.start_celebrations_triggered && game_completed_timer_ >= START_CELEBRATIONS_S) {
        createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("game_text_congratulations"));
        createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("game_text_1000000_points"));

        for (auto& player : players_) {
            if (player->isPlaying()) {
                player->addScore(1000000, Options::settings.hi_score_table.back().score);
                player->setPlayingState(Player::State::CELEBRATING);
            } else {
                player->setPlayingState(Player::State::GAME_OVER);
            }
        }

        updateHiScore();
        playMusic("congratulations.ogg", 1);
        game_completed_flags_.start_celebrations_triggered = true;
    }

    // Fin de celebraciones
    if (!game_completed_flags_.end_celebrations_triggered && game_completed_timer_ >= END_CELEBRATIONS_S) {
        for (auto& player : players_) {
            if (player->isCelebrating()) {
                player->setPlayingState(player->qualifiesForHighScore() ? Player::State::ENTERING_NAME_GAME_COMPLETED : Player::State::LEAVING_SCREEN);
            }
        }
        game_completed_flags_.end_celebrations_triggered = true;
    }
}

// Maneja eventos discretos basados en tiempo durante el estado game over
void Game::handleGameOverEvents() {
    constexpr float MESSAGE_TRIGGER_S = 1.5F;
    constexpr float FADE_TRIGGER_S = GAME_OVER_DURATION_S - 2.5F;

    // Trigger inicial: fade out de música y sonidos de globos
    if (!game_over_flags_.music_fade_triggered) {
        Audio::get()->fadeOutMusic(1000);
        balloon_manager_->setBouncingSounds(true);
        game_over_flags_.music_fade_triggered = true;
    }

    // Trigger del mensaje "Game Over"
    if (!game_over_flags_.message_triggered && game_over_timer_ >= MESSAGE_TRIGGER_S) {
        createMessage({paths_.at(10), paths_.at(11)}, Resource::get()->getTexture("game_text_game_over"));
        playSound("voice_game_over.wav");
        game_over_flags_.message_triggered = true;
    }

    // Trigger del fade out
    if (!game_over_flags_.fade_out_triggered && game_over_timer_ >= FADE_TRIGGER_S && !fade_out_->isEnabled()) {
        fade_out_->activate();
        game_over_flags_.fade_out_triggered = true;
    }
}

// Construye (una vez) el draw_list a partir del vector principal
// draw_list almacena punteros a los elementos y queda reservado
void Game::buildPlayerDrawList(const Players& elements, Players& draw_list) {
    draw_list.clear();
    draw_list.reserve(elements.size());
    for (const auto& e : elements) {
        draw_list.push_back(e);  // copia el shared_ptr
    }
    std::ranges::stable_sort(draw_list, [](const std::shared_ptr<Player>& a, const std::shared_ptr<Player>& b) -> bool {
        return a->getZOrder() < b->getZOrder();
    });
}

// Actualiza draw_list tras cambios en los z_order. Implementación simple:
// reordena draw_list según los z_order actuales. Llamar cuando cambian z_order
void Game::updatePlayerDrawList(const Players& elements, Players& draw_list) {
    // Si draw_list está vacío o su tamaño no coincide, reconstruirlo.
    if (draw_list.size() != elements.size()) {
        buildPlayerDrawList(elements, draw_list);
        return;
    }
    // Dado que apuntan a los mismos elementos, basta ordenar por los z_order actuales.
    std::ranges::stable_sort(draw_list, [](const std::shared_ptr<Player>& a, const std::shared_ptr<Player>& b) -> bool {
        return a->getZOrder() < b->getZOrder();
    });
}

// Dibuja en el orden definido por draw_list
void Game::renderPlayerDrawList(const Players& draw_list) {
    for (const auto& e : draw_list) {
        e->render();
    }
}

// Operaciones sobre z_order que mantienen la invariante y actualizan draw_list.
auto Game::findPlayerIndex(const Players& elems, const std::shared_ptr<Player>& who) -> size_t {
    for (size_t i = 0; i < elems.size(); ++i) {
        if (elems[i] == who) {
            return static_cast<int>(i);  // compara shared_ptr directamente
        }
    }
    return -1;
}

void Game::sendPlayerToBack(Players& elements, const std::shared_ptr<Player>& who, Players& draw_list) {
    int idx = findPlayerIndex(elements, who);
    if (idx < 0) {
        return;  // no encontrado
    }
    const int OLD_Z = elements[idx]->getZOrder();
    if (OLD_Z <= 0) {
        return;
    }
    for (auto& p : elements) {
        int z = p->getZOrder();
        if (z < OLD_Z) {
            p->setZOrder(z + 1);
        }
    }
    elements[idx]->setZOrder(0);
    updatePlayerDrawList(elements, draw_list);
}

void Game::bringPlayerToFront(Players& elements, const std::shared_ptr<Player>& who, Players& draw_list) {
    int idx = findPlayerIndex(elements, who);
    if (idx < 0) {
        return;  // no encontrado
    }
    const int OLD_Z = elements[idx]->getZOrder();
    const int N = static_cast<int>(elements.size());
    if (OLD_Z >= N - 1) {
        return;
    }
    for (auto& p : elements) {
        int z = p->getZOrder();
        if (z > OLD_Z) {
            p->setZOrder(z - 1);
        }
    }
    elements[idx]->setZOrder(N - 1);
    updatePlayerDrawList(elements, draw_list);
}

#ifdef _DEBUG
// Alimenta input de demo a los jugadores durante el autoplay
void Game::autoplayHandleInput() {
    for (const auto& player : players_) {
        if (player->isPlaying()) {
            const auto& demo_data = autoplay_data_.at(player->getDemoFile()).at(autoplay_index_);

            if (demo_data.left == 1) {
                player->setInput(Input::Action::LEFT);
            } else if (demo_data.right == 1) {
                player->setInput(Input::Action::RIGHT);
            } else if (demo_data.no_input == 1) {
                player->setInput(Input::Action::NONE);
            }

            if (demo_data.fire == 1) {
                handleFireInput(player, Bullet::Type::UP);
            } else if (demo_data.fire_left == 1) {
                handleFireInput(player, Bullet::Type::LEFT);
            } else if (demo_data.fire_right == 1) {
                handleFireInput(player, Bullet::Type::RIGHT);
            }
        }
    }
}

// Comprueba los eventos en el modo DEBUG
void Game::handleDebugEvents(const SDL_Event& event) {
    static int formation_id_ = 0;
    if (event.type == SDL_EVENT_KEY_DOWN && static_cast<int>(event.key.repeat) == 0) {
        switch (event.key.key) {
            case SDLK_1: {  // Crea una powerball
                balloon_manager_->createPowerBall();
                break;
            }
            case SDLK_2: {  // Activa o desactiva la aparición de globos
                static bool deploy_balloons_ = true;
                deploy_balloons_ = !deploy_balloons_;
                balloon_manager_->enableBalloonDeployment(deploy_balloons_);
                break;
            }
            case SDLK_3: {  // Activa el modo para pasar el juego automaticamente
                auto_pop_balloons_ = !auto_pop_balloons_;
                Notifier::get()->show({"auto advance: " + boolToString(auto_pop_balloons_)});
                if (auto_pop_balloons_) {
                    balloon_manager_->destroyAllBalloons();
                    playSound("power_ball_explosion.wav");
                }
                balloon_manager_->enableBalloonDeployment(!auto_pop_balloons_);
                break;
            }
            case SDLK_4: {  // Suelta un item
                createItem(ItemType::CLOCK, players_.at(0)->getPosX(), players_.at(0)->getPosY() - 40);
                break;
            }
            case SDLK_5: {  // 5.000
                const int X = players_.at(0)->getPosX() + ((Player::WIDTH - game_text_textures_[3]->getWidth()) / 2);
                createItemText(X, game_text_textures_.at(2));
                break;
            }
            case SDLK_6: {  // Crea un mensaje
                createMessage({paths_.at(0), paths_.at(1)}, Resource::get()->getTexture("game_text_get_ready"));
                break;
            }
            case SDLK_7: {  // 100.000
                const int X = players_.at(0)->getPosX() + ((Player::WIDTH - game_text_textures_[3]->getWidth()) / 2);
                createItemText(X, game_text_textures_.at(6));
                break;
            }
            case SDLK_8: {
                for (const auto& player : players_) {
                    if (player->isPlaying()) {
                        createItem(ItemType::COFFEE_MACHINE, player->getPosX(), param.game.game_area.rect.y - Item::COFFEE_MACHINE_HEIGHT);
                        coffee_machine_enabled_ = true;
                        break;
                    }
                }
                break;
            }
            case SDLK_9: {
                tabe_->enable();
                break;
            }
            case SDLK_KP_PLUS: {
                ++formation_id_;
                balloon_manager_->destroyAllBalloons();
                balloon_manager_->deployFormation(formation_id_);
                std::cout << formation_id_ << '\n';
                break;
            }
            case SDLK_KP_MINUS: {
                --formation_id_;
                balloon_manager_->destroyAllBalloons();
                balloon_manager_->deployFormation(formation_id_);
                std::cout << formation_id_ << '\n';
                break;
            }
            default:
                break;
        }
    }
}
#endif