// game_scene.cpp - Implementació de la lógica del juego
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3

#include "game_scene.hpp"

#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <vector>

#include "core/audio/audio.hpp"
#include "core/entities/entity.hpp"
#include "core/input/input.hpp"
#include "core/math/easing.hpp"
#include "core/physics/collision.hpp"
#include "core/rendering/line_renderer.hpp"
#include "core/system/scene_context.hpp"
#include "game/stage_system/stage_loader.hpp"
#include "game/systems/collision_system.hpp"
#include "game/systems/continue_system.hpp"
#include "game/systems/init_hud_animator.hpp"

// Using declarations per simplificar el codi
using SceneManager::SceneContext;
using SceneType = SceneContext::SceneType;
using Option = SceneContext::Option;

GameScene::GameScene(SDLManager& sdl, SceneContext& context)
    : sdl_(sdl),
      context_(context),
      debris_manager_(sdl.getRenderer()),
      floating_score_manager_(sdl.getRenderer()),
      text_(sdl.getRenderer()),
      init_hud_rect_sound_played_(false) {
    // Recuperar configuración de match des del context
    match_config_ = context_.getMatchConfig();

    // Debug output de la configuración
    std::cout << "[GameScene] Configuración de match - P1: "
              << (match_config_.jugador1_actiu ? "ACTIU" : "INACTIU")
              << ", P2: "
              << (match_config_.jugador2_actiu ? "ACTIU" : "INACTIU")
              << '\n';

    // Consumir opciones (preparació per MODE_DEMO futur)
    auto option = context_.consumeOption();
    (void)option;  // Suprimir warning de variable no usada

    // Inicialitzar naves con renderer (P1=ship.shp, P2=ship2.shp)
    ships_[0] = Ship(sdl.getRenderer(), "ship.shp");   // Jugador 1: nave estàndar
    ships_[1] = Ship(sdl.getRenderer(), "ship2.shp");  // Jugador 2: interceptor con ales

    // Inicialitzar balas con renderer
    for (auto& bullet : bullets_) {
        bullet = Bullet(sdl.getRenderer());
    }

    // Inicialitzar enemigos con renderer
    for (auto& enemy : enemies_) {
        enemy = Enemy(sdl.getRenderer());
    }

    // El resto del estado del juego (física, stages, naves, vidas, puntuación)
    // se inicializa en init(), que se llama al final del constructor para que
    // la escena esté lista en cuanto el Director la haya construido.
    init();
}

auto GameScene::isFinished() const -> bool {
    return context_.nextScene() != SceneType::GAME;
}

void GameScene::handleEvent(const SDL_Event& event) {
    // GameScene no procesa eventos puntuales SDL: la lógica de input se
    // resuelve en update() consultando Input::checkAction.
    (void)event;
}

