// enemy.cpp - Implementación de enemigos (ORNIs)
// © 2026 JailDesigner

#include "game/entities/enemy.hpp"

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

#include "core/defaults.hpp"
#include "core/entities/entity.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/rendering/shape_renderer.hpp"
#include "core/types.hpp"
#include "game/constants.hpp"
#include "game/entities/enemy_config.hpp"
#include "game/entities/enemy_registry.hpp"

namespace {

    // Velocidad inicial vectorial a partir de un ángulo (rad).
    // angle=0 apunta hacia arriba (eje Y negativo SDL), como el resto del juego.
    auto angleToDirection(float angle) -> Vec2 {
        return Vec2{
            .x = std::cos(angle - (Constants::PI / 2.0F)),
            .y = std::sin(angle - (Constants::PI / 2.0F)),
        };
    }

    // Random float [0..1).
    auto randFloat01() -> float {
        return static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
    }

    // Random float [min..max).
    auto randRange(float min, float max) -> float {
        return min + (randFloat01() * (max - min));
    }

}  // namespace

Enemy::Enemy(Rendering::Renderer* renderer)
    : Entity(renderer) {
    brightness_ = Defaults::Brightness::ENEMIC;

    // Body queda amb defaults inocus (radius=0 = no col·lisiona) fins
    // que init() apliqui la configuració del tipus carregada via Registry.
    body_.radius = 0.0F;
}

void Enemy::init(EnemyType type, const Vec2* ship_pos) {
    type_ = type;
    config_ = &EnemyRegistry::get(type);
    const EnemyConfig& cfg = *config_;

    ai_state_ = EnemyAiState{};
    ai_state_.tracking_strength = cfg.ai.movement.tracking_strength;

    // Timers paral·lels a tick: random [0, interval) per evitar que tots els
    // enemics del mateix tipus disparin sincronitzats al spawn.
    ai_tick_timers_.resize(cfg.ai.tick.size());
    for (std::size_t i = 0; i < cfg.ai.tick.size(); ++i) {
        ai_tick_timers_[i] = randFloat01() * cfg.ai.tick[i].interval;
    }

    shape_ = Graphics::ShapeLoader::load(cfg.shape.path);
    if (!shape_ || !shape_->isValid()) {
        std::cerr << "[Enemy] Error: no se ha podido cargar " << cfg.shape.path << '\n';
    }

    // Radi de col·lisió derivat del cercle circumscrit de la shape.
    const float BOUNDING = (shape_ != nullptr) ? shape_->getBoundingRadius() : 0.0F;
    collision_radius_ = BOUNDING * cfg.shape.scale * cfg.shape.collision_factor;

    body_.setMass(cfg.physics.mass);
    body_.radius = collision_radius_;
    body_.restitution = cfg.physics.restitution;
    body_.linear_damping = cfg.physics.linear_damping;
    body_.angular_damping = cfg.physics.angular_damping;

    // Posició aleatòria amb comprovació de safety_distance.
    float min_x;
    float max_x;
    float min_y;
    float max_y;
    Constants::getSafePlayAreaBounds(collision_radius_, min_x, max_x, min_y, max_y);

    if (ship_pos != nullptr) {
        bool found_safe_position = false;
        for (int attempt = 0; attempt < Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS; attempt++) {
            float candidate_x;
            float candidate_y;
            if (attemptSafeSpawn(*ship_pos, collision_radius_, cfg.spawn.safety_distance, candidate_x, candidate_y)) {
                center_.x = candidate_x;
                center_.y = candidate_y;
                found_safe_position = true;
                break;
            }
        }
        if (!found_safe_position) {
            const int RANGE_X = static_cast<int>(max_x - min_x);
            const int RANGE_Y = static_cast<int>(max_y - min_y);
            center_.x = static_cast<float>((std::rand() % RANGE_X) + static_cast<int>(min_x));
            center_.y = static_cast<float>((std::rand() % RANGE_Y) + static_cast<int>(min_y));
            std::cout << "[Enemy] Advertencia: spawn sin zone segura tras "
                      << Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS << " intentos\n";
        }
    } else {
        const int RANGE_X = static_cast<int>(max_x - min_x);
        const int RANGE_Y = static_cast<int>(max_y - min_y);
        center_.x = static_cast<float>((std::rand() % RANGE_X) + static_cast<int>(min_x));
        center_.y = static_cast<float>((std::rand() % RANGE_Y) + static_cast<int>(min_y));
    }

    const float ANGLE_INICIAL = static_cast<float>(std::rand() % 360) * Constants::PI / 180.0F;
    setVelocityFromAngle(ANGLE_INICIAL, cfg.physics.speed);

    body_.position = center_;
    body_.angle = 0.0F;
    body_.angular_velocity = 0.0F;
    body_.clearAccumulators();

    // Rotació visual aleatòria dins del rang del tipus
    rotation_delta_ = randRange(cfg.physics.rotation_delta_min, cfg.physics.rotation_delta_max);
    rotation_ = 0.0F;

    animation_ = EnemyAnimation();
    animation_.rotation_delta_base = rotation_delta_;
    animation_.rotation_delta_target = rotation_delta_;
    animation_.rotation_delta_t = 1.0F;

    invulnerability_timer_ = cfg.spawn.invulnerability_duration;
    brightness_ = cfg.spawn.invulnerability_brightness_start;

    health_ = cfg.health;
    flash_timer_ = 0.0F;

    is_active_ = true;
}

