#include "AttackEvaluator.h"

#include <algorithm>
#include <unordered_map>
#include <vector>

#include <QVector2D>

#include "AttackBehavior.h"
#include "BehaviorScores.h"
#include "BehaviorTargeting.h"
#include "EntityAdmin.h"
#include "FactionComponent.h"
#include "ModuleOwnerComponent.h"
#include "PositionComponent.h"
#include "SensorRangeComponent.h"
#include "tracing.h"
#include "WeaponComponent.h"
#include "WorldConfig.h"

AttackEvaluator::AttackEvaluator(const WorldTargeting& targeting)
    : m_targeting(&targeting)
{
}

void AttackEvaluator::evaluate(EntityAdmin& admin)
{
    TRACE();
    const std::vector<CombatantInfo> combatants = buildCombatants(admin);

    // Pass A: the maximum weapon range per ship, used to normalise target
    // distance. Ships without a weapon fall back to their sensor range below.
    std::unordered_map<entt::entity, float> maxWeaponRange_tiles;
    admin.forEach<WeaponComponent, ModuleOwnerComponent>(
        [&maxWeaponRange_tiles](entt::entity /*we*/, const WeaponComponent& weapon,
                                const ModuleOwnerComponent& owner)
        {
            float& best = maxWeaponRange_tiles[owner.owner];
            best = std::max(best, weapon.range_tiles);
        });

    // Pass B: claim counts, taken from every ship's current target before any
    // target is reassigned this tick. Each ship reads the previous tick's claim
    // state and excludes its own contribution when scoring its current target.
    std::unordered_map<entt::entity, int> claimsByTarget;
    admin.forEach<AttackBehavior>(
        [&claimsByTarget, &admin](entt::entity /*e*/, const AttackBehavior& attack)
        {
            if (attack.currentTarget && admin.isValid(*attack.currentTarget))
            {
                ++claimsByTarget[*attack.currentTarget];
            }
        });

    // Pass C: per-ship target selection.
    admin.forEach<AttackBehavior, PositionComponent, FactionComponent,
                  SensorRangeComponent>(
        [&](entt::entity e, AttackBehavior& attack, const PositionComponent& pos,
            const FactionComponent& faction, const SensorRangeComponent& sensor)
        {
            const float sensorRange_tiles = sensor.value_tiles;

            // Distance normaliser: max weapon range, or sensor range if unarmed.
            float weaponRange_tiles = sensorRange_tiles;
            const auto weaponRangeIt = maxWeaponRange_tiles.find(e);
            if (weaponRangeIt != maxWeaponRange_tiles.end() && weaponRangeIt->second > 0.0f)
            {
                weaponRange_tiles = weaponRangeIt->second;
            }

            // Scores a single candidate: base desirability from distance, reduced
            // by the overclaim penalty. selfClaimed subtracts this ship's own claim
            // so it does not penalise the target it already holds.
            const auto scoreOf =
                [&](const QVector2D& candidatePos, entt::entity candidate) -> float
            {
                const float dist = (candidatePos - pos.value).length();
                const float x = dist / weaponRange_tiles;
                float base = static_cast<float>(m_targeting->targetScoreFormula.evaluate(x));
                base = std::max(base, 0.0f);

                int claims = 0;
                const auto claimIt = claimsByTarget.find(candidate);
                if (claimIt != claimsByTarget.end()) { claims = claimIt->second; }
                if (attack.currentTarget && candidate == *attack.currentTarget) { --claims; }

                float penalty = static_cast<float>(
                    m_targeting->overclaimPenaltyFormula.evaluate(claims));
                penalty = std::clamp(penalty, 0.0f, 1.0f);

                return base * penalty;
            };

            // Find the best candidate among in-range enemies.
            std::optional<entt::entity> bestTarget;
            float bestScore = 0.0f;
            for (const CombatantInfo& c : combatants)
            {
                if (c.entity == e) { continue; }
                const bool isValidTarget = faction.isEnemy ? !c.isEnemy : c.isEnemy;
                if (!isValidTarget) { continue; }

                const float dist = (c.position - pos.value).length();
                if (dist > sensorRange_tiles) { continue; }

                const float score = scoreOf(c.position, c.entity);
                if (!bestTarget || score > bestScore)
                {
                    bestScore  = score;
                    bestTarget = c.entity;
                }
            }

            // Hysteresis: keep the current target if it is still valid and in
            // range, unless a challenger beats its score by more than the margin.
            bool keptCurrent = false;
            if (attack.currentTarget)
            {
                const entt::entity t = *attack.currentTarget;
                if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
                {
                    const QVector2D targetPos = admin.get<PositionComponent>(t).value;
                    const float dist = (targetPos - pos.value).length();
                    if (dist <= sensorRange_tiles)
                    {
                        const float currentScore = scoreOf(targetPos, t);
                        const float margin = 1.0f + static_cast<float>(m_targeting->hysteresis);
                        if (!bestTarget || bestScore <= currentScore * margin)
                        {
                            keptCurrent = true;
                        }
                    }
                }
            }

            if (!keptCurrent)
			{ 
				attack.currentTarget = bestTarget; 
			}

            attack.score = attack.currentTarget
                ? BehaviorScores::kAttack
                : BehaviorScores::kInactive;
        });
}