void GameScene::init() {
    // Inicialitzar generador de números aleatoris
    // Basat en el codi Pascal original: line 376
    std::srand(static_cast<unsigned>(std::time(nullptr)));

    // Configurar el mundo físico con los límites de la zona de juego.
    // Las entidades se registrarán cada una al inicializarse (Fase 6c-e).
    physics_world_.clear();
    physics_world_.setBounds(Defaults::Zones::PLAYAREA);

    // [NEW] Load stage configuration (only once)
    if (!stage_config_) {
        stage_config_ = StageSystem::StageLoader::load("data/stages/stages.yaml");
        if (!stage_config_) {
            std::cerr << "[GameScene] Error: no s'ha pogut load stages.yaml" << '\n';
            // Continue without stage system (will crash, but helps debugging)
        }
    }

    // [NEW] Initialize stage manager
    stage_manager_ = std::make_unique<StageSystem::StageManager>(stage_config_.get());
    stage_manager_->init();

    // [NEW] Set ship position reference for safe spawn (P1 for now, TODO: dual tracking)
    stage_manager_->getSpawnController().setShipPosition(&ships_[0].getCenter());

    // Inicialitzar timers de muerte per player
    hit_timer_per_player_[0] = 0.0F;
    hit_timer_per_player_[1] = 0.0F;

    // Initialize lives and game over state (independent lives per player)
    lives_per_player_[0] = Defaults::Game::STARTING_LIVES;
    lives_per_player_[1] = Defaults::Game::STARTING_LIVES;
    game_over_state_ = GameOverState::NONE;
    continue_counter_ = 0;
    continue_tick_timer_ = 0.0F;
    continues_used_ = 0;
    game_over_timer_ = 0.0F;

    // Initialize scores (separate per player)
    score_per_player_[0] = 0;
    score_per_player_[1] = 0;
    floating_score_manager_.reset();

    // DEPRECATED: spawn_position_ ya no s'usa, es calcula dinàmicament con obtenir_punt_spawn(player_id)
    // const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
    // spawn_position_.x = zona.x + zona.w * 0.5f;
    // spawn_position_.y = zona.y + zona.h * Defaults::Game::INIT_HUD_SHIP_START_Y_RATIO;

    // Inicialitzar naves segons configuración (solo jugadors active)
    for (uint8_t i = 0; i < 2; i++) {
        bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;

        if (jugador_actiu) {
            // Jugador active: init normalment
            Vec2 spawn_pos = obtenir_punt_spawn(i);
            ships_[i].init(&spawn_pos, false);  // No invulnerability at start
            // Registrar el cuerpo físico de la nave en el mundo (Fase 6c)
            physics_world_.addBody(&ships_[i].getBody());
            std::cout << "[GameScene] Jugador " << (i + 1) << " inicialitzat\n";
        } else {
            // Jugador inactiu: marcar como a mort permanent
            ships_[i].markHit();
            hit_timer_per_player_[i] = 999.0F;  // Valor sentinella (permanent inactiu)
            lives_per_player_[i] = 0;         // Sin vides
            std::cout << "[GameScene] Jugador " << (i + 1) << " inactiu\n";
        }
    }

    // [MODIFIED] Initialize enemies as inactive (stage system will spawn them).
    // Registramos el body al world incluso inactivo: con radius=0 no colisiona
    // ni se mueve, y al init() del stage system se activa sin re-registrar.
    for (auto& enemy : enemies_) {
        enemy = Enemy(sdl_.getRenderer());
        enemy.setShipPosition(&ships_[0].getCenter());  // Set ship reference (P1 for now)
        physics_world_.addBody(&enemy.getBody());
        // DON'T call enemy.init() here - stage system handles spawning
    }

    // Inicialitzar balas (now 6 instead of 3).
    // Se registran en el physics_world para integración cinemática.
    // Como su body_.radius=0, no colisionan físicamente con nadie (las
    // colisiones de gameplay se gestionan en detectar_col·lisions_*).
    for (auto& bullet : bullets_) {
        bullet.init();
        physics_world_.addBody(&bullet.getBody());
    }

    // [ELIMINAT] Iniciar música de juego (ara es gestiona en stage_manager)
    // La música s'inicia cuando es transiciona de INIT_HUD a LEVEL_START
    // Audio::get()->playMusic("game.ogg");

    // Reset flag de sons de animación
    init_hud_rect_sound_played_ = false;
}

void GameScene::update(float delta_time) {
    // Orquestador delgado: cada paso vive en su propia función para
    // mantener update() legible y reducir complejidad cognitiva.
    stepPhysics(delta_time);

    if (game_over_state_ == GameOverState::NONE) {
        stepShootingInput();
        stepMidGameJoin();
    }
    if (stepContinueScreen(delta_time)) {
        return;
    }
    if (stepGameOver(delta_time)) {
        return;
    }
    stepDeathSequence(delta_time);
    stepStageStateMachine(delta_time);
}

void GameScene::stepPhysics(float delta_time) {
    // Las fuerzas aplicadas en el frame N-1 por processInput/AI se integran
    // ahora; postUpdate sincroniza los mirrors (center_/angle_) antes de la
    // lógica de juego que los lee.
    physics_world_.update(delta_time);
    for (auto& ship : ships_) {
        ship.postUpdate(delta_time);
    }
    for (auto& enemy : enemies_) {
        enemy.postUpdate(delta_time);
    }
    for (auto& bullet : bullets_) {
        bullet.postUpdate(delta_time);
    }
}

