// enemy_ai_system.cpp - Implementació del dispatcher de moviment d'enemics
// © 2026 JailDesigner

#include "game/systems/enemy_ai_system.hpp"

#include <cmath>
#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.hpp"
#include "game/entities/enemy_ai.hpp"
#include "game/entities/enemy_config.hpp"
#include "game/entities/ship.hpp"

namespace Systems::EnemyAi {

    namespace {

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

        auto velocityToAngle(const Vec2& velocity) -> float {
            if (velocity.lengthSquared() < 0.0001F) {
                return 0.0F;
            }
            return std::atan2(velocity.y, velocity.x) + (Constants::PI / 2.0F);
        }

        // Retorna el centre del ship actiu més proper a l'enemic, o nullptr si
        // no n'hi ha cap viu. Els ships destruïts (is_hit_) i els slots nullptr
        // (player no participant al match) queden filtrats.
        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;
        }

        // ZIGZAG: canvi de direcció probabilístic. Còpia literal del legacy
        // Enemy::behaviorPentagon.
        void moveZigzag(Enemy& enemy, float delta_time) {
            const auto& mv = enemy.getConfig().ai.movement;
            EnemyAiState& state = enemy.getAiState();
            state.direction_change_timer += delta_time;

            if (randFloat01() < mv.zigzag_prob_per_second * delta_time) {
                const Vec2 VEL = enemy.getBody().velocity;
                const float CURRENT_ANGLE = velocityToAngle(VEL);
                const float DELTA = randFloat01() * mv.angle_change_max;
                const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA);
                const float SPEED = VEL.length();
                enemy.setVelocityFromAngle(NEW_ANGLE, SPEED);
                state.direction_change_timer = 0.0F;
            }
        }

        // TRACKING: cada N segons, interpola la velocitat actual cap a la
        // direcció del ship mantenint la mateixa magnitud. Còpia literal del
        // legacy Enemy::behaviorSquare.
        void moveTracking(Enemy& enemy, float delta_time) {
            const auto& mv = enemy.getConfig().ai.movement;
            EnemyAiState& state = enemy.getAiState();
            state.tracking_timer += delta_time;

            const Vec2* ship_pos = findNearestShipPosition(enemy);
            if (state.tracking_timer < mv.tracking_interval || ship_pos == nullptr) {
                return;
            }
            state.tracking_timer = 0.0F;

            const Vec2 TO_SHIP = *ship_pos - enemy.getCenter();
            const float DIST = TO_SHIP.length();
            if (DIST <= 0.0F) {
                return;
            }
            const Vec2 DESIRED_DIR = TO_SHIP / DIST;
            const float SPEED = enemy.getBody().velocity.length();
            const Vec2 DESIRED_VEL = DESIRED_DIR * SPEED;
            const float STRENGTH = state.tracking_strength;

            Vec2 new_vel = (enemy.getBody().velocity * (1.0F - STRENGTH)) +
                (DESIRED_VEL * STRENGTH);

            const float NEW_SPEED = new_vel.length();
            if (NEW_SPEED > 0.0F) {
                new_vel = new_vel * (SPEED / NEW_SPEED);
            }
            enemy.getBody().velocity = new_vel;
        }

        // CHASE / FLEE comparteixen lògica: steering continu cap a (o lluny de)
        // la direcció ideal, preservant la magnitud de velocitat. La força és
        // strength * dt clampejada a 1 (LERP frame-independent simple).
        void steerTowards(Enemy& enemy, const Vec2& desired_dir, float strength, float delta_time) {
            const float SPEED = enemy.getBody().velocity.length();
            if (SPEED <= 0.0F) {
                return;
            }
            const Vec2 DESIRED_VEL = desired_dir * SPEED;
            const float T = std::min(1.0F, strength * delta_time);
            Vec2 new_vel = (enemy.getBody().velocity * (1.0F - T)) + (DESIRED_VEL * T);
            const float NEW_SPEED = new_vel.length();
            if (NEW_SPEED > 0.0F) {
                new_vel = new_vel * (SPEED / NEW_SPEED);
            }
            enemy.getBody().velocity = new_vel;
        }

        void moveChase(Enemy& enemy, float delta_time) {
            const Vec2* ship_pos = findNearestShipPosition(enemy);
            if (ship_pos == nullptr) {
                return;
            }
            const Vec2 TO_SHIP = *ship_pos - enemy.getCenter();
            const float DIST = TO_SHIP.length();
            if (DIST <= 0.0F) {
                return;
            }
            steerTowards(enemy, TO_SHIP / DIST, enemy.getConfig().ai.movement.chase_strength, delta_time);
        }

        void moveFlee(Enemy& enemy, float delta_time) {
            const Vec2* ship_pos = findNearestShipPosition(enemy);
            if (ship_pos == nullptr) {
                return;
            }
            const Vec2 AWAY = enemy.getCenter() - *ship_pos;
            const float DIST = AWAY.length();
            if (DIST <= 0.0F) {
                return;
            }
            steerTowards(enemy, AWAY / DIST, enemy.getConfig().ai.movement.flee_strength, delta_time);
        }

        // RECTILINEAR_PROXIMITY: rectilini (cap modificació a velocity); boost
        // de rotació visual quan distància al ship < proximity_distance. Còpia
        // literal del legacy Enemy::behaviorPinwheel.
        void moveRectilinearProximity(Enemy& enemy, float /*delta_time*/) {
            const auto& mv = enemy.getConfig().ai.movement;
            const Vec2* ship_pos = findNearestShipPosition(enemy);
            if (ship_pos == nullptr) {
                return;
            }
            const Vec2 TO_SHIP = *ship_pos - enemy.getCenter();
            const float DIST = TO_SHIP.length();
            const float BASE = enemy.getRotationBase();
            if (DIST < mv.proximity_distance) {
                enemy.setRotationDelta(BASE * mv.rotation_proximity_multiplier);
            } else {
                enemy.setRotationDelta(BASE);
            }
        }

        // SHOOT: cerca slot lliure a ctx.bullets i el dispara amb el bullet config
        // referenciat per nom (lazy-load via BulletRegistry). Angle segons aim_mode +
        // jitter. owner_id = ENEMY_OWNER_BASE + enemy_index per al filtre d'autoimpacte.
        void doShoot(Systems::Collision::Context& ctx, const Enemy& enemy, std::size_t enemy_index, const AiTickAction& action) {
            if (action.bullet_config_name.empty()) {
                return;
            }
            const BulletConfig* cfg = BulletRegistry::get(action.bullet_config_name);
            if (cfg == nullptr) {
                return;
            }
            // Cerca slot dins la zona reservada per a enemics: així no robem
            // slots als pools de player (que iteren [0, MAX_BULLETS) i [MAX_BULLETS, 2*MAX_BULLETS)).
            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) {
                    // Sense ship viu: degrada a random per no congelar el dispar.
                    angle = randFloat01() * 2.0F * Constants::PI;
                } else {
                    const Vec2 TO = *target - enemy.getCenter();
                    // angle=0 apunta amunt (eix Y negatiu SDL): atan2 + PI/2.
                    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;
            }

            const auto OWNER = static_cast<uint8_t>(Defaults::Entities::ENEMY_OWNER_BASE + enemy_index);
            slot->fire(enemy.getCenter(), angle, OWNER, action.bullet_speed, cfg);
        }

        void runMovement(Enemy& enemy, float delta_time) {
            switch (enemy.getConfig().ai.movement.type) {
                case MovementType::ZIGZAG:
                case MovementType::WANDER:
                    // WANDER reusa la mecànica de canvi de direcció probabilístic;
                    // l'única diferència és semàntica i el tunning dels paràmetres.
                    moveZigzag(enemy, delta_time);
                    break;
                case MovementType::TRACKING:
                    moveTracking(enemy, delta_time);
                    break;
                case MovementType::RECTILINEAR_PROXIMITY:
                    moveRectilinearProximity(enemy, delta_time);
                    break;
                case MovementType::CHASE:
                    moveChase(enemy, delta_time);
                    break;
                case MovementType::FLEE:
                    moveFlee(enemy, delta_time);
                    break;
            }
        }

    }  // namespace

    void move(Enemy& enemy, float delta_time) {
        if (!enemy.isActive() || enemy.isWounded()) {
            return;
        }
        runMovement(enemy, delta_time);
    }

    void tick(Systems::Collision::Context& ctx, Enemy& enemy, std::size_t enemy_index, float delta_time) {
        if (!enemy.isActive() || enemy.isWounded()) {
            return;
        }
        runMovement(enemy, delta_time);

        // Accions periòdiques: decrementa timer, dispara quan ≤0.
        auto& timers = enemy.getAiTickTimers();
        const auto& actions = enemy.getConfig().ai.tick;
        for (std::size_t i = 0; i < actions.size() && i < timers.size(); ++i) {
            timers[i] -= delta_time;
            if (timers[i] > 0.0F) {
                continue;
            }
            timers[i] = actions[i].interval;
            switch (actions[i].type) {
                case AiActionType::SHOOT:
                    doShoot(ctx, enemy, enemy_index, actions[i]);
                    break;
            }
        }
    }

}  // namespace Systems::EnemyAi
