#include "game/entities/player.hpp"

#include <SDL3/SDL.h>  // Para SDL_FlipMode

#include <algorithm>  // Para clamp, max, min
#include <cmath>      // Para fmod
#include <cstdlib>    // Para rand

#include "core/audio/audio.hpp"                       // Para Audio
#include "core/input/input.hpp"                       // Para Input
#include "core/input/input_types.hpp"                 // Para InputAction
#include "core/rendering/sprite/animated_sprite.hpp"  // Para AnimatedSprite
#include "core/rendering/texture.hpp"                 // Para Texture
#include "core/resources/asset.hpp"                   // Para Asset
#include "core/system/stage_interface.hpp"            // Para IStageInfo
#include "game/gameplay/cooldown.hpp"                 // Para Cooldown
#include "game/gameplay/manage_hiscore_table.hpp"     // Para ManageHiScoreTable, HiScoreEntry
#include "game/gameplay/scoreboard.hpp"               // Para Scoreboard
#include "utils/param.hpp"                            // Para Param, ParamGame, param

// Constructor
Player::Player(const Config& config)
    : player_sprite_(std::make_unique<AnimatedSprite>(config.texture.at(0), config.animations.at(0))),
      power_sprite_(std::make_unique<AnimatedSprite>(config.texture.at(4), config.animations.at(1))),
      enter_name_(std::make_unique<EnterName>()),
      cooldown_(std::make_unique<Cooldown>(0.300F, 0.140F)),
      hi_score_table_(config.hi_score_table),
      glowing_entry_(config.glowing_entry),
      stage_info_(config.stage_info),
      play_area_(*config.play_area),
      id_(config.id),
      default_pos_x_(config.x),
      default_pos_y_(config.y),
      demo_(config.demo) {
    // Configura objetos
    player_sprite_->addTexture(config.texture.at(1));
    player_sprite_->addTexture(config.texture.at(2));
    player_sprite_->addTexture(config.texture.at(3));
    player_sprite_->setActiveTexture(coffees_);
    power_sprite_->getTexture()->setAlpha(224);
    power_up_x_offset_ = (power_sprite_->getWidth() - player_sprite_->getWidth()) / 2;
    power_sprite_->setPosY(default_pos_y_ - (power_sprite_->getHeight() - player_sprite_->getHeight()));

    // Inicializa variables
    pos_x_ = default_pos_x_;
    init();
}

// Iniciador
void Player::init() {
    // Inicializa variables de estado
    pos_y_ = default_pos_y_;
    walking_state_ = State::WALKING_STOP;
    firing_state_ = State::FIRING_NONE;
    invulnerable_ = false;
    invulnerable_counter_ = 0;
    power_up_ = false;
    power_up_counter_ = POWERUP_COUNTER;
    extra_hit_ = false;
    coffees_ = 0;
    continue_counter_ = 10;
    name_entry_idle_time_accumulator_ = 0.0F;
    name_entry_total_time_accumulator_ = 0.0F;
    shiftColliders();
    vel_x_ = 0;
    vel_y_ = 0;
    score_ = 0;
    score_multiplier_ = 1.0F;
    enter_name_->init(last_enter_name_);

    // Establece la posición del sprite
    player_sprite_->clear();
    shiftSprite();
}

// Actua en consecuencia de la entrada recibida
void Player::setInput(Input::Action action) {
    switch (playing_state_) {
        case State::PLAYING: {
            setInputPlaying(action);
            break;
        }
        case State::SHOWING_NAME:
        case State::ENTERING_NAME:
        case State::ENTERING_NAME_GAME_COMPLETED: {
            setInputEnteringName(action);
            break;
        }
        default:
            break;
    }
}

// Procesa inputs para cuando está jugando
void Player::setInputPlaying(Input::Action action) {
    switch (action) {
        case Input::Action::LEFT: {
            vel_x_ = -BASE_SPEED;
            setWalkingState(State::WALKING_LEFT);
            break;
        }
        case Input::Action::RIGHT: {
            vel_x_ = BASE_SPEED;
            setWalkingState(State::WALKING_RIGHT);
            break;
        }
        case Input::Action::FIRE_CENTER: {
            setFiringState(State::FIRING_UP);
            break;
        }
        case Input::Action::FIRE_LEFT: {
            setFiringState(State::FIRING_LEFT);
            break;
        }
        case Input::Action::FIRE_RIGHT: {
            setFiringState(State::FIRING_RIGHT);
            break;
        }
        default: {
            vel_x_ = 0;
            setWalkingState(State::WALKING_STOP);
            break;
        }
    }
}

// Gestiona la adición de un carácter o la confirmación del nombre.
void Player::handleNameCharacterAddition() {
    if (enter_name_->endCharSelected()) {
        confirmNameEntry();
    } else {
        enter_name_->addCharacter();
        playSound("service_menu_select.wav");
    }
}

// Gestiona la eliminación del último carácter.
void Player::handleNameCharacterRemoval() {
    if (!enter_name_->nameIsEmpty()) {
        enter_name_->removeLastCharacter();
        playSound("service_menu_back.wav");
    }
}

// Gestiona el movimiento del cursor de selección de letras (izquierda/derecha).
void Player::handleNameSelectionMove(Input::Action action) {
    if (isShowingName() || enter_name_->nameIsFull()) {
        return;  // No hacer nada si se muestra el nombre o si está lleno
    }

    if (cooldown_->tryConsumeOnHeld()) {
        if (action == Input::Action::RIGHT) {
            enter_name_->incIndex();
        } else {
            enter_name_->decIndex();
        }
        playSound("service_menu_move.wav");
    }
}

