/**
 * rl_hindsight_inject.cu — Backward HER: synthetic replay push on trade close
 *
 * Bidirectional HER Phase 2: when a trade closes (dones[b] > 0.5), checks
 * whether peak_pnl >> actual_pnl (controlled by ISV threshold). If the trade
 * left significant money on the table, injects a synthetic "what-if-I-held-
 * to-peak" transition into the replay buffer at boosted priority.
 *
 * Coordination: Grid=(1), Block=(B). Single-block prefix-sum assigns
 * contiguous write slots — same pattern as rl_per_push_flush.
 *
 * ISV slots read:
 *   549 — HINDSIGHT_THRESHOLD     (peak_pnl / actual_pnl threshold)
 *   550 — HINDSIGHT_PRIORITY_BOOST (multiplier on max_priority for synthetic)
 *   551 — HINDSIGHT_INJECT_COUNT  (written: how many synthetics injected)
 *   406 — REWARD_SCALE            (to recover raw PnL from scaled reward)
 *
 * No atomicAdd — block tree-reduce / prefix-sum only.
 */

#define HIDDEN_DIM 128
#define SCALARS_PER_TRANSITION 7

/* ISV slot indices (literals — canonical names in Rust ISV slots module). */
#define RL_HINDSIGHT_THRESHOLD_INDEX      549
#define RL_HINDSIGHT_PRIORITY_BOOST_INDEX 550
#define RL_HINDSIGHT_INJECT_COUNT_INDEX   551
#define RL_REWARD_SCALE_INDEX             406

/* Phase 3B-Y (2026-06-03): hindsight is conceptually paired with the surfer
 * scaffold — both are training-scaffold biases on the gradient. Gate hindsight
 * injection by slot 753 so that pure-pnl mode (scaffold_w = 0.0) also disables
 * hindsight, eliminating the second of three contaminants identified by the
 * Phase 3B-followup audit. */
#define RL_SURFER_SCAFFOLD_WEIGHT_INDEX   753

