// enemy_event_dispatcher.cpp - Implementació del dispatcher d'events d'enemic
// © 2026 JailDesigner

#include "game/systems/enemy_event_dispatcher.hpp"

#include <cmath>
#include <cstdint>
#include <cstdlib>

#include "core/defaults.hpp"
#include "core/types.hpp"
#include "game/constants.hpp"
#include "game/entities/bullet.hpp"
#include "game/entities/bullet_config.hpp"
#include "game/entities/bullet_registry.hpp"
#include "game/entities/enemy_ai.hpp"
#include "game/entities/enemy_config.hpp"
#include "game/entities/ship.hpp"

namespace Systems::EnemyEvents {

    namespace {
        constexpr uint8_t NO_SHOOTER = 0xFF;

        void doAddScore(Systems::Collision::Context& ctx, const Enemy& enemy, uint8_t shooter) {
            const int POINTS = enemy.getConfig().score;
            if (shooter != NO_SHOOTER) {
                ctx.score_per_player[shooter] += POINTS;
            }
            ctx.floating_score_manager.crear(POINTS, enemy.getCenter());
        }

        // Helper compartit per CREATE_DEBRIS i CREATE_DEBRIS_PARTIAL: única
        // crida a explode(), paràmetres alineats; canvien piece_scale (1.0
        // explosió, 0.3 xip), el color (cos vs hit-feedback) i el so
        // (ENEMY_EXPLOSION en la mort vs ENEMY_HIT en l'impacte parcial).
        void spawnDebrisForEnemy(Systems::Collision::Context& ctx, const Enemy& enemy, const Bullet* bullet, float piece_scale, SDL_Color color, const char* sound) {
            constexpr float SPEED_EXPLOSIO = 80.0F;
            const Vec2 INHERITED_VEL = enemy.getVelocityVector() *
                Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
            const Vec2 BULLET_VEL = (bullet != nullptr) ? bullet->getBody().velocity : Vec2{};
            ctx.debris_manager.explode(
                enemy.getShape(),
                enemy.getCenter(),
                0.0F,
                1.0F,
                SPEED_EXPLOSIO,
                enemy.getBrightness(),
                INHERITED_VEL,
                0.0F,
                0.0F,
                sound,
                color,
                Defaults::Physics::Debris::ENEMY_LIFETIME,
                Defaults::Physics::Debris::ENEMY_FRICTION,
                Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER,
                BULLET_VEL,
                piece_scale);
        }

        // Helper compartit per CREATE_FIREWORKS i CREATE_FIREWORKS_SMALL:
        // mateixa crida a spawn(); els callers decideixen line_color, glow_color,
        // n_points i initial_speed segons el "tamany" del burst (mort vs hit).
        void spawnFireworksForEnemy(Systems::Collision::Context& ctx, const Enemy& enemy, int n_points, float initial_speed, SDL_Color line_color, SDL_Color glow_color) {
            ctx.firework_manager.spawn(enemy.getCenter(),
                line_color,
                initial_speed,
                n_points,
                Defaults::FX::Firework::INITIAL_BRIGHTNESS,
                /*glow=*/true,
                glow_color);
        }

        void doApplyImpulse(Enemy& enemy, const Bullet* bullet) {
            if (bullet == nullptr) {
                return;
            }
            const Vec2 IMPULSE = bullet->getBody().velocity *
                (bullet->getBody().mass * bullet->getConfig().physics.impact_momentum_factor);
            enemy.applyImpulse(IMPULSE);
        }

        auto randFloat01() -> float {
            return static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
        }

        // Còpia local de la mateixa primitiva que viu a enemy_ai_system.cpp.
        // No s'ha extret a un header compartit perquè és l'únic punt de
        // duplicació; si apareix un tercer consumidor, refactoritzar.
        auto findNearestShipPosition(const Enemy& enemy) -> const Vec2* {
            const Vec2& self = enemy.getCenter();
            const Vec2* best = nullptr;
            float best_dist_sq = 0.0F;
            for (const Ship* ship : enemy.getShips()) {
                if (ship == nullptr || !ship->isActive()) {
                    continue;
                }
                const Vec2& pos = ship->getCenter();
                const Vec2 DELTA = pos - self;
                const float DIST_SQ = DELTA.lengthSquared();
                if (best == nullptr || DIST_SQ < best_dist_sq) {
                    best = &pos;
                    best_dist_sq = DIST_SQ;
                }
            }
            return best;
        }

        // FIRE_BULLET: paral·lel a doShoot() d'enemy_ai_system.cpp, però disparat
        // per esdeveniment (típicament on_hit per a contra-atacs) en lloc de
        // periòdicament. owner_id es deriva de l'índex dins ctx.enemies via
        // aritmètica de punters (l'array és contigu).
        void doFireBullet(Systems::Collision::Context& ctx, const Enemy& enemy, const EnemyAction& action) {
            if (action.bullet_config_name.empty()) {
                return;
            }
            const BulletConfig* cfg = BulletRegistry::get(action.bullet_config_name);
            if (cfg == nullptr) {
                return;
            }
            Bullet* slot = nullptr;
            constexpr std::size_t START = Defaults::Entities::ENEMY_BULLET_START_IDX;
            constexpr std::size_t END = START + Defaults::Entities::MAX_ENEMY_BULLETS;
            for (std::size_t i = START; i < END; ++i) {
                if (!ctx.bullets[i].isActive()) {
                    slot = &ctx.bullets[i];
                    break;
                }
            }
            if (slot == nullptr) {
                return;  // pool d'enemic ple
            }

            float angle = 0.0F;
            if (action.aim_mode == AimMode::AIMED) {
                const Vec2* target = findNearestShipPosition(enemy);
                if (target == nullptr) {
                    angle = randFloat01() * 2.0F * Constants::PI;
                } else {
                    const Vec2 TO = *target - enemy.getCenter();
                    angle = std::atan2(TO.y, TO.x) + (Constants::PI / 2.0F);
                }
            } else {
                angle = randFloat01() * 2.0F * Constants::PI;
            }
            if (action.jitter_rad > 0.0F) {
                angle += (randFloat01() - 0.5F) * 2.0F * action.jitter_rad;
            }

            // Localitzem l'índex de l'enemic per construir l'owner_id. Evitem
            // aritmètica de punters sobre Enemy (tipus polimòrfic — UB si la
            // jerarquia canvia); cerca lineal a l'array (mida petita, no és hot path).
            std::size_t enemy_index = 0;
            for (std::size_t i = 0; i < ctx.enemies.size(); ++i) {
                if (&ctx.enemies[i] == &enemy) {
                    enemy_index = i;
                    break;
                }
            }
            const auto OWNER = static_cast<uint8_t>(Defaults::Entities::ENEMY_OWNER_BASE + enemy_index);
            slot->fire(enemy.getCenter(), angle, OWNER, action.bullet_speed, cfg);
        }
    }  // namespace

