//! Tests for `SagaBuilder::mermaid()` — DAG-as-data rendering.
//!
//! The spec calls out (saga-worked-example.md §9): "Visualization …
//! DAG is data — render it from `.steps`, `.deps`, `.compensates`."
//! These tests pin the output format so users can diff it in PRs.

#![allow(dead_code)]

use std::future::Future;
use std::pin::Pin;

use mire::{Aggregate, EventData};
use mire_sagas::{Emit, Output, Saga, SagaBuilder, Start, Trigger, comp, task};
use serde::{Deserialize, Serialize};

#[derive(Default, Clone, Serialize, Deserialize)]
struct St;

#[derive(Default, Clone)]
struct Trig;

#[derive(Serialize, Deserialize, EventData)]
#[mire(event_type = "trig.ev")]
struct TrigEv;

impl Aggregate for Trig {
    type Event = TrigEv;
    fn stream_category() -> &'static str {
        "trig"
    }
    fn apply(&mut self, _: &Self::Event) {}
}

#[derive(Serialize, Deserialize, EventData)]
#[mire(event_type = "noop.ev")]
struct Noop;

#[derive(Serialize, Deserialize, EventData)]
#[mire(event_type = "step.ok")]
struct StepOk;

#[derive(Serialize, Deserialize)]
struct OutA;
#[derive(Serialize, Deserialize)]
struct OutB;
#[derive(Serialize, Deserialize)]
struct OutC;

fn ha(_: Trigger<TrigEv>) -> Pin<Box<dyn Future<Output = Emit> + Send>> {
    Box::pin(async { Emit::event(Noop) })
}
fn hb(_: Trigger<TrigEv>) -> Pin<Box<dyn Future<Output = Emit> + Send>> {
    Box::pin(async { Emit::event(Noop) })
}
fn hc(_: Trigger<TrigEv>) -> Pin<Box<dyn Future<Output = Emit> + Send>> {
    Box::pin(async { Emit::event(Noop) })
}

fn comp_a(_: Output<OutA>) -> Pin<Box<dyn Future<Output = Emit> + Send>> {
    Box::pin(async { Emit::none() })
}

struct DiamondSaga;
impl Saga for DiamondSaga {
    type State = St;
    fn name() -> &'static str {
        "diamond"
    }
    fn build(b: SagaBuilder<Self>) -> SagaBuilder<Self> {
        b
    }
}

fn base() -> SagaBuilder<DiamondSaga> {
    SagaBuilder::<DiamondSaga>::for_saga().start_on::<Trig, _>(|_e| {
        Some(Start {
            instance_id: "x".into(),
            state: St,
        })
    })
}

#[test]
fn mermaid_emits_flowchart_header() {
    let topology = base().step("a", task(ha).terminal_success());
    let out = topology.mermaid();
    assert!(out.starts_with("flowchart TD\n"));
}

#[test]
fn mermaid_renders_terminal_step_with_check() {
    let topology = base().step("a", task(ha).terminal_success());
    let out = topology.mermaid();
    assert!(out.contains(r#"a(["a ✓"])"#), "got:\n{out}");
}

#[test]
fn mermaid_renders_after_edges() {
    let topology = base()
        .step("a", task(ha))
        .step("b", task(hb).after("a").terminal_success());
    let out = topology.mermaid();
    assert!(out.contains("a --> b"), "got:\n{out}");
}

#[test]
fn mermaid_renders_compensations_as_dashed_back_edges() {
    let topology = base().step(
        "a",
        task(ha)
            .on_response(|_: StepOk| OutA)
            .terminal_success()
            .compensate(comp(comp_a)),
    );
    let out = topology.mermaid();
    assert!(out.contains("a -.compensate.-> a"), "got:\n{out}");
}

#[test]
fn mermaid_renders_diamond_topology() {
    // Classic fan-out / fan-in: trigger → (a, b) → c.
    let topology = base()
        .step("a", task(ha))
        .step("b", task(hb))
        .step("c", task(hc).after(["a", "b"]).terminal_success());
    let out = topology.mermaid();
    // Both edges to c must appear.
    assert!(out.contains("a --> c"), "got:\n{out}");
    assert!(out.contains("b --> c"), "got:\n{out}");
    // Terminal marker.
    assert!(out.contains(r#"c(["c ✓"])"#), "got:\n{out}");
}

#[test]
fn mermaid_renders_signal_as_floating_trigger() {
    #[derive(Serialize, Deserialize)]
    struct AdminCancel;

    let topology = base()
        .step("a", task(ha).terminal_success())
        .signal::<AdminCancel>()
        .triggers_compensation();
    let out = topology.mermaid();
    // The signal renders as a hex-shape labelled with its type name.
    assert!(out.contains("signal:"), "got:\n{out}");
    assert!(out.contains("-.cancel.->"), "got:\n{out}");
}
