//! Fieldtest c0010 #3 — Recorder four-kind support (axis c).
//!
//! The `aura run` CLI only ever uses the f64 Recorder path, so the rustdoc
//! claim that the Recorder supports all four base scalar kinds (F64/I64/Bool/
//! Timestamp -> Scalar::F64/I64/Bool/Ts in the recorded row) is verified here
//! from the public surface alone, on a NON-f64 column set.
//!
//! There is no shipped aura-std producer of i64/bool/timestamp columns, so we
//! author a tiny custom producer node in Rust (C17 — a downstream project
//! authors its own nodes), emitting a 3-field record (i64, bool, timestamp)
//! each cycle. A multi-column Recorder over [I64, Bool, Timestamp] records it.
//!
//! Topology (single source ticks the clock; the producer emits its own record;
//! three edges bind producer field 0/1/2 into the recorder's slot 0/1/2):
//!
//!   clock --> Counter ==(3 fields)==> Recorder[I64, Bool, Timestamp]
//!
//! Public-surface facts used (rustdoc only):
//!  - aura_core::{Node, NodeSchema, InputSpec, FieldSpec, Ctx, Scalar, ScalarKind,
//!    Firing, Timestamp}: the C8 node contract; a producer's eval returns a
//!    borrowed row matching schema().output.len().
//!  - aura_std::Recorder::new(&[I64, Bool, Timestamp], Firing::Any, tx): records
//!    the newest value of every column once all columns are warm.

use std::sync::mpsc;

use aura_core::{Ctx, FieldSpec, Firing, InputSpec, Node, NodeSchema, Scalar, ScalarKind, Timestamp};
use aura_engine::{Edge, Harness, SourceSpec, Target};
use aura_std::Recorder;

/// A 3-field producer: one f64 clock input (to tick it), emits
/// (i64 counter, bool even?, timestamp ctx.now()) each cycle.
struct Counter {
    n: i64,
    row: Vec<Scalar>,
}
impl Counter {
    fn new() -> Self {
        Counter { n: 0, row: Vec::new() }
    }
}
impl Node for Counter {
    fn schema(&self) -> NodeSchema {
        NodeSchema {
            // one f64 clock input so the engine fires this node each price cycle
            inputs: vec![InputSpec { kind: ScalarKind::F64, lookback: 1, firing: Firing::Any }],
            // a 3-column record: i64 / bool / timestamp
            output: vec![
                FieldSpec { name: "counter", kind: ScalarKind::I64 },
                FieldSpec { name: "is_even", kind: ScalarKind::Bool },
                FieldSpec { name: "stamp", kind: ScalarKind::Timestamp },
            ],
        }
    }
    fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Scalar]> {
        if ctx.f64_in(0).is_empty() {
            return None;
        }
        self.n += 1;
        self.row = vec![
            Scalar::I64(self.n),
            Scalar::Bool(self.n % 2 == 0),
            Scalar::Ts(ctx.now()),
        ];
        Some(&self.row)
    }
}

fn f64_stream(pairs: &[(i64, f64)]) -> Vec<(Timestamp, Scalar)> {
    pairs.iter().map(|&(t, v)| (Timestamp(t), Scalar::F64(v))).collect()
}

fn main() {
    let (tx, rx) = mpsc::channel::<(Timestamp, Vec<Scalar>)>();

    // nodes: 0 = Counter (3-field producer), 1 = Recorder over [I64, Bool, Timestamp].
    let mut h = Harness::bootstrap(
        vec![
            Box::new(Counter::new()),
            Box::new(Recorder::new(
                &[ScalarKind::I64, ScalarKind::Bool, ScalarKind::Timestamp],
                Firing::Any,
                tx,
            )),
        ],
        vec![SourceSpec {
            kind: ScalarKind::F64,
            targets: vec![Target { node: 0, slot: 0 }], // clock -> Counter
        }],
        vec![
            // bind each of the producer's three fields into the recorder's three slots
            Edge { from: 0, to: 1, slot: 0, from_field: 0 }, // i64 counter
            Edge { from: 0, to: 1, slot: 1, from_field: 1 }, // bool is_even
            Edge { from: 0, to: 1, slot: 2, from_field: 2 }, // timestamp stamp
        ],
    )
    .expect("valid Counter -> Recorder[I64,Bool,Timestamp] DAG");

    let clock: &[(i64, f64)] = &[(100, 0.0), (200, 0.0), (300, 0.0)];
    h.run(vec![f64_stream(clock)]);

    let rows: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
    println!("recorded rows = {rows:?}");

    // Hand model from rustdoc alone: the Counter fires each clock cycle, all
    // three recorder columns are warm from the first cycle, so the Recorder
    // records 3 rows, each (ctx.now(), [I64(n), Bool(n even), Ts(now)]).
    let expected: Vec<(Timestamp, Vec<Scalar>)> = vec![
        (Timestamp(100), vec![Scalar::I64(1), Scalar::Bool(false), Scalar::Ts(Timestamp(100))]),
        (Timestamp(200), vec![Scalar::I64(2), Scalar::Bool(true), Scalar::Ts(Timestamp(200))]),
        (Timestamp(300), vec![Scalar::I64(3), Scalar::Bool(false), Scalar::Ts(Timestamp(300))]),
    ];
    assert_eq!(rows.len(), expected.len(), "one row per clock cycle");
    assert_eq!(rows, expected, "i64/bool/timestamp columns recorded as I64/Bool/Ts scalars");

    // Spot-check kinds explicitly (the four-kind claim is about the Scalar variant).
    let (_, first) = &rows[0];
    assert!(matches!(first[0], Scalar::I64(_)), "col0 is Scalar::I64");
    assert!(matches!(first[1], Scalar::Bool(_)), "col1 is Scalar::Bool");
    assert!(matches!(first[2], Scalar::Ts(_)), "col2 is Scalar::Ts");

    println!("c0010_3 OK: Recorder records I64/Bool/Timestamp columns per the four-kind claim");
}
