// collision.hpp - Utilitats de detecció de colisiones
// © 2026 JailDesigner

#pragma once

#include <algorithm>

#include "core/entities/entity.hpp"
#include "core/types.hpp"

namespace Physics {

    // Comprobación genèrica de colisión entre dues entidades
    inline auto checkCollision(const Entities::Entity& a, const Entities::Entity& b, float amplifier = 1.0F) -> bool {
        // Comprovar si ambdós són col·lisionables
        if (!a.isCollidable() || !b.isCollidable()) {
            return false;
        }

        // Calcular radi combinat (con amplificador per hitbox generós)
        float suma_radis = (a.getCollisionRadius() + b.getCollisionRadius()) * amplifier;
        float suma_radis_sq = suma_radis * suma_radis;

        // Comprobación distancia al cuadrado (sin sqrt)
        const Vec2& pos_a = a.getCenter();
        const Vec2& pos_b = b.getCenter();
        float dx = pos_a.x - pos_b.x;
        float dy = pos_a.y - pos_b.y;
        float dist_sq = (dx * dx) + (dy * dy);

        return dist_sq <= suma_radis_sq;
    }

    // Swept collision: una entitat mòbil (radi r_a) s'ha desplaçat de p0 a p1 aquest
    // frame. Comprova si el segment expandit pel radi conjunt (r_a + radi de b, amb
    // amplificador) toca el cercle de l'entity b. Equival al check discrete quan
    // p0 == p1 (sense moviment). Evita tunneling a velocitats altes.
    inline auto checkCollisionSwept(const Vec2& p0, const Vec2& p1, float r_a, const Entities::Entity& b, float amplifier = 1.0F) -> bool {
        if (!b.isCollidable()) {
            return false;
        }
        const float SUM_R = (r_a + b.getCollisionRadius()) * amplifier;
        const float SUM_R_SQ = SUM_R * SUM_R;
        const Vec2& center_b = b.getCenter();
        const float DX_SEG = p1.x - p0.x;
        const float DY_SEG = p1.y - p0.y;
        const float LEN_SQ = (DX_SEG * DX_SEG) + (DY_SEG * DY_SEG);
        // Degenerat: punt-cercle (frame de spawn, o entitat parada).
        if (LEN_SQ <= 0.0F) {
            const float DX = p0.x - center_b.x;
            const float DY = p0.y - center_b.y;
            return ((DX * DX) + (DY * DY)) <= SUM_R_SQ;
        }
        // Projecció del centre sobre la recta del segment, clamp a [0,1] per acotar al segment.
        const float T_RAW = (((center_b.x - p0.x) * DX_SEG) + ((center_b.y - p0.y) * DY_SEG)) / LEN_SQ;
        const float T_CLAMPED = std::clamp(T_RAW, 0.0F, 1.0F);
        const float CLOSEST_X = p0.x + (DX_SEG * T_CLAMPED);
        const float CLOSEST_Y = p0.y + (DY_SEG * T_CLAMPED);
        const float DX = CLOSEST_X - center_b.x;
        const float DY = CLOSEST_Y - center_b.y;
        return ((DX * DX) + (DY * DY)) <= SUM_R_SQ;
    }

}  // namespace Physics
