// physics_world.hpp - Mundo físico 2D
// © 2026 JailDesigner
//
// Gestiona un conjunto de RigidBody, integra sus movimientos y detecta
// colisiones por frame. Diseño minimalista para arcade: broadphase trivial
// O(n²) suficiente para <50 cuerpos (15 enemigos + balas + paredes).
//
// Los RigidBody viven en las entidades (las entidades poseen sus bodies);
// PhysicsWorld solo guarda punteros no-owning. La entidad es responsable
// de añadir/quitar su body del mundo en init/destroy.

#pragma once

#include <SDL3/SDL.h>

#include <functional>
#include <utility>
#include <vector>

#include "core/types.hpp"

namespace Physics {

    struct RigidBody;

    // Notificació d'impacte contra un dels bounds del PLAYAREA. impact_speed és
    // la magnitud de la component de velocity perpendicular a la paret (≥ 0).
    struct BoundsHit {
        Vec2 contact_point;
        float impact_speed;
    };

    class PhysicsWorld {
       public:
        using BoundsHitCallback = std::function<void(const BoundsHit&)>;

        PhysicsWorld() = default;

        // Añade un cuerpo al mundo (no toma ownership).
        void addBody(RigidBody* body);

        // Elimina un cuerpo. No-op si no está registrado.
        void removeBody(RigidBody* body);

        // Vacía la lista (no destruye los cuerpos).
        void clear() { bodies_.clear(); }

        // Define los límites del mundo (paredes implícitas). Pasa un rect
        // PLAYAREA para que los cuerpos reboten contra los bordes según su
        // restitution. Vacío = sin paredes.
        void setBounds(const SDL_FRect& bounds) {
            bounds_ = bounds;
            has_bounds_ = true;
        }
        void clearBounds() { has_bounds_ = false; }

        // Callback opcional invocat cada vegada que un cos impacta contra
        // un dels bounds del PLAYAREA. S'invoca abans de la reflexió de
        // velocity perquè impact_speed sigui la magnitud entrant.
        void setBoundsHitCallback(BoundsHitCallback callback) {
            bounds_hit_callback_ = std::move(callback);
        }

        // Avanza la simulación dt segundos:
        //   1. Integra cada cuerpo (semi-implicit Euler + damping)
        //   2. Resuelve colisiones contra los bounds (si configurados)
        //   3. Resuelve colisiones cuerpo-cuerpo (impulsos elásticos)
        void update(float dt);

        // Consultas
        [[nodiscard]] auto getBodyCount() const -> std::size_t { return bodies_.size(); }
        [[nodiscard]] auto getBodies() const -> const std::vector<RigidBody*>& { return bodies_; }

       private:
        std::vector<RigidBody*> bodies_;
        SDL_FRect bounds_{0.0F, 0.0F, 0.0F, 0.0F};
        bool has_bounds_{false};
        BoundsHitCallback bounds_hit_callback_;

        void integrate(float dt);
        void resolveBoundsCollisions();
        void resolveBodyCollisions();
        // Resol un únic parell (a, b): correcció posicional + impulso elàstic.
        // Estàtic: només toca els dos cossos rebuts, no consulta el world.
        static void resolveBodyPair(RigidBody& a, RigidBody& b);
    };

}  // namespace Physics
