BareGit
#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