//! End-to-end coverage for the `FlatTap` / `FlatGraph.taps` plumbing (#282):
//! a `Composite` can declare a `Tap` (via `with_taps`), and `compile()` now
//! resolves it into the flat graph it hands to `Harness::bootstrap`. These
//! tests pin the two halves of that contract at the public `aura_engine`
//! boundary: `compile()` surfaces a declared `Tap` into `FlatGraph.taps`,
//! resolved to its flat producer `(node, field)`
//! (`compile_resolves_a_declared_tap_at_the_public_boundary`), and doing so
//! is otherwise transparent to compilation of the rest of the graph — the
//! same node/edge structure, just an added taps entry
//! (`tapped_composite_compiles_identically_to_untapped_besides_the_taps_list`).

use aura_core::ScalarKind;
use aura_engine::{BlueprintNode, CompileError, Composite, FlatTap, OutField, Role, Tap, TapWire, Target};
use aura_std::Sub;

/// A trivial one-node composite with a single declared output (`Sub`'s
/// difference), matching the `taps` argument requested by each test. Both of
/// `Sub`'s input slots are fed by one root-bound (source) role — enough to
/// satisfy `compile()`'s port-connectivity + root-binding checks.
fn leaf(taps: Vec<Tap>) -> Composite {
    Composite::new(
        "leaf",
        vec![Sub::builder().into()],
        vec![],
        vec![Role {
            name: "price".into(),
            targets: vec![Target { node: 0, slot: 0 }, Target { node: 0, slot: 1 }],
            source: Some(ScalarKind::F64),
        }],
        vec![OutField { node: 0, field: 0, name: "o".into() }],
    )
    .with_taps(taps)
}

/// Property: **`compile()` resolves a declared `Tap` at the public
/// boundary.** A composite declaring a valid `Tap` (naming a real interior
/// producer, `node: 0, field: 0`) compiles to a `FlatGraph` whose `taps` list
/// carries the resolved `FlatTap` — name preserved, wire resolved to the flat
/// producer index.
#[test]
fn compile_resolves_a_declared_tap_at_the_public_boundary() {
    let tapped = leaf(vec![Tap { name: "spread".into(), from: TapWire { node: 0, field: 0 } }]);
    let flat = tapped.compile().expect("a tap-bearing composite compiles");
    assert_eq!(
        flat.taps,
        vec![FlatTap { name: "spread".into(), node: 0, field: 0 }],
        "compile() must resolve the declared Tap into FlatGraph.taps: {:?}",
        flat.taps
    );
}

/// Property: **a declared `Tap` is otherwise inert at compile time** — it
/// changes nothing about the flattened node/edge structure. Compiling the
/// same composite with and without a `Tap` declaration must produce
/// structurally identical flat graphs (same node count, same edges); the
/// only difference tap-bearing scaffolding is allowed to make is the `taps`
/// list itself (empty vs. carrying the one resolved entry).
#[test]
fn tapped_composite_compiles_identically_to_untapped_besides_the_taps_list() {
    let untapped = leaf(vec![]).compile().expect("untapped compiles");
    let tapped = leaf(vec![Tap { name: "spread".into(), from: TapWire { node: 0, field: 0 } }])
        .compile()
        .expect("tapped compiles");

    assert_eq!(untapped.nodes.len(), tapped.nodes.len(), "tap declaration must not add/remove flat nodes");
    assert_eq!(untapped.edges, tapped.edges, "tap declaration must not perturb flat edges");
    assert!(untapped.taps.is_empty(), "the untapped composite compiles to an empty taps list");
    assert_eq!(
        tapped.taps,
        vec![FlatTap { name: "spread".into(), node: 0, field: 0 }],
        "the tapped composite compiles to the one resolved entry"
    );
}

/// Property: **the `validate_wiring` structural leg rejects a tap whose wire
/// names a node index beyond the composite's own node count**, before any
/// lowering or field-arity resolution ever runs — mirroring how the sibling
/// `output` leg raises `OutputPortOutOfRange` for the same shape of mistake.
/// This is the early, cheap structural gate (`tap.from.node >= nodes.len()`)
/// added alongside tap resolution in this iteration; without it, a
/// wildly-out-of-range tap wire would only be caught by luck at whichever
/// later stage happens to index into a lowering table, not deterministically
/// at compile's first validation pass.
#[test]
fn compile_rejects_a_tap_wire_node_out_of_range() {
    let tapped = leaf(vec![Tap { name: "ghost".into(), from: TapWire { node: 1, field: 0 } }]);
    match tapped.compile() {
        Err(CompileError::TapWireOutOfRange { tap }) => assert_eq!(tap, "ghost"),
        other => panic!("expected TapWireOutOfRange{{tap: \"ghost\"}}, got {:?}", other.map(|_| ())),
    }
}

/// Property: **a `Tap` declared on a nested composite hoists all the way to
/// the root `FlatGraph.taps`, with its wire remapped through the inline
/// offset** accumulated by every flat node lowered ahead of it — not just
/// carried through unresolved, and not resolved to its interior-local index.
/// The root here lowers a plain leaf node first (flat index 0) followed by
/// the tapped composite (whose interior `Sub` therefore lands at flat index
/// 1, not 0), so a remap that silently stayed at the interior-local index —
/// or hoisted at all — is caught by asserting the hoisted `FlatTap` points at
/// index 1. This is the exact shape the by-name tap-binding CLI wrapper
/// needs: a tap declared arbitrarily deep in an authored blueprint must
/// surface as one flat, addressable measurement point at the root.
#[test]
fn compile_hoists_a_nested_composite_tap_to_the_root_with_remapped_index() {
    let inner = Composite::new(
        "inner",
        vec![Sub::builder().into()],
        vec![],
        vec![Role { name: "x".into(), targets: vec![Target { node: 0, slot: 0 }, Target { node: 0, slot: 1 }], source: None }],
        vec![OutField { node: 0, field: 0, name: "o".into() }],
    )
    .with_taps(vec![Tap { name: "inner_d".into(), from: TapWire { node: 0, field: 0 } }]);

    let root = Composite::new(
        "root",
        vec![Sub::builder().into(), BlueprintNode::Composite(inner)],
        vec![],
        vec![
            Role {
                name: "lead".into(),
                targets: vec![Target { node: 0, slot: 0 }, Target { node: 0, slot: 1 }],
                source: Some(ScalarKind::F64),
            },
            Role { name: "a".into(), targets: vec![Target { node: 1, slot: 0 }], source: Some(ScalarKind::F64) },
        ],
        vec![],
    );

    let flat = root.compile().expect("root with a nested tapped composite compiles");
    assert_eq!(
        flat.taps,
        vec![FlatTap { name: "inner_d".into(), node: 1, field: 0 }],
        "the inner tap must hoist to root FlatGraph.taps, remapped to the inline-offset flat node index"
    );
}
