#include "PlacementRules.h"

#include "BuildingType.h"
#include "FactoryQueries.h"
#include "SurfaceMask.h"

bool bodyCellsWithinWorldBounds(const FactoryState& state, const GameConfig& config,const std::vector<QPoint>& bodyCells,
                                                QPoint anchor)
{
    const int heightTiles = config.world.heightTiles;
    const int leftEdgeX   = -state.asteroidWidth_tiles;
    for (const QPoint& cell : bodyCells)
    {
        const QPoint worldCell = anchor + cell;
        if (worldCell.y() < 0 || worldCell.y() >= heightTiles)
        {
            return false;
        }
        if (worldCell.x() < leftEdgeX)
        {
            return false;
        }
    }
    return true;
}

bool isPlacementValid(const FactoryState& state, const GameConfig& config,BuildingType type, QPoint anchor,
                                      Rotation rotation)
{
    const BuildingDef* def = config.buildings.findBuildingDef(type);
    if (def == nullptr)
    {
        return false;
    }
    const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rotation);

    if (!bodyCellsWithinWorldBounds(state, config, mask.bodyCells, anchor))
    {
        return false;
    }

    // Terrain: ship-dock (S) cells must sit in space (x >= 0); all other body
    // (A) cells must sit on the asteroid (x < 0). (REQ-BLD-PLACE-VALID)
    for (const QPoint& cell : mask.bodyCells)
    {
        const QPoint worldCell = anchor + cell;
        bool isShipDock = false;
        for (const QPoint& dock : mask.shipDockCells)
        {
            if (dock == cell)
            {
                isShipDock = true;
                break;
            }
        }
        if (isShipDock)
        {
            if (worldCell.x() < 0)
            {
                return false;
            }
        }
        else if (worldCell.x() >= 0)
        {
            return false;
        }
    }
    return true;
}


std::optional<CoincidingBuilding> findCoincidingSameTypeBuilding(const FactoryState& state,
    const GameConfig& config, BuildingType type, QPoint anchor, Rotation rot)
{
    const BuildingDef* def = config.buildings.findBuildingDef(type);
    if (!def) { return std::nullopt; }

    const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rot);
    if (mask.bodyCells.empty()) { return std::nullopt; }

    // All body cells must be occupied by the same entity.
    const QPoint firstAbs = anchor + mask.bodyCells[0];
    const std::optional<BuildingId> firstOwner = state.grid.findOwner(firstAbs);
    if (!firstOwner.has_value()) { return std::nullopt; }
    const BuildingId candidateId = *firstOwner;

    for (const QPoint& rel : mask.bodyCells)
    {
        const std::optional<BuildingId> owner = state.grid.findOwner(anchor + rel);
        if (!owner.has_value() || *owner != candidateId)
        {
            return std::nullopt;
        }
    }

    // Verify the candidate is the same building type with the same cell count.
    for (const ConstructionSite& site : state.constructionQueue)
    {
        if (site.id != candidateId) { continue; }
        if (site.type != type) { return std::nullopt; }
        if (site.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
        return CoincidingBuilding{candidateId, site.rotation};
    }
    for (const Building& b : state.buildings)
    {
        if (b.id != candidateId) { continue; }
        if (b.type != type) { return std::nullopt; }
        if (b.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
        return CoincidingBuilding{candidateId, b.rotation};
    }

    return std::nullopt;
}


std::optional<BuildingId> findRotateInPlaceTarget(const FactoryState& state, const GameConfig& config,
    BuildingType type, QPoint anchor, Rotation rot)
{
    // Tunnel Entries and Tunnel Exits cannot be rotated in place; re-orienting a
    // tunnel requires deconstructing and re-placing it (REQ-BLD-ROTATE-IN-PLACE).
    if (type == BuildingType::TunnelEntry || type == BuildingType::TunnelExit)
    {
        return std::nullopt;
    }

    const std::optional<CoincidingBuilding> target =
        findCoincidingSameTypeBuilding(state, config, type, anchor, rot);
    if (!target.has_value()) { return std::nullopt; }
    return target->id;
}


namespace
{

// The building or construction site occupying `tile`, if it is of `type`, together with
// where it stands. The single-building transfer gesture hit-tests the cursor with this
// instead of comparing footprints (REQ-UI-BLUEPRINT-TRANSFER).
std::optional<CoincidingBuilding> findSameTypeBuildingAt(const FactoryState& state,
                                                         BuildingType type, QPoint tile,
                                                         QPoint& anchorOut)
{
    const std::optional<BuildingId> owner = state.grid.findOwner(tile);
    if (!owner.has_value()) { return std::nullopt; }

    if (const ConstructionSite* site = findSite(state, *owner))
    {
        if (site->type != type) { return std::nullopt; }
        anchorOut = site->anchor;
        return CoincidingBuilding{site->id, site->rotation};
    }
    if (const Building* building = findBuilding(state, *owner))
    {
        if (building->type != type) { return std::nullopt; }
        anchorOut = building->anchor;
        return CoincidingBuilding{building->id, building->rotation};
    }
    return std::nullopt;
}

}  // namespace


BlueprintGhostResolved resolveBlueprintGhost(const FactoryState& state, const GameConfig& config,
    BuildingType type, QPoint anchor, Rotation rotation, std::optional<QPoint> hoverTile)
{
    // The single-building gesture first, because it answers without looking at the
    // ghost's own position at all: whatever same-type building the cursor is on takes the
    // settings, at any facing, and the ghost snaps onto it (REQ-UI-BLUEPRINT-TRANSFER).
    if (hoverTile.has_value() && isConfigurableBuildingType(type))
    {
        QPoint hoveredAnchor;
        const std::optional<CoincidingBuilding> hovered =
            findSameTypeBuildingAt(state, type, *hoverTile, hoveredAnchor);
        if (hovered.has_value())
        {
            return BlueprintGhostResolved{BlueprintGhostAction::Transfer, hovered->id,
                                          hoveredAnchor, hovered->rotation};
        }
    }

    // Terrain and world bounds next: nothing rescues a ghost hanging off the asteroid
    // (REQ-BLD-PLACE-VALID condition (a)).
    if (!isPlacementValid(state, config, type, anchor, rotation))
    {
        return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt,
                                      anchor, rotation};
    }

    const std::optional<CoincidingBuilding> coinciding =
        findCoincidingSameTypeBuilding(state, config, type, anchor, rotation);
    if (coinciding.has_value())
    {
        // The building the blueprint wants is already there, facing the same way. It
        // takes the blueprint's settings if it has any to take (REQ-UI-BLUEPRINT-TRANSFER)
        // and is otherwise left exactly as it is (REQ-UI-BLUEPRINT-OVERLAP). Tunnels are
        // not excluded from the latter: nothing is rotated, so the reason for their
        // REQ-BLD-ROTATE-IN-PLACE exception does not arise.
        if (coinciding->rotation == rotation)
        {
            return BlueprintGhostResolved{isConfigurableBuildingType(type)
                                              ? BlueprintGhostAction::Transfer
                                              : BlueprintGhostAction::CompatibleOverlap,
                                          coinciding->id, anchor, rotation};
        }

        // Facing the other way, and placement may not re-orient it.
        return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt,
                                      anchor, rotation};
    }

    // No coincidence: any occupancy at all is an ordinary overlap
    // (REQ-BLD-PLACE-VALID condition (b)).
    const BuildingDef* def = config.buildings.findBuildingDef(type);
    if (!def)
    {
        return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt,
                                      anchor, rotation};
    }
    const ParsedSurfaceMask parsed = parseSurfaceMask(def->surfaceMask, rotation);
    for (const QPoint& relativeCell : parsed.bodyCells)
    {
        if (isTileOccupied(state, anchor + relativeCell))
        {
            return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt,
                                          anchor, rotation};
        }
    }
    return BlueprintGhostResolved{BlueprintGhostAction::PlaceNew, std::nullopt,
                                  anchor, rotation};
}


