#include "RepairSystem.h"

#include <algorithm>
#include <optional>
#include <vector>

#include <QVector2D>

#include "BehaviorTargeting.h"
#include "EntityAdmin.h"
#include "HealthComponent.h"
#include "ModuleOwnerComponent.h"
#include "PositionComponent.h"
#include "RepairToolComponent.h"
#include "tracing.h"

RepairSystem::RepairSystem(EntityAdmin& admin)
    : m_admin(admin)
{
}

void RepairSystem::tick(Tick currentTick, std::vector<BeamFiredEvent>& outBeamFiredEvents)
{
    TRACE();
    // Apply heals whose mid-beam delay has elapsed (cycles started on prior ticks).
    applyPendingHeals(currentTick);

    const std::vector<RepairableInfo> repairables = buildRepairables(m_admin);

    m_admin.forEach<RepairToolComponent, ModuleOwnerComponent>(
        [&](entt::entity /*re*/, RepairToolComponent& tool, const ModuleOwnerComponent& owner)
        {
            if (tool.cooldownTicksRemaining > 0) { --tool.cooldownTicksRemaining; }
            if (tool.cooldownTicksRemaining > 0) { return; }
            if (tool.repairIntervalTicks <= 0) { return; }
            if (!m_admin.hasAll<PositionComponent>(owner.owner)) { return; }
            const QVector2D ownerPos = m_admin.get<PositionComponent>(owner.owner).value;

            // Choose a target: honour the executor-set target if it is still valid
            // and in range, else fall back to the nearest damaged friendly in range.
            std::optional<entt::entity> target;
            if (tool.currentTarget)
            {
                const entt::entity t = *tool.currentTarget;
                if (m_admin.isValid(t) && m_admin.hasAll<HealthComponent, PositionComponent>(t))
                {
                    const HealthComponent& th = m_admin.get<HealthComponent>(t);
                    const float dist =
                        (m_admin.get<PositionComponent>(t).value - ownerPos).length();
                    if (th.hp > 0.0f && th.hp < th.maxHp && dist <= tool.range_tiles)
                    {
                        target = t;
                    }
                }
            }

            if (!target)
            {
                tool.currentTarget = std::nullopt;
                float bestDist = tool.range_tiles;
                for (const RepairableInfo& r : repairables)
                {
                    if (r.isEnemy) { continue; }
                    if (r.hp <= 0.0f || r.hp >= r.maxHp) { continue; }
                    const float dist = (r.position - ownerPos).length();
                    if (dist < bestDist)
                    {
                        bestDist = dist;
                        tool.currentTarget = r.entity;
                    }
                }
                target = tool.currentTarget;
            }

            if (!target) { return; }

            // Start a repair cycle: emit the beam now, apply the heal mid-beam, and
            // begin the cooldown at cycle start (not at effect application).
            outBeamFiredEvents.push_back(
                BeamFiredEvent{BeamKind::Repair, owner.owner, *target, currentTick});
            m_pendingHeals.push_back({*target, tool.repairAmountHp,
                                      currentTick + kBeamImpactDelayTicks});
            tool.cooldownTicksRemaining = tool.repairIntervalTicks;
        });
}

void RepairSystem::applyPendingHeals(Tick currentTick)
{
    std::vector<PendingHeal>::iterator it = m_pendingHeals.begin();
    while (it != m_pendingHeals.end())
    {
        if (it->appliesAt <= currentTick)
        {
            if (m_admin.isValid(it->target) && m_admin.hasAll<HealthComponent>(it->target))
            {
                HealthComponent& h = m_admin.get<HealthComponent>(it->target);
                if (h.hp > 0.0f && h.hp < h.maxHp)
                {
                    h.hp = std::min(h.hp + it->amountHp, h.maxHp);
                }
            }
            it = m_pendingHeals.erase(it);
        }
        else
        {
            ++it;
        }
    }
}
