#include "FactoryQueries.h"

#include <algorithm>
#include <limits>

#include "BeltSlot.h"
#include "PortGeometry.h"
#include "ProductionRules.h"
#include "SelectionBox.h"
#include "SurfaceMask.h"

#include "Item.h"
#include "ItemType.h"

const Building* findBuilding(const FactoryState& state, BuildingId id)
{
    for (const Building& building : state.buildings)
    {
        if (building.id == id)
        {
            return &building;
        }
    }
    return nullptr;
}

Building* findBuilding(FactoryState& state, BuildingId id)
{
    for (Building& building : state.buildings)
    {
        if (building.id == id)
        {
            return &building;
        }
    }
    return nullptr;
}

const ConstructionSite* findSite(const FactoryState& state, BuildingId id)
{
    for (const ConstructionSite& site : state.constructionQueue)
    {
        if (site.id == id)
        {
            return &site;
        }
    }
    return nullptr;
}

std::vector<Building> getAllBuildings(const FactoryState& state)
{
    return state.buildings;
}

std::vector<ConstructionSite> getAllSites(const FactoryState& state)
{
    return std::vector<ConstructionSite>(state.constructionQueue.begin(),
                                         state.constructionQueue.end());
}

int getProductionBuildingCount(const FactoryState& state)
{
    int count = 0;
    for (const Building& b : state.buildings)
    {
        if (isProductionBuildingType(b.type)) { ++count; }
    }
    return count;
}

int getActiveProductionBuildingCount(const FactoryState& state)
{
    int count = 0;
    for (const Building& b : state.buildings)
    {
        if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; }
    }
    return count;
}

bool isTileOccupied(const FactoryState& state, QPoint tile)
{
    return state.grid.isOccupied(tile);
}

bool isQueuedForDeconstruction(const FactoryState& state, BuildingId id)
{
    const Building* building = findBuilding(state, id);
    return building && building->queuedForDeconstruction;
}

const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos,
                                    BuildingType type)
{
    const Building* best = nullptr;
    float bestDist = std::numeric_limits<float>::max();
    for (const Building& b : state.buildings)
    {
        if (b.type != type)
        {
            continue;
        }
        QVector2D center(b.anchor.x() + b.footprint.width()  / 2.0f,
                         b.anchor.y() + b.footprint.height() / 2.0f);
        float dist = (center - worldPos).length();
        if (dist < bestDist)
        {
            bestDist = dist;
            best = &b;
        }
    }
    return best;
}

bool deliverScrapToSalvageBay(FactoryState& state, BuildingId bayId)
{
    Building* bay = findBuilding(state, bayId);
    if (!bay || bay->type != BuildingType::SalvageBay)
    {
        return false;
    }
    if (bay->queuedForDeconstruction)
    {
        return false;  // queued for deconstruction: stopped operating (REQ-BLD-DECON-QUEUE)
    }
    // Emerging scrap still counts against the bay's holding capacity
    // (REQ-MAT-OUTPUT-EMERGE). Scrap is all the bay ever holds, so its single buffer is
    // the one being filled (REQ-BLD-SALVAGE-BAY).
    const ItemType scrap{"scrap"};
    if (!outputBufferHasRoom(*bay, scrap, 1))
    {
        return false;
    }
    bay->outputBuffer.items.push_back(Item{scrap});
    return true;
}

std::vector<Port> getInputPorts(const FactoryState& state, const GameConfig& config,
                                BuildingId id)
{
    if (const Building* building = findBuilding(state, id))
    {
        return building->inputPorts;
    }
    if (const ConstructionSite* site = findSite(state, id))
    {
        // A site stores no ports; derive its output ports from the mask (absolute)
        // and run the same input-edge scan (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS).
        const BuildingDef* def = config.buildings.findBuildingDef(site->type);
        if (def == nullptr) { return {}; }
        const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, site->rotation);
        std::vector<Port> outputPortsAbsolute;
        outputPortsAbsolute.reserve(mask.outputPorts.size());
        for (const Port& port : mask.outputPorts)
        {
            outputPortsAbsolute.push_back(Port{ site->anchor + port.tile, port.direction });
        }
        return computeInputPorts(site->bodyCells, outputPortsAbsolute);
    }
    return {};
}


