//! Fieldtest c0009 #4 — the f64_field bridge + the to_json structured face,
//! probed from the docs alone.
//!
//! Axis (carrier, north-star sub-surface): f64_field is the documented bridge
//! from a recording sink's `Vec<Scalar>` rows to summarize. The rustdoc says:
//!   "extract one f64 field of each row into (ts, f64) samples. Panics if a row
//!    has no such field or the field is not an f64 scalar — a wiring bug ...
//!    surfaced like the engine's other 'checked at wiring' contract violations."
//! And RunReport::to_json: "field order is fixed; f64 uses the round-trippable
//! {} shortest form; params renders as a JSON object in insertion order."
//!
//! This example exercises:
//!  (a) projecting a NON-zero column out of a multi-column recorded row,
//!  (b) the documented panic on a wrong-kind field (caught with catch_unwind so
//!      the program reports it instead of aborting — a downstream consumer
//!      wanting to validate a sink would want this),
//!  (c) the exact to_json byte shape, and whether the documented "round-trippable
//!      {}" claim survives a 0.0001-style pip_size and a NEGATIVE/fractional pip.

use std::panic::{self, AssertUnwindSafe};

use aura_core::{Scalar, Timestamp};
use aura_engine::{RunManifest, RunMetrics, RunReport, f64_field};

fn main() {
    // (a) A recorded row may carry K columns (C7/C8 — a record is a bundle of
    // base columns). f64_field(field=1) must pick the SECOND column.
    let rows: Vec<(Timestamp, Vec<Scalar>)> = vec![
        (Timestamp(1), vec![Scalar::F64(10.0), Scalar::F64(-1.5)]),
        (Timestamp(2), vec![Scalar::F64(20.0), Scalar::F64(2.5)]),
    ];
    let col0 = f64_field(&rows, 0);
    let col1 = f64_field(&rows, 1);
    println!("col0 = {col0:?}");
    println!("col1 = {col1:?}");
    assert_eq!(col0, vec![(Timestamp(1), 10.0), (Timestamp(2), 20.0)]);
    assert_eq!(col1, vec![(Timestamp(1), -1.5), (Timestamp(2), 2.5)]);

    // (b) Documented panic: a non-f64 field (here an i64 in the row) is a wiring
    // bug. A real consumer building a generic "drain any sink" helper would hit
    // this if it mis-declared a column kind. We confirm it PANICS (per docs)
    // rather than silently coercing or dropping.
    let bad_rows: Vec<(Timestamp, Vec<Scalar>)> =
        vec![(Timestamp(1), vec![Scalar::I64(7)])];
    let r = panic::catch_unwind(AssertUnwindSafe(|| f64_field(&bad_rows, 0)));
    match r {
        Err(_) => println!("(b) f64_field on an i64 field PANICKED as documented (wiring bug)"),
        Ok(v) => panic!("(b) expected panic on non-f64 field, got {v:?}"),
    }

    // Also: field index out of range panics ("a row has no such field").
    let r = panic::catch_unwind(AssertUnwindSafe(|| f64_field(&rows, 5)));
    assert!(r.is_err(), "(b) out-of-range field index panics as documented");
    println!("(b) f64_field on an out-of-range field index PANICKED as documented");

    // (c) to_json byte shape + the "round-trippable {}" claim under a realistic
    // FX pip_size (0.0001) and fractional/negative metric values.
    let report = RunReport {
        manifest: RunManifest {
            commit: "abc123".to_string(),
            params: vec![("len".to_string(), 14.0), ("k".to_string(), 2.5)],
            window: (Timestamp(1_700_000_000_000_000_000), Timestamp(1_700_000_086_400_000_000)),
            seed: 42,
            broker: "sim-optimal(pip_size=0.0001)".to_string(),
        },
        metrics: RunMetrics {
            total_pips: -12.5,
            max_drawdown: 33.25,
            exposure_sign_flips: 7,
        },
    };
    let json = report.to_json();
    println!("(c) to_json() = {json}");

    // What the rustdoc lets me check WITHOUT reading src: it is one flat-ish JSON
    // object, params is a nested object in insertion order, values are present in
    // {} shortest form. I assert structure I can justify from the surface; the
    // exact KEY NAMES are not on the public surface (recorded as a spec_gap), so
    // I check the values I supplied appear, and that the ns timestamps survive as
    // integers (round-trippable claim) rather than scientific notation.
    assert!(json.starts_with('{') && json.ends_with('}'));
    assert!(json.contains("-12.5"), "negative fractional total_pips present");
    assert!(json.contains("33.25"), "fractional drawdown present");
    assert!(json.contains("\"k\":2.5"), "fractional param in insertion order, object form");
    // params object ordering: len appears before k (insertion order, documented).
    let li = json.find("\"len\"").expect("len key");
    let ki = json.find("\"k\":2.5").expect("k key");
    assert!(li < ki, "params keys render in insertion order (len before k)");
    // round-trip claim: the big ns window bound must be a bare integer, not 1.7e18.
    assert!(json.contains("1700000000000000000"), "ns timestamp as integer, not sci-notation");
    assert!(!json.to_lowercase().contains("e1"), "no scientific-notation float leaked");

    println!("c0009_4 OK: f64_field column-pick + documented panics + to_json shape");
}
