#pragma once

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

#include <QPoint>

#include "BeltSystem.h"
#include "FactoryState.h"
#include "EntityAdmin.h"
#include "entt/entity/entity.hpp"
#include "SchematicChoiceOption.h"
#include "BuildingType.h"
#include "BuildingId.h"
#include "EventHandler.h"
#include "BeamFiredEvent.h"
#include "GameConfig.h"
#include "Rotation.h"
#include "Tick.h"
#include "TracePrintRequestedEvent.h"
#include "UnlockState.h"

class AiSystem;
class BuildingSystem;
class ConstructionSystem;
class DeconstructionSystem;
class ProductionSystem;
struct Command;
class Hasher;
class CombatSystem;
class DynamicBodySystem;
class MovementIntentSystem;
class RepairSystem;
class SalvagerSystem;
class ShipSystem;
class DebrisSystem;
class WaveSystem;

class Simulation: public CombinedEventHandler<TracePrintRequestedEvent>
{
public:
    explicit Simulation(GameConfig config, unsigned int seed = 0);
    ~Simulation();

    const GameConfig& getConfig() const;

    // Reinitializes all simulation state as if constructed fresh.
    void reset(unsigned int seed = 0);

    // Reloads config then reinitializes all simulation state.
    void reset(GameConfig newConfig, unsigned int seed = 0);

    // Advances the simulation by one tick. Tick order per architecture.md §Tick Order.
    void tick();

    // The single command chokepoint: applies one player command by dispatching
    // to the underlying mutators. Every sim mutation during play must flow
    // through here so it can be recorded and replayed (see docs/replay_design.md
    // and CommandManager). Reached via CommandManager::drain.
    void apply(const Command& command);

    // Returns all fire events accumulated since the last drain, clearing the
    // internal queue. Call once per rendered frame (REQ-SHP-FIRING-BEAM).
    std::vector<BeamFiredEvent> drainBeamFiredEvents();

    // Returns the pending schematic choices (empty if no drop is pending).
    const std::vector<SchematicChoiceOption>& getPendingSchematicChoices() const;

    // Returns true if there are pending schematic choices waiting for player input.
    bool hasSchematicChoicesPending() const;

    Tick   getCurrentTick()          const;
    // The seed this run was (re)initialized with; written to the replay header.
    unsigned int getSeed()        const;
    int    getBuildingBlocksStock()  const;
    // Current asteroid width in tiles = base width + purchased expansions
    // (REQ-EXP-UNLOCK, REQ-GW-ASTEROID-EXPAND).
    int    getCurrentAsteroidWidth_tiles() const;
    // Building block cost of the next expansion, floored to an integer
    // (REQ-EXP-COST); x = number of expansions already purchased.
    int    getCurrentExpansionCost()  const;
    bool   isGameOver()           const;
    bool   isWon()                const;
    int    getArtifactCount()        const;
    double getThreatLevel()          const;
    double getThreatAccumulationRate() const;
    double getMaxFactoryProductionThreatRate()     const;
    double getCurrentFactoryProductionThreatRate() const;
    int    getBossWaveCounter()      const;
    Tick   getBossCountdownTicks()         const;
    Tick   getNormalGapRemainingTicks()    const;

    // Ship schematic state query.
    bool isSchematicUnlocked(const std::string& shipId) const;

    // Module schematic state query.
    bool isModuleSchematicUnlocked(const std::string& moduleId) const;

    // Implicit recipe/item unlock queries (REQ-LOCK-IMPLICIT).
    bool isRecipeUnlocked(const std::string& recipeId) const;
    bool isItemUnlocked(const std::string& itemId) const;

    // Building unlock query (REQ-LOCK-BUILDING). True if the building type is not
    // gated by any unlock group, or its granting group has been awarded.
    bool isBuildingUnlocked(BuildingType type) const;

    // -- Determinism (see docs/replay_design.md) -----------------------------
    // 64-bit fingerprint of the RNG stream state. Cheap; written to the replay
    // file periodically + after each command for desync detection.
    unsigned long long getRngFingerprint() const;

    // 64-bit fingerprint of the full simulation state (RNG, scalars, buildings,
    // belts, and ECS component state). Used by the double-run determinism test;
    // a superset of getRngFingerprint().
    unsigned long long computeStateChecksum() const;

    // Const subsystem accessors (queries only). The mutable counterparts are
    // private and reachable only through Simulation::apply (the command
    // chokepoint) or, in tests, SimulationTestAccess — so production code cannot
    // mutate the factory outside the recorded command path (docs/replay_design.md).
    // BuildingSystem has no const accessor: it answers no queries, so nothing outside
    // the command path has a reason to reach it (FactoryQueries.h).