void GameScene::stepShootingInput() {
    auto* input = Input::get();
    if (match_config_.jugador1_actiu &&
        input->checkActionPlayer1(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
        disparar_bala(0);
    }
    if (match_config_.jugador2_actiu &&
        input->checkActionPlayer2(InputAction::SHOOT, Input::DO_NOT_ALLOW_REPEAT)) {
        disparar_bala(1);
    }
}

void GameScene::stepMidGameJoin() {
    // Permitir join solo durante PLAYING.
    if (stage_manager_->get_estat() != StageSystem::EstatStage::PLAYING) {
        return;
    }

    // Solo se permite join si hay al menos un jugador vivo (no se puede
    // hacer join en pantalla vacía).
    const bool ALGU_VIU =
        (match_config_.jugador1_actiu && hit_timer_per_player_[0] != 999.0F) ||
        (match_config_.jugador2_actiu && hit_timer_per_player_[1] != 999.0F);
    if (!ALGU_VIU) {
        return;
    }

    auto* input = Input::get();
    for (uint8_t pid = 0; pid < 2; pid++) {
        const bool ACTIU = (pid == 0) ? match_config_.jugador1_actiu
                                       : match_config_.jugador2_actiu;
        const bool MUERTO_SIN_VIDAS = hit_timer_per_player_[pid] == 999.0F;
        if (ACTIU && !MUERTO_SIN_VIDAS) {
            continue;  // jugador ya está jugando
        }
        const bool START_PRESSED = (pid == 0)
            ? input->checkActionPlayer1(InputAction::START, Input::DO_NOT_ALLOW_REPEAT)
            : input->checkActionPlayer2(InputAction::START, Input::DO_NOT_ALLOW_REPEAT);
        if (START_PRESSED) {
            unir_jugador(pid);
        }
    }
}

auto GameScene::stepContinueScreen(float delta_time) -> bool {
    if (game_over_state_ != GameOverState::CONTINUE) {
        return false;
    }

    Systems::ContinueScreen::Context cont_ctx{
        .state = game_over_state_,
        .counter = continue_counter_,
        .tick_timer = continue_tick_timer_,
        .continues_used = continues_used_,
        .game_over_timer = game_over_timer_,
        .lives_per_player = lives_per_player_,
        .score_per_player = score_per_player_,
        .hit_timer_per_player = hit_timer_per_player_,
        .ships = ships_,
        .match_config = match_config_,
        .get_spawn_point = [this](uint8_t pid) { return obtenir_punt_spawn(pid); },
    };
    Systems::ContinueScreen::update(cont_ctx, delta_time);
    Systems::ContinueScreen::processInput(cont_ctx);

    // Enemies, bullets y efectos siguen moviéndose en background.
    for (auto& enemy : enemies_) {
        enemy.update(delta_time);
    }
    for (auto& bullet : bullets_) {
        bullet.update(delta_time);
    }
    debris_manager_.update(delta_time);
    floating_score_manager_.update(delta_time);
    return true;
}

auto GameScene::stepGameOver(float delta_time) -> bool {
    if (game_over_state_ != GameOverState::GAME_OVER) {
        return false;
    }

    game_over_timer_ -= delta_time;
    if (game_over_timer_ <= 0.0F) {
        Audio::get()->stopMusic();
        context_.setNextScene(SceneType::TITLE);
        return true;
    }

    // Enemies, bullets y efectos siguen moviéndose como fondo.
    for (auto& enemy : enemies_) {
        enemy.update(delta_time);
    }
    for (auto& bullet : bullets_) {
        bullet.update(delta_time);
    }
    debris_manager_.update(delta_time);
    floating_score_manager_.update(delta_time);
    return true;
}

auto GameScene::stepDeathSequence(float delta_time) -> bool {
    bool algun_mort = false;
    for (uint8_t i = 0; i < 2; i++) {
        if (hit_timer_per_player_[i] <= 0.0F || hit_timer_per_player_[i] >= 999.0F) {
            continue;
        }
        algun_mort = true;
        hit_timer_per_player_[i] += delta_time;

        if (hit_timer_per_player_[i] < Defaults::Game::DEATH_DURATION) {
            continue;
        }

        // *** PHASE 3: RESPAWN OR GAME OVER ***
        lives_per_player_[i]--;
        if (lives_per_player_[i] > 0) {
            Vec2 spawn_pos = obtenir_punt_spawn(i);
            ships_[i].init(&spawn_pos, /*activar_invulnerabilitat=*/true);
            hit_timer_per_player_[i] = 0.0F;
            continue;
        }

        // Sin vidas: marcar definitivamente muerto y comprobar transición a CONTINUE.
        hit_timer_per_player_[i] = 999.0F;
        const bool P1_DEAD = !match_config_.jugador1_actiu || lives_per_player_[0] <= 0;
        const bool P2_DEAD = !match_config_.jugador2_actiu || lives_per_player_[1] <= 0;
        if (P1_DEAD && P2_DEAD) {
            game_over_state_ = GameOverState::CONTINUE;
            continue_counter_ = Defaults::Game::CONTINUE_COUNT_START;
            continue_tick_timer_ = Defaults::Game::CONTINUE_TICK_DURATION;
        }
    }

    // Si hay algún muerto, los enemigos/balas/efectos siguen actualizándose
    // aunque otros jugadores aún jueguen.
    if (algun_mort) {
        for (auto& enemy : enemies_) {
            enemy.update(delta_time);
        }
        for (auto& bullet : bullets_) {
            bullet.update(delta_time);
        }
        debris_manager_.update(delta_time);
        floating_score_manager_.update(delta_time);
    }
    return algun_mort;
}

void GameScene::stepStageStateMachine(float delta_time) {
    const StageSystem::EstatStage STATE = stage_manager_->get_estat();
    switch (STATE) {
        case StageSystem::EstatStage::INIT_HUD:
            runStageInitHud(delta_time);
            break;
        case StageSystem::EstatStage::LEVEL_START:
            runStageLevelStart(delta_time);
            break;
        case StageSystem::EstatStage::PLAYING:
            runStagePlaying(delta_time);
            break;
        case StageSystem::EstatStage::LEVEL_COMPLETED:
            runStageLevelCompleted(delta_time);
            break;
    }
}

void GameScene::runStageInitHud(float delta_time) {
    // Update stage manager timer (puede cambiar el state).
    stage_manager_->update(delta_time);
    // Si el state cambió, salir para no usar el timer del nuevo state.
    if (stage_manager_->get_estat() != StageSystem::EstatStage::INIT_HUD) {
        return;
    }

    float global_progress = 1.0F - (stage_manager_->get_timer_transicio() / Defaults::Game::INIT_HUD_DURATION);
    global_progress = std::min(1.0F, global_progress);

    const float SHIP1_P = Systems::InitHud::computeRangeProgress(
        global_progress,
        Defaults::Game::INIT_HUD_SHIP1_RATIO_INIT,
        Defaults::Game::INIT_HUD_SHIP1_RATIO_END);
    const float SHIP2_P = Systems::InitHud::computeRangeProgress(
        global_progress,
        Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT,
        Defaults::Game::INIT_HUD_SHIP2_RATIO_END);

    if (match_config_.jugador1_actiu && SHIP1_P < 1.0F) {
        ships_[0].setCenter(Systems::InitHud::computeShipPosition(SHIP1_P, obtenir_punt_spawn(0)));
    }
    if (match_config_.jugador2_actiu && SHIP2_P < 1.0F) {
        ships_[1].setCenter(Systems::InitHud::computeShipPosition(SHIP2_P, obtenir_punt_spawn(1)));
    }
}

void GameScene::runStageLevelStart(float delta_time) {
    stage_manager_->update(delta_time);

    // Ambas naves pueden moverse y disparar durante el intro.
    for (uint8_t i = 0; i < 2; i++) {
        const bool ACTIU = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
        if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
            ships_[i].processInput(delta_time, i);
            ships_[i].update(delta_time);
        }
    }
    for (auto& bullet : bullets_) {
        bullet.update(delta_time);
    }
    debris_manager_.update(delta_time);
}

