// 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"
#include "game/entities/bullet_config.hpp"
#include "game/systems/enemy_event_dispatcher.hpp"

namespace Systems::Collision {

    namespace {

        // Trenca una bala en debris (8 fragments de l'octàgon) + so BULLET_ZAP + 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::BULLET_ZAP,
                bullet.getConfig().colors.normal,
                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;
        constexpr uint8_t NO_SHOOTER = 0xFF;
        constexpr uint8_t ENEMY_BASE = Defaults::Entities::ENEMY_OWNER_BASE;

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

            for (std::size_t i = 0; i < ctx.enemies.size(); ++i) {
                Enemy& enemy = ctx.enemies[i];
                // Self-shot: una bala d'enemic mai no impacta el seu propi creador.
                if (IS_ENEMY_BULLET && BULLET_OWNER == static_cast<uint8_t>(ENEMY_BASE + i)) {
                    continue;
                }
                if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), bullet.getCollisionRadius(), enemy, AMPLIFIER)) {
                    continue;
                }

                // *** COLISIÓN bullet → enemy ***
                // La cadena d'efectes (impulse, hurt, destroy, debris, score...) viu
                // al YAML de l'enemic via la secció `events:`. Si la bala és d'enemic,
                // no atribuïm el kill a cap player (NO_SHOOTER).
                const uint8_t SHOOTER = IS_ENEMY_BULLET ? NO_SHOOTER : BULLET_OWNER;
                Systems::EnemyEvents::dispatchEvent(ctx, enemy, EnemyEventType::ON_HIT, SHOOTER, &bullet);
                breakBullet(ctx.debris_manager, bullet);
                break;
            }
        }
    }

    void processWoundedDeaths(Context& ctx) {
        for (auto& enemy : ctx.enemies) {
            if (!enemy.woundExpiredThisFrame()) {
                continue;
            }
            enemy.consumeWoundExpired();
            Systems::EnemyEvents::dispatchEvent(ctx, enemy, EnemyEventType::ON_HURT_END, enemy.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 float DEATH_FACTOR = ctx.ships[i].getConfig().physics.death_impact_factor;
                    const Vec2 IMPULSE = SHIP_VEL * (ctx.ships[i].getBody().mass * DEATH_FACTOR);
                    touched_enemy->applyImpulse(IMPULSE);
                    // Sense bala: cap impuls de bala per als debris (mort per
                    // col·lisió cos-cos). Els debris hereten la inèrcia del ship.
                    ctx.on_player_hit(i, Vec2{});
                } else {
                    // Primer impacte → estat HURT (rebot físic ja resolt per PhysicsWorld;
                    // l'enemic no rep dany per decisió de disseny).
                    ctx.ships[i].hurt();
                }
            }
            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();
            // Les bales d'enemic NO són friendly fire: les gestiona
            // detectEnemyBulletShip. Sense aquest filtre, owner >= ENEMY_OWNER_BASE
            // entraria ací i faria un out-of-bounds a lives_per_player[owner].
            if (BULLET_OWNER >= Defaults::Entities::ENEMY_OWNER_BASE) {
                continue;
            }

            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.player1_active
                    : ctx.match_config.player2_active;
                if (!JUGADOR_ACTIU) {
                    continue;
                }

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

                // *** TEAMMATE HIT (friendly fire) ***
                // Víctima perd 1 vida, atacant en guanya 1. Friendly fire sempre
                // mata: el bullet va als debris (via tocado) i NO al cos del ship
                // — el cos està a punt de desactivar-se, qualsevol impuls seria
                // double-count amb la velocitat que ja reben els trossos.
                const Vec2 BULLET_VEL = bullet.getBody().velocity;
                ctx.on_player_hit(player_id, BULLET_VEL);
                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 detectEnemyBulletShip(Context& ctx) {
        constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER;
        constexpr uint8_t ENEMY_BASE = Defaults::Entities::ENEMY_OWNER_BASE;

        for (auto& bullet : ctx.bullets) {
            if (!bullet.isActive() || bullet.getOwnerId() < ENEMY_BASE) {
                continue;
            }
            for (uint8_t player_id = 0; player_id < 2; player_id++) {
                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.player1_active
                    : ctx.match_config.player2_active;
                if (!JUGADOR_ACTIU) {
                    continue;
                }
                if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), bullet.getCollisionRadius(), ctx.ships[player_id], AMPLIFIER)) {
                    continue;
                }

                // *** BALA D'ENEMIC → SHIP ***
                // Mata d'un sol toc (decisió de disseny). El bullet va als trossos
                // via tocado; el cos no rep impuls (seria double-count amb la
                // velocitat que ja hereten els trossos).
                const Vec2 BULLET_VEL = bullet.getBody().velocity;
                ctx.on_player_hit(player_id, BULLET_VEL);
                breakBullet(ctx.debris_manager, bullet);
                break;  // una bala impacta una vegada per frame
            }
        }
    }

    void detectAll(Context& ctx) {
        processWoundedDeaths(ctx);  // expiran ANTES de ser tocadas por bala este frame
        detectBulletEnemy(ctx);
        // Wounded chain desactivat: era massa fàcil que un enemic ferit topés
        // amb l'orb (10 HP) i el matés instantàniament. La regla
        // "ferit-toca-sa → ferit" queda permanentment fora.
        detectShipEnemy(ctx);
        detectBulletPlayer(ctx);
        detectEnemyBulletShip(ctx);
    }

    void desactivateOutOfBoundsBullets(
        std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& 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);

        for (auto& bullet : bullets) {
            if (!bullet.isActive()) {
                continue;
            }
            const float R = bullet.getCollisionRadius();
            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