void Enemy::update(float delta_time) {
    if (!is_active_) {
        return;
    }

    wound_expired_this_frame_ = false;
    if (wounded_timer_ > 0.0F) {
        wounded_timer_ -= delta_time;
        if (wounded_timer_ <= 0.0F) {
            wounded_timer_ = 0.0F;
            wound_expired_this_frame_ = true;
        }
    }

    if (flash_timer_ > 0.0F) {
        flash_timer_ -= delta_time;
        flash_timer_ = std::max(flash_timer_, 0.0F);
    }

    if (invulnerability_timer_ > 0.0F) {
        invulnerability_timer_ -= delta_time;
        invulnerability_timer_ = std::max(invulnerability_timer_, 0.0F);

        const float T_INV = invulnerability_timer_ / config_->spawn.invulnerability_duration;
        const float T = 1.0F - T_INV;
        const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
        const float START = config_->spawn.invulnerability_brightness_start;
        const float END = config_->spawn.invulnerability_brightness_end;
        brightness_ = START + ((END - START) * SMOOTH_T);
    }

    // El moviment es delega a Systems::EnemyAi::tick, invocat des de l'scene
    // ABANS d'aquest update (manté l'ordre: AI escriu velocity/rotation_delta,
    // després animation pot modular rotation_delta via rotation_accel).

    updateAnimation(delta_time);

    rotation_ += rotation_delta_ * delta_time;
}

void Enemy::postUpdate(float /*delta_time*/) {
    if (is_active_) {
        center_ = body_.position;
    }
}

void Enemy::draw() const {
    if (!is_active_ || !shape_) {
        return;
    }
    const float SCALE = config_->shape.scale * computeCurrentScale();
    SDL_Color color = config_->colors.normal;

    if (wounded_timer_ > 0.0F) {
        const float CYCLE = 1.0F / config_->wounded.blink_hz;
        const float T = std::fmod(wounded_timer_, CYCLE);
        if (T < (CYCLE / 2.0F)) {
            color = config_->colors.wounded;
        }
    }

    // Flash d'impacte parcial (HP>1): força el color a blanc i el brillo a
    // 1.0 durant la finestra de flash. Té prioritat sobre el blink wounded.
    float effective_brightness = brightness_;
    if (flash_timer_ > 0.0F) {
        color = SDL_Color{.r = 255, .g = 255, .b = 255, .a = 255};
        effective_brightness = 1.0F;
    }

    Rendering::renderShape(renderer_, shape_, center_, rotation_, SCALE, 1.0F, effective_brightness, color);
}

void Enemy::destroy() {
    is_active_ = false;
    body_.velocity = Vec2{};
    body_.angular_velocity = 0.0F;
    body_.radius = 0.0F;
    wounded_timer_ = 0.0F;
    wound_expired_this_frame_ = false;
    last_hit_by_ = 0xFF;
    flash_timer_ = 0.0F;
}

void Enemy::hurt(uint8_t shooter_id) {
    wounded_timer_ = config_->wounded.duration;
    last_hit_by_ = shooter_id;
}

void Enemy::applyImpulse(const Vec2& impulse) {
    body_.applyImpulse(impulse);
}

void Enemy::setVelocity(float speed) {
    const float CURRENT_SPEED = body_.velocity.length();
    if (CURRENT_SPEED > 0.0F) {
        body_.velocity = body_.velocity * (speed / CURRENT_SPEED);
    } else {
        const float A = static_cast<float>(std::rand() % 360) * Constants::PI / 180.0F;
        setVelocityFromAngle(A, speed);
    }
}

