#pragma once

#include <functional>
#include <map>
#include <optional>

#include <QPoint>
#include <QVector2D>

#include "BuildingType.h"
#include "Rotation.h"

// QPoint has no operator<, so an explicit ordering is needed to key a map or set
// by tile.
struct QPointCompare
{
    bool operator()(const QPoint& a, const QPoint& b) const
    {
        if (a.x() != b.x()) { return a.x() < b.x(); }
        return a.y() < b.y();
    }
};

// A tunnel building occupying a single tile: whether it is an entry or an exit and
// the direction it faces. Used by the tunnel pairing scan (REQ-BLD-TUNNEL-PAIR) and
// the unified tunnel build mode (REQ-BLD-TUNNEL-MODE).
struct TunnelTileInfo
{
    BuildingType type;      // TunnelEntry or TunnelExit
    Rotation     rotation;  // facing direction
};

// Given a tile, returns the tunnel building on it (entry or exit) with its facing
// direction, or std::nullopt when the tile holds no tunnel building.
using TunnelLookup = std::function<std::optional<TunnelTileInfo>(QPoint)>;

// Tunnel entries/exits indexed by their single-cell tile (REQ-BLD-TUNNEL-MODE).
using TunnelTileMap = std::map<QPoint, TunnelTileInfo, QPointCompare>;

// Wraps a tunnel tile index in the lookup functor the helpers below take. The
// returned functor references `tunnels`, which must outlive it.
TunnelLookup makeTunnelLookup(const TunnelTileMap& tunnels);

// Steps from `start` in `stepDir` over the tiles at distance 1..maxDistance and
// returns the first tile whose tunnel faces `targetFacing`. Tunnel buildings facing
// any other direction are skipped, mirroring the "stop at the first same-direction
// tunnel" rule of REQ-BLD-TUNNEL-PAIR. Returns std::nullopt if none is found in range.
std::optional<QPoint> firstTunnelFacing(const TunnelLookup& lookup, QPoint start,
                                        Rotation stepDir, Rotation targetFacing,
                                        int maxDistance);

// Result of resolving which tunnel end the ghost should become at a hovered tile
// (REQ-BLD-TUNNEL-MODE): the type to place and the existing tunnel it would complete.
struct TunnelCompletion
{
    BuildingType          resolvedType;  // TunnelEntry (default) or TunnelExit
    std::optional<QPoint> partnerTile;   // matched existing tunnel, if any
};

// Resolves the tunnel ghost type for a hover at `hoverTile` with the ghost facing
// `rotation` (REQ-BLD-TUNNEL-MODE):
//  - exit-completion: placing an exit here would pair with an existing entry behind
//    it (the entry's forward search reaches this tile as its first same-direction
//    tunnel) — the ghost becomes a TunnelExit;
//  - entry-completion: placing an entry here would pair with an existing exit ahead
//    of it — the ghost stays a TunnelEntry.
// When both apply, the end whose existing partner is closer to `cursorWorldPos` (the
// sub-tile cursor position in world tile units) wins, so nudging the cursor within a
// single tile can flip the target. With no match the ghost stays a TunnelEntry with
// no partner.
TunnelCompletion resolveTunnelCompletion(const TunnelLookup& lookup, QPoint hoverTile,
                                         Rotation rotation, int maxDistance,
                                         QVector2D cursorWorldPos);

// Finds the matching end of an existing tunnel building at `tile` of the given `type`
// (TunnelEntry or TunnelExit) facing `rotation`, applying the pairing scan of
// REQ-BLD-TUNNEL-PAIR over `lookup`:
//  - for an entry, the first same-direction tunnel along its facing direction, if it
//    is an exit;
//  - for an exit, the first same-direction tunnel opposite its facing direction, if it
//    is an entry.
// Returns the partner tile, or std::nullopt when the tunnel is unpaired.
std::optional<QPoint> findTunnelPartner(const TunnelLookup& lookup, QPoint tile,
                                        BuildingType type, Rotation rotation,
                                        int maxDistance);
