#pragma once

#include "gfx/gpu_ssbo.h"

namespace fn::ds {

// Estado opaco del simulador. Cachea el programa compute, los SSBOs de
// salida (summary + tier_counts), el SSBO de tiers (parametros) y el
// SSBO de seeds RNG. Reusable across runs con n_sessions y max_tiers
// constantes; si cambian hay que destroy + create.
struct McSessionSim {
    unsigned int program = 0;

    fn::gfx::Ssbo summary;       // float[N*8]: final, pnl, max_dd, peak,
                                 //             trough, spins, wins, longest_miss
    fn::gfx::Ssbo tier_counts;   // uint[N*max_tiers]: hits por tier
    fn::gfx::Ssbo tiers;         // vec2[max_tiers]: (q, m) por tier
    fn::gfx::Ssbo rng_seeds;     // uint[N] PCG state por sesion

    int n_sessions = 0;
    int max_tiers  = 8;

    // Uniform locations (cacheados)
    unsigned int loc_n_sessions = 0;
    unsigned int loc_n_spins    = 0;
    unsigned int loc_n_tiers    = 0;
    unsigned int loc_p_base     = 0;
    unsigned int loc_cost       = 0;
    unsigned int loc_start_bal  = 0;
    unsigned int loc_mode       = 0;
    unsigned int loc_mode_p1    = 0;
    unsigned int loc_mode_p2    = 0;
};

enum class McSessionMode : int {
    Pure   = 0,    // p_eff = p_base (sin pity ni streak)
    Pity   = 1,    // ramp lineal p_base -> 1 entre soft y hard miss_streak
    Streak = 2     // p_eff = min(1, p_base * (1 + lambda * miss_streak))
};

struct McSessionParams {
    float p_base       = 0.2f;
    float cost         = 1.0f;
    float start_balance = 100.0f;
    int   n_spins      = 1000;
    int   n_tiers      = 0;          // <= max_tiers del create
    const float* tiers_q = nullptr;  // [n_tiers] pesos relativos (no normalizados)
    const float* tiers_m = nullptr;  // [n_tiers] multiplicadores de payout
    McSessionMode mode = McSessionMode::Pure;
    float mode_p1 = 0.0f;            // pity.soft / streak.lambda
    float mode_p2 = 0.0f;            // pity.hard (no usado en streak)
};

// Crea el simulador para N sesiones y hasta max_tiers tiers distintos.
// Compila el compute y reserva los SSBOs.
McSessionSim mc_session_sim_create(int n_sessions, int max_tiers = 8);

// Re-siembra los seeds RNG a partir de master_seed (deterministico). Llamar
// al menos una vez antes del primer run; subsiguientes runs persisten el
// state (ofreciendo "continuacion" — util para acumular mas spins).
void mc_session_sim_reseed(McSessionSim& s, unsigned long long master_seed);

// Ejecuta una corrida completa: cada uno de los N sessions independientes
// hace n_spins spins. Bloquea hasta que el dispatch termina y el barrier
// esta satisfecho. NO hace readback — el caller decide cuando leer.
void mc_session_sim_run(McSessionSim& s, const McSessionParams& p);

// Lee summary a CPU. out debe tener n_sessions * 8 floats.
// Layout por sesion: [final_balance, pnl, max_dd, peak, trough, spins,
//                     wins, longest_miss].
void mc_session_sim_readback_summary(const McSessionSim& s, float* out);

// Lee tier_counts a CPU. out debe tener n_sessions * max_tiers uints.
// Layout: out[sid * max_tiers + tier_idx] = hits del tier_idx en la sesion.
void mc_session_sim_readback_tier_counts(const McSessionSim& s, unsigned int* out);

void mc_session_sim_destroy(McSessionSim& s);

} // namespace fn::ds