std::optional<BeltSystem::SplitterInfo>
getSiteSplitterInfo(const FactoryState& state, const GameConfig& config, BuildingId id)
{
    for (const ConstructionSite& site : state.constructionQueue)
    {
        if (site.id != id) { continue; }
        if (site.type != BuildingType::Splitter) { return std::nullopt; }

        const BuildingDef* def = config.buildings.findBuildingDef(site.type);
        const ParsedSurfaceMask mask = parseSurfaceMask(
            def ? def->surfaceMask : std::vector<std::string>{}, site.rotation);
        if (mask.outputPorts.size() < 2) { return std::nullopt; }

        BeltSystem::SplitterInfo info;
        info.outputA = mask.outputPorts[0].direction;
        info.outputB = mask.outputPorts[1].direction;
        info.filterA = site.splitterFilterA;
        info.filterB = site.splitterFilterB;
        return info;
    }
    return std::nullopt;
}


std::vector<BuildingId> buildingsInBox(const FactoryState& state,
                                       const QRectF& worldBox)
{
    const auto covers = [&](const std::vector<QPoint>& bodyCells)
    {
        for (const QPoint& cell : bodyCells)
        {
            if (boxCoversTile(worldBox, cell)) { return true; }
        }
        return false;
    };

    std::vector<BuildingId> ids;
    for (const Building& building : getAllBuildings(state))
    {
        if (covers(building.bodyCells)) { ids.push_back(building.id); }
    }
    for (const ConstructionSite& site : getAllSites(state))
    {
        if (covers(site.bodyCells)) { ids.push_back(site.id); }
    }
    return ids;
}

TunnelTileMap collectTunnelTiles(const FactoryState& state)
{
    // Index every tunnel entry/exit — built or still a construction site — by its
    // single-cell tile, so a just-placed tunnel (not yet constructed) is matchable
    // (REQ-BLD-TUNNEL-MODE, REQ-BLD-TUNNEL-SELECT-HIGHLIGHT).
    TunnelTileMap tunnels;
    for (const Building& building : getAllBuildings(state))
    {
        if (building.type == BuildingType::TunnelEntry
            || building.type == BuildingType::TunnelExit)
        {
            tunnels[building.anchor] = TunnelTileInfo{building.type, building.rotation};
        }
    }
    for (const ConstructionSite& site : getAllSites(state))
    {
        if (site.type == BuildingType::TunnelEntry
            || site.type == BuildingType::TunnelExit)
        {
            tunnels[site.anchor] = TunnelTileInfo{site.type, site.rotation};
        }
    }
    return tunnels;
}

BuildingId allocateBuildingId(FactoryState& state)
{
    return state.nextBuildingId++;
}

void forEachEmergingItem(const FactoryState& state,
                         const std::function<void(const ItemType&, QPointF)>& visit)
{
    for (const Building& building : state.buildings)
    {
        for (std::size_t p = 0; p < building.outputPorts.size(); ++p)
        {
            const Port& port = building.outputPorts[p];
            const QPoint bodyTile = outputBodyTile(port.tile, port.direction);
            const std::vector<BeltItemSlot>& lane = building.emergingItems[p];

            // Render least-progressed first (bottom) -> most-progressed last (top),
            // matching belt item ordering (REQ-GW-TILE-SIZE).
            for (int i = static_cast<int>(lane.size()) - 1; i >= 0; --i)
            {
                visit(lane[i].item.type,
                      beltSlotWorldPos(bodyTile, port.direction, lane[i].progress));
            }
        }
    }
}

void forEachIncomingItem(const FactoryState& state,
                         const std::function<void(const ItemType&, QPointF)>& visit)
{
    for (const Building& building : state.buildings)
    {
        for (std::size_t p = 0; p < building.inputPorts.size(); ++p)
        {
            const Port& port = building.inputPorts[p];
            const QPoint bodyTile = inputBodyTile(port.tile, port.direction);
            const std::vector<BeltItemSlot>& lane = building.incomingItems[p];

            // Render least-progressed first (bottom) -> most-progressed last (top),
            // matching belt item ordering (REQ-GW-TILE-SIZE).
            for (int i = static_cast<int>(lane.size()) - 1; i >= 0; --i)
            {
                visit(lane[i].item.type,
                      beltSlotWorldPos(bodyTile, port.direction, lane[i].progress));
            }
        }
    }
}

std::vector<QPoint> collectBeltTiles(const FactoryState& state,
                                     const std::vector<BuildingId>& ids)
{
    std::vector<QPoint> tiles;
    for (BuildingId id : ids)
    {
        // findBuilding fails for a construction site, which is what keeps a site out:
        // its tile carries nothing and is not registered with the belt subsystem until
        // it is built (REQ-BLD-SITE-CONFIG).
        const Building* building = findBuilding(state, id);
        if (!building || !isBeltSubsystemType(building->type))
        {
            continue;
        }
        for (const QPoint& cell : building->bodyCells)
        {
            tiles.push_back(cell);
        }
    }
    return tiles;
}
