// enemy.cpp - Implementación de enemigos (ORNIs)
// © 1999 Visente i Sergi (versión Pascal)
// © 2025 Port a C++20 con SDL3

#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"

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)),
    };
}

// Recupera el "ángulo equivalente" de un body en movimiento (para zigzag).
// Si está parado, devuelve 0.
auto velocityToAngle(const Vec2& velocity) -> float {
    if (velocity.lengthSquared() < 0.0001F) {
        return 0.0F;
    }
    // El movimiento (vx, vy) corresponde a angle - PI/2; invertimos.
    return std::atan2(velocity.y, velocity.x) + (Constants::PI / 2.0F);
}

}  // namespace

Enemy::Enemy(Rendering::Renderer* renderer)
    : Entity(renderer),
      drotacio_(0.0F),
      rotacio_(0.0F),
      esta_(false),
      type_(EnemyType::PENTAGON),
      tracking_timer_(0.0F),
      ship_position_(nullptr),
      tracking_strength_(0.5F),
      direction_change_timer_(0.0F),
      timer_invulnerabilitat_(0.0F) {
    brightness_ = Defaults::Brightness::ENEMIC;

    // Configuración del cuerpo físico — defaults para enemy genérico.
    // init() ajusta velocidad y masa según el tipo (Pentagon/Quadrat/Molinillo).
    body_.setMass(5.0F);                                  // Más liviano que la nave (10.0)
    body_.radius = 0.0F;                                  // 0 hasta spawn (no colisiona inactivo)
    body_.restitution = 1.0F;                             // Rebote elástico perfecto contra paredes
    body_.linear_damping = 0.0F;                          // Sin fricción: mantienen velocidad
    body_.angular_damping = 0.0F;                         // Idem
}

void Enemy::init(EnemyType type, const Vec2* ship_pos) {
    type_ = type;

    const char* shape_file = nullptr;
    float base_speed = 0.0F;
    float drotacio_min = 0.0F;
    float drotacio_max = 0.0F;
    float type_mass = 5.0F;

    switch (type_) {
        case EnemyType::PENTAGON:
            shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
            base_speed = Defaults::Enemies::Pentagon::VELOCITAT;
            drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN;
            drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX;
            type_mass = 5.0F;
            break;

        case EnemyType::QUADRAT:
            shape_file = Defaults::Enemies::Cuadrado::SHAPE_FILE;
            base_speed = Defaults::Enemies::Cuadrado::VELOCITAT;
            drotacio_min = Defaults::Enemies::Cuadrado::DROTACIO_MIN;
            drotacio_max = Defaults::Enemies::Cuadrado::DROTACIO_MAX;
            type_mass = 8.0F;  // Más pesado, "tanque"
            tracking_timer_ = 0.0F;
            break;

        case EnemyType::MOLINILLO:
            shape_file = Defaults::Enemies::Molinillo::SHAPE_FILE;
            base_speed = Defaults::Enemies::Molinillo::VELOCITAT;
            drotacio_min = Defaults::Enemies::Molinillo::DROTACIO_MIN;
            drotacio_max = Defaults::Enemies::Molinillo::DROTACIO_MAX;
            type_mass = 4.0F;  // Más liviano, ágil
            break;

        default:
            std::cerr << "[Enemy] Error: tipo desconocido ("
                      << static_cast<int>(type_) << "), usando PENTAGON\n";
            shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
            base_speed = Defaults::Enemies::Pentagon::VELOCITAT;
            drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN;
            drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX;
            break;
    }

    body_.setMass(type_mass);
    body_.radius = Defaults::Entities::ENEMY_RADIUS;

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

    // Posición aleatoria con comprobación de seguridad
    float min_x;
    float max_x;
    float min_y;
    float max_y;
    Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_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, 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 zona 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));
    }

    // Dirección inicial aleatoria, velocidad escalar según tipo
    const float ANGLE_INICIAL = (std::rand() % 360) * Constants::PI / 180.0F;
    setVelocityFromAngle(ANGLE_INICIAL, base_speed);

    // Sincronizar body_ con posición inicial
    body_.position = center_;
    body_.angle = 0.0F;
    body_.angular_velocity = 0.0F;
    body_.clearAccumulators();

    // Rotación visual aleatoria (independiente del body)
    const float DROTACIO_RANGE = drotacio_max - drotacio_min;
    drotacio_ = drotacio_min + ((static_cast<float>(std::rand()) / RAND_MAX) * DROTACIO_RANGE);
    rotacio_ = 0.0F;

    // Estado de animación
    animacio_ = EnemyAnimation();
    animacio_.drotacio_base = drotacio_;
    animacio_.drotacio_objetivo = drotacio_;
    animacio_.drotacio_t = 1.0F;

    // Invulnerabilidad post-spawn
    timer_invulnerabilitat_ = Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
    brightness_ = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;

    // Timer para próximo cambio de dirección (Pentagon)
    direction_change_timer_ = 0.0F;

    esta_ = true;
}

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

    // Decremento de invulnerabilidad + LERP de brightness
    if (timer_invulnerabilitat_ > 0.0F) {
        timer_invulnerabilitat_ -= delta_time;
        timer_invulnerabilitat_ = std::max(timer_invulnerabilitat_, 0.0F);

        const float T_INV = timer_invulnerabilitat_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
        const float T = 1.0F - T_INV;
        const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
        constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
        constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_END;
        brightness_ = START + ((END - START) * SMOOTH_T);
    }

    // Comportamiento por tipo (ajusta body_.velocity, NO mueve posición)
    switch (type_) {
        case EnemyType::PENTAGON:
            behaviorPentagon(delta_time);
            break;
        case EnemyType::QUADRAT:
            behaviorQuadrat(delta_time);
            break;
        case EnemyType::MOLINILLO:
            behaviorMolinillo(delta_time);
            break;
    }

    // Animaciones (palpitación + rotación acelerada)
    updateAnimation(delta_time);

    // Rotación visual (decoración, no afecta movimiento)
    rotacio_ += drotacio_ * delta_time;
}