// Confirma el nombre introducido y cambia el estado del jugador.
void Player::confirmNameEntry() {
    last_enter_name_ = getRecordName();
    setPlayingState(Player::State::SHOWING_NAME);
    playSound("name_input_accept.wav");
}

// Procesa inputs para cuando está introduciendo el nombre
void Player::setInputEnteringName(Input::Action action) {
    switch (action) {
        case Input::Action::FIRE_LEFT:
            if (isShowingName()) {
                passShowingName();
            } else {
                handleNameCharacterAddition();
            }
            break;

        case Input::Action::FIRE_CENTER:
            if (isShowingName()) {
                passShowingName();
            } else {
                handleNameCharacterRemoval();
            }
            break;

        case Input::Action::START:
            if (isShowingName()) {
                passShowingName();
            } else {
                confirmNameEntry();
            }
            break;

        case Input::Action::RIGHT:
        case Input::Action::LEFT:
            handleNameSelectionMove(action);
            break;

        default:
            cooldown_->onReleased();
            break;
    }

    name_entry_idle_time_accumulator_ = 0.0F;
}

// Sistema de movimiento
void Player::move(float delta_time) {
    switch (playing_state_) {
        case State::PLAYING:
            handlePlayingMovement(delta_time);
            break;
        case State::ROLLING:
            handleRollingMovement();
            break;
        case State::TITLE_ANIMATION:
            handleTitleAnimation(delta_time);
            break;
        case State::CONTINUE_TIME_OUT:
            handleContinueTimeOut();
            break;
        case State::LEAVING_SCREEN:
            updateStepCounter(delta_time);
            handleLeavingScreen(delta_time);
            break;
        case State::ENTERING_SCREEN:
            updateStepCounter(delta_time);
            handleEnteringScreen(delta_time);
            break;
        case State::CREDITS:
            handleCreditsMovement(delta_time);
            break;
        case State::WAITING:
            handleWaitingMovement(delta_time);
            break;
        case State::RECOVER:
            handleRecoverMovement();
            break;
        default:
            break;
    }
}

// Movimiento time-based durante el juego
void Player::handlePlayingMovement(float delta_time) {
    // Mueve el jugador a derecha o izquierda (time-based en segundos)
    pos_x_ += vel_x_ * delta_time;

    // Si el jugador abandona el area de juego por los laterales, restaura su posición
    const float MIN_X = play_area_.x - 5;
    const float MAX_X = play_area_.w + 5 - WIDTH;
    pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);

    shiftSprite();
}

void Player::handleRecoverMovement() {
    if (player_sprite_->getCurrentAnimationFrame() == 10 && !recover_sound_triggered_) {
        playSound("voice_recover.wav");
        recover_sound_triggered_ = true;
    }
    if (player_sprite_->animationIsCompleted()) { setPlayingState(State::RESPAWNING); }
}

void Player::handleRollingMovement() {
    handleRollingBoundaryCollision();
    handleRollingGroundCollision();
}

void Player::handleRollingBoundaryCollision() {
    const int X = player_sprite_->getPosX();
    const int MIN_X = play_area_.x;
    const int MAX_X = play_area_.x + play_area_.w - WIDTH;

    if ((X < MIN_X) || (X > MAX_X)) {
        player_sprite_->setPosX(std::clamp(X, MIN_X, MAX_X));
        player_sprite_->setVelX(-player_sprite_->getVelX());
        playSound("jump.wav");
    }
}

void Player::handleRollingGroundCollision() {
    if (player_sprite_->getPosY() <= play_area_.h - HEIGHT) {
        return;
    }

    if (player_sprite_->getVelY() < 120.0F) {  // 2.0F * 60fps = 120.0F pixels/segundo
        handleRollingStop();
    } else {
        handleRollingBounce();
    }
}

void Player::handleRollingStop() {
    const auto NEXT_PLAYER_STATE = qualifiesForHighScore() ? State::ENTERING_NAME : State::CONTINUE;
    const auto NEXT_STATE = demo_ ? State::LYING_ON_THE_FLOOR_FOREVER : NEXT_PLAYER_STATE;

    setPlayingState(NEXT_STATE);
    pos_x_ = player_sprite_->getPosX();
    pos_y_ = default_pos_y_;
    player_sprite_->clear();
    shiftSprite();
    playSound("jump.wav");
}

void Player::handleRollingBounce() {
    player_sprite_->setPosY(play_area_.h - HEIGHT);
    player_sprite_->setVelY(player_sprite_->getVelY() * -0.5F);
    player_sprite_->setVelX(player_sprite_->getVelX() * 0.75F);
    player_sprite_->setAnimationSpeed(player_sprite_->getAnimationSpeed() * 2);
    playSound("jump.wav");
}

void Player::handleTitleAnimation(float delta_time) {
    setInputBasedOnPlayerId();

    pos_x_ += (vel_x_ * 2.0F) * delta_time;
    const float MIN_X = -WIDTH;
    const float MAX_X = play_area_.w;
    pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
    shiftSprite();

    if (pos_x_ == MIN_X || pos_x_ == MAX_X) {
        setPlayingState(State::TITLE_HIDDEN);
    }
}

