#include "CombatSystem.h"

#include "EntityAdmin.h"
#include "FactionComponent.h"
#include "HealthComponent.h"
#include "ModuleOwnerComponent.h"
#include "PositionComponent.h"
#include "SensorRangeComponent.h"
#include "ShipIdentityComponent.h"
#include "tracing.h"
#include "WeaponComponent.h"

CombatSystem::CombatSystem(const GameConfig& config)
    : m_config(config)
{
}

void CombatSystem::tick(Tick currentTick,
                         EntityAdmin& admin,
                         std::vector<BeamFiredEvent>& outBeamFiredEvents)
{
	TRACE();
    // All weapons (ships and stations) are child entities linked via ModuleOwnerComponent.
    // AttackExecutor has already set each weapon's preferred (in-range) target; here we
    // validate it, fall back to nearest-target acquisition, and fire.
    admin.forEach<WeaponComponent, ModuleOwnerComponent>(
        [&](entt::entity /*e*/, WeaponComponent& weapon, const ModuleOwnerComponent& owner)
        {
            const PositionComponent& pos     = admin.get<PositionComponent>(owner.owner);
            const FactionComponent&  faction = admin.get<FactionComponent>(owner.owner);
            resolveWeapon(owner.owner, weapon, pos, faction, currentTick, admin, outBeamFiredEvents);
        });
}

void CombatSystem::resolveWeapon(
    entt::entity shipEntity,
    WeaponComponent& weapon,
    const PositionComponent& ownPos,
    const FactionComponent& ownFaction,
    Tick currentTick,
    EntityAdmin& admin,
    std::vector<BeamFiredEvent>& out)
{
    if (weapon.cooldownTicks > 0.0f)
    {
        weapon.cooldownTicks -= 1.0f;
    }
    if (weapon.cooldownTicks > 0.0f)
    {
        return;
    }

    // Validate or clear existing target.
    if (weapon.currentTarget)
    {
        const entt::entity t = *weapon.currentTarget;
        if (!admin.isValid(t) || !admin.hasAll<PositionComponent>(t))
        {
            weapon.currentTarget = std::nullopt;
        }
        else
        {
            const float distanceSquared =
                (ownPos.value - admin.get<PositionComponent>(t).value).lengthSquared();
            if (distanceSquared > weapon.range_tiles * weapon.range_tiles)
            {
                weapon.currentTarget = std::nullopt;
            }
        }
    }

    // Acquire a new target if needed.
    // Ships use their sensor range; stations fall back to weapon range.
    if (!weapon.currentTarget)
    {
        const float acquisitionRange = admin.hasAll<SensorRangeComponent>(shipEntity)
            ? admin.get<SensorRangeComponent>(shipEntity).value_tiles
            : weapon.range_tiles;
        float bestDistanceSquared = acquisitionRange * acquisitionRange;
        admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
            [&](entt::entity candidate, const ShipIdentityComponent& /*si*/,
                const PositionComponent& candidatePos,
                const FactionComponent& candidateFaction)
            {
                const bool isValidTarget = ownFaction.isEnemy
                    ? !candidateFaction.isEnemy
                    : candidateFaction.isEnemy;
                if (!isValidTarget)
                {
                    return;
                }
                const float distanceSquared =
                    (candidatePos.value - ownPos.value).lengthSquared();
                if (distanceSquared < bestDistanceSquared)
                {
                    bestDistanceSquared = distanceSquared;
                    weapon.currentTarget = candidate;
                }
            });
    }

    if (!weapon.currentTarget)
    {
        return;
    }

    const entt::entity targetEntity = *weapon.currentTarget;
    m_pendingDamage.push_back({targetEntity, weapon.damage,
                               currentTick + kBeamImpactDelayTicks});

    BeamFiredEvent evt;
    evt.kind      = BeamKind::Weapon;
    evt.shooter   = shipEntity;
    evt.target    = targetEntity;
    evt.emittedAt = currentTick;
    out.push_back(evt);

    weapon.cooldownTicks = static_cast<float>(kTickRateHz) / weapon.fireRateHz;
}

void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin)
{
	TRACE();
    std::vector<PendingDamage>::iterator it = m_pendingDamage.begin();
    while (it != m_pendingDamage.end())
    {
        if (it->appliesAt <= currentTick)
        {
            if (admin.isValid(it->target) && admin.hasAll<HealthComponent>(it->target))
            {
                admin.get<HealthComponent>(it->target).hp -= it->amount;
            }
            it = m_pendingDamage.erase(it);
        }
        else
        {
            ++it;
        }
    }
}
