#pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <deque>
#include <filesystem>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "game_record_store.hpp"
#include "game_session.hpp"
#include "server_config.hpp"
namespace nethack_mcp
{
/// Own the active-game registry and route agent calls to isolated sessions.
class GameManager
{
public:
/// Open the record store, recover old rows, and start the expiry timer.
static mw::E<std::unique_ptr<GameManager>> create(
ServerConfig config, std::filesystem::path runtime_source);
~GameManager();
GameManager(const GameManager&) = delete;
GameManager& operator=(const GameManager&) = delete;
/// Create and admit a new game, returning its secret once to the caller.
ToolResult createGame(const Json& arguments, const std::string& client_id);
/// Authorize and dispatch one game-specific MCP tool call.
ToolResult dispatch(const std::string& tool_name, const Json& arguments,
const std::string& client_id);
/// Return a shared active-game handle for read-only spectator access.
std::shared_ptr<GameSession> findActive(const std::string& game_id) const;
/// Return a durable record for a valid game ID, if one exists.
mw::E<std::optional<GameRecord>> getRecord(const std::string& game_id);
/// Return the ten most recently completed records.
mw::E<std::vector<GameRecord>> recentGames();
/// Stop expiry maintenance and terminate workers during server shutdown.
void shutdown();
/// Return the canonical public base URL used in generated viewer links.
const std::string& publicBaseUrl() const;
/// Return the configured game-independent HTTP service settings.
const ServerConfig& config() const;
/// Return the number of sessions currently present in the registry.
std::size_t activeGameCount() const;
/// Count worker child processes that are still running.
std::size_t activeWorkerCount() const;
/// Count files under the private runtime root for capacity monitoring.
std::uint64_t runtimeBytes() const;
/// Return cleanup failures seen by the expiry maintenance thread.
std::uint64_t expiryCleanupFailures() const;
/// Return latency of the most recent SQLite write in microseconds.
std::uint64_t databaseWriteLatencyMicroseconds() const;
private:
GameManager(ServerConfig config,
std::filesystem::path runtime_source,
std::shared_ptr<GameRecordStore> records);
void sweepLoop();
void removeOrphanedDirectories();
void releaseReservation(const std::string& game_id);
bool allowCreation(const std::string& client_id,
std::chrono::steady_clock::time_point now);
bool allowControlAttempt(const std::string& client_id);
void recordControlFailure(const std::string& client_id);
void clearControlFailures(const std::string& client_id);
void pruneClientHistory(std::chrono::steady_clock::time_point now);
struct ClientHistory
{
std::deque<std::chrono::steady_clock::time_point> creation_times;
std::deque<std::chrono::steady_clock::time_point> auth_failures;
};
ServerConfig config_;
std::filesystem::path runtime_source_;
std::shared_ptr<GameRecordStore> records_;
mutable std::mutex registry_mutex_;
std::unordered_map<std::string, std::shared_ptr<GameSession>> sessions_;
std::unordered_set<std::string> reserved_ids_;
std::size_t pending_creations_ = 0;
std::unordered_map<std::string, ClientHistory> client_history_;
std::mutex sweep_mutex_;
std::condition_variable sweep_condition_;
std::atomic<bool> stopping_ = false;
std::atomic<std::uint64_t> expiry_cleanup_failures_ = 0;
std::thread sweep_thread_;
};
} // namespace nethack_mcp