void Player::handleContinueTimeOut() {
    // Si el cadaver desaparece por el suelo, cambia de estado
    if (player_sprite_->getPosY() > play_area_.h) {
        player_sprite_->stop();
        setPlayingState(State::WAITING);
    }
}

void Player::handleLeavingScreen(float delta_time) {
    // updateStepCounter se llama desde move() con deltaTime
    setInputBasedOnPlayerId();

    pos_x_ += vel_x_ * delta_time;
    const float MIN_X = -WIDTH;
    const float MAX_X = play_area_.w;
    pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
    shiftSprite();

    if (pos_x_ == MIN_X || pos_x_ == MAX_X) {
        setPlayingState(State::GAME_OVER);
    }
}

void Player::handleEnteringScreen(float delta_time) {
    // updateStepCounter se llama desde move() con deltaTime

    switch (id_) {
        case Id::PLAYER1:
            handlePlayer1Entering(delta_time);
            break;
        case Id::PLAYER2:
            handlePlayer2Entering(delta_time);
            break;
        default:
            break;
    }

    shiftSprite();
}

void Player::handlePlayer1Entering(float delta_time) {
    setInputPlaying(Input::Action::RIGHT);
    pos_x_ += vel_x_ * delta_time;
    if (pos_x_ > default_pos_x_) {
        pos_x_ = default_pos_x_;
        setPlayingState(State::PLAYING);
    }
}

void Player::handlePlayer2Entering(float delta_time) {
    setInputPlaying(Input::Action::LEFT);
    pos_x_ += vel_x_ * delta_time;
    if (pos_x_ < default_pos_x_) {
        pos_x_ = default_pos_x_;
        setPlayingState(State::PLAYING);
    }
}

// Movimiento general en la pantalla de créditos (time-based)
void Player::handleCreditsMovement(float delta_time) {
    pos_x_ += (vel_x_ / 2.0F) * delta_time;

    if (vel_x_ > 0) {
        handleCreditsRightMovement();
    } else {
        handleCreditsLeftMovement();
    }

    updateWalkingStateForCredits();
    shiftSprite();
}

void Player::handleCreditsRightMovement() {
    if (pos_x_ > param.game.game_area.rect.w - WIDTH) {
        pos_x_ = param.game.game_area.rect.w - WIDTH;
        vel_x_ *= -1;
    }
}

void Player::handleCreditsLeftMovement() {
    if (pos_x_ < param.game.game_area.rect.x) {
        pos_x_ = param.game.game_area.rect.x;
        vel_x_ *= -1;
    }
}

// Controla la animación del jugador saludando (time-based)
void Player::handleWaitingMovement(float delta_time) {
    waiting_time_accumulator_ += delta_time;
    const float WAITING_DURATION_S = static_cast<float>(WAITING_COUNTER) / 60.0F;  // Convert frames to seconds
    if (waiting_time_accumulator_ >= WAITING_DURATION_S) {
        waiting_time_accumulator_ = 0.0F;
        player_sprite_->resetAnimation();
    }
}

void Player::updateWalkingStateForCredits() {
    if (pos_x_ > param.game.game_area.center_x - (WIDTH / 2)) {
        setWalkingState(State::WALKING_LEFT);
    } else {
        setWalkingState(State::WALKING_RIGHT);
    }
}

void Player::setInputBasedOnPlayerId() {
    switch (id_) {
        case Id::PLAYER1:
            setInputPlaying(Input::Action::LEFT);
            break;
        case Id::PLAYER2:
            setInputPlaying(Input::Action::RIGHT);
            break;
        default:
            break;
    }
}

// Incrementa o ajusta el contador de pasos (time-based)
void Player::updateStepCounter(float delta_time) {
    step_time_accumulator_ += delta_time;
    const float STEP_INTERVAL_S = 10.0F / 60.0F;  // 10 frames converted to seconds
    if (step_time_accumulator_ >= STEP_INTERVAL_S) {
        step_time_accumulator_ = 0.0F;
        playSound("walk.wav");
    }
}

// Pinta el jugador en pantalla
void Player::render() {
    if (power_sprite_visible_ && isPlaying()) {
        power_sprite_->render();
    }

    if (isRenderable()) {
        player_sprite_->render();
    }
}

// Calcula la animacion de moverse y disparar del jugador
auto Player::computeAnimation() const -> std::pair<std::string, SDL_FlipMode> {
    const std::string BASE_ANIM = (walking_state_ == State::WALKING_STOP) ? "stand" : "walk";
    std::string anim_name;
    SDL_FlipMode flip_mode = SDL_FLIP_NONE;

    switch (firing_state_) {
        case State::FIRING_NONE:
            anim_name = BASE_ANIM;
            flip_mode = (walking_state_ == State::WALKING_RIGHT) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            break;

        case State::FIRING_UP:
            anim_name = BASE_ANIM + "-fire-center";
            flip_mode = (walking_state_ == State::WALKING_RIGHT) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            break;

        case State::FIRING_LEFT:
        case State::FIRING_RIGHT:
            anim_name = BASE_ANIM + "-fire-side";
            flip_mode = (firing_state_ == State::FIRING_RIGHT) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            break;

        case State::RECOILING_UP:
            anim_name = BASE_ANIM + "-recoil-center";
            flip_mode = (walking_state_ == State::WALKING_RIGHT) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            break;

        case State::RECOILING_LEFT:
        case State::RECOILING_RIGHT:
            anim_name = BASE_ANIM + "-recoil-side";
            flip_mode = (firing_state_ == State::RECOILING_RIGHT) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            break;

        case State::COOLING_UP:
            anim_name = BASE_ANIM + "-cool-center";
            flip_mode = (walking_state_ == State::WALKING_RIGHT) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            break;

        case State::COOLING_LEFT:
        case State::COOLING_RIGHT:
            anim_name = BASE_ANIM + "-cool-side";
            flip_mode = (firing_state_ == State::COOLING_RIGHT) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            break;

        default:
            anim_name = BASE_ANIM;
            flip_mode = (walking_state_ == State::WALKING_RIGHT) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            break;
    }

    return {anim_name, flip_mode};
}