void GameScene::runStagePlaying(float delta_time) {
    const bool PAUSE_SPAWN = (hit_timer_per_player_[0] > 0.0F && hit_timer_per_player_[1] > 0.0F);
    stage_manager_->getSpawnController().update(delta_time, enemies_, PAUSE_SPAWN);

    // Stage completado: cuando al menos un jugador está vivo y todos los enemies muertos.
    const bool ALGU_VIU = (hit_timer_per_player_[0] == 0.0F || hit_timer_per_player_[1] == 0.0F);
    if (ALGU_VIU && stage_manager_->getSpawnController().tots_enemics_destruits(enemies_)) {
        stage_manager_->stage_completat();
        Audio::get()->playSound(Defaults::Sound::GOOD_JOB_COMMANDER, Audio::Group::GAME);
        return;
    }

    // Gameplay normal: ships activos + entidades + colisiones + efectos.
    for (uint8_t i = 0; i < 2; i++) {
        const bool ACTIU = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
        if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
            ships_[i].processInput(delta_time, i);
            ships_[i].update(delta_time);
        }
    }
    for (auto& enemy : enemies_) {
        enemy.update(delta_time);
    }
    for (auto& bullet : bullets_) {
        bullet.update(delta_time);
    }

    runCollisionDetections();
    debris_manager_.update(delta_time);
    floating_score_manager_.update(delta_time);
}

void GameScene::runStageLevelCompleted(float delta_time) {
    stage_manager_->update(delta_time);
    for (uint8_t i = 0; i < 2; i++) {
        const bool ACTIU = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
        if (ACTIU && hit_timer_per_player_[i] == 0.0F) {
            ships_[i].processInput(delta_time, i);
            ships_[i].update(delta_time);
        }
    }
    for (auto& bullet : bullets_) {
        bullet.update(delta_time);
    }
    debris_manager_.update(delta_time);
    floating_score_manager_.update(delta_time);
}

void GameScene::runCollisionDetections() {
    Systems::Collision::Context col_ctx{
        .ships = ships_,
        .enemies = enemies_,
        .bullets = bullets_,
        .hit_timer_per_player = hit_timer_per_player_,
        .score_per_player = score_per_player_,
        .lives_per_player = lives_per_player_,
        .debris_manager = debris_manager_,
        .floating_score_manager = floating_score_manager_,
        .match_config = match_config_,
        .on_player_hit = [this](uint8_t pid) { tocado(pid); },
    };
    Systems::Collision::detectAll(col_ctx);
}