bool canPlaceBuilding(const FactoryState& state, const GameConfig& config,
                      BuildingType type, QPoint anchor, Rotation rotation)
{
    // Terrain and world-bounds validity first (REQ-BLD-PLACE-VALID); occupancy is
    // the extra rule this adds.
    if (!isPlacementValid(state, config, type, anchor, rotation))
    {
        return false;
    }

    const BuildingDef* def = config.buildings.findBuildingDef(type);
    if (!def) { return false; }
    const ParsedSurfaceMask parsed = parseSurfaceMask(def->surfaceMask, rotation);

    bool anyOccupied = false;
    for (const QPoint& relativeCell : parsed.bodyCells)
    {
        if (isTileOccupied(state, anchor + relativeCell))
        {
            anyOccupied = true;
            break;
        }
    }

    if (anyOccupied)
    {
        // Occupied is still placeable when what is there is the same building being
        // re-oriented (REQ-BLD-ROTATE-IN-PLACE).
        return findRotateInPlaceTarget(state, config, type, anchor, rotation).has_value();
    }
    return true;
}

std::vector<BeltDragResolved> resolveBeltDragPath(const std::vector<BeltPathTile>& path,
                                                  const FactoryState& state,
                                                  const GameConfig& config,
                                                  int buildingBlocksStock)
{
    std::vector<BeltDragResolved> resolved;
    resolved.reserve(path.size());

    const BuildingDef* def = config.buildings.findBuildingDef(BuildingType::Belt);
    const int beltCost = (def != nullptr) ? def->cost : 0;
    int       spent    = 0;

    for (const BeltPathTile& entry : path)
    {
        BeltDragResolved item;
        const std::optional<BuildingId> rotateTarget =
            findRotateInPlaceTarget(state, config, BuildingType::Belt,
                                    entry.tile, entry.rotation);
        if (rotateTarget.has_value())
        {
            // A tile holding only a belt (or belt site) is re-oriented, no cost.
            item.action     = BeltTileAction::RotateInPlace;
            item.affordable = true;
            item.rotateId   = rotateTarget;
        }
        else if (canPlaceBuilding(state, config, BuildingType::Belt,
                                  entry.tile, entry.rotation))
        {
            // Empty, valid cell: a new belt, subject to cumulative affordability.
            item.action     = BeltTileAction::PlaceNew;
            item.affordable = (spent + beltCost <= buildingBlocksStock);
            item.rotateId   = std::nullopt;
            if (item.affordable) { spent += beltCost; }
        }
        else
        {
            // Occupied by a non-belt building/site, or otherwise invalid terrain.
            item.action     = BeltTileAction::Invalid;
            item.affordable = false;
            item.rotateId   = std::nullopt;
        }
        resolved.push_back(item);
    }
    return resolved;
}
