#pragma once

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include <QPoint>

#include "BuildingId.h"
#include "BuildingType.h"
#include "ItemType.h"
#include "Rotation.h"
#include "ShipLayout.h"

struct GameConfig;

// Player intent, resolved to domain ids / tile coordinates and serializable, that
// mutates the simulation. Every sim mutation during play flows through a Command
// applied at the single Simulation::apply chokepoint, so it can be recorded and
// replayed (see docs/replay_design.md).
//
// Commands form a closed set dispatched by kind. They reference stable,
// deterministic ids (BuildingId, tile coordinates, choice indices) — never raw
// entt handles — so a recorded command resolves to the same target on replay.

enum class CommandKind
{
    PlaceBuilding,
    Deconstruct,
    CancelDeconstruction,
    RotateInPlace,
    SetRecipe,
    SetShipLayout,
    SetSiteSplitterFilters,
    SetSplitterFilters,
    ClearBeltTiles,
    ApplySchematicChoice,
    ExpandAsteroid,
    Reset
};

struct Command
{
    explicit Command(CommandKind kind) : kind(kind) {}
    virtual ~Command() = default;

    CommandKind kind;
    // Source of the command. Always 0 in single-player; lockstep multiplayer
    // later merges commands from multiple sources into one ordered stream.
    int playerId = 0;
};

// Places a building and, atomically, configures it. The configuration fields are
// bundled here (rather than as follow-up commands) because commands are applied
// at a deferred tick boundary, so the caller never sees the new BuildingId — the
// place-and-configure must happen as one unit inside apply().
struct PlaceBuildingCommand : Command
{
    PlaceBuildingCommand() : Command(CommandKind::PlaceBuilding) {}

    BuildingType type     = BuildingType::Miner;
    QPoint       anchor;
    Rotation     rotation = Rotation::East;

    // Optional configuration applied to the freshly placed (still-construction)
    // building. The caller (UI) is responsible for unlock/validity pre-filtering;
    // only fields that should apply are set.
    std::optional<std::string>      recipeId;
    std::optional<ShipLayoutConfig> shipLayout;
    bool                            hasSplitterFilters = false;
    std::vector<ItemType>           splitterFilterA;
    std::vector<ItemType>           splitterFilterB;
};

// Marks a building for demolition: a construction site is removed instantly, a
// built building is queued for deconstruction (REQ-BLD-DECONSTRUCT, REQ-BLD-DECON-QUEUE).
struct DeconstructCommand : Command
{
    DeconstructCommand() : Command(CommandKind::Deconstruct) {}
    std::optional<BuildingId> id;
};

// Takes a building back out of the deconstruction queue (REQ-BLD-DECONSTRUCT-CLICK,
// REQ-BLD-DECONSTRUCT-BOX). No-op if the id is not currently queued.
struct CancelDeconstructionCommand : Command
{
    CancelDeconstructionCommand() : Command(CommandKind::CancelDeconstruction) {}
    std::optional<BuildingId> id;
};

struct RotateInPlaceCommand : Command
{
    RotateInPlaceCommand() : Command(CommandKind::RotateInPlace) {}
    std::optional<BuildingId> id;
    Rotation                  newRotation = Rotation::East;
};

struct SetRecipeCommand : Command
{
    SetRecipeCommand() : Command(CommandKind::SetRecipe) {}
    std::optional<BuildingId> id;
    std::string               recipeId;
};

struct SetShipLayoutCommand : Command
{
    SetShipLayoutCommand() : Command(CommandKind::SetShipLayout) {}
    std::optional<BuildingId> id;
    ShipLayoutConfig          layout;
};

// Splitter filters for a queued / under-construction Splitter site (configured by
// BuildingSystem before the splitter is registered with BeltSystem).
struct SetSiteSplitterFiltersCommand : Command
{
    SetSiteSplitterFiltersCommand() : Command(CommandKind::SetSiteSplitterFilters) {}
    std::optional<BuildingId> id;
    std::vector<ItemType>     filterA;
    std::vector<ItemType> filterB;
};

// Splitter filters for an operational splitter, configured by tile via BeltSystem.
struct SetSplitterFiltersCommand : Command
{
    SetSplitterFiltersCommand() : Command(CommandKind::SetSplitterFilters) {}
    QPoint                tile;
    std::vector<ItemType> filterA;
    std::vector<ItemType> filterB;
};

struct ClearBeltTilesCommand : Command
{
    ClearBeltTilesCommand() : Command(CommandKind::ClearBeltTiles) {}
    std::vector<QPoint> tiles;
};

struct ApplySchematicChoiceCommand : Command
{
    ApplySchematicChoiceCommand() : Command(CommandKind::ApplySchematicChoice) {}
    int choiceIndex = 0;
};

// Unlocks the next asteroid expansion (REQ-EXP-UNLOCK). Carries no payload: the
// simulation derives the cost and column count from its own expansion counter
// and config, so a recorded command replays identically.
struct ExpandAsteroidCommand : Command
{
    ExpandAsteroidCommand() : Command(CommandKind::ExpandAsteroid) {}
};

// Restart boundary: reinitializes the simulation with a fresh seed and, if
// config is set, a reloaded config (GameConfig is move-only, so it is carried by
// shared_ptr and moved into the sim on apply). A null config keeps the current
// config. One replay file = one run between Reset boundaries.
struct ResetCommand : Command
{
    ResetCommand() : Command(CommandKind::Reset) {}
    std::shared_ptr<GameConfig> config;       // null = keep current config
    unsigned int                seed = 0;
};
