#pragma once
#include <filesystem>
#include <cstdint>
#include <atomic>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <vector>
#include <mw/database.hpp>
#include "protocol.hpp"
namespace nethack_mcp
{
/// A durable summary of one game, including nullable live and terminal fields.
struct GameRecord
{
/// Canonical lowercase UUIDv7 that identifies this game.
std::string game_id;
/// Validated character name supplied when the game was created.
std::string character_name;
/// Caller-supplied model identifier associated with the game.
std::string model_slug;
/// UTC Unix seconds when game creation was accepted.
std::int64_t started_at_s = 0;
/// UTC Unix seconds of the most recent valid agent call.
std::int64_t last_activity_at_s = 0;
/// UTC Unix seconds when the record became terminal, if completed.
std::optional<std::int64_t> ended_at_s;
/// Whether the ending time was observed or recovered after restart.
std::optional<std::string> end_time_kind;
/// Terminal game outcome or administrative ending reason.
std::optional<std::string> end_reason;
/// Last numeric dungeon depth observed before termination.
std::optional<int> last_depth;
/// Greatest numeric dungeon depth observed during the game.
std::optional<int> deepest_depth;
/// Return public record fields for HTML or JSON responses.
Json toJson() const;
};
/// Store compact records outside the per-game runtime directories.
class GameRecordStore
{
public:
/// Open the persistent database and apply numbered schema migrations.
static mw::E<std::unique_ptr<GameRecordStore>> open(
const std::filesystem::path& database_path);
/// Insert a new active game, returning false for a UUID collision.
mw::E<bool> insertGame(const GameRecord& record);
/// Update current and deepest location after a depth change.
mw::E<void> updateLocation(const std::string& game_id, int depth);
/// Update the wall-clock audit time for accepted agent activity.
mw::E<void> updateActivity(const std::string& game_id,
std::int64_t activity_at_s);
/// Finalize an active row once and report whether this call won.
mw::E<bool> finishGame(const std::string& game_id,
std::int64_t ended_at_s,
const std::string& end_time_kind,
const std::string& end_reason);
/// Return exactly the ten most recently completed game records.
mw::E<std::vector<GameRecord>> recentGames();
/// Look up a completed or active row by its canonical game ID.
mw::E<std::optional<GameRecord>> getGame(const std::string& game_id);
/// Mark unfinished records interrupted after an application restart.
mw::E<void> recoverInterruptedGames(std::int64_t recovery_time_s);
/// Return latency of the most recent record-store write in microseconds.
std::uint64_t lastWriteLatencyMicroseconds() const;
private:
explicit GameRecordStore(std::unique_ptr<mw::SQLite> database);
mw::E<void> migrate();
std::unique_ptr<mw::SQLite> database_;
std::mutex mutex_;
std::atomic<std::uint64_t> last_write_latency_us_ = 0;
};
} // namespace nethack_mcp