#pragma once

#include <map>
#include <optional>
#include <string>
#include <vector>

#include <QPoint>
#include <QSize>

#include "BuildingType.h"
#include "BuildingId.h"

#include "entt/entity/entity.hpp"
#include "BeltSlot.h"
#include "Item.h"
#include "ItemType.h"
#include "Port.h"
#include "Rotation.h"
#include "ShipLayout.h"
#include "Tick.h"

// Per-material input buffer for a production building.
struct InputBuffer
{
    std::map<ItemType, int> counts;  // current item counts per material
    std::map<ItemType, int> caps;    // max items per material (2× per-cycle requirement)
};

// Per-material output buffer for a production building. The items are held in one
// production-ordered queue -- that is the order they leave at the output port
// (REQ-MAT-OUTPUT-EMERGE) -- while the capacity is per item type, so one item's backlog
// never occupies another's room (REQ-MAT-OUTPUT-BUFFER).
struct OutputBuffer
{
    std::vector<Item>       items;  // production order; feeds the output belt
    std::map<ItemType, int> caps;   // max items per material (2x its per-cycle amount)
};

// Active production cycle for a building.
struct Production
{
    std::string recipeId;
    Tick completesAt = 0;
    std::vector<Item> chosenOutputs;  // resolved at cycle start (reprocessing rolls here)
};

// A building placed on the map that is still under construction.
// Occupies tiles but does not produce.
struct ConstructionSite
{
    BuildingId     id            = kInvalidBuildingId;
    QPoint       anchor;                        // top-left of body bounding box
    QSize        footprint;
    std::vector<QPoint> bodyCells;              // absolute world tile coordinates
    Rotation     rotation      = Rotation::East;
    BuildingType type          = BuildingType::Miner;
    std::string  recipeId;                      // may be configured before completion
    Tick         completesAt   = 0;             // 0 = queued but not yet started
    std::optional<ShipLayoutConfig> shipLayout;

    // Splitter output filters configured before completion (REQ-BLD-SITE-CONFIG).
    // Empty = accept all; applied to the BeltSystem when the splitter is built.
    std::vector<ItemType> splitterFilterA;
    std::vector<ItemType> splitterFilterB;
};

// A fully constructed, operational building.
struct Building
{
    BuildingId     id            = kInvalidBuildingId;
    QPoint       anchor;                        // top-left of body bounding box
    QSize        footprint;
    Rotation     rotation      = Rotation::East;
    BuildingType type          = BuildingType::Miner;
    std::string  recipeId;                      // empty = none selected

    InputBuffer  inputBuffer;
    OutputBuffer outputBuffer;
    std::optional<Production> production;

    // Items currently emerging from each output port on its virtual output belt
    // (REQ-MAT-OUTPUT-EMERGE); one lane per output port, parallel to outputPorts.
    // Each lane holds slots at progress [0.5, 1.0], front (highest progress) first.
    // An emerging item still counts as residing in the output buffer until it hands
    // off onto a real belt at progress 1.0.
    std::vector<std::vector<BeltItemSlot>> emergingItems;

    // Total items held on the output side: buffered plus still-emerging. The
    // output-buffer capacity rule (REQ-MAT-OUTPUT-BUFFER) counts emerging items,
    // since they have not yet left the building.
    int getOutputItemCount() const
    {
        int count = static_cast<int>(outputBuffer.items.size());
        for (const std::vector<BeltItemSlot>& lane : emergingItems)
        {
            count += static_cast<int>(lane.size());
        }
        return count;
    }

    // The same over one material, which is what its own capacity is measured against
    // (REQ-MAT-OUTPUT-BUFFER).
    int getOutputItemCount(const ItemType& type) const
    {
        int count = 0;
        for (const Item& item : outputBuffer.items)
        {
            if (item.type == type) { ++count; }
        }
        for (const std::vector<BeltItemSlot>& lane : emergingItems)
        {
            for (const BeltItemSlot& slot : lane)
            {
                if (slot.item.type == type) { ++count; }
            }
        }
        return count;
    }

    // Items currently travelling inward on each input port's virtual input belt
    // (REQ-MAT-INPUT-INTAKE); one lane per input port, parallel to inputPorts. Each
    // lane holds slots at progress [0.0, 0.5], front (highest progress) first. An
    // in-transit item has reserved a slot in its per-material input buffer but is
    // not yet consumable — it enters the buffer only on reaching progress 0.5.
    std::vector<std::vector<BeltItemSlot>> incomingItems;

    // Buffered plus in-transit count of one input material. The acceptance/space
    // test (REQ-MAT-INPUT-PORTS, REQ-MAT-INPUT-INTAKE) counts in-transit items, so
    // buffered + reserved never exceeds the material's cap (REQ-MAT-INPUT-BUFFER).
    int pendingInputCount(const ItemType& type) const
    {
        int count = 0;
        const std::map<ItemType, int>::const_iterator it = inputBuffer.counts.find(type);
        if (it != inputBuffer.counts.end()) { count = it->second; }
        for (const std::vector<BeltItemSlot>& lane : incomingItems)
        {
            for (const BeltItemSlot& slot : lane)
            {
                if (slot.item.type == type) { ++count; }
            }
        }
        return count;
    }

    // Pre-computed from surface mask at placement; in absolute world coordinates.
    std::vector<QPoint> bodyCells;
    std::vector<Port>   outputPorts;
    std::vector<Port>   inputPorts;  // perimeter tiles (minus output-port tiles),
                                     // direction pointing INTO building

    // Module layout for shipyards (REQ-MOD-LAYOUT).
    std::optional<ShipLayoutConfig> shipLayout;

    // True while this building sits in the deconstruction queue (REQ-BLD-DECON-QUEUE).
    // A queued building stops operating immediately (all tick loops skip it) but keeps
    // occupying its tiles until its deconstruction completes.
    bool queuedForDeconstruction = false;
};

