#pragma once

#include <functional>
#include <vector>

#include <QPoint>
#include <QPointF>
#include <QRectF>
#include <QVector2D>

#include "Building.h"
#include "BuildingId.h"
#include "BuildingType.h"
#include "BeltSystem.h"
#include "FactoryState.h"
#include "GameConfig.h"
#include "Port.h"
#include "TunnelCompletion.h"

// Queries and operations over the factory's world data that need nothing but that
// data — no config, no belts, no RNG. Free functions rather than BuildingSystem
// methods so that callers depend on the data they read instead of on the system
// that happens to tick it (see FactoryState.h).
//
// Most need nothing but the state. The two at the bottom also take the config,
// because answering them means reading a building definition — but still no belts,
// no RNG and no system.

// The building with the given id, or nullptr when no building has it. Construction
// sites are not buildings yet — use findSite for those.
const Building* findBuilding(const FactoryState& state, BuildingId id);
Building*       findBuilding(FactoryState& state, BuildingId id);

// The queued construction site with the given id, or nullptr.
const ConstructionSite* findSite(const FactoryState& state, BuildingId id);

std::vector<Building>         getAllBuildings(const FactoryState& state);
std::vector<ConstructionSite> getAllSites(const FactoryState& state);

// REQ-UI-DEBUG-OVERLAY "Max Factory Production": count of completed
// (operational) Miner/Smelter/Assembler/ReprocessingPlant/Shipyard buildings.
int getProductionBuildingCount(const FactoryState& state);

// REQ-UI-DEBUG-OVERLAY "Current Factory Production": subset of the above that
// currently has an active production cycle.
int getActiveProductionBuildingCount(const FactoryState& state);

bool isTileOccupied(const FactoryState& state, QPoint tile);

// True while the building is in the deconstruction queue (REQ-BLD-DECON-QUEUE).
bool isQueuedForDeconstruction(const FactoryState& state, BuildingId id);

// The nearest building of the given type to a world position, or nullptr when
// none exists. Distance is measured to the building's footprint centre.
const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos,
                                    BuildingType type);

// Hands one scrap to a Salvage Bay's output buffer (REQ-BLD-SALVAGE-BAY). Fails
// if the id is not a Salvage Bay, it is queued for deconstruction
// (REQ-BLD-DECON-QUEUE), or its holding capacity is already taken — emerging
// scrap counts against that capacity (REQ-MAT-OUTPUT-EMERGE).
bool deliverScrapToSalvageBay(FactoryState& state, BuildingId bayId);

// Every belt-facing edge of the building or site with this id (REQ-MAT-INPUT-PORTS,
// REQ-BLD-BELT-DRAG). Empty when the id is unknown. A site has no stored ports, so
// they are derived from its surface mask.
std::vector<Port> getInputPorts(const FactoryState& state, const GameConfig& config,
                                BuildingId id);

// The two output directions and stored filters of a queued Splitter site
// (REQ-BLD-SITE-CONFIG), or nullopt if the id is not one. Operational splitters are
// configured through BeltSystem by tile instead.
std::optional<BeltSystem::SplitterInfo> getSiteSplitterInfo(const FactoryState& state,
                                                            const GameConfig& config,
                                                            BuildingId id);

// Ids of all buildings and construction sites the selection box covers — those with
// a body cell the box overlaps, per boxCoversTile (REQ-UI-MULTI-SELECT,
// REQ-BLD-DECONSTRUCT-BOX). `worldBox` is in world coordinates and normalized; it is
// not snapped to tiles.
std::vector<BuildingId> buildingsInBox(const FactoryState& state,
                                       const QRectF& worldBox);

// Every tunnel entry and exit, built or still a construction site, indexed by its
// single-cell tile. Shared by the placement preview and the selection highlight.
TunnelTileMap collectTunnelTiles(const FactoryState& state);

// Hands out the next building id and advances the counter (BuildingId.h). A mutation
// rather than a query, like deliverScrapToSalvageBay above: it is one line over the state
// and belongs to no system, every id-issuing path being a factory path.
BuildingId allocateBuildingId(FactoryState& state);

// Visits every item currently emerging from a building output port on its virtual output
// belt (REQ-MAT-OUTPUT-EMERGE), passing the item type and its world-space centre (in tile
// units). Least-progressed first (drawn bottom) so callers can paint in visit order
// (REQ-GW-TILE-SIZE ordering).
void forEachEmergingItem(const FactoryState& state,
                         const std::function<void(const ItemType&, QPointF)>& visit);

// Visits every item currently travelling inward on a building input port's virtual input
// belt (REQ-MAT-INPUT-INTAKE), in the same form and the same order.
void forEachIncomingItem(const FactoryState& state,
                         const std::function<void(const ItemType&, QPointF)>& visit);

// The tiles of the belt-subsystem buildings among the given ids, in the order the ids
// arrive; anything else in the selection is skipped, as are construction sites, whose
// tiles the belt subsystem does not know yet (REQ-BLD-SITE-CONFIG). Shared by the panel's
// item list and its clear action, so both speak of exactly the same tiles
// (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR).
std::vector<QPoint> collectBeltTiles(const FactoryState& state,
                                     const std::vector<BuildingId>& ids);
