//! SP18 v2 Phase 4 — B-leg `q_next` target-Q bootstrap oracle tests.
//!
//! Plan: docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md
//!
//! Phase 4 lands additive infrastructure for the
//! `Q_target(s', argmax_a Q_online(s', a))` (DDQN) bootstrap that replaces
//! the `q_next = rewards` self-bootstrap at
//! `gpu_experience_collector.rs:~4252` in Phase 5. Phase 4 is dead-code
//! — `compute_q_next_target_bootstrap` has no production caller, only
//! the GPU oracle tests T8 + T11 (added in Tasks 4.4) once Tasks 4.3
//! and 4.4 fill in the body.
//!
//! At Phase 4 Task 4.2 close (this file's initial state), only the
//! source-introspection contract tests are present:
//!
//!   - **T_SKEL.1**: Method signature exists in
//!     `crates/ml/src/cuda_pipeline/gpu_experience_collector.rs` with the
//!     required parameters per the plan §"Phase 4 Task 4.2".
//!   - **T_SKEL.2**: `next_states` build-site precedes the
//!     `td_lambda_kernel` launch (Phase 4 Task 4.1 ordering invariant).
//!   - **T_SKEL.3**: Phase-4-pre-Phase-5 atomic-refactor guard —
//!     `td_lambda_kernel` STILL consumes the self-bootstrap `q_next`
//!     clone (Phase 5 Task 5.1 swaps).
//!
//! Tasks 4.3-4.4 will add GPU oracle tests T8 + T11
//! (`#[ignore = "requires GPU"]`) once the body is filled in.
//!
//! Run on any host (introspection-only, no GPU needed for T_SKEL):
//!
//!     SQLX_OFFLINE=true cargo test -p ml \
//!         --test sp18_td_lambda_q_next_oracle_tests
//!
//! Per `feedback_no_partial_refactor`, NO L40S DISPATCH between the
//! Phase 4 close commit and the Phase 5.1 commit that swaps the `q_next`
//! argument at line ~4252 to consume `compute_q_next_target_bootstrap`'s
//! output.

const COLLECTOR_SRC: &str = include_str!(
    "../src/cuda_pipeline/gpu_experience_collector.rs"
);

/// **T_SKEL.1** (Phase 4 Task 4.2 source-introspection contract).
///
/// Asserts the new method signature exists with the documented parameters
/// per the plan §"Phase 4 Task 4.2". Until Task 4.5 closes Phase 4, the
/// method body is a hard-error early-return per `feedback_no_stubs` —
/// no production caller exists yet (Phase 5 Task 5.1 wires it).
#[test]
fn compute_q_next_target_bootstrap_method_exists() {
    // Method declaration with full signature line — match the exact
    // declaration produced by Task 4.2.
    assert!(
        COLLECTOR_SRC.contains("pub fn compute_q_next_target_bootstrap("),
        "Phase 4 Task 4.2: `compute_q_next_target_bootstrap` method must \
         exist on `GpuExperienceCollector`"
    );

    // Required parameters from the plan §"Task 4.2".
    let want_args = [
        "next_states: &cudarc::driver::CudaSlice<f32>",
        "target_params_ptr: u64",
        "online_params_ptr: u64",
        "batch_size: usize",
    ];
    for arg in want_args {
        assert!(
            COLLECTOR_SRC.contains(arg),
            "Phase 4 Task 4.2: missing required parameter signature `{arg}` \
             on `compute_q_next_target_bootstrap`"
        );
    }

    // Return type must be CudaSlice<f32> (B-DD8.a scalar form per the plan).
    assert!(
        COLLECTOR_SRC.contains(
            "Result<cudarc::driver::CudaSlice<f32>, MLError>"
        ),
        "Phase 4 Task 4.2: return type must be \
         `Result<CudaSlice<f32>, MLError>` per B-DD8.a scalar bootstrap"
    );

    // Dead-method body must hard-error with a pointer to the close-out
    // sub-tasks (Tasks 4.3-4.5). Returning Err satisfies Invariant 9
    // (no deferred-work markers) AND `feedback_no_stubs` — any accidental
    // Phase-5-pre-empting call site hard-errors with a clear pointer to
    // the open work. This guard string is REPLACED in Task 4.5 commit
    // alongside the body fill-in.
    assert!(
        COLLECTOR_SRC.contains(
            "compute_q_next_target_bootstrap body is dead-code"
        ),
        "Phase 4 Task 4.2: dead-method body must hard-error with the \
         documented Phase-4-pending pointer per `feedback_no_partial_refactor`"
    );
}

/// **T_SKEL.2** (Phase 4 Task 4.1 build-site relocation invariant).
///
/// Asserts `build_next_states_f32` is invoked BEFORE the `td_lambda_kernel`
/// launch site, so that Phase 5's `q_next` replacement can pass
/// `&next_states` to `compute_q_next_target_bootstrap` (which lives where
/// the current self-bootstrap clone is). Pure ordering invariant —
/// behaviorally a no-op since `next_states` had no observer between its
/// pre-Phase-4 build site and the bottom-of-fn batch assembly.
#[test]
fn next_states_built_before_td_lambda() {
    let build_pos = COLLECTOR_SRC.find("let next_states = build_next_states_f32(")
        .expect("`let next_states = build_next_states_f32(` must exist in collector");

    let td_lambda_pos = COLLECTOR_SRC.find(".launch_builder(&self.td_lambda_kernel)")
        .expect("`.launch_builder(&self.td_lambda_kernel)` must exist in collector");

    assert!(
        build_pos < td_lambda_pos,
        "Phase 4 Task 4.1: `build_next_states_f32` (offset {build_pos}) must \
         precede the `td_lambda_kernel` launch (offset {td_lambda_pos}) so that \
         Phase 5 `compute_q_next_target_bootstrap(&next_states, ...)` can \
         consume it. Audit script grep `next_states\\b` flagged 0 \
         post-TD(λ) consumers — this hoist is a pure ordering change."
    );
}

/// **T_SKEL.3** (Phase 4 Phase 5 atomic-refactor guard).
///
/// Asserts that the `td_lambda_kernel` launch STILL consumes the
/// self-bootstrap `q_next = rewards_out` clone. Phase 5 (Task 5.1) flips
/// this — at that point, this test will be deleted/inverted alongside the
/// `q_next` argument swap.
///
/// Until Phase 5, this test is a regression guard: if a future change
/// accidentally removes the self-bootstrap before `compute_q_next_target_bootstrap`
/// is wired, the test fails with a clear pointer to the atomic-refactor
/// invariant.
#[test]
fn td_lambda_still_consumes_self_bootstrap_q_next_in_phase4() {
    // The self-bootstrap clone — Phase 5 Task 5.1 replaces this with
    // `self.compute_q_next_target_bootstrap(&next_states, ...)`.
    assert!(
        COLLECTOR_SRC.contains(r#""q_next_bootstrap")"#),
        "Phase 4 atomicity: self-bootstrap `q_next = rewards_out` clone \
         must still be wired to `td_lambda_kernel` (literal label \
         \"q_next_bootstrap\") until Phase 5 Task 5.1. Per \
         `feedback_no_partial_refactor`, the swap is atomic in Phase 5 \
         alongside the `compute_q_next_target_bootstrap` body fill-in \
         (Tasks 4.3-4.5)."
    );
}
