// ship.cpp - Implementación de la nave del player
// © 2026 JailDesigner

#include "game/entities/ship.hpp"

#include <SDL3/SDL.h>

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <iostream>

#include "core/audio/audio.hpp"
#include "core/defaults.hpp"
#include "core/entities/entity.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/input/input.hpp"
#include "core/input/input_types.hpp"
#include "core/rendering/shape_renderer.hpp"
#include "core/types.hpp"
#include "game/constants.hpp"

Ship::Ship(Rendering::Renderer* renderer, const char* shape_file)
    : Entity(renderer) {
    // Brightness específico para naves
    brightness_ = Defaults::Brightness::NAU;

    // Configuración del cuerpo físico
    body_.setMass(Defaults::Ship::MASS);
    body_.radius = Defaults::Entities::SHIP_RADIUS;
    body_.restitution = Defaults::Ship::RESTITUTION;
    body_.linear_damping = Defaults::Ship::LINEAR_DAMPING;
    body_.angular_damping = Defaults::Ship::ANGULAR_DAMPING;

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

void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) {
    // Posición inicial
    if (spawn_point != nullptr) {
        center_ = *spawn_point;
    } else {
        float centre_x;
        float centre_y;
        Constants::getPlayAreaCenter(centre_x, centre_y);
        center_ = {.x = centre_x, .y = centre_y};
    }

    // Reset orientación
    angle_ = 0.0F;

    // Sincronizar cuerpo físico con la posición/orientación inicial
    body_.position = center_;
    body_.angle = angle_;
    body_.velocity = Vec2{};
    body_.angular_velocity = 0.0F;
    body_.clearAccumulators();

    // Activar invulnerabilidad solo si es respawn
    invulnerable_timer_ = activar_invulnerabilitat ? Defaults::Ship::INVULNERABILITY_DURATION : 0.0F;
    is_hit_ = false;
    hurt_timer_ = 0.0F;
    touching_enemy_prev_frame_ = false;
}

void Ship::processInput(float delta_time, uint8_t player_id) {
    // Solo procesa input si la nave está viva
    if (is_hit_) {
        return;
    }

    auto* input = Input::get();

    // Rotación: control directo del ángulo (no física, no inercial).
    // Se actualiza también body_.angle para que el dibujado tras
    // postUpdate refleje el cambio inmediatamente.
    const bool ROTATE_RIGHT = (player_id == 0)
        ? input->checkActionPlayer1(InputAction::RIGHT, Input::ALLOW_REPEAT)
        : input->checkActionPlayer2(InputAction::RIGHT, Input::ALLOW_REPEAT);
    const bool ROTATE_LEFT = (player_id == 0)
        ? input->checkActionPlayer1(InputAction::LEFT, Input::ALLOW_REPEAT)
        : input->checkActionPlayer2(InputAction::LEFT, Input::ALLOW_REPEAT);
    const bool THRUST = (player_id == 0)
        ? input->checkActionPlayer1(InputAction::THRUST, Input::ALLOW_REPEAT)
        : input->checkActionPlayer2(InputAction::THRUST, Input::ALLOW_REPEAT);

    if (ROTATE_RIGHT) {
        body_.angle += Defaults::Physics::ROTATION_SPEED * delta_time;
    }
    if (ROTATE_LEFT) {
        body_.angle -= Defaults::Physics::ROTATION_SPEED * delta_time;
    }

    // Thrust: fuerza vectorial en la dirección de la nariz.
    // angle - PI/2 porque angle=0 apunta hacia arriba (eje Y negativo SDL).
    if (THRUST) {
        const float DIR_X = std::cos(body_.angle - (Constants::PI / 2.0F));
        const float DIR_Y = std::sin(body_.angle - (Constants::PI / 2.0F));
        // Fuerza = masa * aceleración: 10 kg * 400 px/s² = 4000 (unidades arcade)
        const float MAGNITUDE = body_.mass * Defaults::Physics::ACCELERATION;
        body_.applyForce(Vec2{.x = DIR_X * MAGNITUDE, .y = DIR_Y * MAGNITUDE});
    }
}

void Ship::update(float delta_time) {
    // Solo update si la nave está viva
    if (is_hit_) {
        return;
    }

    // Decrementar timer de invulnerabilidad
    if (invulnerable_timer_ > 0.0F) {
        invulnerable_timer_ -= delta_time;
        invulnerable_timer_ = std::max(invulnerable_timer_, 0.0F);
    }

    // Decrementar timer d'estat HURT (a 0 → torna a normal sense efecte extern)
    if (hurt_timer_ > 0.0F) {
        hurt_timer_ -= delta_time;
        hurt_timer_ = std::max(hurt_timer_, 0.0F);
    }

    // El movimiento real lo hace PhysicsWorld::update().
    // Aquí solo lógica de estado.

    // Cap de velocidad: el thrust acumula fuerza sin límite; limitamos
    // la magnitud de body_.velocity tras aplicar fuerzas para preservar
    // el feel arcade del MAX_VELOCITY original.
    const float CURRENT_SPEED = body_.velocity.length();
    if (CURRENT_SPEED > Defaults::Physics::MAX_VELOCITY) {
        body_.velocity = body_.velocity * (Defaults::Physics::MAX_VELOCITY / CURRENT_SPEED);
    }
}

void Ship::postUpdate(float /*delta_time*/) {
    // Sincronizar mirror desde body_ tras la integración del world.
    center_ = body_.position;
    angle_ = body_.angle;
}

void Ship::draw() const {
    if (is_hit_) {
        return;
    }

    // Parpadeo si invulnerable
    if (isInvulnerable()) {
        const float BLINK_CYCLE = Defaults::Ship::BLINK_VISIBLE_TIME + Defaults::Ship::BLINK_INVISIBLE_TIME;
        const float TIME_IN_CYCLE = std::fmod(invulnerable_timer_, BLINK_CYCLE);
        if (TIME_IN_CYCLE < Defaults::Ship::BLINK_INVISIBLE_TIME) {
            return;
        }
    }

    if (!shape_) {
        return;
    }

    // Efecto visual de empuje: escala proporcional a la velocidad.
    // 0..200 px/s → escala 1.0..1.5 (manteniendo la sensación del Pascal original).
    const float SPEED = getSpeed();
    const float VISUAL_PUSH = SPEED / Defaults::Ship::VISUAL_PUSH_DIVISOR;
    const float SCALE = 1.0F + (VISUAL_PUSH / Defaults::Ship::VISUAL_SCALE_DIVISOR);

    // Parpelleig daurat mentre està ferida: alterna color normal ↔ color hurt
    // a Hurt::BLINK_HZ (mateixa estètica que el wounded dels enemics).
    SDL_Color color = color_normal_;
    if (hurt_timer_ > 0.0F) {
        const float CYCLE = 1.0F / Defaults::Ship::Hurt::BLINK_HZ;
        const float T = std::fmod(hurt_timer_, CYCLE);
        if (T < (CYCLE / 2.0F)) {
            color = color_hurt_;
        }
    }

    Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, color);
}

void Ship::herir() {
    hurt_timer_ = Defaults::Ship::Hurt::DURATION;
    Audio::get()->playSound(Defaults::Sound::HURT, Audio::Group::GAME);
}