void GameScene::draw() {
    // Handle CONTINUE screen
    if (game_over_state_ == GameOverState::CONTINUE) {
        // Draw game background elements first
        dibuixar_marges();

        for (const auto& enemy : enemies_) {
            enemy.draw();
        }

        for (const auto& bullet : bullets_) {
            bullet.draw();
        }

        debris_manager_.draw();
        floating_score_manager_.draw();
        dibuixar_marcador();

        // Draw CONTINUE screen overlay
        dibuixar_continue();
        return;
    }

    // Handle final GAME OVER state
    if (game_over_state_ == GameOverState::GAME_OVER) {
        // Game over: draw enemies, bullets, debris, and "GAME OVER" text
        dibuixar_marges();

        for (const auto& enemy : enemies_) {
            enemy.draw();
        }

        for (const auto& bullet : bullets_) {
            bullet.draw();
        }

        debris_manager_.draw();
        floating_score_manager_.draw();

        // Draw centered "GAME OVER" text
        const std::string game_over_text = "GAME OVER";
        constexpr float scale = Defaults::Game::GameOverScreen::TEXT_SCALE;
        constexpr float spacing = Defaults::Game::GameOverScreen::TEXT_SPACING;

        // Calcular centro de l'àrea de juego usant constants
        const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
        float centre_x = play_area.x + (play_area.w / 2.0F);
        float centre_y = play_area.y + (play_area.h / 2.0F);

        text_.renderCentered(game_over_text, {.x = centre_x, .y = centre_y}, scale, spacing);

        dibuixar_marcador();
        return;
    }

    // [NEW] Stage state rendering
    StageSystem::EstatStage state = stage_manager_->get_estat();

    switch (state) {
        case StageSystem::EstatStage::INIT_HUD: {
            // Calcular progrés de cada animación independent
            float timer = stage_manager_->get_timer_transicio();
            float total_time = Defaults::Game::INIT_HUD_DURATION;
            float global_progress = 1.0F - (timer / total_time);

            // [NEW] Calcular progress independiente para cada elemento
            float rect_progress = Systems::InitHud::computeRangeProgress(
                global_progress,
                Defaults::Game::INIT_HUD_RECT_RATIO_INIT,
                Defaults::Game::INIT_HUD_RECT_RATIO_END);

            float score_progress = Systems::InitHud::computeRangeProgress(
                global_progress,
                Defaults::Game::INIT_HUD_SCORE_RATIO_INIT,
                Defaults::Game::INIT_HUD_SCORE_RATIO_END);

            float ship1_progress = Systems::InitHud::computeRangeProgress(
                global_progress,
                Defaults::Game::INIT_HUD_SHIP1_RATIO_INIT,
                Defaults::Game::INIT_HUD_SHIP1_RATIO_END);

            float ship2_progress = Systems::InitHud::computeRangeProgress(
                global_progress,
                Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT,
                Defaults::Game::INIT_HUD_SHIP2_RATIO_END);

            // Dibuixar elements animats
            if (rect_progress > 0.0F) {
                // [NOU] Reproduir so cuando comença l'animación del rectangle
                if (!init_hud_rect_sound_played_) {
                    Audio::get()->playSound(Defaults::Sound::INIT_HUD, Audio::Group::GAME);
                    init_hud_rect_sound_played_ = true;
                }

                Systems::InitHud::drawBordersAnimated(sdl_.getRenderer(), rect_progress);
            }

            if (score_progress > 0.0F) {
                Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboard(), score_progress);
            }

            // [MODIFICAT] Dibuixar naves con progress independent
            if (ship1_progress > 0.0F && match_config_.jugador1_actiu && !ships_[0].isHit()) {
                ships_[0].draw();
            }

            if (ship2_progress > 0.0F && match_config_.jugador2_actiu && !ships_[1].isHit()) {
                ships_[1].draw();
            }

            break;
        }

        case StageSystem::EstatStage::LEVEL_START:
            dibuixar_marges();
            // [NEW] Draw both ships if active and alive
            for (uint8_t i = 0; i < 2; i++) {
                bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
                if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) {
                    ships_[i].draw();
                }
            }

            // [NEW] Draw bullets
            for (const auto& bullet : bullets_) {
                bullet.draw();
            }

            // [NEW] Draw debris
            debris_manager_.draw();
            floating_score_manager_.draw();

            // [EXISTING] Draw intro message and score
            dibuixar_missatge_stage(stage_manager_->get_missatge_level_start());
            dibuixar_marcador();
            break;

        case StageSystem::EstatStage::PLAYING:
            dibuixar_marges();

            // [EXISTING] Normal rendering - active ships
            for (uint8_t i = 0; i < 2; i++) {
                bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
                if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) {
                    ships_[i].draw();
                }
            }

            for (const auto& enemy : enemies_) {
                enemy.draw();
            }

            for (const auto& bullet : bullets_) {
                bullet.draw();
            }

            debris_manager_.draw();
            floating_score_manager_.draw();
            dibuixar_marcador();
            break;

        case StageSystem::EstatStage::LEVEL_COMPLETED:
            dibuixar_marges();
            // [NEW] Draw both ships if active and alive
            for (uint8_t i = 0; i < 2; i++) {
                bool jugador_actiu = (i == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu;
                if (jugador_actiu && hit_timer_per_player_[i] == 0.0F) {
                    ships_[i].draw();
                }
            }

            // [NEW] Draw bullets (allow last shots to be visible)
            for (const auto& bullet : bullets_) {
                bullet.draw();
            }

            // [NEW] Draw debris (from last destroyed enemies)
            debris_manager_.draw();
            floating_score_manager_.draw();

            // [EXISTING] Draw completion message and score
            dibuixar_missatge_stage(StageSystem::Constants::MISSATGE_LEVEL_COMPLETED);
            dibuixar_marcador();
            break;
    }
}

