#pragma once

#include <map>
#include <set>
#include <string>
#include <vector>

#include "BuildingType.h"
#include "GameConfig.h"
#include "SchematicChoiceOption.h"

class Hasher;

// Owns schematic/unlock bookkeeping for one run: which ship, module, and
// building schematics are unlocked (REQ-LOCK-EXPLICIT), which assembler recipe
// schematics have been explicitly granted, and the implicit recipe/item unlock
// sets derived from that state (REQ-LOCK-IMPLICIT). Reads config the same way
// BuildingSystem does (a bound const reference to Simulation::m_config, which is
// safe across restart because that member's storage address never changes —
// reset() move-assigns into it rather than replacing it).
//
// Simulation forwards its isXUnlocked-style public queries here and drives
// state changes (awarding an unlock group) here; the RNG-touching schematic
// choice generation itself stays in Simulation (call ordering of m_rng is the
// determinism backbone and must not move).
class UnlockState
{
public:
    explicit UnlockState(const GameConfig& config);

    // Builds the granted-id sets and initializes all per-item unlock maps from
    // them (shared by the constructor and Simulation::reset). Ends with
    // recomputeUnlocked().
    void initializeUnlockState();

    // 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;

    // True if the unlock group has already been awarded to the player.
    bool isUnlockGroupAwarded(const std::string& groupId) const;

    // True if every prerequisite unlock group has been awarded (REQ-LOCK-PREREQ).
    bool prerequisitesSatisfied(const std::vector<std::string>& requiredGroupIds) const;

    // Builds a schematic choice option for one unlock group (REQ-DEF-SCHEMATIC-DROP).
    SchematicChoiceOption makeUnlockOption(const UnlockGroupDef& group) const;

    // Awards the unlock group backing `chosen` (REQ-DEF-SCHEMATIC-DROP): marks
    // every granted ship/module/building schematic unlocked, records granted
    // recipe schematics, marks the group as awarded, and recomputes the implicit
    // unlock sets. Mirrors the non-artifact branch of the original
    // Simulation::applySchematicChoice exactly; callers still special-case
    // chosen.isArtifact themselves before calling this.
    void awardUnlockGroup(const SchematicChoiceOption& chosen);

    // Determinism helper (see Simulation::computeStateChecksum): folds unlock
    // state into the hasher via the same seven calls, in the same order, that
    // used to live at the Simulation::computeStateChecksum call site.
    void appendChecksum(Hasher& hasher) const;

private:
    // Schematic unlock state (REQ-DEF-SCHEMATIC-DROP).
    struct SchematicState
    {
        bool unlocked;
    };

    // Recomputes m_unlockedRecipeIds and m_unlockedItemIds from current schematic state.
    void recomputeUnlocked();

    // Result of the REQ-LOCK-IMPLICIT traversal.
    struct UnlockedSets
    {
        std::set<std::string> itemIds;
        std::set<std::string> recipeIds;
    };

    // Pure REQ-LOCK-IMPLICIT traversal given hypothetical explicit-unlock sets.
    UnlockedSets computeUnlockedSets(const std::set<std::string>& unlockedShipSchematicIds,
                                      const std::set<std::string>& unlockedModuleSchematicIds,
                                      const std::set<std::string>& unlockedRecipeSchematicIds) const;

    // Current explicit-unlock id sets, derived from m_schematicLevels / m_moduleSchematicLevels.
    std::set<std::string> getUnlockedShipSchematicIds() const;
    std::set<std::string> getUnlockedModuleSchematicIds() const;
    std::set<std::string> getUnlockedBuildingIds() const;

    // What the player can actually get out of the game in a given state: for every recipe
    // they could run, which of its output groups they could be handed. A recipe absent
    // from the map is of no use to them -- either it is locked (REQ-LOCK-IMPLICIT) or the
    // building that runs it cannot be placed (REQ-LOCK-BUILDING), and a recipe they
    // cannot run is no different from one that does not exist.
    //
    // Diffing this between the current state and the state an award would bring about is
    // what the schematic choice dialog previews (REQ-DEF-SCHEMATIC-DROP).
    std::map<std::string, std::vector<int>> computeUsableRecipeGroups(
        const UnlockedSets& sets, const std::set<std::string>& unlockedBuildingIds) const;

    // Determinism helpers — fold sub-state into the hasher in deterministic order.
    static void appendSchematicMap(Hasher& hasher,
                                   const std::map<std::string, SchematicState>& levels);
    static void appendStringSet(Hasher& hasher, const std::set<std::string>& ids);

    const GameConfig& m_config;

    std::map<std::string, SchematicState> m_schematicLevels;
    std::map<std::string, SchematicState> m_moduleSchematicLevels;
    std::map<std::string, SchematicState> m_buildingLevels;

    // Unlock groups awarded so far (REQ-LOCK-EXPLICIT). Group ids.
    std::set<std::string> m_awardedUnlockGroupIds;

    // Ids granted by some unlock group, per kind — cached from config at init.
    // An item starts locked iff it appears in the corresponding set.
    std::set<std::string> m_grantedShipIds;
    std::set<std::string> m_grantedModuleIds;
    std::set<std::string> m_grantedBuildingIds;
    std::set<std::string> m_grantedRecipeIds;

    // Explicitly unlocked assembler recipe schematics (REQ-LOCK-EXPLICIT).
    std::set<std::string> m_unlockedRecipeSchematicIds;

    // Implicit unlock sets derived from schematic state (REQ-LOCK-IMPLICIT).
    std::set<std::string> m_unlockedRecipeIds;
    std::set<std::string> m_unlockedItemIds;
};