    // The factory's world data, for the free queries in FactoryQueries.h.
    const FactoryState&   getFactoryState() const;
    const BeltSystem&     getBelts() const;
    ShipSystem&           getShips();
    const ShipSystem&     getShips() const;
    DebrisSystem&          getDebrisSystem();
    const DebrisSystem&    getDebrisSystem() const;
    EntityAdmin&          getAdmin();
    const EntityAdmin&    getAdmin() const;

private:
    // Grants tests access to the private player-action mutators below without
    // opening them to production code (see src/test/SimulationTestAccess.h).
    friend struct SimulationTestAccess;

    // -- Player-action mutators (command chokepoint only) --------------------
    // Reached during play exclusively via apply(); never called by UI/app code.

    // Checks affordability, deducts building blocks, and places the building.
    // Returns the new entity id, or nullopt if blocks are insufficient.
    std::optional<BuildingId> tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);

    // Marks the building with the given id for demolition: a construction site is
    // removed instantly (full refund), a built building is queued for timed
    // deconstruction (REQ-BLD-DECON-QUEUE), refunded on completion.
    void deconstruct(BuildingId id);

    // Takes a queued building back out of the deconstruction queue (REQ-BLD-DECON-QUEUE).
    void cancelDeconstruction(BuildingId id);

    // Applies the player's chosen schematic from the pending choices.
    // choiceIndex must be in [0, pendingChoices.size()).
    // Clears the pending choices after application.
    void applySchematicChoice(int choiceIndex);

    // Unlocks one asteroid expansion if affordable (REQ-EXP-UNLOCK): checks the
    // current cost against the stock, deducts it, increments the expansion
    // counter, and widens the buildable asteroid. No-op if blocks are short.
    void tryExpandAsteroid();

    // Mutable subsystem accessors; same chokepoint rule as the mutators above.
    BuildingSystem&       getBuildingsMutable();
    BeltSystem&           getBeltsMutable();

	void handleEvent(std::shared_ptr<const TracePrintRequestedEvent> event) override;


    // (Re-)create every owned subsystem. Shared by the constructor and reset();
    // the construction order is load-bearing for determinism, so both paths must
    // go through here. Only called before the first tick of a run.
    void initializeSubsystems();

    // Populate HQ, player defence stations, and the first enemy station set.
    void placeInitialStructures();

    // Place two enemy defence stations for the given generation level.
    // Stores their IDs in m_currentEnemyStationIds.
    void placeEnemyStationSet(int generation);

    // Tick step 9: remove dead ships and buildings, drop debris, handle push.
    void tickDeathsAndLoot();

    // Generate up to 3 schematic choices (REQ-DEF-SCHEMATIC-DROP) for the player.
    void generateSchematicChoices(int destroyedStationLevel);

    GameConfig m_config;
    std::mt19937 m_rng;
    unsigned int m_seed;

    Tick     m_currentTick;
    Tick     m_nextDepartureTick;
    int      m_expansionsPurchased = 0;  // REQ-EXP-COST formula variable x
    bool     m_gameOver    = false;
    bool     m_isWon       = false;
    int      m_artifactCount = 0;

    // Pre-placed structure IDs.
    std::optional<BuildingId> m_hqBuildingId;  // Building id (for belt integration)
    entt::entity m_hqProxyEntity;          // ECS entity (HP, targeting)
    entt::entity m_playerStation1Entity;
    entt::entity m_playerStation2Entity;
    entt::entity m_currentEnemyStationEntities[2];

    // Schematic/unlock bookkeeping (REQ-DEF-SCHEMATIC-DROP, REQ-LOCK-EXPLICIT,
    // REQ-LOCK-IMPLICIT, REQ-LOCK-BUILDING, REQ-LOCK-PREREQ). Constructed before
    // initializeSubsystems() runs since BuildingSystem's spawn-gating lambda
    // calls into it (see initializeSubsystems()).
    UnlockState m_unlockState;

    EntityAdmin                       m_admin;
    // The factory's world data. Owned here, not by BuildingSystem, so the systems
    // that operate on it can be handed the same state (see FactoryState.h).
    FactoryState                      m_factoryState;
    BeltSystem                        m_beltSystem;
    std::unique_ptr<BuildingSystem>   m_buildingSystem;
    std::unique_ptr<ProductionSystem>   m_productionSystem;
    std::unique_ptr<ConstructionSystem> m_constructionSystem;
    std::unique_ptr<DeconstructionSystem> m_deconstructionSystem;
    std::unique_ptr<ShipSystem>       m_shipSystem;
    std::unique_ptr<AiSystem>         m_aiSystem;
    std::unique_ptr<MovementIntentSystem> m_movementIntentSystem;
    std::unique_ptr<DynamicBodySystem>    m_dynamicBodySystem;
    std::unique_ptr<DebrisSystem>      m_debrisSystem;
    std::unique_ptr<SalvagerSystem>   m_salvagerSystem;
    std::unique_ptr<RepairSystem>     m_repairSystem;
    std::unique_ptr<WaveSystem>       m_waveSystem;
    std::unique_ptr<CombatSystem>     m_combatSystem;

    std::vector<BeamFiredEvent>          m_beamFiredEvents;
    std::vector<SchematicChoiceOption> m_pendingSchematicChoices;
};