void Enemy::setVelocityFromAngle(float angle_movement, float speed) {
    body_.velocity = angleToDirection(angle_movement) * speed;
}

void Enemy::updateAnimation(float delta_time) {
    updatePulse(delta_time);
    updateRotationAcceleration(delta_time);
}

void Enemy::updatePulse(float delta_time) {
    const auto& cfg = config_->animation.pulse;
    if (animation_.pulse_active) {
        animation_.pulse_phase += 2.0F * Constants::PI * animation_.pulse_frequency * delta_time;
        animation_.pulse_time_remaining -= delta_time;
        if (animation_.pulse_time_remaining <= 0.0F) {
            animation_.pulse_active = false;
        }
        return;
    }
    if (randFloat01() < cfg.trigger_prob_per_second * delta_time) {
        animation_.pulse_active = true;
        animation_.pulse_phase = 0.0F;
        animation_.pulse_frequency = randRange(cfg.frequency_min, cfg.frequency_max);
        animation_.pulse_amplitude = randRange(cfg.amplitude_min, cfg.amplitude_max);
        animation_.pulse_time_remaining = randRange(cfg.duration_min, cfg.duration_max);
    }
}

void Enemy::updateRotationAcceleration(float delta_time) {
    const auto& cfg = config_->animation.rotation_accel;
    if (animation_.rotation_delta_t < 1.0F) {
        animation_.rotation_delta_t += delta_time / animation_.rotation_delta_duration;
        if (animation_.rotation_delta_t >= 1.0F) {
            animation_.rotation_delta_t = 1.0F;
            animation_.rotation_delta_base = animation_.rotation_delta_target;
            rotation_delta_ = animation_.rotation_delta_base;
        } else {
            const float T = animation_.rotation_delta_t;
            const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
            const float INITIAL = animation_.rotation_delta_base;
            const float TARGET = animation_.rotation_delta_target;
            rotation_delta_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
        }
        return;
    }
    if (randFloat01() < cfg.trigger_prob_per_second * delta_time) {
        animation_.rotation_delta_t = 0.0F;
        const float MULTIPLIER = randRange(cfg.multiplier_min, cfg.multiplier_max);
        animation_.rotation_delta_target = animation_.rotation_delta_base * MULTIPLIER;
        animation_.rotation_delta_duration = randRange(cfg.duration_min, cfg.duration_max);
    }
}

auto Enemy::computeCurrentScale() const -> float {
    float scale = 1.0F;
    if (invulnerability_timer_ > 0.0F) {
        const float T_INV = invulnerability_timer_ / config_->spawn.invulnerability_duration;
        const float T = 1.0F - T_INV;
        const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
        const float START = config_->spawn.invulnerability_scale_start;
        const float END = config_->spawn.invulnerability_scale_end;
        scale = START + ((END - START) * SMOOTH_T);
    } else if (animation_.pulse_active) {
        scale += animation_.pulse_amplitude * std::sin(animation_.pulse_phase);
    }
    return scale;
}

auto Enemy::getBaseVelocity() const -> float {
    return EnemyRegistry::get(type_).physics.speed;
}

auto Enemy::getBaseRotation() const -> float {
    return animation_.rotation_delta_base != 0.0F ? animation_.rotation_delta_base : rotation_delta_;
}

void Enemy::setTrackingStrength(float strength) {
    if (type_ == EnemyType::SQUARE) {
        ai_state_.tracking_strength = strength;
    }
}

auto Enemy::attemptSafeSpawn(const Vec2& ship_pos, float collision_radius, float safety_distance, float& out_x, float& out_y) -> bool {
    float min_x;
    float max_x;
    float min_y;
    float max_y;
    Constants::getSafePlayAreaBounds(collision_radius, min_x, max_x, min_y, max_y);

    const int RANGE_X = static_cast<int>(max_x - min_x);
    const int RANGE_Y = static_cast<int>(max_y - min_y);
    out_x = static_cast<float>((std::rand() % RANGE_X) + static_cast<int>(min_x));
    out_y = static_cast<float>((std::rand() % RANGE_Y) + static_cast<int>(min_y));

    const float DX = out_x - ship_pos.x;
    const float DY = out_y - ship_pos.y;
    const float DISTANCE = std::sqrt((DX * DX) + (DY * DY));
    return DISTANCE >= safety_distance;
}
