//! Runnable World-family demo: **`sweep`** the GER40 session-breakout `Composite`
//! blueprint over its two genuine tuning knobs — `entry_bar.target` ×
//! `exit_bar.target` — on REAL GER40 M1 bars, ranked by `total_pips`. This is the
//! core proof that the shipped strategy is World-consumable: the SAME
//! `ger40_breakout_blueprint` the single backtest bootstraps is driven through the
//! engine's `sweep` machinery with NO re-authoring (the #94 friction is gone), its
//! `param_space()` exercised directly.
//!
//! Run:
//! ```text
//! cargo run -p aura-ingest --example ger40_breakout_sweep
//! ```
//! Prints the ranked entry×exit grid (best `total_pips` first) over a full-year
//! 2024 window. Skips cleanly (no panic) where the archive is absent.

use std::sync::Arc;

use aura_core::{Cell, Scalar, Timestamp};
use aura_engine::{sweep, summarize, GridSpace, RunManifest, RunReport};
use chrono_tz::Europe::Berlin;
use aura_ingest::{default_data_server, open_ohlc, DataServer, DEFAULT_DATA_PATH};

#[path = "shared/breakout_real.rs"]
mod breakout_real;
use breakout_real::*;

// The sweep grid: entry bar ∈ {2,3,4}, exit bar ∈ {4,5,6} — the two EqConst
// targets that ARE the blueprint's param_space().
const ENTRY_BARS: &[i64] = &[2, 3, 4];
const EXIT_BARS: &[i64] = &[4, 5, 6];

/// Bootstrap the blueprint at one grid point, run it over the epoch-ns
/// [`Timestamp`] window `[from, to]` of real GER40 OHLC, and fold the recorded
/// equity/held taps into a `RunReport` (`summarize` over the drained channels).
/// A fresh blueprint per call (fresh nodes + channels) keeps the disjoint sweep
/// points independent (C1).
fn run_point(server: &Arc<DataServer>, point: &[Cell], from: Timestamp, to: Timestamp) -> RunReport {
    let (bp, taps) = ger40_breakout_blueprint(
        pip_size_of(SYMBOL),
        BAR_MINUTES,
        SESSION_HOUR,
        SESSION_MINUTE,
        Berlin,
    );
    let mut h = bp
        .bootstrap_with_cells(point)
        .expect("sweep point kind-checked against param_space");
    let sources =
        open_ohlc(server, SYMBOL, from, to).expect("window overlaps GER40 data");
    h.run(sources);
    drop(h);
    let equity: Vec<(Timestamp, f64)> = taps
        .equity
        .try_iter()
        .map(|(t, r)| (t, as_f64(&r[0])))
        .collect();
    let exposure: Vec<(Timestamp, f64)> = taps
        .held
        .try_iter()
        .map(|(t, r)| (t, as_f64(&r[0])))
        .collect();
    RunReport {
        manifest: RunManifest {
            commit: "ger40-breakout-sweep".to_string(),
            params: vec![],
            defaults: vec![],
            window: (from, to),
            seed: 0,
            broker: "sim-optimal(pip_size=1)".to_string(),
            selection: None,
            instrument: None,
            topology_hash: None,
            project: None,
        },
        metrics: summarize(&equity, &exposure),
    }
}

fn main() {
    let server = default_data_server();
    if !server.has_symbol(SYMBOL) {
        println!("skip: no local data at {DEFAULT_DATA_PATH} (symbol {SYMBOL} absent).");
        return;
    }

    // Full-year 2024.
    let (from, _) = utc_month_window(2024, 1);
    let (_, to) = utc_month_window(2024, 12);

    let pip_size = pip_size_of(SYMBOL);

    // The grid is built from the blueprint's param_space() directly — the two
    // EqConst targets are the swept axes (no re-authoring).
    let space = ger40_breakout_blueprint(pip_size, BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin)
        .0
        .param_space();
    let grid = GridSpace::new(
        &space,
        space
            .iter()
            .map(|ps| match ps.name.as_str() {
                "entry_bar.target" => ENTRY_BARS.iter().map(|&v| Scalar::i64(v)).collect(),
                "exit_bar.target" => EXIT_BARS.iter().map(|&v| Scalar::i64(v)).collect(),
                other => panic!("unexpected param slot {other}"),
            })
            .collect(),
    )
    .expect("grid well-formed for the breakout param_space");

    println!("=== GER40 session-breakout — World `sweep` over entry×exit ===");
    println!(
        "symbol={SYMBOL}  window=2024 (UTC, full year)  bars=15m  pip_size={pip_size}  \
         grid={}×{} = {} points",
        ENTRY_BARS.len(),
        EXIT_BARS.len(),
        grid.len(),
    );
    println!("param_space() (the only swept knobs): {:?}", space.iter().map(|p| p.name.as_str()).collect::<Vec<_>>());
    println!();

    let server_for_closure = Arc::clone(&server);
    let family = sweep(&grid, |pt: &[Cell]| {
        run_point(&server_for_closure, pt, from, to)
    });

    // Rank the family by total_pips, descending.
    let mut ranked: Vec<usize> = (0..family.points.len()).collect();
    ranked.sort_by(|&a, &b| {
        family.points[b]
            .report
            .metrics
            .total_pips
            .partial_cmp(&family.points[a].report.metrics.total_pips)
            .unwrap_or(std::cmp::Ordering::Equal)
    });

    println!(
        "{:>4}  {:<10} {:<9} {:>12} {:>10} {:>7}",
        "rank", "entry_bar", "exit_bar", "total_pips", "max_dd", "flips"
    );
    println!("{}", "-".repeat(58));
    for (rank, &i) in ranked.iter().enumerate() {
        let named = family.named_params(i);
        let entry = named.iter().find(|(n, _)| n == "entry_bar.target").map(|(_, v)| v.as_i64()).unwrap_or(-1);
        let exit = named.iter().find(|(n, _)| n == "exit_bar.target").map(|(_, v)| v.as_i64()).unwrap_or(-1);
        let m = &family.points[i].report.metrics;
        println!(
            "{:>4}  {:<10} {:<9} {:>12.1} {:>10.1} {:>7}",
            rank + 1,
            entry,
            exit,
            m.total_pips,
            m.max_drawdown,
            m.bias_sign_flips,
        );
    }
    println!();
    let best = ranked[0];
    let best_named = family.named_params(best);
    println!(
        "best point: {:?}  total_pips={:.1}",
        best_named
            .iter()
            .map(|(n, v)| format!("{}={}", n.replace(".target", ""), v.as_i64()))
            .collect::<Vec<_>>(),
        family.points[best].report.metrics.total_pips,
    );
    println!("\nOK: the shipped breakout blueprint was swept over its param_space() — no re-authoring.");
}

fn as_f64(s: &Scalar) -> f64 {
    match s {
        Scalar::F64(v) => *v,
        other => panic!("expected f64, got {other:?}"),
    }
}
