#pragma once

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

#include <QPoint>
#include <QPointF>
#include <QRect>

#include "BeltSlot.h"
#include "Item.h"
#include "ItemType.h"
#include "Port.h"
#include "Rotation.h"

class Hasher;

// Carries item type and fractional world position for the renderer.
// worldPos is in tile units (1 tile = 1.0 unit); origin matches tile coords.
struct VisualItem
{
    ItemType type;
    QPointF worldPos;
};

// Isolated belt-and-splitter transport layer.  See architecture.md §Belt Subsystem.
//
// Buildings interact only through tryPutItem / tryTakeItem.
// Rendering reads only through forEachVisualItem.
// No other system inspects tile contents.
class BeltSystem
{
public:
    explicit BeltSystem(double beltSpeed_tps);

    // -- Placement -----------------------------------------------------------
    // Register a new belt tile.  Any items already on this tile are cleared.
    void placeBelt(QPoint tile, Rotation direction);

    // Register a new tunnel entry tile.
    void placeTunnelEntry(QPoint tile, Rotation direction, int maxDistance);

    // Register a new tunnel exit tile.
    void placeTunnelExit(QPoint tile, Rotation direction);

    // Register a new splitter tile.  outputA and outputB are the two exit
    // directions (e.g. West and East for a default-rotation splitter).
    // Items entering from any adjacent belt whose direction points into this
    // tile are held and routed to one of the two outputs.
    void placeSplitter(QPoint tile, Rotation outputA, Rotation outputB);

    // Remove a belt or splitter tile (on deconstruct).  Items are discarded.
    void removeTile(QPoint tile);

    // -- Splitter filter configuration (REQ-BLD-SPLITTER) -------------------
    // filterA / filterB: empty means "accept all".
    void setSplitterFilters(QPoint tile,
                            const std::vector<ItemType>& filterA,
                            const std::vector<ItemType>& filterB);

    struct SplitterInfo
    {
        Rotation outputA;
        Rotation outputB;
        std::vector<ItemType> filterA;
        std::vector<ItemType> filterB;
    };
    std::optional<SplitterInfo> getSplitterInfo(QPoint tile) const;

    // -- Port interface (buildings <-> belts) --------------------------------
    // port.tile      = the belt tile adjacent to the building
    // port.direction = direction items flow on that tile
    //
    // tryPutItem: place item onto tile.
    //   Returns false if the tile is not a belt/splitter/tunnel entry, tile full,
    //   or the item would enter through the tile's output edge (REQ-MAT-ACCEPT-DIR).
    //   fromDir: travel direction of the item (used for splitter animation and for
    //   the output-edge check).
    bool tryPutItem(QPoint tile, Item item, Rotation fromDir = Rotation::West);

    // tryTakeItem: remove and return the leading item from port.tile.
    //   Returns nullopt if tile is not a belt, direction mismatches, or tile empty.
    std::optional<Item> tryTakeItem(Port port);

    // peekItem: return the type of the leading item without removing it.
    //   Returns nullopt if tile is not a belt, direction mismatches, or tile empty.
    std::optional<ItemType> peekItem(Port port) const;

    // Progress advanced per tick at the configured belt speed (tile fraction per
    // tick). Shared with building output belts so emerging items travel at exactly
    // the same speed as real belts (REQ-MAT-OUTPUT-EMERGE).
    double getProgressPerTick_tpt() const { return m_progressPerTick_tpt; }

    // -- Maintenance ---------------------------------------------------------
    // Removes every item from the given tiles (REQ-UI-BELT-CLEAR). A tunnel exit takes
    // the items in transit through its tunnel with it; a tunnel entry does not, so what
    // a clear removes is exactly what countItems reported for the same tile.
    void clearTiles(const std::vector<QPoint>& tiles);
    void tick();

    // -- Rendering -----------------------------------------------------------
    void forEachVisualItem(QRect viewportTiles,
                           std::function<void(VisualItem)> visit) const;