// Establece la animación correspondiente al estado
void Player::setAnimation(float delta_time) {
    switch (playing_state_) {
        case State::PLAYING:
        case State::ENTERING_SCREEN:
        case State::LEAVING_SCREEN:
        case State::TITLE_ANIMATION:
        case State::CREDITS: {
            auto [animName, flipMode] = computeAnimation();
            player_sprite_->setCurrentAnimation(animName, false);
            player_sprite_->setFlip(flipMode);
            break;
        }
        case State::RECOVER:
            player_sprite_->setCurrentAnimation("recover");
            break;
        case State::WAITING:
        case State::GAME_OVER:
            player_sprite_->setCurrentAnimation("hello");
            break;
        case State::ROLLING:
        case State::CONTINUE_TIME_OUT:
            player_sprite_->setCurrentAnimation("rolling");
            break;
        case State::LYING_ON_THE_FLOOR_FOREVER:
        case State::ENTERING_NAME:
        case State::CONTINUE:
            player_sprite_->setCurrentAnimation("dizzy");
            break;
        case State::ENTERING_NAME_GAME_COMPLETED:
        case State::CELEBRATING:
            player_sprite_->setCurrentAnimation("celebration");
            break;
        default:
            break;
    }

    // La diferencia clave: usa deltaTime para las animaciones
    player_sprite_->update(delta_time);
    power_sprite_->update(delta_time);
}

// Actualiza al jugador con deltaTime (time-based)
void Player::update(float delta_time) {
    move(delta_time);                    // Sistema de movimiento
    setAnimation(delta_time);            // Animaciones
    shiftColliders();                    // Actualiza caja de colisiones
    updateFireSystem(delta_time);        // Sistema de disparo de dos líneas
    updatePowerUp(delta_time);           // Sistema de power-up
    updateInvulnerable(delta_time);      // Sistema de invulnerabilidad
    updateScoreboard();                  // Sistema de marcador
    updateContinueCounter(delta_time);   // Sistema de continue
    updateEnterNameCounter(delta_time);  // Sistema de name entry
    updateShowingName(delta_time);       // Sistema de showing name
    cooldown_->update(delta_time);
}

void Player::passShowingName() {
    if (game_completed_) {
        setPlayingState(State::LEAVING_SCREEN);
    } else {
        setPlayingState(State::CONTINUE);
    }
}

// Incrementa la puntuación del jugador
void Player::addScore(int score, int lowest_hi_score_entry) {
    if (isPlaying()) {
        score_ += score;
        qualifies_for_high_score_ = score_ > lowest_hi_score_entry;
    }
}

// Actualiza el panel del marcador
void Player::updateScoreboard() {
    switch (playing_state_) {
        case State::CONTINUE: {
            Scoreboard::get()->setContinue(scoreboard_panel_, getContinueCounter());
            break;
        }
        case State::ENTERING_NAME:
        case State::ENTERING_NAME_GAME_COMPLETED: {
            Scoreboard::get()->setEnterName(scoreboard_panel_, enter_name_->getCurrentName());
            Scoreboard::get()->setCharacterSelected(scoreboard_panel_, enter_name_->getSelectedCharacter());
            Scoreboard::get()->setCarouselAnimation(scoreboard_panel_, enter_name_->getSelectedIndex(), enter_name_.get());
            break;
        }
        default:
            break;
    }
}

// Cambia el modo del marcador
void Player::setScoreboardMode(Scoreboard::Mode mode) const {
    if (!demo_) {
        Scoreboard::get()->setMode(scoreboard_panel_, mode);
    }
}

