//! Shared types for the integrated RL trainer.

/// Discrete action grid cardinality. Matches the legacy DQN's 9-action
/// layout (`crates/ml/src/cuda_pipeline/sp5_isv_slots.rs`) so the two
/// systems stay comparable. See `action.rs` (Phase C/D) for the
/// enum-style mapping {Short-large, Short-small, Hold, Flat-from-long,
/// Flat-from-short, Long-small, Long-large, Trail-tighten, Trail-loosen}.
pub const N_ACTIONS: usize = 11;

/// C51 distributional Q-head atom count. Matches existing
/// `pearl_per_branch_c51_atom_span` infrastructure (21 atoms over the
/// learned support `[v_min, v_max]`).
pub const Q_N_ATOMS: usize = 21;

/// Forward-Return-Distribution (FRD) head — hidden-layer width of the
/// `[HIDDEN_DIM → FRD_HIDDEN_DIM → FRD_N_HORIZONS × FRD_N_ATOMS]` MLP.
/// Per SP20 P3 design.
pub const FRD_HIDDEN_DIM: usize = 64;

/// FRD head — number of forward-return horizons supervised per step
/// (h1 = 60 ticks, h2 = 300, h3 = 1800; tick offsets ISV-driven).
pub const FRD_N_HORIZONS: usize = 3;

/// FRD head — return-bucket atom count per horizon. Span is ISV-driven
/// (`RL_FRD_BUCKET_RANGE_SIGMA_INDEX`, default ±3σ), atom count is the
/// only structural compile-time dim per §0.1 audit.
pub const FRD_N_ATOMS: usize = 21;

/// FRD head — forward-return horizons in tick units, matching the ISV
/// seeds at `RL_FRD_HORIZON_{1,2,3}_TICKS_INDEX` (slots 500/501/502).
/// Used by the loader's label-generation pass at file-load time, where
/// ISV isn't available. F.5+ caller is responsible for keeping these
/// in sync with the runtime ISV slots if the latter are overridden.
pub const FRD_HORIZON_TICKS: [usize; FRD_N_HORIZONS] = [60, 300, 1800];

/// FRD head — bucket-range default, matching the ISV seed at
/// `RL_FRD_BUCKET_RANGE_SIGMA_INDEX` (slot 503). See `FRD_HORIZON_TICKS`
/// comment on loader-side vs trainer-side ISV alignment.
pub const FRD_BUCKET_RANGE_SIGMA: f32 = 3.0;

/// C51 atom support `v_min`. After standardisation by the reward-scale
/// controller (`RL_REWARD_SCALE_INDEX`), per-step reward + γ-discounted
/// returns are expected to fit inside `[V_MIN, V_MAX]`. The atom delta
/// `Δ_z = (V_MAX - V_MIN) / (Q_N_ATOMS - 1)` determines the minimum
/// reward difference Q can resolve. At ±1.0 with 21 atoms: Δ_z = 0.1,
/// matching the effective reward range after apply_reward_scale. The
/// reward clamp controller (ISV 484/485) holds these at runtime.
pub const Q_V_MIN: f32 = -1.0;
/// C51 atom support `v_max`. See [`Q_V_MIN`].
pub const Q_V_MAX: f32 = 1.0;

/// Discrete 11-action grid identifiers. Was 9 pre-SP20 P4; extended
/// with HalfFlatLong/HalfFlatShort to enable per-unit partial-close
/// routing (consumed by SP20 P5 trail-stop check + future P7
/// pyramid-aware actions_to_market_targets logic).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Action {
    ShortLarge    = 0,
    ShortSmall    = 1,
    Hold          = 2,
    FlatFromLong  = 3,
    FlatFromShort = 4,
    LongSmall     = 5,
    LongLarge     = 6,
    TrailTighten  = 7,
    TrailLoosen   = 8,
    HalfFlatLong  = 9,   // SP20 P4: close ⌈|pos|/2⌉ of long position (min 1 lot)
    HalfFlatShort = 10,  // SP20 P4: close ⌈|pos|/2⌉ of short position (min 1 lot)
}

impl Action {
    /// Map a raw `u32` action index back to the typed enum. Errors on
    /// any value `>= N_ACTIONS` — callers should bound-check at the
    /// kernel/loader boundary, this is the type-safety net.
    pub fn try_from_u32(v: u32) -> Result<Self, String> {
        match v {
            0  => Ok(Action::ShortLarge),
            1  => Ok(Action::ShortSmall),
            2  => Ok(Action::Hold),
            3  => Ok(Action::FlatFromLong),
            4  => Ok(Action::FlatFromShort),
            5  => Ok(Action::LongSmall),
            6  => Ok(Action::LongLarge),
            7  => Ok(Action::TrailTighten),
            8  => Ok(Action::TrailLoosen),
            9  => Ok(Action::HalfFlatLong),
            10 => Ok(Action::HalfFlatShort),
            _  => Err(format!("invalid action {v}; valid range 0..{}", N_ACTIONS)),
        }
    }
}

/// PPO update epochs per rollout. Algorithmic constant (no signal exists
/// to drive it adaptively); standard PPO uses 3-10, we default to 4.
pub const PPO_N_EPOCHS: usize = 4;

