//! Fieldtest c0006 #2 — multi-stage fan-out / fan-in DAG, bootstrapped + run.
//!
//! Axes: "Build a multi-stage fan-out / fan-in DAG (one source fanning into
//! SMA(2) and SMA(4), then a combiner)" and "Bootstrap and run a Harness (note
//! the new bootstrap arity and that run returns ())."
//!
//! source --> SMA(2) --\
//!        \             Sub(fast - slow) --> Recorder
//!         \-> SMA(4) --/
//!
//! A downstream author wires the classic spread DAG and records the combiner's
//! output. This fixture leans on the post-0006 surface: bootstrap takes exactly
//! THREE positional args (no observe), and run returns () — retention is the
//! recorder's channel, owned by the caller.

use std::sync::mpsc::{self, Sender};

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

struct Recorder {
    tx: Sender<(Timestamp, f64)>,
}
impl Recorder {
    fn new(tx: Sender<(Timestamp, f64)>) -> Self {
        Self { tx }
    }
}
impl Node for Recorder {
    fn schema(&self) -> NodeSchema {
        NodeSchema {
            inputs: vec![InputSpec {
                kind: ScalarKind::F64,
                lookback: 1,
                firing: Firing::Any,
            }],
            output: vec![],
        }
    }
    fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Scalar]> {
        let w = ctx.f64_in(0);
        if w.is_empty() {
            return None;
        }
        let _ = self.tx.send((ctx.now(), w[0]));
        None
    }
}

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();

    // nodes: 0 = SMA(2), 1 = SMA(4), 2 = Sub(0 - 1), 3 = Recorder taps Sub.
    let mut h = Harness::bootstrap(
        vec![
            Box::new(Sma::new(2)),
            Box::new(Sma::new(4)),
            Box::new(Sub::new()),
            Box::new(Recorder::new(tx)),
        ],
        vec![SourceSpec {
            kind: ScalarKind::F64,
            // source fans out into both SMAs
            targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
        }],
        vec![
            Edge { from: 0, to: 2, slot: 0, from_field: 0 }, // SMA(2) -> Sub.in0
            Edge { from: 1, to: 2, slot: 1, from_field: 0 }, // SMA(4) -> Sub.in1
            Edge { from: 2, to: 3, slot: 0, from_field: 0 }, // Sub -> Recorder
        ],
    )
    .expect("valid DAG");

    // ascending-in-price stream: SMA(2) leads SMA(4), spread stays constant once warm.
    h.run(vec![f64_stream(&[
        (1, 10.0),
        (2, 12.0),
        (3, 14.0),
        (4, 16.0),
        (5, 18.0),
        (6, 20.0),
    ])]);

    let recorded: Vec<(Timestamp, f64)> = rx.try_iter().collect();

    // Sub fires once both SMAs are warm. SMA(4) warms at the 4th sample (t=4).
    //   t=4: SMA2=mean(16,14)=15, SMA4=mean(16,14,12,10)=13 -> 2
    //   t=5: SMA2=mean(18,16)=17, SMA4=mean(18,16,14,12)=15 -> 2
    //   t=6: SMA2=mean(20,18)=19, SMA4=mean(20,18,16,14)=17 -> 2
    let expected = vec![
        (Timestamp(4), 2.0),
        (Timestamp(5), 2.0),
        (Timestamp(6), 2.0),
    ];

    println!("recorded = {recorded:?}");
    println!("expected = {expected:?}");
    assert_eq!(recorded, expected, "fan-out/fan-in spread DAG recorded output");
    println!("c0006_2 OK: fan-out/fan-in DAG bootstrapped (3-arg) + run () + recorded");
}
