// Reduce per-experience rewards and actions into a compact summary.
// One block, parallel reduction across N elements.
// Rewards are f32 (no bf16 NaN risk from done-flag contamination).
//
// Determinism: warp partials are written to per-warp shared scratch slots
// (deterministic, no race), then tid=0 reduces sequentially in fixed warp
// order. Was atomicAdd-into-shared-scalar — atomic is order-of-arrival, so
// for floats the result varied across runs by a few ULPs. Now bit-stable.
//
// Shared memory: ~3.0 KB for MAX_WARPS=32 warp scratch (well under 48 KB).
extern "C" __global__ void monitoring_reduce(
    const float* __restrict__ rewards,   // [N] f32
    const int*   __restrict__ actions,   // [N]
    float* summary,              // [27]: mean, std, min, max, sharpe, exp[12], order[3], urgency[3], total, trades, _pad2
    int          N,
    int          num_exposure_bins,      // runtime: b0*b1 = dir*mag combos (12 = 4*3)
    int          order_actions,          // runtime: DQN_ORDER_ACTIONS (3)
    int          urgency_actions         // runtime: DQN_URGENCY_ACTIONS (3)
) {
    /* Per-warp scratch arrays — one slot per warp (max 32 warps for 1024-thread block).
     * EXP_MAX = 12 matches the 4-direction × 3-magnitude layout used by the runtime
     * DQN (Short/Hold/Long/Flat × Small/Half/Full). */
    constexpr int EXP_MAX = 12;
    __shared__ float w_sum[32];
    __shared__ float w_sq_sum[32];
    __shared__ float w_min[32];
    __shared__ float w_max[32];
    __shared__ int   w_nonzero[32];
    __shared__ int   w_exp[32][EXP_MAX];
    __shared__ int   w_ord[32][3];
    __shared__ int   w_urg[32][3];

    int tid = threadIdx.x;
    int stride = blockDim.x;
    int b1b2 = order_actions * urgency_actions;
    int warp_id = tid / 32;
    int num_warps = (blockDim.x + 31) / 32;

    float local_sum = 0.0f;
    float local_sq = 0.0f;
    float local_min = 1e30f;
    float local_max = -1e30f;
    int local_nonzero = 0;
    int lc_exp[EXP_MAX] = {0,0,0,0,0,0,0,0,0,0,0,0};
    int lc_ord[3] = {0,0,0};
    int lc_urg[3] = {0,0,0};

    for (int i = tid; i < N; i += stride) {
        float rf = rewards[i];
        float r = rf;
        /* Only accumulate non-zero rewards (trade completions).
         * Sparse reward design: reward=0.0 during hold/flat, non-zero at trade exit.
         * Computing mean/std/sharpe over all bars masks the signal in 98% zeros. */
        if (rf != 0.0f) {
            local_sum = local_sum + r;
            local_sq = local_sq + r * r;
            local_min = fminf(local_min, r);
            local_max = fmaxf(local_max, r);
            local_nonzero++;
        }
        /* Decode dir*mag + order + urgency from factored action:
         * action = exp * (b1*b2) + ord * b2 + urg
         * where exp = dir * b1 + mag, with b0=4 (dir: Short/Hold/Long/Flat)
         * and b1=3 (mag: Small/Half/Full). exp range = [0, 12). */
        int a = actions[i];
        int exp_idx = a / b1b2;
        int rem     = a % b1b2;
        int ord_idx = rem / urgency_actions;
        int urg_idx = rem % urgency_actions;
        if (exp_idx >= 0 && exp_idx < num_exposure_bins) lc_exp[exp_idx]++;
        if (ord_idx >= 0 && ord_idx < order_actions) lc_ord[ord_idx]++;
        if (urg_idx >= 0 && urg_idx < urgency_actions) lc_urg[urg_idx]++;
    }

    // Warp-level reduction (in-warp shuffle — deterministic by definition,
    // every lane sees the same value at each step).
    for (int mask = 16; mask >= 1; mask >>= 1) {
        local_sum = local_sum + __shfl_xor_sync(0xFFFFFFFF, local_sum, mask);
        local_sq  = local_sq + __shfl_xor_sync(0xFFFFFFFF, local_sq, mask);
        local_min = fminf(local_min, __shfl_xor_sync(0xFFFFFFFF, local_min, mask));
        local_max = fmaxf(local_max, __shfl_xor_sync(0xFFFFFFFF, local_max, mask));
        local_nonzero += __shfl_xor_sync(0xFFFFFFFF, local_nonzero, mask);
        for (int i = 0; i < EXP_MAX; i++)
            lc_exp[i] += __shfl_xor_sync(0xFFFFFFFF, lc_exp[i], mask);
        for (int i = 0; i < 3; i++) {
            lc_ord[i] += __shfl_xor_sync(0xFFFFFFFF, lc_ord[i], mask);
            lc_urg[i] += __shfl_xor_sync(0xFFFFFFFF, lc_urg[i], mask);
        }
    }

    /* Lane-0 of each warp publishes the warp's partial to per-warp scratch.
     * No atomic, no race — each warp's slot is written exactly once. */
    if ((tid & 31) == 0) {
        w_sum[warp_id] = local_sum;
        w_sq_sum[warp_id] = local_sq;
        w_min[warp_id] = local_min;
        w_max[warp_id] = local_max;
        w_nonzero[warp_id] = local_nonzero;
        for (int i = 0; i < EXP_MAX; i++) w_exp[warp_id][i] = lc_exp[i];
        for (int i = 0; i < 3; i++) {
            w_ord[warp_id][i] = lc_ord[i];
            w_urg[warp_id][i] = lc_urg[i];
        }
    }
    __syncthreads();

    if (tid == 0) {
        /* Sequential reduction over warp slots in fixed (warp_id ascending)
         * order — bit-stable across runs even for non-associative float adds. */
        float t_sum = 0.0f, t_sq = 0.0f;
        float t_min = 1e30f, t_max = -1e30f;
        int t_nonzero = 0;
        int t_exp[EXP_MAX] = {0,0,0,0,0,0,0,0,0,0,0,0};
        int t_ord[3] = {0,0,0};
        int t_urg[3] = {0,0,0};
        for (int w = 0; w < num_warps; w++) {
            t_sum += w_sum[w];
            t_sq  += w_sq_sum[w];
            t_min  = fminf(t_min, w_min[w]);
            t_max  = fmaxf(t_max, w_max[w]);
            t_nonzero += w_nonzero[w];
            for (int i = 0; i < EXP_MAX; i++) t_exp[i] += w_exp[w][i];
            for (int i = 0; i < 3; i++) {
                t_ord[i] += w_ord[w][i];
                t_urg[i] += w_urg[w][i];
            }
        }

        /* Per-trade statistics: divide by number of trade completions (non-zero rewards),
         * not total bars. With sparse rewards (98% zero), per-bar mean ≈ 0 always.
         * Per-trade mean gives a meaningful signal for monitoring. */
        int n_trades = t_nonzero;
        float denom = (n_trades > 0) ? (float)n_trades : 1.0f;
        float mean = t_sum / denom;
        float var = (n_trades > 1)
            ? t_sq / denom - mean * mean
            : 0.0f;
        float std_val = sqrtf(fmaxf(var, 0.0f));
        /* Summary layout (27 floats):
         *   [0..5)  : mean, std, min, max, sharpe
         *   [5..17) : exposure bins (12 = b0*b1 = 4*3)
         *   [17..20): order type counts (3)
         *   [20..23): urgency counts (3)
         *   [23]    : total bars N
         *   [24]    : total trades (non-zero rewards)
         *   [25..27): pad */
        summary[0] = mean;
        summary[1] = std_val;
        summary[2] = (n_trades > 0) ? t_min : 0.0f;
        summary[3] = (n_trades > 0) ? t_max : 0.0f;
        summary[4] = (std_val > 1e-8f) ? mean / std_val : 0.0f;
        for (int i = 0; i < num_exposure_bins; i++) summary[5 + i] = (float)t_exp[i];
        for (int i = 0; i < 3; i++) summary[17 + i] = (float)t_ord[i];
        for (int i = 0; i < 3; i++) summary[20 + i] = (float)t_urg[i];
        summary[23] = (float)N;
        summary[24] = (float)n_trades;
        summary[25] = 0.0f;
        summary[26] = 0.0f;
    }
}