void GameScene::tocado(uint8_t player_id) {
    // Death sequence: 3 phases
    // Phase 1: First call (hit_timer_per_player_[player_id] == 0) - trigger explosion
    // Phase 2: Animation (0 < itocado_ < 3.0s) - debris animation
    // Phase 3: Respawn or game over (itocado_ >= 3.0s) - handled in update()

    if (hit_timer_per_player_[player_id] == 0.0F) {
        // *** PHASE 1: TRIGGER DEATH ***

        // Mark ship as dead (stops rendering and input)
        ships_[player_id].markHit();

        // Create ship explosion
        const Vec2& ship_pos = ships_[player_id].getCenter();
        float ship_angle = ships_[player_id].getAngle();
        Vec2 vel_nau = ships_[player_id].getVelocityVector();
        // Reduir a 80% la velocity heretada per la ship (més realista)
        Vec2 vel_nau_80 = {.x = vel_nau.x * 0.8F, .y = vel_nau.y * 0.8F};

        debris_manager_.explode(
            ships_[player_id].getShape(),               // Ship shape (3 lines)
            ship_pos,                                   // Center position
            ship_angle,                                 // Ship orientation
            1.0F,                                       // Normal scale
            Defaults::Physics::Debris::VELOCITAT_BASE,  // 80 px/s
            ships_[player_id].getBrightness(),          // Heredar brightness
            vel_nau_80,                                 // Heredar 80% velocity
            0.0F,                                       // Nave: trayectorias rectas (sin drotacio)
            0.0F,                                       // Sin herencia visual (rotación aleatoria)
            Defaults::Sound::EXPLOSION2,                // Sonido alternativo para la explosión
            Defaults::Palette::SHIP                     // Debris hereda color de la nave
        );

        // Start death timer (non-zero to avoid re-triggering)
        hit_timer_per_player_[player_id] = 0.001F;
    }
    // Phase 2 is automatic (debris updates in update())
    // Phase 3 is handled in update() when hit_timer_per_player_ >= DEATH_DURATION
}

void GameScene::dibuixar_marges() const {
    // Dibuixar rectangle de la zona de juego
    const SDL_FRect& zona = Defaults::Zones::PLAYAREA;

    // Coordenades dels cantons
    int x1 = static_cast<int>(zona.x);
    int y1 = static_cast<int>(zona.y);
    int x2 = static_cast<int>(zona.x + zona.w);
    int y2 = static_cast<int>(zona.y + zona.h);

    // 4 línies per formar el rectangle
    Rendering::linea(sdl_.getRenderer(), x1, y1, x2, y1);  // Top
    Rendering::linea(sdl_.getRenderer(), x1, y2, x2, y2);  // Bottom
    Rendering::linea(sdl_.getRenderer(), x1, y1, x1, y2);  // Left
    Rendering::linea(sdl_.getRenderer(), x2, y1, x2, y2);  // Right
}

void GameScene::dibuixar_marcador() {
    // Construir text del marcador
    std::string text = buildScoreboard();

    // Parámetros de renderització
    const float scale = 0.85F;
    const float spacing = 0.0F;

    // Calcular centro de la zona del marcador
    const SDL_FRect& scoreboard = Defaults::Zones::SCOREBOARD;
    float centre_x = scoreboard.w / 2.0F;
    float centre_y = scoreboard.y + (scoreboard.h / 2.0F);

    // Renderizar centrat
    text_.renderCentered(text, {.x = centre_x, .y = centre_y}, scale, spacing);
}

