// collision_system.cpp - Implementación del sistema de colisiones

#include "game/systems/collision_system.hpp"

#include <cstdint>

#include "core/audio/audio.hpp"
#include "core/physics/collision.hpp"
#include "core/types.hpp"
#include "game/constants.hpp"

namespace Systems::Collision {

    namespace {
        constexpr uint8_t NO_SHOOTER = 0xFF;

        // Lookup tabla puntos / color por tipo de enemy (mantiene la lógica
        // anterior pero centralizada para reutilizar entre paths).
        auto scoreForType(EnemyType type) -> int {
            switch (type) {
                case EnemyType::PENTAGON:
                    return Defaults::Enemies::Scoring::PENTAGON_SCORE;
                case EnemyType::QUADRAT:
                    return Defaults::Enemies::Scoring::QUADRAT_SCORE;
                case EnemyType::MOLINILLO:
                    return Defaults::Enemies::Scoring::MOLINILLO_SCORE;
            }
            return 0;
        }

        auto colorForType(EnemyType type) -> SDL_Color {
            switch (type) {
                case EnemyType::PENTAGON:
                    return Defaults::Palette::PENTAGON;
                case EnemyType::QUADRAT:
                    return Defaults::Palette::QUADRAT;
                case EnemyType::MOLINILLO:
                    return Defaults::Palette::MOLINILLO;
            }
            return SDL_Color{};
        }

        // Mata al enemy con explosión: floating score, debris con velocity heredada,
        // sonido. Si shooter_id ≠ NO_SHOOTER, suma puntos a ese jugador.
        // CRUCIAL: leer velocity/datos ANTES de destruir() (que zera la velocity).
        void explodeNow(Context& ctx, Enemy& enemy, uint8_t shooter_id) {
            const Vec2 ENEMY_POS = enemy.getCenter();
            const Vec2 ENEMY_VEL = enemy.getVelocityVector();
            const float BRIGHTNESS = enemy.getBrightness();
            const auto SHAPE = enemy.getShape();
            const EnemyType TYPE = enemy.getType();

            const int POINTS = scoreForType(TYPE);
            const SDL_Color COLOR = colorForType(TYPE);

            if (shooter_id != NO_SHOOTER) {
                ctx.score_per_player[shooter_id] += POINTS;
            }
            ctx.floating_score_manager.crear(POINTS, ENEMY_POS);

            enemy.destruir();

            constexpr float VELOCITAT_EXPLOSIO = 80.0F;  // px/s (explosión suave)
            const Vec2 INHERITED_VEL = ENEMY_VEL * Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
            ctx.debris_manager.explode(
                SHAPE,
                ENEMY_POS,
                0.0F,  // angle (rotación interna del enemy)
                1.0F,  // escala
                VELOCITAT_EXPLOSIO,
                BRIGHTNESS,
                INHERITED_VEL,
                0.0F,  // sense herència angular: evita que els 5 trossos curvin en bloc
                0.0F,  // sin herencia visual
                Defaults::Sound::EXPLOSION,
                COLOR,
                Defaults::Physics::Debris::ENEMY_LIFETIME,
                Defaults::Physics::Debris::ENEMY_FRICTION,
                Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER);

            // Firework burst radial des del centro de l'enemic (efecte adicional al debris).
            // Línia blanca + halo daurat (WOUNDED) per a feel d'espurnes.
            ctx.firework_manager.spawn(ENEMY_POS,
                Defaults::FX::Firework::DEFAULT_COLOR,
                Defaults::FX::Firework::SPEED,
                Defaults::FX::Firework::N_POINTS,
                Defaults::FX::Firework::INITIAL_BRIGHTNESS,
                /*glow=*/true,
                Defaults::Palette::WOUNDED);
        }

        // Trenca una bala en debris (8 fragments de l'octàgon) + so HIT + desactiva.
        // S'invoca des de qualsevol desactivació de bala (impacte amb enemic, amb jugador,
        // o sortida del PLAYAREA) per a un feedback visual i sonor consistent.
        void breakBullet(Effects::DebrisManager& debris_manager, Bullet& bullet) {
            constexpr float DEBRIS_VELOCITY = 60.0F;
            debris_manager.explode(
                bullet.getShape(),
                bullet.getCenter(),
                bullet.getAngle(),
                1.0F,  // scale
                DEBRIS_VELOCITY,
                bullet.getBrightness(),
                Vec2{},  // sense herència de velocitat (fragments radials)
                0.0F,    // sense velocity angular heretada
                0.0F,    // sense rotació visual heretada
                Defaults::Sound::HIT,
                Defaults::Palette::BULLET,
                Defaults::Physics::Debris::TEMPS_VIDA,
                Defaults::Physics::Debris::ACCELERACIO,
                1);  // sense duplicat de segments
            bullet.desactivar();
        }
    }  // anonymous namespace