// Establece el estado del jugador en el juego
void Player::setPlayingState(State state) {
    playing_state_ = state;

    switch (playing_state_) {
        case State::RECOVER: {
            recover_sound_triggered_ = false;
            score_ = 0;  // Pon los puntos a cero para que no se vea en el marcador
            score_multiplier_ = 1.0F;
            setScoreboardMode(Scoreboard::Mode::SCORE);
            break;
        }
        case State::RESPAWNING: {
            playSound("voice_thankyou.wav");
            setPlayingState(State::PLAYING);
            setInvulnerable(true);
            break;
        }
        case State::PLAYING: {
            init();
            setInvulnerable(true);
            setScoreboardMode(Scoreboard::Mode::SCORE);
            stage_info_->enablePowerCollection();
            break;
        }
        case State::CONTINUE: {
            // Inicializa el contador de continuar
            continue_counter_ = 9;
            continue_time_accumulator_ = 0.0F;  // Initialize time accumulator
            playSound("continue_clock.wav");
            setScoreboardMode(Scoreboard::Mode::CONTINUE);
            break;
        }
        case State::WAITING: {
            switch (id_) {
                case Id::PLAYER1:
                    pos_x_ = param.game.game_area.rect.x;
                    break;
                case Id::PLAYER2:
                    pos_x_ = param.game.game_area.rect.w - WIDTH;
                    break;
                default:
                    pos_x_ = 0;
                    break;
            }
            pos_y_ = default_pos_y_;
            waiting_counter_ = 0;
            waiting_time_accumulator_ = 0.0F;  // Initialize time accumulator
            shiftSprite();
            player_sprite_->setCurrentAnimation("hello");
            player_sprite_->animtionPause();
            setScoreboardMode(Scoreboard::Mode::WAITING);
            break;
        }
        case State::ENTERING_NAME: {
            setScoreboardMode(Scoreboard::Mode::SCORE_TO_ENTER_NAME);  // Iniciar animación de transición
            break;
        }
        case State::SHOWING_NAME: {
            showing_name_time_accumulator_ = 0.0F;                    // Inicializar acumulador time-based
            setScoreboardMode(Scoreboard::Mode::ENTER_TO_SHOW_NAME);  // Iniciar animación de transición
            Scoreboard::get()->setEnterName(scoreboard_panel_, last_enter_name_);
            addScoreToScoreBoard();
            break;
        }
        case State::ROLLING: {
            // Activa la animación de rodar dando botes
            player_sprite_->setCurrentAnimation("rolling");
            player_sprite_->setAnimationSpeed(4.0F / 60.0F);                                         // 4 frames convertido a segundos
            player_sprite_->setVelY(-396.0F);                                                        // Velocidad inicial (6.6 * 60 = 396 pixels/s)
            player_sprite_->setAccelY(720.0F);                                                       // Gravedad (0.2 * 60² = 720 pixels/s²)
            player_sprite_->setPosY(pos_y_ - 2);                                                     // Para "sacarlo" del suelo, ya que está hundido un pixel para ocultar el outline de los pies
            (rand() % 2 == 0) ? player_sprite_->setVelX(198.0F) : player_sprite_->setVelX(-198.0F);  // 3.3 * 60 = 198 pixels/s
            break;
        }
        case State::TITLE_ANIMATION: {
            // Activa la animación de caminar
            player_sprite_->setCurrentAnimation("walk");
            playSound("voice_credit_thankyou.wav");
            break;
        }
        case State::TITLE_HIDDEN: {
            player_sprite_->setVelX(0.0F);
            player_sprite_->setVelY(0.0F);
            break;
        }
        case State::CONTINUE_TIME_OUT: {
            // Activa la animación de sacar al jugador de la zona de juego
            player_sprite_->setAccelY(720.0F);  // 0.2 * 60² = 720 pixels/s²
            player_sprite_->setVelY(-240.0F);   // -4.0 * 60 = -240 pixels/s
            player_sprite_->setVelX(0.0F);
            player_sprite_->setCurrentAnimation("rolling");
            player_sprite_->setAnimationSpeed(5.0F / 60.0F);  // 5 frames convertido a segundos
            setScoreboardMode(Scoreboard::Mode::GAME_OVER);
            playSound("voice_aw_aw_aw.wav");
            playSound("jump.wav");
            break;
        }
        case State::GAME_OVER: {
            setScoreboardMode(Scoreboard::Mode::GAME_OVER);
            break;
        }
        case State::CELEBRATING: {
            game_completed_ = true;
            setScoreboardMode(Scoreboard::Mode::SCORE);
            break;
        }
        case State::ENTERING_NAME_GAME_COMPLETED: {
            // setWalkingState(State::WALKING_STOP);
            // setFiringState(State::FIRING_NONE);
            setScoreboardMode(Scoreboard::Mode::SCORE_TO_ENTER_NAME);  // Iniciar animación de transición
            break;
        }
        case State::LEAVING_SCREEN: {
            step_counter_ = 0;
            step_time_accumulator_ = 0.0F;  // Initialize time accumulator
            setScoreboardMode(Scoreboard::Mode::GAME_COMPLETED);
            break;
        }
        case State::ENTERING_SCREEN: {
            init();
            step_counter_ = 0;
            step_time_accumulator_ = 0.0F;  // Initialize time accumulator
            setScoreboardMode(Scoreboard::Mode::SCORE);
            switch (id_) {
                case Id::PLAYER1:
                    pos_x_ = param.game.game_area.rect.x - WIDTH;
                    break;

                case Id::PLAYER2:
                    pos_x_ = param.game.game_area.rect.x + param.game.game_area.rect.w;
                    break;

                default:
                    break;
            }
            break;
        }
        case State::CREDITS: {
            vel_x_ = (walking_state_ == State::WALKING_RIGHT) ? BASE_SPEED : -BASE_SPEED;
            break;
        }
        default:
            break;
    }
}

// Aumenta el valor de la variable hasta un máximo
void Player::incScoreMultiplier() {
    score_multiplier_ += 0.1F;
    score_multiplier_ = std::min(score_multiplier_, 5.0F);
}

// Decrementa el valor de la variable hasta un mínimo
void Player::decScoreMultiplier() {
    score_multiplier_ -= 0.1F;
    score_multiplier_ = std::max(score_multiplier_, 1.0F);
}

// Establece el valor del estado
void Player::setInvulnerable(bool value) {
    invulnerable_ = value;
    invulnerable_counter_ = invulnerable_ ? INVULNERABLE_COUNTER : 0;
    invulnerable_time_accumulator_ = invulnerable_ ? static_cast<float>(INVULNERABLE_COUNTER) / 60.0F : 0.0F;  // Convert frames to seconds
}