    // -- Inspection ----------------------------------------------------------
    // What the given tiles carry, summed per item type (REQ-UI-BELT-ITEMS). Items in
    // transit through a tunnel are counted on its exit, that being where they will
    // arrive and the only end whose clear discards them (REQ-BLD-TUNNEL-TRANSIT).
    //
    // A method rather than an exposed container: what a tile holds and how it holds it
    // is this subsystem's alone to know, so the per-tile representation stays swappable
    // (architecture.md, Belt Subsystem).
    std::map<ItemType, int> countItems(const std::vector<QPoint>& tiles) const;

    // -- Determinism ---------------------------------------------------------
    // Folds all transport state (belt/splitter/tunnel tiles and their items)
    // into the hasher in deterministic order (see docs/replay_design.md).
    void appendChecksum(Hasher& hasher) const;

private:
    void advanceProgress();
    void advanceTunnelProgress();
    void moveItemsToNextTile();
    void moveTunnelItems();
    void routeSplitterItems();

    // Place item into back slot of an existing belt tile at progress 0.
    // Returns false if tile is not a belt or is full.
    bool tryPlaceOnBelt(QPoint tile, Item item);

    // Push an item to any tile type (belt, splitter, tunnel entry, tunnel exit).
    bool tryPushToTile(QPoint dest, Item item, Rotation fromDir);

    void reevaluateTunnelPairing();

    static std::pair<int, int> key(QPoint tile);
    static QPoint adjacentTile(QPoint tile, Rotation dir);
    static Rotation oppositeRotation(Rotation dir);

    // True if an item travelling in travelDir would enter the transport tile at
    // `tile` through one of that tile's output edges (and must therefore be
    // refused).  Returns false if no transport tile occupies `tile`.
    bool entersThroughOutputEdge(QPoint tile, Rotation travelDir) const;

    struct BeltTile
    {
        Rotation direction;
        // front (highest progress) at index 0; back (just entered) at end.  Max 4.
        std::vector<BeltItemSlot> itemSlots;
    };

    struct SplitterTile
    {
        Rotation outputA;
        Rotation outputB;
        std::vector<ItemType> filterA;   // empty = accept all
        std::vector<ItemType> filterB;
        bool nextOutputIsA;              // alternation state
        // Unassigned items: [0] = routing candidate (higher progress, caps at 0.5).  Max 2.
        std::vector<BeltItemSlot> back;
        std::vector<Rotation>     backDir; // feeding belt direction, parallel to back
        std::optional<BeltItemSlot> frontA;  // progress [0, 1]; routed to outputA
        std::optional<BeltItemSlot> frontB;  // progress [0, 1]; routed to outputB
    };

    struct TunnelEntryTile
    {
        Rotation direction;
        int maxDistance;
        // front (highest progress) at index 0; back at end.  Max 4.
        std::vector<BeltItemSlot> itemSlots;
    };

    struct TunnelExitTile
    {
        Rotation direction;
        // front (highest progress) at index 0; back at end.  Max 4.
        std::vector<BeltItemSlot> itemSlots;
    };

    struct TunnelTransitItem
    {
        Item item;
        double progress;
    };

    struct TunnelLink
    {
        QPoint entryTile;
        QPoint exitTile;
        double length;
        std::vector<TunnelTransitItem> items;  // front (highest progress) to back
    };

    // Folds a run of item slots (front-to-back order is canonical) into the hasher.
    static void appendItemSlots(Hasher& hasher, const std::vector<BeltItemSlot>& slotRun);

    double m_progressPerTick_tpt;  // beltSpeed_tps / kTickRateHz

    std::map<std::pair<int, int>, BeltTile>        m_belts;
    std::map<std::pair<int, int>, SplitterTile>    m_splitters;
    std::map<std::pair<int, int>, TunnelEntryTile> m_tunnelEntries;
    std::map<std::pair<int, int>, TunnelExitTile>  m_tunnelExits;
    std::vector<TunnelLink>                        m_tunnelLinks;
};