std::string GameScene::buildScoreboard() const {
    // Puntuación P1 (6 dígits) - mostrar zeros si inactiu
    std::string score_p1;
    std::string vides_p1;
    if (match_config_.jugador1_actiu) {
        score_p1 = std::to_string(score_per_player_[0]);
        score_p1 = std::string(6 - std::min(6, static_cast<int>(score_p1.length())), '0') + score_p1;
        vides_p1 = (lives_per_player_[0] < 10)
            ? "0" + std::to_string(lives_per_player_[0])
            : std::to_string(lives_per_player_[0]);
    } else {
        score_p1 = "000000";
        vides_p1 = "00";
    }

    // Nivel (2 dígits)
    uint8_t stage_num = stage_manager_->get_stage_actual();
    std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num)
                                             : std::to_string(stage_num);

    // Puntuación P2 (6 dígits) - mostrar zeros si inactiu
    std::string score_p2;
    std::string vides_p2;
    if (match_config_.jugador2_actiu) {
        score_p2 = std::to_string(score_per_player_[1]);
        score_p2 = std::string(6 - std::min(6, static_cast<int>(score_p2.length())), '0') + score_p2;
        vides_p2 = (lives_per_player_[1] < 10)
            ? "0" + std::to_string(lives_per_player_[1])
            : std::to_string(lives_per_player_[1]);
    } else {
        score_p2 = "000000";
        vides_p2 = "00";
    }

    // Format: "123456 03  LEVEL 01  654321 02"
    // Nota: dos espais entre seccions, mantenir ambdós slots siempre visibles
    return score_p1 + " " + vides_p1 + "  LEVEL " + stage_str + "  " + score_p2 + " " + vides_p2;
}

// [NEW] Stage system helper methods

void GameScene::dibuixar_missatge_stage(const std::string& message) {
    constexpr float escala_base = 1.0F;
    constexpr float spacing = 2.0F;

    const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
    const float max_width = play_area.w * Defaults::Game::STAGE_MESSAGE_MAX_WIDTH_RATIO;

    // ========== TYPEWRITER EFFECT (PARAMETRIZED) ==========
    // Get state-specific timing configuration
    float total_time;
    float typing_ratio;

    if (stage_manager_->get_estat() == StageSystem::EstatStage::LEVEL_START) {
        total_time = Defaults::Game::LEVEL_START_DURATION;
        typing_ratio = Defaults::Game::LEVEL_START_TYPING_RATIO;
    } else {  // LEVEL_COMPLETED
        total_time = Defaults::Game::LEVEL_COMPLETED_DURATION;
        typing_ratio = Defaults::Game::LEVEL_COMPLETED_TYPING_RATIO;
    }

    // Calculate progress from timer (0.0 at start → 1.0 at end)
    float remaining_time = stage_manager_->get_timer_transicio();
    float progress = 1.0F - (remaining_time / total_time);

    // Determine how many characters to show
    size_t visible_chars;

    if (typing_ratio > 0.0F && progress < typing_ratio) {
        // Typewriter phase: show partial text
        float typing_progress = progress / typing_ratio;  // Normalize to 0.0-1.0
        visible_chars = static_cast<size_t>(message.length() * typing_progress);
        if (visible_chars == 0 && progress > 0.0F) {
            visible_chars = 1;  // Show at least 1 character after first frame
        }
    } else {
        // Display phase: show complete text
        // (Either after typing phase, or immediately if typing_ratio == 0.0)
        visible_chars = message.length();
    }

    // Create partial message (substring for typewriter)
    std::string partial_message = message.substr(0, visible_chars);
    // ===================================================

    // Calculate text width at base scale (using FULL message for position calculation)
    float text_width_at_base = text_.get_text_width(message, escala_base, spacing);

    // Auto-scale if text exceeds max width
    float scale = (text_width_at_base <= max_width)
        ? escala_base
        : max_width / text_width_at_base;

    // Recalculate dimensions with final scale (using FULL message for centering)
    float full_text_width = text_.get_text_width(message, scale, spacing);
    float text_height = text_.get_text_height(scale);

    // Calculate position as if FULL text was there (for fixed position typewriter)
    float x = play_area.x + ((play_area.w - full_text_width) / 2.0F);
    float y = play_area.y + (play_area.h * Defaults::Game::STAGE_MESSAGE_Y_RATIO) - (text_height / 2.0F);

    // Render only the partial message (typewriter effect)
    Vec2 pos = {.x = x, .y = y};
    text_.render(partial_message, pos, scale, spacing);
}