    void detectBulletEnemy(Context& ctx) {
        constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER;

        for (auto& bullet : ctx.bullets) {
            if (!bullet.isActive()) {
                continue;
            }
            for (auto& enemy : ctx.enemies) {
                if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), Defaults::Entities::BULLET_RADIUS, enemy, AMPLIFIER)) {
                    continue;
                }

                // *** COLISIÓN bullet → enemy ***
                // Empuje físico cuasi-realista: el impulse és el moment de la bala
                // (m·v) multiplicat pel factor de transferència. Direcció = vector
                // velocity de la bala (cap a on viatjava).
                const Vec2 IMPULSE = bullet.getBody().velocity *
                    (bullet.getBody().mass * Defaults::Physics::Bullet::IMPACT_MOMENTUM_FACTOR);
                enemy.applyImpulse(IMPULSE);

                const uint8_t SHOOTER = bullet.getOwnerId();

                if (enemy.isWounded()) {
                    // Segundo impacto sobre enemy ya herido → muerte instantánea,
                    // puntos al nuevo shooter.
                    explodeNow(ctx, enemy, SHOOTER);
                } else {
                    // Primer impacto → entra en estado herido (explosión diferida).
                    enemy.herir(SHOOTER);
                }

                breakBullet(ctx.debris_manager, bullet);
                break;  // Una bala impacta a un enemy y muere
            }
        }
    }

    void processWoundedDeaths(Context& ctx) {
        for (auto& enemy : ctx.enemies) {
            if (!enemy.woundExpiredThisFrame()) {
                continue;
            }
            enemy.consumeWoundExpired();
            explodeNow(ctx, enemy, enemy.getLastHitBy());
        }
    }

    void detectWoundedChain(Context& ctx) {
        const std::size_t N = ctx.enemies.size();
        for (std::size_t i = 0; i < N; i++) {
            Enemy& a = ctx.enemies[i];
            if (!a.isCollidable()) {
                continue;
            }
            for (std::size_t j = i + 1; j < N; j++) {
                Enemy& b = ctx.enemies[j];
                if (!b.isCollidable()) {
                    continue;
                }
                const bool A_WOUNDED = a.isWounded();
                const bool B_WOUNDED = b.isWounded();
                if (A_WOUNDED == B_WOUNDED) {
                    continue;  // ambos sanos o ambos heridos: nada que propagar
                }
                if (!Physics::checkCollision(a, b, 1.0F)) {
                    continue;
                }
                // El sano queda herido, propagando el shooter original.
                if (A_WOUNDED) {
                    b.herir(a.getLastHitBy());
                } else {
                    a.herir(b.getLastHitBy());
                }
            }
        }
    }

    void detectShipEnemy(Context& ctx) {
        constexpr float AMPLIFIER = Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER;

        for (uint8_t i = 0; i < 2; i++) {
            // Skip si ya tocado / muerto / invulnerable. NO actualitzem el flag de contacte:
            // mentre estem inactius no hi ha "frame anterior" rellevant, i el respawn ja el resetea.
            if (ctx.hit_timer_per_player[i] > 0.0F ||
                !ctx.ships[i].isActive() ||
                ctx.ships[i].isInvulnerable()) {
                continue;
            }

            // Comprovem si la nau toca QUALSEVOL enemic vulnerable aquest frame.
            Enemy* touched_enemy = nullptr;
            for (auto& enemy : ctx.enemies) {
                if (enemy.isInvulnerable()) {
                    continue;
                }
                if (Physics::checkCollision(ctx.ships[i], enemy, AMPLIFIER)) {
                    touched_enemy = &enemy;
                    break;
                }
            }
            const bool TOUCHING_NOW = touched_enemy != nullptr;

            // Edge-trigger: només compta com a impacte la transició no-tocant → tocant.
            // Així el contacte continu durant el rebot frame-a-frame no dispara HURT i mort
            // en frames consecutius.
            const bool RISING_EDGE = TOUCHING_NOW && !ctx.ships[i].wasTouchingEnemyPrevFrame();
            if (RISING_EDGE) {
                if (ctx.ships[i].isHurt()) {
                    // Segon impacte durant HURT → mort. Aplica un impuls afegit
                    // perquè l'enemic surti disparat (feedback visible).
                    const Vec2 SHIP_VEL = ctx.ships[i].getVelocityVector();
                    const Vec2 IMPULSE = SHIP_VEL * (Defaults::Ship::MASS * Defaults::Physics::Ship::DEATH_IMPACT_MOMENTUM_FACTOR);
                    touched_enemy->applyImpulse(IMPULSE);
                    ctx.on_player_hit(i);
                } else {
                    // Primer impacte → estat HURT (rebot físic ja resolt per PhysicsWorld;
                    // l'enemic no rep dany per decisió de disseny).
                    ctx.ships[i].herir();
                }
            }
            ctx.ships[i].setTouchingEnemyPrevFrame(TOUCHING_NOW);
        }
    }

    void detectBulletPlayer(Context& ctx) {
        if (!Defaults::Game::FRIENDLY_FIRE_ENABLED) {
            return;
        }

        constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER;

        for (auto& bullet : ctx.bullets) {
            if (!bullet.isActive()) {
                continue;
            }
            const uint8_t BULLET_OWNER = bullet.getOwnerId();

            for (uint8_t player_id = 0; player_id < 2; player_id++) {
                // Una bala mai no impacta al seu propi shooter: les bales d'aquest joc no
                // reboten ni el shooter pot atrapar-les, així que la prevenció és per disseny.
                if (BULLET_OWNER == player_id) {
                    continue;
                }
                if (ctx.hit_timer_per_player[player_id] > 0.0F ||
                    !ctx.ships[player_id].isActive() ||
                    ctx.ships[player_id].isInvulnerable()) {
                    continue;
                }
                const bool JUGADOR_ACTIU = (player_id == 0)
                    ? ctx.match_config.jugador1_actiu
                    : ctx.match_config.jugador2_actiu;
                if (!JUGADOR_ACTIU) {
                    continue;
                }

                if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), Defaults::Entities::BULLET_RADIUS, ctx.ships[player_id], AMPLIFIER)) {
                    continue;
                }

                // *** TEAMMATE HIT (friendly fire) ***
                // Víctima perd 1 vida, atacant en guanya 1. Apliquem l'impuls
                // de la bala a la nau ABANS de on_player_hit perquè tocado()
                // captura la velocitat per als debris (si no, queden quiets).
                const Vec2 BULLET_IMPULSE = bullet.getBody().velocity *
                    (bullet.getBody().mass * Defaults::Physics::Bullet::IMPACT_MOMENTUM_FACTOR);
                ctx.ships[player_id].getBody().applyImpulse(BULLET_IMPULSE);
                ctx.on_player_hit(player_id);
                ctx.lives_per_player[BULLET_OWNER]++;
                Audio::get()->playSound(Defaults::Sound::FRIENDLY_FIRE_HIT, Audio::Group::GAME);
                breakBullet(ctx.debris_manager, bullet);
                break;  // Una bullet solo impacta una vez por frame
            }
        }
    }

    void detectAll(Context& ctx) {
        processWoundedDeaths(ctx);  // expiran ANTES de ser tocadas por bala este frame
        detectBulletEnemy(ctx);
        detectWoundedChain(ctx);  // un herit pot ferir a un sa al fregar-lo
        detectShipEnemy(ctx);
        detectBulletPlayer(ctx);
    }

    void desactivateOutOfBoundsBullets(
        std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BALES) * 2>& bullets,
        Effects::DebrisManager& debris_manager) {
        float min_x;
        float max_x;
        float min_y;
        float max_y;
        Constants::getPlayAreaBounds(min_x, max_x, min_y, max_y);
        constexpr float R = Defaults::Entities::BULLET_RADIUS;

        for (auto& bullet : bullets) {
            if (!bullet.isActive()) {
                continue;
            }
            const Vec2& pos = bullet.getCenter();
            if (pos.x < min_x + R || pos.x > max_x - R ||
                pos.y < min_y + R || pos.y > max_y - R) {
                breakBullet(debris_manager, bullet);
            }
        }
    }

}  // namespace Systems::Collision
