#include "EntityHitTest.h"

#include <algorithm>
#include <cmath>

#include "EntityAdmin.h"
#include "SelectionBox.h"
#include "PositionComponent.h"
#include "DebrisComponent.h"
#include "ShipIdentityComponent.h"
#include "StationBodyComponent.h"
#include "HealthComponent.h"

entt::entity entityAtWorldPos(EntityAdmin& admin, QVector2D worldPos)
{
    const QPoint tile(static_cast<int>(std::floor(worldPos.x())),
                      static_cast<int>(std::floor(worldPos.y())));

    entt::entity stationHit = entt::null;
    admin.forEach<StationBodyComponent, HealthComponent>(
        [&](entt::entity entity, const StationBodyComponent& sb, const HealthComponent& h)
        {
            if (stationHit != entt::null) { return; }
            if (h.hp <= 0.0f) { return; }
            for (const QPoint& cell : sb.bodyCells)
            {
                if (cell == tile)
                {
                    stationHit = entity;
                    return;
                }
            }
        });

    if (stationHit != entt::null)
    {
        return stationHit;
    }

    constexpr float kShipHitRadiusSquared = 0.5f * 0.5f;
    entt::entity bestShip = entt::null;
    float bestDistSquared = kShipHitRadiusSquared;

    admin.forEach<PositionComponent, HealthComponent>(
        [&](entt::entity entity, const PositionComponent& pos, const HealthComponent& h)
        {
            if (h.hp <= 0.0f) { return; }
            if (admin.hasAll<StationBodyComponent>(entity)) { return; }
            const float dx = pos.value.x() - worldPos.x();
            const float dy = pos.value.y() - worldPos.y();
            const float distSquared = dx * dx + dy * dy;
            if (distSquared < bestDistSquared)
            {
                bestDistSquared = distSquared;
                bestShip = entity;
            }
        });

    return bestShip;
}

entt::entity debrisAtWorldPos(EntityAdmin& admin, QVector2D worldPos)
{
    // Slightly larger than the debris's rendered radius (0.2 tiles) so small pieces
    // remain easy to click; tunable.
    constexpr float kDebrisHitRadiusSquared = 0.35f * 0.35f;
    entt::entity bestDebris = entt::null;
    float bestDistSquared = kDebrisHitRadiusSquared;

    admin.forEach<DebrisComponent, PositionComponent>(
        [&](entt::entity entity, const DebrisComponent& /*sd*/, const PositionComponent& pos)
        {
            const float dx = pos.value.x() - worldPos.x();
            const float dy = pos.value.y() - worldPos.y();
            const float distSquared = dx * dx + dy * dy;
            if (distSquared < bestDistSquared)
            {
                bestDistSquared = distSquared;
                bestDebris = entity;
            }
        });

    return bestDebris;
}

std::vector<entt::entity> debrisInBox(EntityAdmin& admin, const QRectF& worldBox)
{
    std::vector<entt::entity> result;
    admin.forEach<DebrisComponent, PositionComponent>(
        [&](entt::entity entity, const DebrisComponent& /*sd*/, const PositionComponent& pos)
        {
            if (boxCoversPoint(worldBox, pos.value)) { result.push_back(entity); }
        });
    return result;
}

std::vector<entt::entity> actorsInBox(EntityAdmin& admin, const QRectF& worldBox)
{
    std::vector<entt::entity> result;

    // Stations occupy whole tiles: included when the box overlaps any occupied cell.
    admin.forEach<StationBodyComponent, HealthComponent>(
        [&](entt::entity entity, const StationBodyComponent& sb, const HealthComponent& h)
        {
            if (h.hp <= 0.0f) { return; }
            for (const QPoint& cell : sb.bodyCells)
            {
                if (boxCoversTile(worldBox, cell))
                {
                    result.push_back(entity);
                    return;
                }
            }
        });

    // Ships have a position rather than a footprint: included when the box contains
    // that position. Requiring ShipIdentityComponent excludes the HQ proxy and any
    // station bodies.
    admin.forEach<ShipIdentityComponent, PositionComponent, HealthComponent>(
        [&](entt::entity entity, const ShipIdentityComponent& /*id*/,
            const PositionComponent& pos, const HealthComponent& h)
        {
            if (h.hp <= 0.0f) { return; }
            if (boxCoversPoint(worldBox, pos.value)) { result.push_back(entity); }
        });

    return result;
}
