BareGit
#pragma once

#include <atomic>
#include <array>
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <mutex>
#include <optional>
#include <string>

#include "engine_process.hpp"
#include "game_record_store.hpp"
#include "observation_store.hpp"
#include "server_config.hpp"

namespace nethack_mcp
{

/// Result returned by one MCP tool implementation.
struct ToolResult
{
    /// Report whether the requested tool operation completed.
    bool success = false;
    /// Return the complete state associated with the operation.
    Json value = Json::object();
    /// Stable machine-readable error code when `success` is false.
    std::string code;
    /// Human-readable error detail when `success` is false.
    std::string message;
};

/// Own one isolated game worker and serialize its gameplay input.
class GameSession
{
public:
    /// Create a session with a fixed public ID and one-time control secret.
    GameSession(std::filesystem::path data_root,
                std::filesystem::path runtime_dir,
                std::string game_id,
                std::string control_token,
                std::string character_name,
                std::string model_slug,
                std::chrono::steady_clock::time_point created_at,
                std::shared_ptr<GameRecordStore> records,
                const ServerConfig& config);

    GameSession(const GameSession&) = delete;
    GameSession& operator=(const GameSession&) = delete;

    /// Start this game's worker and wait for its first boundary.
    ToolResult startGame(const Json& arguments);

    /// Authorize and refresh one game-specific agent call.
    ToolResult authorize(const Json& arguments);

    /// Return the latest state, optionally waiting for a revision.
    ToolResult observe(const Json& arguments);

    /// Answer a pending one-key input boundary.
    ToolResult press(const Json& arguments);

    /// Answer a pending text, choice, command, or acknowledgement boundary.
    ToolResult respond(const Json& arguments);

    /// Answer a pending menu boundary with complete selections.
    ToolResult selectMenu(const Json& arguments);

    /// Administratively stop the worker and retain its final state.
    ToolResult quitGame(const Json& arguments);

    /// Stop any active worker during server shutdown.
    void shutdown();

    /// Mark a game terminal when its idle or absolute deadline has passed.
    bool expireIfNeeded();

    /// Reap a completed worker and remove its private runtime directory.
    bool cleanup();

    /// Report whether this game has reached a terminal transition.
    bool terminal() const;

    /// Report whether the worker child is still running.
    bool workerRunning() const;

    /// Return the earlier idle or absolute lifetime deadline.
    std::chrono::steady_clock::time_point nextDeadline() const;

    /// Return the immutable UUID associated with this session.
    const std::string& gameId() const;

    /// Return the latest state for the spectator server.
    Json snapshot() const;

private:
    ToolResult sendInput(const Json& arguments, Json response,
                         const std::string& expected_kind);
    ToolResult errorResult(std::string code, std::string message) const;
    ToolResult refreshAgentActivity(std::int64_t& activity_at_s);
    bool persistAgentActivity(std::int64_t activity_at_s);
    void handleWorkerMessage(const Json& message);
    bool copyRuntimeFiles(const std::filesystem::path& run_directory,
                          std::string& error) const;
    static bool validGameId(const Json& arguments,
                            const Json& state,
                            std::string& error);
    static bool parseKey(const Json& key, int& value, std::string& error);
    bool markTerminal(const std::string& reason,
                      const std::string& end_time_kind,
                      const std::string& lifecycle);
    bool finishRecord(const std::string& reason,
                      const std::string& end_time_kind,
                      std::int64_t ended_at_s);
    static std::int64_t wallClockSeconds();

    std::filesystem::path data_root_;
    std::filesystem::path runtime_dir_;
    std::string game_id_;
    std::string character_name_;
    std::string model_slug_;
    std::shared_ptr<GameRecordStore> records_;
    std::chrono::steady_clock::time_point created_at_;
    std::chrono::steady_clock::time_point last_agent_activity_;
    std::chrono::seconds idle_timeout_;
    std::chrono::seconds max_game_duration_;
    std::array<unsigned char, 32> control_salt_{};
    std::array<unsigned char, 32> control_digest_{};
    std::size_t max_worker_output_bytes_;
    std::size_t max_character_name_bytes_;
    ObservationStore observations_;
    std::unique_ptr<EngineProcess> process_;
    mutable std::mutex action_mutex_;
    mutable std::mutex lifecycle_mutex_;
    bool closing_ = false;
    std::optional<std::string> end_reason_;
    std::optional<std::string> end_time_kind_;
    std::int64_t terminal_at_s_ = 0;
    std::optional<std::string> terminal_result_;
    std::optional<int> last_depth_;
    std::atomic<bool> shutdown_requested_ = false;
    std::uint64_t operation_counter_ = 0;
};

} // namespace nethack_mcp