// Monitoriza el estado (time-based)
void Player::updateInvulnerable(float delta_time) {
    if (playing_state_ == State::PLAYING && invulnerable_) {
        if (invulnerable_time_accumulator_ > 0) {
            invulnerable_time_accumulator_ -= delta_time;

            // Frecuencia fija de parpadeo adaptada a deltaTime (en segundos)
            constexpr float BLINK_PERIOD_S = 8.0F / 60.0F;  // 8 frames convertidos a segundos

            // Calcula proporción decreciente basada en tiempo restante
            const float TOTAL_INVULNERABLE_TIME_S = static_cast<float>(INVULNERABLE_COUNTER) / 60.0F;
            float progress = 1.0F - (invulnerable_time_accumulator_ / TOTAL_INVULNERABLE_TIME_S);
            float white_proportion = 0.5F - (progress * 0.2F);  // Menos blanco hacia el final

            // Calcula si debe mostrar textura de invulnerabilidad basado en el ciclo temporal
            float cycle_position = std::fmod(invulnerable_time_accumulator_, BLINK_PERIOD_S) / BLINK_PERIOD_S;
            bool should_show_invulnerable = cycle_position < white_proportion;
            size_t target_texture = should_show_invulnerable ? INVULNERABLE_TEXTURE : coffees_;

            // Solo cambia textura si es diferente (optimización)
            if (player_sprite_->getActiveTexture() != target_texture) {
                player_sprite_->setActiveTexture(target_texture);
            }
        } else {
            // Fin de invulnerabilidad
            invulnerable_time_accumulator_ = 0;
            setInvulnerable(false);
            player_sprite_->setActiveTexture(coffees_);
        }
    }
}

// Establece el valor de la variable
void Player::setPowerUp() {
    power_up_ = true;
    power_up_counter_ = POWERUP_COUNTER;
    power_up_time_accumulator_ = static_cast<float>(POWERUP_COUNTER) / 60.0F;  // Convert frames to seconds
    power_sprite_visible_ = true;                                              // Inicialmente visible cuando se activa el power-up
    in_power_up_ending_phase_ = false;                                         // Empezar en fase normal
    bullet_color_toggle_ = false;                                              // Resetear toggle
}

// Actualiza el valor de la variable (time-based)
void Player::updatePowerUp(float delta_time) {
    if (playing_state_ == State::PLAYING) {
        if (power_up_) {
            power_up_time_accumulator_ -= delta_time;
            power_up_ = power_up_time_accumulator_ > 0;
            if (!power_up_) {
                power_up_time_accumulator_ = 0;
                power_sprite_visible_ = false;
                in_power_up_ending_phase_ = false;
                bullet_color_toggle_ = false;
                // Los colores ahora se manejan dinámicamente en getNextBulletColor()
            } else {
                // Calcular visibilidad del power sprite
                const float TOTAL_POWERUP_TIME_S = static_cast<float>(POWERUP_COUNTER) / 60.0F;
                const float QUARTER_TIME_S = TOTAL_POWERUP_TIME_S / 4.0F;

                if (power_up_time_accumulator_ > QUARTER_TIME_S) {
                    // En los primeros 75% del tiempo, siempre visible
                    power_sprite_visible_ = true;
                    in_power_up_ending_phase_ = false;
                } else {
                    // En el último 25%, parpadea cada 20 frames (≈0.333s)
                    constexpr float BLINK_PERIOD_S = 20.0F / 60.0F;
                    constexpr float VISIBLE_PROPORTION = 4.0F / 20.0F;

                    float cycle_position = std::fmod(power_up_time_accumulator_, BLINK_PERIOD_S) / BLINK_PERIOD_S;
                    power_sprite_visible_ = cycle_position >= VISIBLE_PROPORTION;
                    in_power_up_ending_phase_ = true;  // Activar modo alternancia de colores de balas
                }
            }
        } else {
            power_sprite_visible_ = false;
            in_power_up_ending_phase_ = false;
            bullet_color_toggle_ = false;
        }
    }
}

// Concede un toque extra al jugador
void Player::giveExtraHit() {
    extra_hit_ = true;
    if (coffees_ < 2) {
        coffees_++;
        player_sprite_->setActiveTexture(coffees_);
    }
}

// Quita el toque extra al jugador
void Player::removeExtraHit() {
    if (coffees_ > 0) {
        coffees_--;
        setInvulnerable(true);
        player_sprite_->setActiveTexture(coffees_);
    }

    extra_hit_ = coffees_ != 0;
}

// Actualiza el circulo de colisión a la posición del jugador
void Player::shiftColliders() {
    collider_.x = static_cast<int>(pos_x_ + (WIDTH / 2));
    collider_.y = (pos_y_ + (HEIGHT / 2));
}

// Pone las texturas del jugador
void Player::setPlayerTextures(const std::vector<std::shared_ptr<Texture>>& texture) {  // NOLINT(readability-named-parameter)
    player_sprite_->setTexture(texture[0]);
    power_sprite_->setTexture(texture[1]);
}

