#pragma once

#include <deque>
#include <functional>
#include <map>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include <QPoint>
#include <QVector2D>

#include "BeltSystem.h"
#include "Building.h"
#include "FactoryState.h"
#include "BuildingBuffers.h"
#include "DeconstructionSystem.h"
#include "PlacementRules.h"
#include "BuildingType.h"
#include "BuildingId.h"
#include "GameConfig.h"
#include "Rotation.h"
#include "ModulesConfig.h"
#include "ShipLayout.h"
#include "ShipsConfig.h"
#include "Tick.h"

// What buildings are: placing them, demolishing them, rotating them, and configuring
// what they will run. What flows through them once they stand -- intake, production
// cycles, output -- belongs to ProductionSystem, which took the RNG, the ship spawner and
// the unlock test with it.
//
// All types including Belt and Splitter are stored as Building instances; BeltSystem owns
// the per-tile simulation data (item slots, flow).
class BuildingSystem
{
public:
    explicit BuildingSystem(const GameConfig& config);

    // -- Placement / deconstruct ------------------------------------------------
    // Returns the new entity id, or nullopt if the placement falls outside the
    // world bounds (vertical extent and asteroid left edge).  Belt and Splitter
    // register with BeltSystem directly; other types enter the construction
    // queue.  Terrain type (A vs S) is NOT checked here so that tests can stage
    // arbitrary layouts; the player-facing entry point
    // (Simulation::tryPlaceBuilding) enforces the full rule via isPlacementValid.
    std::optional<BuildingId> place(FactoryState& state, BuildingType type, QPoint anchor, Rotation rotation,
                   Tick currentTick);

    // Sets the current buildable asteroid width in tiles. Grows the left
    // placement bound as the player unlocks asteroid expansions (REQ-EXP-UNLOCK).
    // Defaults to world.regions.asteroid_width_tiles at construction.
    void setAsteroidWidth_tiles(FactoryState& state, int widthTiles) const
    { state.asteroidWidth_tiles = widthTiles; }

    // Mark a building or construction site for demolition (REQ-BLD-DECONSTRUCT).
    // A construction site is removed instantly and its full cost credited back to the
    // block stock. A fully-built building is instead appended to the deconstruction
    // queue (REQ-BLD-DECON-QUEUE) and stops operating at once; its (partial) refund is
    // credited later, on completion, by DeconstructionSystem. No-op for unknown ids and
    // for a building already queued.
    void deconstruct(FactoryState& state, BeltSystem& belts, BuildingId id,
                     Tick currentTick);

    // Take a building back out of the deconstruction queue before it is removed
    // (REQ-BLD-DECON-QUEUE). Clears its queued flag and resumes operation
    // (re-registering belt/tunnel/splitter tiles); discards deconstruction
    // progress and credits no refund. No-op if the id is not queued.
    void cancelDeconstruction(FactoryState& state, BeltSystem& belts, BuildingId id);

    // Set the recipe (or schematic id for shipyard) on a building or queued
    // construction site.  Clears both buffers on an operational building.
    void setRecipe(FactoryState& state, BuildingId id, const std::string& recipeId);

    // Set the module layout for a shipyard. Cancels in-progress production
    // (materials discarded) and reinitializes input buffers (REQ-BLD-SHIPYARD).
    void setShipLayout(FactoryState& state, BuildingId id, const ShipLayoutConfig& layout);

    // Splitter filter configuration for a queued/under-construction Splitter
    // site (REQ-BLD-SITE-CONFIG). Operational splitters are configured through
    // BeltSystem by tile; these mirror that for sites, which are not yet
    // registered with BeltSystem. getSiteSplitterInfo returns the site's two
    // output directions (derived from its surface mask) and stored filters, or
    // nullopt if the id is not a Splitter site. The stored filters are applied
    // to BeltSystem when the splitter finishes building (tickConstruction).
    void setSiteSplitterFilters(FactoryState& state, BuildingId id,
                                const std::vector<ItemType>& filterA,
                                const std::vector<ItemType>& filterB);

    // This system has no tick hook of its own: a building placed, configured or
    // demolished is a player action, not something that advances every tick
    // (ProductionSystem, ConstructionSystem, DeconstructionSystem tick instead).

    // This system answers no queries: reading the factory needs none, the queries being
    // free functions over the state (FactoryQueries.h), the placement rules
    // (PlacementRules.h) and the production rules (ProductionRules.h). What is left here
    // mutates.

    // Rotate an existing building or construction site to newRotation in place.
    // For belt-type operational buildings, re-registers with BeltSystem (items
    // currently on the tile are discarded by BeltSystem::removeTile).
    void rotateInPlace(FactoryState& state, BeltSystem& belts, BuildingId id,
                       Rotation newRotation);

    // Register / unregister tile occupancy for ECS station entities.
    void registerTileOccupancy(FactoryState& state, const std::vector<QPoint>& cells, BuildingId ownerPlaceholder);
    void unregisterTileOccupancy(FactoryState& state, const std::vector<QPoint>& cells);

    // Bypass the construction queue and create a fully-operational Building
    // immediately. Used for pre-placed structures (HQ, defence stations).
    // surfaceMask comes from the relevant config struct.
    BuildingId placeImmediate(FactoryState& state, BuildingType type,
                            const std::vector<std::string>& surfaceMask,
                            QPoint anchor, Rotation rotation);

    // Mutable iteration over all operational buildings.
    void forEachBuilding(FactoryState& state, std::function<void(Building&)> fn);

private:
    // The config alone: what a building costs, occupies and can run. No world data --
    // the factory and the transport layer both arrive per call, as they do for
    // ConstructionSystem and ProductionSystem (FactoryState.h) -- and no RNG or callbacks,
    // those having gone to ProductionSystem with the material flow that needed them.
    const GameConfig& m_config;
};
