#include "SalvagerSystem.h"
#include "FactoryQueries.h"

#include <vector>

#include <QVector2D>

#include "Building.h"
#include "BuildingSystem.h"
#include "CargoComponent.h"
#include "DeliverScrapBehavior.h"
#include "EntityAdmin.h"
#include <map>

#include "ModuleOwnerComponent.h"
#include "PositionComponent.h"
#include "SalvagerComponent.h"
#include "DebrisComponent.h"
#include "DebrisSystem.h"
#include "tracing.h"

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

void SalvagerSystem::tick(Tick currentTick, FactoryState& state,
                          std::vector<BeamFiredEvent>& outBeamFiredEvents)
{
    TRACE();
    // Apply collections whose mid-beam delay has elapsed (cycles started earlier).
    applyPendingCollections(currentTick);

    const std::vector<DebrisInfo> allDebris = getAllDebrisInfo(m_admin);

    // Tick down per-module collection cooldowns.
    m_admin.forEach<SalvagerComponent>(
        [](entt::entity /*e*/, SalvagerComponent& s)
        {
            if (s.cooldownTicksRemaining > 0) { --s.cooldownTicksRemaining; }
        });

    // Scrap units already claimed by not-yet-applied collection cycles, so two
    // modules don't both target the last unit of the same debris (the claim would be
    // dropped at apply time). Debris is available while its amount exceeds its claims.
    std::map<entt::entity, int> claimedUnits;
    // Collection cycles already in flight toward each ship's shared cargo pool, so
    // concurrent modules on the same ship never start more cycles than the remaining
    // capacity can hold (REQ-SHP-SALVAGE).
    std::map<entt::entity, int> pendingByShip;
    for (const PendingCollection& pc : m_pendingCollections)
    {
        ++claimedUnits[pc.debris];
        ++pendingByShip[pc.ship];
    }

    // Cycle start: each ready, in-range module whose ship's pool has free space begins
    // a collection cycle — emit the beam now, collect one scrap mid-beam, start the
    // cooldown now.
    m_admin.forEach<SalvagerComponent, ModuleOwnerComponent>(
        [&](entt::entity /*moduleEntity*/, SalvagerComponent& s, const ModuleOwnerComponent& o)
        {
            if (s.cooldownTicksRemaining > 0 || s.collectionIntervalTicks <= 0) { return; }
            if (!m_admin.hasAll<PositionComponent, CargoComponent>(o.owner)) { return; }

            const CargoComponent& cargo = m_admin.get<CargoComponent>(o.owner);
            if (cargo.current + pendingByShip[o.owner] >= cargo.maxCapacity) { return; }

            const QVector2D ownerPos = m_admin.get<PositionComponent>(o.owner).value;
            for (const DebrisInfo& si : allDebris)
            {
                if ((si.position - ownerPos).length() > s.collectionRange_tiles) { continue; }
                if (claimedUnits[si.entity] >= m_admin.get<DebrisComponent>(si.entity).amount)
                {
                    continue;  // every remaining unit of this debris is already spoken for
                }
                outBeamFiredEvents.push_back(
                    BeamFiredEvent{BeamKind::Salvage, o.owner, si.entity, currentTick});
                m_pendingCollections.push_back({o.owner, si.entity,
                                                currentTick + kBeamImpactDelayTicks});
                ++claimedUnits[si.entity];
                ++pendingByShip[o.owner];
                s.cooldownTicksRemaining = s.collectionIntervalTicks;
                break;
            }
        });

    // Delivery: a ship at its assigned bay hands over one unit of cargo per tick.
    m_admin.forEach<DeliverScrapBehavior, PositionComponent>(
        [&](entt::entity ship, const DeliverScrapBehavior& deliver, const PositionComponent& pos)
        {
            if (!deliver.deliveryBay.has_value()) { return; }
            const Building* bay = findBuilding(state, *deliver.deliveryBay);
            if (!bay) { return; }

            const QVector2D bayCenter(bay->anchor.x() + bay->footprint.width()  / 2.0f,
                                      bay->anchor.y() + bay->footprint.height() / 2.0f);
            if ((pos.value - bayCenter).length() > 1.0f) { return; }

            // Hand over one unit from the ship's shared cargo pool.
            if (!m_admin.hasAll<CargoComponent>(ship)) { return; }
            CargoComponent& cargo = m_admin.get<CargoComponent>(ship);
            if (cargo.current <= 0) { return; }
            if (deliverScrapToSalvageBay(state, *deliver.deliveryBay))
            {
                --cargo.current;
            }
        });
}

void SalvagerSystem::applyPendingCollections(Tick currentTick)
{
    std::vector<PendingCollection>::iterator it = m_pendingCollections.begin();
    while (it != m_pendingCollections.end())
    {
        if (it->appliesAt <= currentTick)
        {
            if (m_admin.isValid(it->ship) && m_admin.hasAll<CargoComponent>(it->ship))
            {
                CargoComponent& cargo = m_admin.get<CargoComponent>(it->ship);
                if (cargo.current < cargo.maxCapacity && collectOne(m_admin, it->debris))
                {
                    ++cargo.current;
                }
            }
            it = m_pendingCollections.erase(it);
        }
        else
        {
            ++it;
        }
    }
}