    void dispatchEvent(Systems::Collision::Context& ctx, Enemy& enemy, EnemyEventType event, uint8_t shooter_id, const Bullet* bullet) {
        const auto& actions = enemy.getConfig().events.getActions(event);

        // Pre-scan: aquest event matarà l'enemic? Si sí, l'impuls de la bala
        // va directament als debris (via doCreateDebris) i NO s'aplica al cos
        // — així evitem el "double-count" on els trossos hereten la velocitat
        // del cos (boostat per la bala) I a més el seu propi impuls de bala.
        // Regla: el bullet impacta al cos O als trossos, mai a tots dos.
        bool will_die = false;
        for (const auto& action : actions) {
            if (action.type == EnemyActionType::DESTROY) {
                will_die = true;
                break;
            }
            if (action.type == EnemyActionType::SET_HURT && enemy.isWounded()) {
                will_die = true;
                break;
            }
        }

        for (const auto& action : actions) {
            // Si una acció prèvia d'aquest chain ha destruït l'enemic
            // (típicament DECREASE_HEALTH→ON_NO_HEALTH→SET_HURT-wounded→DESTROY),
            // saltem la resta — no té sentit aplicar APPLY_IMPULSE o FLASH a un
            // cos ja inactiu.
            if (!enemy.isActive()) {
                break;
            }
            switch (action.type) {
                case EnemyActionType::SET_HURT:
                    if (enemy.isWounded()) {
                        // Segon hit sobre wounded → mort immediata (regla 2-hits).
                        dispatchEvent(ctx, enemy, EnemyEventType::ON_DESTROY, shooter_id, bullet);
                        enemy.destroy();
                    } else {
                        enemy.hurt(shooter_id);
                    }
                    break;
                case EnemyActionType::DESTROY:
                    dispatchEvent(ctx, enemy, EnemyEventType::ON_DESTROY, shooter_id, bullet);
                    enemy.destroy();
                    break;
                case EnemyActionType::ADD_SCORE:
                    doAddScore(ctx, enemy, shooter_id);
                    break;
                case EnemyActionType::CREATE_DEBRIS:
                    // Explosió de mort: trossos en color cos + so ENEMY_EXPLOSION.
                    spawnDebrisForEnemy(ctx, enemy, bullet, 1.0F, enemy.getConfig().colors.normal, Defaults::Sound::ENEMY_EXPLOSION);
                    break;
                case EnemyActionType::CREATE_DEBRIS_PARTIAL:
                    // Xip d'impacte: trossos en color wounded (daurat) + so
                    // ENEMY_HIT, diferent de l'explosió per marcar damage no letal.
                    spawnDebrisForEnemy(ctx, enemy, bullet, Defaults::Enemies::Debris::PARTIAL_PIECE_SCALE, enemy.getConfig().colors.wounded, Defaults::Sound::ENEMY_HIT);
                    break;
                case EnemyActionType::CREATE_FIREWORKS:
                    // Burst de mort: línia blanca + glow wounded (daurat) per
                    // marcar la mort com a esdeveniment "calent" i lluminós.
                    spawnFireworksForEnemy(ctx, enemy, Defaults::FX::Firework::N_POINTS, Defaults::FX::Firework::SPEED, Defaults::FX::Firework::DEFAULT_COLOR, enemy.getConfig().colors.wounded);
                    break;
                case EnemyActionType::CREATE_FIREWORKS_SMALL:
                    // Espurna d'impacte: línia + glow tots dos en wounded
                    // (daurat) per contrastar amb el cos i unificar la "tema
                    // de damage" amb el debris parcial.
                    spawnFireworksForEnemy(ctx, enemy, Defaults::Enemies::Fireworks::SMALL_N_POINTS, Defaults::Enemies::Fireworks::SMALL_SPEED, enemy.getConfig().colors.wounded, enemy.getConfig().colors.wounded);
                    break;
                case EnemyActionType::APPLY_IMPULSE:
                    if (!will_die) {
                        doApplyImpulse(enemy, bullet);
                    }
                    break;
                case EnemyActionType::DECREASE_HEALTH:
                    enemy.decrementHealth(shooter_id);
                    if (enemy.getHealth() <= 0) {
                        dispatchEvent(ctx, enemy, EnemyEventType::ON_NO_HEALTH, shooter_id, bullet);
                    }
                    break;
                case EnemyActionType::FLASH:
                    enemy.triggerFlash();
                    break;
                case EnemyActionType::FIRE_BULLET:
                    doFireBullet(ctx, enemy, action);
                    break;
            }
        }
    }

}  // namespace Systems::EnemyEvents