// Actualiza el contador de continue (time-based)
void Player::updateContinueCounter(float delta_time) {
    if (playing_state_ == State::CONTINUE) {
        continue_time_accumulator_ += delta_time;
        constexpr float CONTINUE_INTERVAL_S = 1.0F;  // 1 segundo
        if (continue_time_accumulator_ >= CONTINUE_INTERVAL_S) {
            continue_time_accumulator_ -= CONTINUE_INTERVAL_S;
            decContinueCounter();
            if (continue_counter_ >= 0) { Scoreboard::get()->triggerPanelPulse(scoreboard_panel_, 0.4F); }
        }
    }
}

// Actualiza el contador de entrar nombre (time-based)
void Player::updateEnterNameCounter(float delta_time) {
    if (playing_state_ == State::ENTERING_NAME || playing_state_ == State::ENTERING_NAME_GAME_COMPLETED) {
        name_entry_time_accumulator_ += delta_time;
        constexpr float NAME_ENTRY_INTERVAL_S = 1.0F;  // 1 segundo
        if (name_entry_time_accumulator_ >= NAME_ENTRY_INTERVAL_S) {
            name_entry_time_accumulator_ -= NAME_ENTRY_INTERVAL_S;
            decNameEntryCounter();
        }
    }
}

// Actualiza el estado de SHOWING_NAME (time-based)
void Player::updateShowingName(float delta_time) {
    if (playing_state_ == State::SHOWING_NAME) {
        showing_name_time_accumulator_ += delta_time;
        constexpr float SHOWING_NAME_DURATION_S = 5.0F;  // 5 segundos
        if (showing_name_time_accumulator_ >= SHOWING_NAME_DURATION_S) {
            game_completed_ ? setPlayingState(State::LEAVING_SCREEN) : setPlayingState(State::CONTINUE);
        }
    }
}

// Decrementa el contador de continuar
void Player::decContinueCounter() {
    continue_time_accumulator_ = 0.0F;  // Reset time accumulator
    --continue_counter_;
    if (continue_counter_ < 0) {
        setPlayingState(State::CONTINUE_TIME_OUT);
    } else {
        playSound("continue_clock.wav");
    }
}

// Decrementa el contador de entrar nombre
void Player::decNameEntryCounter() {
    name_entry_time_accumulator_ = 0.0F;  // Reset time accumulator

    // Incrementa acumuladores de tiempo (1 segundo)
    name_entry_idle_time_accumulator_ += 1.0F;
    name_entry_total_time_accumulator_ += 1.0F;

    if ((name_entry_total_time_accumulator_ >= param.game.name_entry_total_time) ||
        (name_entry_idle_time_accumulator_ >= param.game.name_entry_idle_time)) {
        name_entry_total_time_accumulator_ = 0.0F;
        name_entry_idle_time_accumulator_ = 0.0F;
        if (playing_state_ == State::ENTERING_NAME) {
            last_enter_name_ = getRecordName();
            setPlayingState(State::SHOWING_NAME);
        } else {
            setPlayingState(State::LEAVING_SCREEN);
        }
    }
}

// Recoloca los sprites
void Player::shiftSprite() {
    player_sprite_->setPosX(pos_x_);
    player_sprite_->setPosY(pos_y_);
    power_sprite_->setPosX(getPosX() - power_up_x_offset_);
}

