// Milestone fieldtest — axis (d): introspect a built Blueprint via the
// read-only graph-as-data accessors — can a consumer walk the graph as data
// WITHOUT reading engine internals?
//
// Accessors under test (all public, from rustdoc):
//   Blueprint::nodes() / sources() / edges()
//   Composite::name() / nodes() / edges() / input_roles() / output()
//   BlueprintNode::{Leaf, Composite}; Node::label() on a boxed leaf
//
// Public interface only.

use aura_core::ScalarKind;
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, Role, SourceSpec, Target};
// NB (fieldtest finding): `aura_engine::ScalarKind` does NOT resolve — the
// construction surface (aura-engine) does not re-export the core scalar
// vocabulary (ScalarKind / Scalar / Firing) that a graph-builder needs. A
// downstream author building a Blueprint must reach into aura_core directly.
// Recorded as friction in the spec. `node.label()` below dispatches on the
// boxed leaf via the Node trait object without an explicit `use` of Node.

fn sma_cross() -> Composite {
    Composite::new(
        "sma_cross",
        vec![
            aura_std::Sma::factory().into(),
            aura_std::Sma::factory().into(),
            aura_std::Sub::factory().into(),
        ],
        vec![
            Edge { from: 0, to: 2, slot: 0, from_field: 0 },
            Edge { from: 1, to: 2, slot: 1, from_field: 0 },
        ],
        vec![Role {
            name: "price".into(),
            targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
        }],
        vec![],
        vec![OutField { node: 2, field: 0, name: "out".into() }],
    )
}

fn describe_node(prefix: &str, n: &BlueprintNode) {
    match n {
        BlueprintNode::Leaf(factory) => {
            // A blueprint leaf is a value-empty LeafFactory (post cycle-0016): no
            // built node, so no input/output schema yet — introspect its declared
            // render label + tunable param count (inputs/outputs exist only after
            // build, on the compiled flat node).
            println!(
                "{prefix}LEAF {:<14} params={}",
                factory.label(),
                factory.params().len()
            );
        }
        BlueprintNode::Composite(c) => {
            // output() is now a record (&[OutField]); render the whole field list
            // (one entry today, but the type is the multi-field shape of #40).
            let out_fields: Vec<(usize, usize)> =
                c.output().iter().map(|f| (f.node, f.field)).collect();
            println!(
                "{prefix}COMPOSITE name={:?} interior_items={} roles={} out={:?}",
                c.name(),
                c.nodes().len(),
                c.input_roles().len(),
                out_fields,
            );
            for inner in c.nodes() {
                describe_node(&format!("{prefix}  "), inner);
            }
            for (r, role) in c.input_roles().iter().enumerate() {
                println!("{prefix}  role[{r}] {:?} fans into {:?}", role.name, role.targets);
            }
            for e in c.edges() {
                println!("{prefix}  interior edge {e:?}");
            }
        }
    }
}

fn main() {
    let bp = Blueprint::new(
        vec![
            BlueprintNode::Composite(sma_cross()),
            aura_std::Exposure::factory().into(),
        ],
        vec![SourceSpec {
            kind: ScalarKind::F64,
            targets: vec![Target { node: 0, slot: 0 }],
        }],
        vec![Edge { from: 0, to: 1, slot: 0, from_field: 0 }],
    );

    println!("== Blueprint graph-as-data walk ==");
    println!("sources: {}", bp.sources().len());
    for (i, s) in bp.sources().iter().enumerate() {
        println!("  source[{i}] kind={:?} -> {:?}", s.kind, s.targets);
    }
    println!("top-level items: {}", bp.nodes().len());
    for n in bp.nodes() {
        describe_node("  ", n);
    }
    println!("top-level edges: {}", bp.edges().len());
    for e in bp.edges() {
        println!("  edge {e:?}");
    }

    // The walk must reach the interior of the composite purely through the
    // public accessors — no engine internals.
    let first = &bp.nodes()[0];
    if let BlueprintNode::Composite(c) = first {
        assert_eq!(c.name(), "sma_cross");
        assert_eq!(c.nodes().len(), 3, "composite interior should have 3 items");
        assert_eq!(c.input_roles().len(), 1, "one price role");
        assert_eq!(c.input_roles()[0].name, "price", "the role carries its boundary name");
        assert_eq!(c.input_roles()[0].targets.len(), 2, "price fans into both SMAs");
        let out = c.output();
        assert_eq!(out.len(), 1, "the cross re-exports one output field");
        assert_eq!((out[0].node, out[0].field), (2, 0), "Sub output is the port");
        println!("OK: walked the composite interior via public accessors only.");
    } else {
        panic!("expected composite as first item");
    }
}