extern "C" __global__ void rl_hindsight_inject(
    const float* __restrict__ dones,              /* [B]             */
    const float* __restrict__ rewards_raw,        /* [B]             */
    const float* __restrict__ peak_mid,           /* [B]             */
    const float* __restrict__ entry_mid,          /* [B]             */
    const int* __restrict__ position_dir,         /* [B]             */
    const float* __restrict__ h_t_current,        /* [B * 128]       */
    const float* __restrict__ isv,
    /* Replay write targets */
    float* __restrict__ replay_h_t,               /* [cap * 128]     */
    float* __restrict__ replay_h_tp1,             /* [cap * 128]     */
    float* __restrict__ replay_scalars,           /* [cap * 7]       */
    float* __restrict__ priority_tree,            /* [2 * cap]       */
    unsigned int* __restrict__ write_head,        /* [1]             */
    unsigned int* __restrict__ replay_len,        /* [1]             */
    float* __restrict__ max_priority,             /* [1]             */
    /* Reset outputs */
    unsigned int* __restrict__ ring_write_idx,    /* [B] — reset on done */
    float* __restrict__ peak_mid_out,             /* [B] — reset on done */
    int b_size,
    int capacity
)
{
    int b = threadIdx.x;
    if (b >= b_size) return;

    /* ── Phase 1: Determine which threads want to inject ────────── */
    int wants_inject = 0;

    if (dones[b] > 0.5f) {
        float reward_scale = isv[RL_REWARD_SCALE_INDEX];
        float actual_pnl = fabsf(rewards_raw[b]) / fmaxf(reward_scale, 1e-9f);

        int dir = position_dir[b];
        float peak_pnl = (float)dir * (peak_mid[b] - entry_mid[b]);

        float threshold = isv[RL_HINDSIGHT_THRESHOLD_INDEX];
        /* Phase 3B-Y: gate by surfer-scaffold weight (slot 753). When scaffold
         * is fully faded (w = 0.0, pure-pnl mode), hindsight injection is also
         * disabled — they are paired training scaffolds. */
        float scaffold_w = isv[RL_SURFER_SCAFFOLD_WEIGHT_INDEX];

        /* Peak PnL must exceed actual by the threshold factor AND be positive,
         * AND the scaffold gate must be on. */
        if (peak_pnl > 0.0f
            && peak_pnl > actual_pnl * threshold
            && scaffold_w > 0.5f) {
            wants_inject = 1;
        }

        /* Reset tracking state for this env regardless of injection. */
        ring_write_idx[b] = 0;
        peak_mid_out[b] = 0.0f;
    }

    /* ── Phase 2: Prefix-sum to assign contiguous write slots ──── */
    /* Shared memory for the prefix-sum scan (single block, B threads). */
    extern __shared__ unsigned int smem[];
    /* smem[0..b_size): prefix-sum workspace
     * smem[b_size]:    total inject count
     * smem[b_size+1]:  base write offset (old write_head value) */

    smem[b] = (unsigned int)wants_inject;
    __syncthreads();

    /* Thread 0 performs exclusive prefix-sum and advances write_head. */
    if (b == 0) {
        unsigned int running = 0;
        for (int i = 0; i < b_size; i++) {
            unsigned int val = smem[i];
            smem[i] = running;       /* exclusive prefix */
            running += val;
        }
        smem[b_size] = running;      /* total inject count */

        /* Advance write_head (circular) */
        unsigned int old_head = write_head[0];
        smem[b_size + 1] = old_head;
        write_head[0] = (old_head + running) % (unsigned int)capacity;

        /* Update replay_len (capped at capacity). */
        unsigned int new_len = replay_len[0] + running;
        if (new_len > (unsigned int)capacity) new_len = (unsigned int)capacity;
        replay_len[0] = new_len;
    }
    __syncthreads();

    /* ── Phase 3: Injecting threads write their synthetic transitions ── */
    if (!wants_inject) return;

    unsigned int my_offset = smem[b];          /* exclusive prefix for this thread */
    unsigned int base_head = smem[b_size + 1]; /* old write_head */
    unsigned int slot = (base_head + my_offset) % (unsigned int)capacity;

    /* Recover raw peak PnL for the synthetic reward. */
    int dir = position_dir[b];
    float peak_pnl = (float)dir * (peak_mid[b] - entry_mid[b]);
    float reward_scale = isv[RL_REWARD_SCALE_INDEX];

    /* h_t: copy current hidden state (the "what if" starts from same state). */
    for (int d = 0; d < HIDDEN_DIM; d++) {
        float h = h_t_current[(long long)b * HIDDEN_DIM + d];
        replay_h_t[(long long)slot * HIDDEN_DIM + d] = h;
        /* h_tp1 = self-loop (synthetic terminal transition). */
        replay_h_tp1[(long long)slot * HIDDEN_DIM + d] = h;
    }

    /* Scalars layout: [action, reward, raw_reward, done, log_pi, n_step_gamma, return]
     *                  [  0   ,   1   ,     2     ,  3  ,   4   ,      5      ,   6  ] */
    long long sbase = (long long)slot * SCALARS_PER_TRANSITION;
    replay_scalars[sbase + 0] = 2.0f;                        /* action = Hold (2) */
    replay_scalars[sbase + 1] = peak_pnl * reward_scale;     /* reward (scaled) */
    replay_scalars[sbase + 2] = peak_pnl;                    /* raw_reward */
    replay_scalars[sbase + 3] = 1.0f;                        /* done = terminal */
    replay_scalars[sbase + 4] = -1.0f;                       /* log_pi (synthetic) */
    replay_scalars[sbase + 5] = 0.0f;                        /* n_step_gamma = 0 (terminal) */
    replay_scalars[sbase + 6] = peak_pnl;                    /* return = peak PnL */

    /* Priority: boosted fraction of current max_priority.
     * Tree layout: leaves at [cap..2*cap), internal nodes at [0..cap). */
    float boost = isv[RL_HINDSIGHT_PRIORITY_BOOST_INDEX];
    float prio = boost * max_priority[0];
    if (prio < 1e-6f) prio = 1e-6f;
    priority_tree[capacity + (int)slot] = prio;

    /* Thread 0 writes the inject count to ISV for diagnostics. */
    if (b == 0) {
        /* Cast away const for the diagnostic write — ISV is writable from GPU. */
        ((float*)isv)[RL_HINDSIGHT_INJECT_COUNT_INDEX] = (float)smem[b_size];
    }
}