// Hace sonar un sonido
void Player::playSound(const std::string& name) const {
    if (demo_) {
        return;
    }

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

// Indica si se puede dibujar el objeto
auto Player::isRenderable() const -> bool {
    return !isTitleHidden();
};

// Devuelve el color actual de bala según el estado
auto Player::getBulletColor() const -> Bullet::Color {
    return power_up_ ? bullet_colors_.powered_color : bullet_colors_.normal_color;
}

// Devuelve el color para la próxima bala (alterna si está en modo toggle)
auto Player::getNextBulletColor() -> Bullet::Color {
    if (in_power_up_ending_phase_) {
        // En fase final: alternar entre colores powered y normal
        bullet_color_toggle_ = !bullet_color_toggle_;
        return bullet_color_toggle_ ? bullet_colors_.powered_color : bullet_colors_.normal_color;
    }
    // Modo normal: sin power-up = normal_color, con power-up = powered_color
    return power_up_ ? bullet_colors_.powered_color : bullet_colors_.normal_color;
}

// Establece los colores de bala para este jugador
void Player::setBulletColors(Bullet::Color normal, Bullet::Color powered) {
    bullet_colors_.normal_color = normal;
    bullet_colors_.powered_color = powered;
}

// Establece el archivo de sonido de bala para este jugador
void Player::setBulletSoundFile(const std::string& filename) {
    bullet_sound_file_ = filename;
}

// Añade una puntuación a la tabla de records
void Player::addScoreToScoreBoard() const {
    if (hi_score_table_ == nullptr) {
        return;  // Verificar esto antes de crear el manager
    }

    const auto ENTRY = HiScoreEntry(trim(getLastEnterName()), getScore(), get1CC());
    auto manager = std::make_unique<ManageHiScoreTable>(*hi_score_table_);

    if (glowing_entry_ != nullptr) {
        *glowing_entry_ = manager->add(ENTRY);
    }

    manager->saveToFile(Asset::get()->getPath("score.bin"));
}

void Player::addCredit() {
    ++credits_used_;
    playSound("credit.wav");
}

// ========================================
// SISTEMA DE DISPARO DE DOS LÍNEAS
// ========================================

// Método principal del sistema de disparo
void Player::updateFireSystem(float delta_time) {
    updateFunctionalLine(delta_time);  // Línea 1: CanFire
    updateVisualLine(delta_time);      // Línea 2: Animaciones
}

// LÍNEA 1: Sistema Funcional (CanFire)
void Player::updateFunctionalLine(float delta_time) {
    if (fire_cooldown_timer_ > 0) {
        fire_cooldown_timer_ -= delta_time;
        can_fire_new_system_ = false;
    } else {
        fire_cooldown_timer_ = 0;  // Evitar valores negativos
        can_fire_new_system_ = true;
    }
}

// LÍNEA 2: Sistema Visual (Animaciones)
void Player::updateVisualLine(float delta_time) {
    if (visual_fire_state_ == VisualFireState::NORMAL) {
        return;  // No hay temporizador activo en estado NORMAL
    }

    visual_state_timer_ -= delta_time;

    switch (visual_fire_state_) {
        case VisualFireState::AIMING:
            if (visual_state_timer_ <= 0) {
                transitionToRecoilingNew();
            }
            break;

        case VisualFireState::RECOILING:
            if (visual_state_timer_ <= 0) {
                transitionToThreatPose();
            }
            break;

        case VisualFireState::THREAT_POSE:
            if (visual_state_timer_ <= 0) {
                transitionToNormalNew();
            }
            break;

        case VisualFireState::NORMAL:
            // Ya manejado arriba
            break;
    }
}

// Inicia un disparo en ambas líneas
void Player::startFiringSystem(int cooldown_frames) {
    // LÍNEA 1: Inicia cooldown funcional
    fire_cooldown_timer_ = static_cast<float>(cooldown_frames) / 60.0F;  // Convertir frames a segundos
    can_fire_new_system_ = false;

    // LÍNEA 2: Resetea completamente el estado visual
    aiming_duration_ = fire_cooldown_timer_ * AIMING_DURATION_FACTOR;        // 50% del cooldown
    recoiling_duration_ = aiming_duration_ * RECOILING_DURATION_MULTIPLIER;  // 4 veces la duración de aiming

    visual_fire_state_ = VisualFireState::AIMING;
    visual_state_timer_ = aiming_duration_;

    updateFiringStateFromVisual();  // Sincroniza firing_state_ para animaciones
}

// Sincroniza firing_state_ con visual_fire_state_
void Player::updateFiringStateFromVisual() {
    // Mantener la dirección actual del disparo
    State base_state = State::FIRING_NONE;

    if (firing_state_ == State::FIRING_LEFT || firing_state_ == State::RECOILING_LEFT || firing_state_ == State::COOLING_LEFT) {
        base_state = State::FIRING_LEFT;
    } else if (firing_state_ == State::FIRING_RIGHT || firing_state_ == State::RECOILING_RIGHT || firing_state_ == State::COOLING_RIGHT) {
        base_state = State::FIRING_RIGHT;
    } else if (firing_state_ == State::FIRING_UP || firing_state_ == State::RECOILING_UP || firing_state_ == State::COOLING_UP) {
        base_state = State::FIRING_UP;
    }

    switch (visual_fire_state_) {
        case VisualFireState::NORMAL:
            firing_state_ = State::FIRING_NONE;
            break;

        case VisualFireState::AIMING:
            firing_state_ = base_state;  // FIRING_LEFT/RIGHT/UP
            break;

        case VisualFireState::RECOILING:
            switch (base_state) {
                case State::FIRING_LEFT:
                    firing_state_ = State::RECOILING_LEFT;
                    break;
                case State::FIRING_RIGHT:
                    firing_state_ = State::RECOILING_RIGHT;
                    break;
                case State::FIRING_UP:
                    firing_state_ = State::RECOILING_UP;
                    break;
                default:
                    firing_state_ = State::RECOILING_UP;
                    break;
            }
            break;

        case VisualFireState::THREAT_POSE:
            switch (base_state) {
                case State::FIRING_LEFT:
                    firing_state_ = State::COOLING_LEFT;
                    break;
                case State::FIRING_RIGHT:
                    firing_state_ = State::COOLING_RIGHT;
                    break;
                case State::FIRING_UP:
                    firing_state_ = State::COOLING_UP;
                    break;
                default:
                    firing_state_ = State::COOLING_UP;
                    break;
            }
            break;
    }
}

// Transiciones del sistema visual
void Player::transitionToRecoilingNew() {
    visual_fire_state_ = VisualFireState::RECOILING;
    visual_state_timer_ = recoiling_duration_;
    updateFiringStateFromVisual();
}

void Player::transitionToThreatPose() {
    visual_fire_state_ = VisualFireState::THREAT_POSE;

    // Calcular threat_pose_duration ajustada:
    // Duración original (833ms) menos el tiempo extra que ahora dura recoiling
    float original_recoiling_duration = fire_cooldown_timer_;                         // Era 100% del cooldown
    float new_recoiling_duration = aiming_duration_ * RECOILING_DURATION_MULTIPLIER;  // Ahora es más del cooldown
    float extra_recoiling_time = new_recoiling_duration - original_recoiling_duration;
    float adjusted_threat_duration = THREAT_POSE_DURATION - extra_recoiling_time;

    // Asegurar que no sea negativo
    visual_state_timer_ = std::max(adjusted_threat_duration, MIN_THREAT_POSE_DURATION);

    updateFiringStateFromVisual();
}

void Player::transitionToNormalNew() {
    visual_fire_state_ = VisualFireState::NORMAL;
    visual_state_timer_ = 0;
    updateFiringStateFromVisual();
}