void Enemy::postUpdate(float /*delta_time*/) {
    // Sincronizar mirror tras la integración del world.
    if (esta_) {
        center_ = body_.position;
    }
}

void Enemy::draw() const {
    if (!esta_ || !shape_) {
        return;
    }
    const float SCALE = computeCurrentScale();
    SDL_Color color{};
    switch (type_) {
        case EnemyType::PENTAGON:  color = Defaults::Palette::PENTAGON;  break;
        case EnemyType::QUADRAT:   color = Defaults::Palette::QUADRAT;   break;
        case EnemyType::MOLINILLO: color = Defaults::Palette::MOLINILLO; break;
    }
    Rendering::render_shape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_,
                            /*rotation_3d=*/nullptr, color);
}

void Enemy::destruir() {
    esta_ = false;
    body_.velocity = Vec2{};
    body_.angular_velocity = 0.0F;
    body_.radius = 0.0F;  // No colisiona mientras está inactivo
}

void Enemy::setVelocity(float speed) {
    // Mantener la dirección actual del body, cambiar solo la magnitud.
    const float CURRENT_SPEED = body_.velocity.length();
    if (CURRENT_SPEED > 0.0F) {
        body_.velocity = body_.velocity * (speed / CURRENT_SPEED);
    } else {
        // Sin dirección actual: usar ángulo aleatorio
        const float A = (std::rand() % 360) * Constants::PI / 180.0F;
        setVelocityFromAngle(A, speed);
    }
}

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

// PENTAGON: zigzag esquivador. Cambios de dirección periódicos (probabilísticos)
// en lugar de detectar paredes; el rebote contra muros lo hace PhysicsWorld
// con restitution=1.0.
void Enemy::behaviorPentagon(float delta_time) {
    direction_change_timer_ += delta_time;

    // Probabilidad de zigzag por segundo (calibrada para sensación equivalente
    // a la versión vieja que disparaba en cada toque de pared).
    constexpr float ZIGZAG_PROB_PER_SECOND = 0.8F;
    const float RAND_VAL = static_cast<float>(std::rand()) / RAND_MAX;
    if (RAND_VAL < ZIGZAG_PROB_PER_SECOND * delta_time) {
        const float CURRENT_ANGLE = velocityToAngle(body_.velocity);
        const float DELTA = (static_cast<float>(std::rand()) / RAND_MAX) *
                            Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
        const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA);
        const float SPEED = body_.velocity.length();
        setVelocityFromAngle(NEW_ANGLE, SPEED);
        direction_change_timer_ = 0.0F;
    }
}

// QUADRAT: tracking discreto cada TRACKING_INTERVAL. Ajusta dirección
// hacia el ship mezclando con tracking_strength_.
void Enemy::behaviorQuadrat(float delta_time) {
    tracking_timer_ += delta_time;

    if (tracking_timer_ >= Defaults::Enemies::Cuadrado::TRACKING_INTERVAL && ship_position_ != nullptr) {
        tracking_timer_ = 0.0F;

        const Vec2 TO_SHIP = *ship_position_ - center_;
        const float DIST = TO_SHIP.length();
        if (DIST > 0.0F) {
            const Vec2 DESIRED_DIR = TO_SHIP / DIST;
            const float SPEED = body_.velocity.length();
            const Vec2 DESIRED_VEL = DESIRED_DIR * SPEED;

            // Mezcla LERP: velocidad actual con la deseada según tracking_strength_.
            body_.velocity = (body_.velocity * (1.0F - tracking_strength_)) +
                             (DESIRED_VEL * tracking_strength_);

            // Renormalizar a la velocidad escalar original
            const float NEW_SPEED = body_.velocity.length();
            if (NEW_SPEED > 0.0F) {
                body_.velocity = body_.velocity * (SPEED / NEW_SPEED);
            }
        }
    }
}