// ========================================
// Helper methods for 2-player support
// ========================================

Vec2 GameScene::obtenir_punt_spawn(uint8_t player_id) const {
    const SDL_FRect& zona = Defaults::Zones::PLAYAREA;

    float x_ratio;
    if (match_config_.es_un_jugador()) {
        // Un sol player: spawn al centro (50%)
        x_ratio = 0.5F;
    } else {
        // Dos jugadors: spawn a posicions separades
        x_ratio = (player_id == 0)
            ? Defaults::Game::P1_SPAWN_X_RATIO   // 33%
            : Defaults::Game::P2_SPAWN_X_RATIO;  // 67%
    }

    return {
        .x = zona.x + (zona.w * x_ratio),
        .y = zona.y + (zona.h * Defaults::Game::SPAWN_Y_RATIO)};
}

void GameScene::disparar_bala(uint8_t player_id) {
    // Verificar que el player está vivo
    if (hit_timer_per_player_[player_id] > 0.0F) {
        return;
    }
    if (!ships_[player_id].isAlive()) {
        return;
    }

    // Calcular posición en la punta de la nave
    const Vec2& ship_centre = ships_[player_id].getCenter();
    float ship_angle = ships_[player_id].getAngle();

    constexpr float LOCAL_TIP_X = 0.0F;
    constexpr float LOCAL_TIP_Y = -12.0F;
    float cos_a = std::cos(ship_angle);
    float sin_a = std::sin(ship_angle);
    float tip_x = (LOCAL_TIP_X * cos_a) - (LOCAL_TIP_Y * sin_a) + ship_centre.x;
    float tip_y = (LOCAL_TIP_X * sin_a) + (LOCAL_TIP_Y * cos_a) + ship_centre.y;
    Vec2 posicio_dispar = {.x = tip_x, .y = tip_y};

    // Buscar primera bullet inactiva en el pool del player
    int start_idx = player_id * 3;  // P1=[0,1,2], P2=[3,4,5]
    for (int i = start_idx; i < start_idx + 3; i++) {
        if (!bullets_[i].esta_activa()) {
            bullets_[i].disparar(posicio_dispar, ship_angle, player_id);
            break;
        }
    }
}

void GameScene::dibuixar_continue() {
    const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
    constexpr float spacing = 4.0F;

    // "CONTINUE" text (using constants)
    const std::string continue_text = "CONTINUE";
    float escala_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_SCALE;
    float y_ratio_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_Y_RATIO;

    float centre_x = play_area.x + (play_area.w / 2.0F);
    float centre_y_continue = play_area.y + (play_area.h * y_ratio_continue);

    text_.renderCentered(continue_text, {.x = centre_x, .y = centre_y_continue}, escala_continue, spacing);

    // Countdown number (using constants)
    const std::string counter_str = std::to_string(continue_counter_);
    float escala_counter = Defaults::Game::ContinueScreen::COUNTER_TEXT_SCALE;
    float y_ratio_counter = Defaults::Game::ContinueScreen::COUNTER_TEXT_Y_RATIO;

    float centre_y_counter = play_area.y + (play_area.h * y_ratio_counter);

    text_.renderCentered(counter_str, {.x = centre_x, .y = centre_y_counter}, escala_counter, spacing);

    // "CONTINUES LEFT" (conditional + using constants)
    if (!Defaults::Game::INFINITE_CONTINUES) {
        const std::string continues_text = "CONTINUES LEFT: " + std::to_string(Defaults::Game::MAX_CONTINUES - continues_used_);
        float escala_info = Defaults::Game::ContinueScreen::INFO_TEXT_SCALE;
        float y_ratio_info = Defaults::Game::ContinueScreen::INFO_TEXT_Y_RATIO;

        float centre_y_info = play_area.y + (play_area.h * y_ratio_info);

        text_.renderCentered(continues_text, {.x = centre_x, .y = centre_y_info}, escala_info, spacing);
    }
}

void GameScene::unir_jugador(uint8_t player_id) {
    // Activate player
    if (player_id == 0) {
        match_config_.jugador1_actiu = true;
    } else {
        match_config_.jugador2_actiu = true;
    }

    // Reset stats
    lives_per_player_[player_id] = Defaults::Game::STARTING_LIVES;
    score_per_player_[player_id] = 0;
    hit_timer_per_player_[player_id] = 0.0F;

    // Spawn with invulnerability
    Vec2 spawn_pos = obtenir_punt_spawn(player_id);
    ships_[player_id].init(&spawn_pos, true);

    // No visual message, just spawn (per user requirement)

    std::cout << "[GameScene] Jugador " << (int)(player_id + 1) << " s'ha unit a la match!" << '\n';
}