// MOLINILLO: movimiento recto + boost de rotación visual cerca del ship.
// Sin tracking — solo cambios de dirección raros (igual que Pentagon pero
// con probabilidad mucho menor).
void Enemy::behaviorMolinillo(float /*delta_time*/) {
    // Boost de rotación visual por proximidad al ship
    if (ship_position_ != nullptr) {
        const Vec2 TO_SHIP = *ship_position_ - center_;
        const float DIST = TO_SHIP.length();
        if (DIST < Defaults::Enemies::Molinillo::PROXIMITY_DISTANCE) {
            drotacio_ = animacio_.drotacio_base * Defaults::Enemies::Molinillo::DROTACIO_PROXIMITY_MULTIPLIER;
        } else {
            drotacio_ = animacio_.drotacio_base;
        }
    }
    // Movimiento lineal puro: el world se encarga de integrar y rebotar.
}

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

void Enemy::updatePalpitation(float delta_time) {
    if (animacio_.palpitacio_activa) {
        animacio_.palpitacio_fase += 2.0F * Constants::PI * animacio_.palpitacio_frequencia * delta_time;
        animacio_.palpitacio_temps_restant -= delta_time;
        if (animacio_.palpitacio_temps_restant <= 0.0F) {
            animacio_.palpitacio_activa = false;
        }
    } else {
        const float RAND_VAL = static_cast<float>(std::rand()) / RAND_MAX;
        const float TRIGGER_PROB = Defaults::Enemies::Animation::PALPITACIO_TRIGGER_PROB * delta_time;
        if (RAND_VAL < TRIGGER_PROB) {
            animacio_.palpitacio_activa = true;
            animacio_.palpitacio_fase = 0.0F;

            const float FREQ_RANGE = Defaults::Enemies::Animation::PALPITACIO_FREQ_MAX -
                                     Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN;
            animacio_.palpitacio_frequencia = Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN +
                                              ((static_cast<float>(std::rand()) / RAND_MAX) * FREQ_RANGE);

            const float AMP_RANGE = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MAX -
                                    Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN;
            animacio_.palpitacio_amplitud = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN +
                                            ((static_cast<float>(std::rand()) / RAND_MAX) * AMP_RANGE);

            const float DUR_RANGE = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MAX -
                                    Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN;
            animacio_.palpitacio_temps_restant = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN +
                                                 ((static_cast<float>(std::rand()) / RAND_MAX) * DUR_RANGE);
        }
    }
}

void Enemy::updateRotationAcceleration(float delta_time) {
    if (animacio_.drotacio_t < 1.0F) {
        animacio_.drotacio_t += delta_time / animacio_.drotacio_duracio;
        if (animacio_.drotacio_t >= 1.0F) {
            animacio_.drotacio_t = 1.0F;
            animacio_.drotacio_base = animacio_.drotacio_objetivo;
            drotacio_ = animacio_.drotacio_base;
        } else {
            const float T = animacio_.drotacio_t;
            const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
            const float INITIAL = animacio_.drotacio_base;
            const float TARGET = animacio_.drotacio_objetivo;
            drotacio_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
        }
    } else {
        const float RAND_VAL = static_cast<float>(std::rand()) / RAND_MAX;
        const float TRIGGER_PROB = Defaults::Enemies::Animation::ROTACIO_ACCEL_TRIGGER_PROB * delta_time;
        if (RAND_VAL < TRIGGER_PROB) {
            animacio_.drotacio_t = 0.0F;

            const float MULT_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MAX -
                                     Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN;
            const float MULTIPLIER = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN +
                                     ((static_cast<float>(std::rand()) / RAND_MAX) * MULT_RANGE);
            animacio_.drotacio_objetivo = animacio_.drotacio_base * MULTIPLIER;

            const float DUR_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MAX -
                                    Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN;
            animacio_.drotacio_duracio = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN +
                                         ((static_cast<float>(std::rand()) / RAND_MAX) * DUR_RANGE);
        }
    }
}

auto Enemy::computeCurrentScale() const -> float {
    float scale = 1.0F;
    if (timer_invulnerabilitat_ > 0.0F) {
        const float T_INV = timer_invulnerabilitat_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
        const float T = 1.0F - T_INV;
        const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
        constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_START;
        constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_END;
        scale = START + ((END - START) * SMOOTH_T);
    } else if (animacio_.palpitacio_activa) {
        scale += animacio_.palpitacio_amplitud * std::sin(animacio_.palpitacio_fase);
    }
    return scale;
}

auto Enemy::getBaseVelocity() const -> float {
    switch (type_) {
        case EnemyType::PENTAGON:
            return Defaults::Enemies::Pentagon::VELOCITAT;
        case EnemyType::QUADRAT:
            return Defaults::Enemies::Cuadrado::VELOCITAT;
        case EnemyType::MOLINILLO:
            return Defaults::Enemies::Molinillo::VELOCITAT;
        default:
            return Defaults::Enemies::Pentagon::VELOCITAT;
    }
}

auto Enemy::getBaseRotation() const -> float {
    return animacio_.drotacio_base != 0.0F ? animacio_.drotacio_base : drotacio_;
}

void Enemy::setTrackingStrength(float strength) {
    if (type_ == EnemyType::QUADRAT) {
        tracking_strength_ = strength;
    }
}

auto Enemy::attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool {
    float min_x;
    float max_x;
    float min_y;
    float max_y;
    Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_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 >= Defaults::Enemies::Spawn::SAFETY_DISTANCE;
}
